Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/grpc/mpi/v1/command.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/grpc/mpi/v1/common.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/grpc/mpi/v1/files.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 74 additions & 1 deletion internal/collector/otel_collector_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"log/slog"
"net"
"net/url"
"os"
"strings"
"sync"
Expand Down Expand Up @@ -221,7 +222,7 @@ func (oc *Collector) processReceivers(ctx context.Context, receivers map[string]
}
}

//nolint:revive // cognitive complexity is 13
//nolint:revive,cyclop // cognitive complexity is 13
func (oc *Collector) bootup(ctx context.Context) error {
errChan := make(chan error)

Expand All @@ -231,6 +232,10 @@ func (oc *Collector) bootup(ctx context.Context) error {
return
}

if oc.config.IsCommandServerProxyConfigured() {
oc.setProxyIfNeeded(ctx)
}

appErr := oc.service.Run(ctx)
if appErr != nil {
errChan <- appErr
Expand Down Expand Up @@ -394,6 +399,10 @@ func (oc *Collector) restartCollector(ctx context.Context) {
}
oc.service = oTelCollector

if oc.config.IsCommandServerProxyConfigured() {
oc.setProxyIfNeeded(ctx)
}

var runCtx context.Context
runCtx, oc.cancel = context.WithCancel(ctx)

Expand All @@ -409,6 +418,14 @@ func (oc *Collector) restartCollector(ctx context.Context) {
}
}

func (oc *Collector) setProxyIfNeeded(ctx context.Context) {
if oc.config.Collector.Exporters.OtlpExporters != nil ||
oc.config.Collector.Exporters.PrometheusExporter != nil {
// Set proxy env vars for OTLP exporter if proxy is configured.
oc.setExporterProxyEnvVars(ctx)
}
}

func (oc *Collector) checkForNewReceivers(ctx context.Context, nginxConfigContext *model.NginxConfigContext) bool {
nginxReceiverFound, reloadCollector := oc.updateExistingNginxPlusReceiver(nginxConfigContext)

Expand Down Expand Up @@ -742,3 +759,59 @@ func escapeString(input string) string {

return output
}

func (oc *Collector) setExporterProxyEnvVars(ctx context.Context) {
proxy := oc.config.Command.Server.Proxy
proxyURL := proxy.URL
parsedProxyURL, err := url.Parse(proxyURL)
if err != nil {
slog.ErrorContext(ctx, "Malformed proxy URL, unable to configure proxy for OTLP exporter",
"url", proxyURL, "error", err)

return
}

if parsedProxyURL.Scheme == "https" {
slog.ErrorContext(ctx, "HTTPS protocol not supported by OTLP exporter, unable to configure proxy for "+
"OTLP exporter", "url", proxyURL)
}

auth := ""
if proxy.AuthMethod != "" && strings.TrimSpace(proxy.AuthMethod) != "" {
auth = strings.TrimSpace(proxy.AuthMethod)
}

// Use the standalone setProxyWithBasicAuth function
if auth == "" {
setProxyEnvs(ctx, proxyURL, "Setting Proxy from command.Proxy (no auth)")
return
}
authLower := strings.ToLower(auth)
if authLower == "basic" {
setProxyWithBasicAuth(ctx, proxy, parsedProxyURL)
} else {
slog.ErrorContext(ctx, "Unknown auth type for proxy, unable to configure proxy for OTLP exporter",
"auth", auth, "url", proxyURL)
}
}

// setProxyEnvs sets the HTTP_PROXY and HTTPS_PROXY environment variables and logs the action.
func setProxyEnvs(ctx context.Context, proxyEnvURL, msg string) {
slog.DebugContext(ctx, msg, "url", proxyEnvURL)
if setenvErr := os.Setenv("HTTPS_PROXY", proxyEnvURL); setenvErr != nil {
slog.ErrorContext(ctx, "Failed to set OTLP exporter proxy environment variables", "error", setenvErr)
}
}

// setProxyWithBasicAuth sets the proxy environment variables with basic auth credentials.
func setProxyWithBasicAuth(ctx context.Context, proxy *config.Proxy, parsedProxyURL *url.URL) {
username := proxy.Username
password := proxy.Password
if username == "" || password == "" {
slog.ErrorContext(ctx, "Unable to configure OTLP exporter proxy, username or password missing for basic auth")
return
}
parsedProxyURL.User = url.UserPassword(username, password)
proxyURL := parsedProxyURL.String()
setProxyEnvs(ctx, proxyURL, "Setting Proxy with basic auth")
}
134 changes: 133 additions & 1 deletion internal/collector/otel_collector_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"context"
"errors"
"net"
"net/url"
"os"
"path/filepath"
"testing"

Expand Down Expand Up @@ -246,7 +248,11 @@ func TestCollector_ProcessNginxConfigUpdateTopic(t *testing.T) {

conf := types.OTelConfig(t)

conf.Command = nil
conf.Command = &config.Command{
Server: &config.ServerConfig{
Proxy: &config.Proxy{},
},
}

conf.Collector.Log.Path = ""
conf.Collector.Receivers.HostMetrics = nil
Expand Down Expand Up @@ -782,6 +788,132 @@ func TestCollector_updateNginxAppProtectTcplogReceivers(t *testing.T) {
})
}

func Test_setProxyEnvs(t *testing.T) {
ctx := context.Background()
proxyURL := "http://localhost:8080"
msg := "Setting test proxy"

// Unset first to ensure clean state
_ = os.Unsetenv("HTTPS_PROXY")

setProxyEnvs(ctx, proxyURL, msg)

httpProxy := os.Getenv("HTTPS_PROXY")
assert.Equal(t, proxyURL, httpProxy)
}

func Test_setProxyWithBasicAuth(t *testing.T) {
ctx := context.Background()
u, _ := url.Parse("http://localhost:8080")
proxy := &config.Proxy{
URL: "http://localhost:8080",
Username: "user",
Password: "pass",
}

// Unset first to ensure clean state
_ = os.Unsetenv("HTTPS_PROXY")

setProxyWithBasicAuth(ctx, proxy, u)

proxyURL := u.String()
httpProxy := os.Getenv("HTTPS_PROXY")
assert.Equal(t, proxyURL, httpProxy)

logBuf := &bytes.Buffer{}
stub.StubLoggerWith(logBuf)
// Test missing username/password
proxyMissing := &config.Proxy{URL: "http://localhost:8080"}
setProxyWithBasicAuth(ctx, proxyMissing, u)
helpers.ValidateLog(t, "Unable to configure OTLP exporter proxy, "+
"username or password missing for basic auth", logBuf)
}

//nolint:contextcheck // Can not update the "OTelConfig" function definition
func TestSetExporterProxyEnvVars(t *testing.T) {
ctx := context.Background()
logBuf := &bytes.Buffer{}
stub.StubLoggerWith(logBuf)

tests := []struct {
name string
proxy *config.Proxy
expectedLog string
setEnv bool
}{
{
name: "Test 1: No proxy config",
proxy: nil,
expectedLog: "Proxy configuration is not setup. Unable to configure proxy for OTLP exporter",
setEnv: false,
},
{
name: "Test 2: Malformed proxy URL",
proxy: &config.Proxy{URL: "://bad_url"},
expectedLog: "Malformed proxy URL, unable to configure proxy for OTLP exporter",
setEnv: false,
},
{
name: "Test 3: No auth, valid URL",
proxy: &config.Proxy{URL: "http://proxy.example.com:8080"},
expectedLog: "Setting Proxy from command.Proxy (no auth)",
setEnv: true,
},
{
name: "Basic auth, valid URL",
proxy: &config.Proxy{
URL: "http://proxy.example.com:8080",
AuthMethod: "basic",
Username: "user",
Password: "pass",
},
expectedLog: "Setting Proxy with basic auth",
setEnv: true,
},
{
name: "Unknown auth method",
proxy: &config.Proxy{URL: "http://proxy.example.com:8080", AuthMethod: "digest"},
expectedLog: "Unknown auth type for proxy, unable to configure proxy for OTLP exporter",
setEnv: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logBuf.Reset()

_ = os.Unsetenv("HTTPS_PROXY")

tmpDir := t.TempDir()
cfg := types.OTelConfig(t)
cfg.Collector.Log.Path = filepath.Join(tmpDir, "otel-collector-test.log")
cfg.Command.Server.Proxy = tt.proxy

// If the proxy is nil, the production code would never call the setter functions.
// added this check to prevent the panic error in UT.
if cfg.Command.Server.Proxy == nil {
// For the nil proxy case, we expect nothing to happen.
assert.Empty(t, os.Getenv("HTTPS_PROXY"))

return
}

collector, err := NewCollector(cfg)
require.NoError(t, err)

collector.setExporterProxyEnvVars(ctx)

helpers.ValidateLog(t, tt.expectedLog, logBuf)

if tt.setEnv {
assert.NotEmpty(t, os.Getenv("HTTPS_PROXY"))
} else {
assert.Empty(t, os.Getenv("HTTPS_PROXY"))
}
})
}
}

func TestCollector_findAvailableSyslogServers(t *testing.T) {
ctx := context.Background()
conf := types.OTelConfig(t)
Expand Down
Loading