diff --git a/CHANGELOG.md b/CHANGELOG.md index dd04b6ead8..7e29e7de0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,30 @@ This document contains a historical list of changes between releases. Only changes that impact end-user behavior are listed; changes to documentation or internal API changes are not present. +v1.1.1 +------ + +### Bugfixes + +- Fix panic when component ID contains `/` in `otelcomponent.MustNewType(ID)`.(@qclaogui) + +- Exit Alloy immediately if the port it runs on is not available. + This port can be configured with `--server.http.listen-addr` or using + the default listen address`127.0.0.1:12345`. (@mattdurham) + +- Fix a panic in `loki.source.docker` when trying to stop a target that was never started. (@wildum) + +- Fix error on boot when using IPv6 advertise addresses without explicitly + specifying a port. (@matthewpi) + +- Fix an issue where having long component labels (>63 chars) on otelcol.auth + components lead to a panic. (@tpaschalis) + +- Update `prometheus.exporter.snowflake` with the [latest](https://github.com/grafana/snowflake-prometheus-exporter) version of the exporter as of May 28, 2024 (@StefanKurek) + - Fixes issue where returned `NULL` values from database could cause unexpected errors. + +- Bubble up SSH key conversion error to facilitate failed `import.git`. (@hainenber) + v1.1.0 ------ diff --git a/go.mod b/go.mod index 8fe5d40b3d..4b6c4b59bb 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( github.com/grafana/pyroscope/api v0.4.0 github.com/grafana/pyroscope/ebpf v0.4.7 github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db - github.com/grafana/snowflake-prometheus-exporter v0.0.0-20221213150626-862cad8e9538 + github.com/grafana/snowflake-prometheus-exporter v0.0.0-20240524135656-12b7c9be6cbf github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 github.com/grafana/vmware_exporter v0.0.5-beta github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect @@ -644,7 +644,7 @@ require ( github.com/xo/dburl v0.20.0 // indirect github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 // indirect github.com/yl2chen/cidranger v1.0.2 // indirect - github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.etcd.io/etcd/api/v3 v3.5.10 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect diff --git a/go.sum b/go.sum index 81107247e8..2298ddbc3c 100644 --- a/go.sum +++ b/go.sum @@ -1080,8 +1080,8 @@ github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzed github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/grafana/smimesign v0.2.1-0.20220408144937-2a5adf3481d3 h1:UPkAxuhlAcRmJT3/qd34OMTl+ZU7BLLfOO2+NXBlJpY= github.com/grafana/smimesign v0.2.1-0.20220408144937-2a5adf3481d3/go.mod h1:iZiiwNT4HbtGRVqCQu7uJPEZCuEE5sfSSttcnePkDl4= -github.com/grafana/snowflake-prometheus-exporter v0.0.0-20221213150626-862cad8e9538 h1:tkT0yha3JzB5S5VNjfY4lT0cJAe20pU8XGt3Nuq73rM= -github.com/grafana/snowflake-prometheus-exporter v0.0.0-20221213150626-862cad8e9538/go.mod h1:VxVydRyq8f6w1qmX/5MSYIdSbgujre8rdFRLgU6u/RI= +github.com/grafana/snowflake-prometheus-exporter v0.0.0-20240524135656-12b7c9be6cbf h1:272jCM4WJtrv4JsVTyjSlzRavZRur60rBGgaCKQOK5k= +github.com/grafana/snowflake-prometheus-exporter v0.0.0-20240524135656-12b7c9be6cbf/go.mod h1:DANNLd5vSKqHloqNX4yeS+9ZRI89dj8ySZeEWlI5UU4= github.com/grafana/stackdriver_exporter v0.0.0-20240228143257-3a2c9acef5a2 h1:xBGGPnQyQNK0Apz269BZoKTnFxKKxYhhXzI++N2phE0= github.com/grafana/stackdriver_exporter v0.0.0-20240228143257-3a2c9acef5a2/go.mod h1:Ce7MjYSAUzZZeFb5jBNqSUUZ45w5IMdnNEKfz3jJRos= github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU= @@ -2263,8 +2263,9 @@ github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryB github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 h1:tBiBTKHnIjovYoLX/TPkcf+OjqqKGQrPtGT3Foz+Pgo= +github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76/go.mod h1:SQliXeA7Dhkt//vS29v3zpbEwoa+zb2Cn5xj5uO4K5U= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/alloycli/cluster_builder.go b/internal/alloycli/cluster_builder.go index d97f9352a8..2847d7a636 100644 --- a/internal/alloycli/cluster_builder.go +++ b/internal/alloycli/cluster_builder.go @@ -45,7 +45,6 @@ func buildClusterService(opts clusterOptions) (*cluster.Service, error) { EnableClustering: opts.EnableClustering, NodeName: opts.NodeName, - AdvertiseAddress: opts.AdvertiseAddress, RejoinInterval: opts.RejoinInterval, ClusterMaxJoinPeers: opts.ClusterMaxJoinPeers, ClusterName: opts.ClusterName, @@ -59,28 +58,10 @@ func buildClusterService(opts clusterOptions) (*cluster.Service, error) { config.NodeName = hostname } - if config.AdvertiseAddress == "" { - advertiseAddress := fmt.Sprintf("%s:%d", net.ParseIP("127.0.0.1"), listenPort) - if opts.EnableClustering { - advertiseInterfaces := opts.AdvertiseInterfaces - if useAllInterfaces(advertiseInterfaces) { - advertiseInterfaces = nil - } - addr, err := advertise.FirstAddress(advertiseInterfaces) - if err != nil { - level.Warn(opts.Log).Log("msg", "could not find advertise address using network interfaces", opts.AdvertiseInterfaces, - "falling back to localhost", "err", err) - } else if addr.Is4() { - advertiseAddress = fmt.Sprintf("%s:%d", addr.String(), listenPort) - } else if addr.Is6() { - advertiseAddress = fmt.Sprintf("[%s]:%d", addr.String(), listenPort) - } else { - return nil, fmt.Errorf("type unknown for address: %s", addr.String()) - } - } - config.AdvertiseAddress = advertiseAddress - } else { - config.AdvertiseAddress = appendDefaultPort(config.AdvertiseAddress, listenPort) + var err error + config.AdvertiseAddress, err = getAdvertiseAddress(opts, listenPort) + if err != nil { + return nil, err } switch { @@ -110,6 +91,29 @@ func useAllInterfaces(interfaces []string) bool { return len(interfaces) == 1 && interfaces[0] == "all" } +func getAdvertiseAddress(opts clusterOptions, listenPort int) (string, error) { + if opts.AdvertiseAddress != "" { + return appendDefaultPort(opts.AdvertiseAddress, listenPort), nil + } + advertiseAddress := net.JoinHostPort("127.0.0.1", strconv.Itoa(listenPort)) + if opts.EnableClustering { + advertiseInterfaces := opts.AdvertiseInterfaces + if useAllInterfaces(advertiseInterfaces) { + advertiseInterfaces = nil + } + addr, err := advertise.FirstAddress(advertiseInterfaces) + if err != nil { + level.Warn(opts.Log).Log("msg", "could not find advertise address using network interfaces", opts.AdvertiseInterfaces, + "falling back to localhost", "err", err) + } else if !addr.Is4() && !addr.Is6() { + return "", fmt.Errorf("type unknown for address: %s", addr.String()) + } else { + advertiseAddress = net.JoinHostPort(addr.String(), strconv.Itoa(listenPort)) + } + } + return advertiseAddress, nil +} + func findPort(addr string, defaultPort int) int { _, portStr, err := net.SplitHostPort(addr) if err != nil { @@ -128,7 +132,7 @@ func appendDefaultPort(addr string, port int) string { // No error means there was a port in the string return addr } - return fmt.Sprintf("%s:%d", addr, port) + return net.JoinHostPort(addr, strconv.Itoa(port)) } type discoverFunc func() ([]string, error) diff --git a/internal/alloycli/cluster_builder_test.go b/internal/alloycli/cluster_builder_test.go index 2da42c6633..9d9622e68e 100644 --- a/internal/alloycli/cluster_builder_test.go +++ b/internal/alloycli/cluster_builder_test.go @@ -3,6 +3,7 @@ package alloycli import ( "testing" + "github.com/go-kit/log" "github.com/stretchr/testify/require" ) @@ -16,3 +17,40 @@ func TestBuildClusterService(t *testing.T) { require.Nil(t, cs) require.EqualError(t, err, "at most one of join peers and discover peers may be set") } + +func TestGetAdvertiseAddress(t *testing.T) { + // This tests that an IPv4 advertise address is properly joined to it's port. + t.Run("IPv4", func(t *testing.T) { + opts := clusterOptions{ + AdvertiseAddress: "1.1.1.1", + } + + addr, err := getAdvertiseAddress(opts, 80) + require.Nil(t, err) + require.Equal(t, "1.1.1.1:80", addr) + }) + + // This tests that an IPv6 advertise address is properly joined to it's port. + t.Run("IPv6", func(t *testing.T) { + opts := clusterOptions{ + AdvertiseAddress: "2606:4700:4700::1111", + } + + addr, err := getAdvertiseAddress(opts, 80) + require.Nil(t, err) + require.Equal(t, "[2606:4700:4700::1111]:80", addr) + }) + + // This tests the loopback fallback. + t.Run("loopback Fallback", func(t *testing.T) { + opts := clusterOptions{ + Log: log.NewNopLogger(), + EnableClustering: true, + AdvertiseInterfaces: []string{"lo"}, + } + + addr, err := getAdvertiseAddress(opts, 80) + require.Nil(t, err) + require.Equal(t, "127.0.0.1:80", addr) + }) +} diff --git a/internal/component/loki/source/docker/docker_test.go b/internal/component/loki/source/docker/docker_test.go index 96d847a66c..bf5453ad43 100644 --- a/internal/component/loki/source/docker/docker_test.go +++ b/internal/component/loki/source/docker/docker_test.go @@ -125,6 +125,22 @@ func TestRestart(t *testing.T) { }, time.Second, 20*time.Millisecond, "Expected log lines were not found within the time limit after restart.") } +func TestTargetNeverStarted(t *testing.T) { + runningState := false + client := clientMock{ + logLine: "2024-05-02T13:11:55.879889Z caller=module_service.go:114 msg=\"module stopped\" module=distributor", + running: func() bool { return runningState }, + } + + tailer, _ := setupTailer(t, client) + ctx, cancel := context.WithCancel(context.Background()) + go tailer.Run(ctx) + + time.Sleep(20 * time.Millisecond) + + require.NotPanics(t, func() { cancel() }) +} + func setupTailer(t *testing.T, client clientMock) (tailer *tailer, entryHandler *fake.Client) { w := log.NewSyncWriter(os.Stderr) logger := log.NewLogfmtLogger(w) diff --git a/internal/component/loki/source/docker/internal/dockertarget/target.go b/internal/component/loki/source/docker/internal/dockertarget/target.go index 02d48b31c9..012047c1ea 100644 --- a/internal/component/loki/source/docker/internal/dockertarget/target.go +++ b/internal/component/loki/source/docker/internal/dockertarget/target.go @@ -235,9 +235,11 @@ func (t *Target) StartIfNotRunning() { // Stop shuts down the target. func (t *Target) Stop() { - t.cancel() - t.wg.Wait() - level.Debug(t.logger).Log("msg", "stopped Docker target", "container", t.containerName) + if t.Ready() { + t.cancel() + t.wg.Wait() + level.Debug(t.logger).Log("msg", "stopped Docker target", "container", t.containerName) + } } // Ready reports whether the target is running. diff --git a/internal/component/otelcol/auth/auth.go b/internal/component/otelcol/auth/auth.go index 63adfa6c38..a404962806 100644 --- a/internal/component/otelcol/auth/auth.go +++ b/internal/component/otelcol/auth/auth.go @@ -7,6 +7,8 @@ package auth import ( "context" + "fmt" + "hash/fnv" "os" "strings" @@ -168,7 +170,7 @@ func (a *Auth) Update(args component.Arguments) error { components = append(components, ext) } - cTypeStr := strings.ReplaceAll(a.opts.ID, ".", "_") + cTypeStr := NormalizeType(a.opts.ID) // Inform listeners that our handler changed. a.opts.OnStateChange(Exports{ @@ -187,3 +189,19 @@ func (a *Auth) Update(args component.Arguments) error { func (a *Auth) CurrentHealth() component.Health { return a.sched.CurrentHealth() } + +func getHash(in string) string { + fnvHash := fnv.New32() + fnvHash.Write([]byte(in)) + return fmt.Sprintf("%x", fnvHash.Sum(nil)) +} + +func NormalizeType(in string) string { + res := strings.ReplaceAll(strings.ReplaceAll(in, ".", "_"), "/", "_") + + if len(res) > 63 { + res = res[:40] + getHash(res) + } + + return res +} diff --git a/internal/component/otelcol/auth/auth_test.go b/internal/component/otelcol/auth/auth_test.go index 3647c91cbe..6784afd36a 100644 --- a/internal/component/otelcol/auth/auth_test.go +++ b/internal/component/otelcol/auth/auth_test.go @@ -2,6 +2,7 @@ package auth_test import ( "context" + "regexp" "testing" "time" @@ -93,3 +94,32 @@ func (fa fakeAuthArgs) Extensions() map[otelcomponent.ID]otelextension.Extension func (fa fakeAuthArgs) Exporters() map[otelcomponent.DataType]map[otelcomponent.ID]otelcomponent.Component { return nil } + +func TestNormalizeType(t *testing.T) { + type tc struct { + input string + expected string + } + testcases := []tc{ + {"foo", "foo"}, + {"foo1", "foo1"}, + {"fooBar", "fooBar"}, + + {"foo.bar", "foo_bar"}, + {"foo/bar", "foo_bar"}, + {"foo.bar/baz", "foo_bar_baz"}, + {"foo/bar_baz", "foo_bar_baz"}, + + {"thisStringsConstructedSoThatItsLengthIsSetToBeAtSixtyThreeChars", "thisStringsConstructedSoThatItsLengthIsSetToBeAtSixtyThreeChars"}, + {"whileThisOneHeresConstructedSoThatItsSizeIsEqualToSixtyFourChars", "whileThisOneHeresConstructedSoThatItsSiz2d7fa5d2"}, + } + + // https://github.com/open-telemetry/opentelemetry-collector/blob/e09b25f7d1b4090a9b7b73ef7d3c514592331554/component/config.go#L127-L131 + var typeRegexp = regexp.MustCompile(`^[a-zA-Z][0-9a-zA-Z_]{0,62}$`) + + for _, tt := range testcases { + actual := auth.NormalizeType(tt.input) + require.Equal(t, tt.expected, actual) + require.True(t, typeRegexp.MatchString(actual)) + } +} diff --git a/internal/component/otelcol/receiver/prometheus/prometheus.go b/internal/component/otelcol/receiver/prometheus/prometheus.go index fcdf7de4cd..0b2d48e23e 100644 --- a/internal/component/otelcol/receiver/prometheus/prometheus.go +++ b/internal/component/otelcol/receiver/prometheus/prometheus.go @@ -5,7 +5,6 @@ import ( "context" "os" "regexp" - "strings" "sync" "time" @@ -115,12 +114,7 @@ func (c *Component) Update(newConfig component.Arguments) error { gcInterval = 5 * time.Minute ) - cTypeStr := strings.ReplaceAll(c.opts.ID, ".", "_") - settings := otelreceiver.CreateSettings{ - - ID: otelcomponent.NewID(otelcomponent.MustNewType(cTypeStr)), - TelemetrySettings: otelcomponent.TelemetrySettings{ Logger: zapadapter.New(c.opts.Logger), diff --git a/internal/service/http/http.go b/internal/service/http/http.go index 155470753c..b20d265c56 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -8,6 +8,7 @@ import ( "net" "net/http" _ "net/http/pprof" // Register pprof handlers + "os" "path" "sort" "strings" @@ -151,7 +152,9 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { netLis, err := net.Listen("tcp", s.opts.HTTPListenAddr) if err != nil { - return fmt.Errorf("failed to listen on %s: %w", s.opts.HTTPListenAddr, err) + // There is no recovering from failing to listen on the port. + level.Error(s.log).Log("msg", fmt.Sprintf("failed to listen on %s", s.opts.HTTPListenAddr), "err", err) + os.Exit(1) } if err := s.tcpLis.SetInner(netLis); err != nil { return fmt.Errorf("failed to use listener: %w", err) diff --git a/internal/vcs/auth.go b/internal/vcs/auth.go index b9d0610734..7e8960a4c5 100644 --- a/internal/vcs/auth.go +++ b/internal/vcs/auth.go @@ -16,20 +16,18 @@ type GitAuthConfig struct { // Convert converts HTTPClientConfig to the native Prometheus type. If h is // nil, the default client config is returned. -func (h *GitAuthConfig) Convert() transport.AuthMethod { +func (h *GitAuthConfig) Convert() (transport.AuthMethod, error) { if h == nil { - return nil + return nil, nil } - if h.BasicAuth != nil { - return h.BasicAuth.Convert() + return h.BasicAuth.Convert(), nil } if h.SSHKey != nil { - key, _ := h.SSHKey.Convert() - return key + return h.SSHKey.Convert() } - return nil + return nil, nil } type BasicAuth struct { diff --git a/internal/vcs/git.go b/internal/vcs/git.go index 41bd209490..fead62c611 100644 --- a/internal/vcs/git.go +++ b/internal/vcs/git.go @@ -40,11 +40,19 @@ func NewGitRepo(ctx context.Context, storagePath string, opts GitRepoOptions) (* err error ) + authConfig, err := opts.Auth.Convert() + if err != nil { + return nil, DownloadFailedError{ + Repository: opts.Repository, + Inner: err, + } + } + if !isRepoCloned(storagePath) { repo, err = git.PlainCloneContext(ctx, storagePath, false, &git.CloneOptions{ URL: opts.Repository, ReferenceName: plumbing.HEAD, - Auth: opts.Auth.Convert(), + Auth: authConfig, RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, Tags: git.AllTags, }) @@ -69,7 +77,7 @@ func NewGitRepo(ctx context.Context, storagePath string, opts GitRepoOptions) (* pullRepoErr := wt.PullContext(ctx, &git.PullOptions{ RemoteName: "origin", Force: true, - Auth: opts.Auth.Convert(), + Auth: authConfig, }) if pullRepoErr != nil && !errors.Is(pullRepoErr, git.NoErrAlreadyUpToDate) { workTree, err := repo.Worktree() @@ -109,10 +117,18 @@ func isRepoCloned(dir string) bool { // Update updates the repository by pulling new content and re-checking out to // latest version of Revision. func (repo *GitRepo) Update(ctx context.Context) error { + authConfig, err := repo.opts.Auth.Convert() + if err != nil { + return UpdateFailedError{ + Repository: repo.opts.Repository, + Inner: err, + } + } + pullRepoErr := repo.workTree.PullContext(ctx, &git.PullOptions{ RemoteName: "origin", Force: true, - Auth: repo.opts.Auth.Convert(), + Auth: authConfig, }) if pullRepoErr != nil && !errors.Is(pullRepoErr, git.NoErrAlreadyUpToDate) { return UpdateFailedError{