From 1c83946299bc151314a2ae6f16429000c3d83858 Mon Sep 17 00:00:00 2001 From: Mischa Thompson Date: Wed, 1 Nov 2023 15:49:38 -0700 Subject: [PATCH] [Agent Management] Support External Labels from API (#5670) --- .../agent_management_remote_config_test.go | 74 +++++++++++++++++++ pkg/config/agentmanagement_remote_config.go | 26 ++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/pkg/config/agent_management_remote_config_test.go b/pkg/config/agent_management_remote_config_test.go index 64444f2f169a..887173411994 100644 --- a/pkg/config/agent_management_remote_config_test.go +++ b/pkg/config/agent_management_remote_config_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/grafana/agent/pkg/metrics/instance" + "github.com/prometheus/prometheus/model/labels" "github.com/stretchr/testify/require" ) @@ -179,4 +180,77 @@ integration_configs: require.Equal(t, true, c.Integrations.ConfigV1.ReplaceInstanceLabel) require.Equal(t, 5*time.Second, c.Integrations.ConfigV1.IntegrationRestartBackoff) }) + + t.Run("no external labels provided", func(t *testing.T) { + rc := RemoteConfig{ + BaseConfig: BaseConfigContent(baseConfig), + Snippets: allSnippets, + } + c, err := rc.BuildAgentConfig() + require.NoError(t, err) + require.Equal(t, 1, len(c.Logs.Configs)) + require.Empty(t, c.Metrics.Global.Prometheus.ExternalLabels) + }) + + t.Run("no external labels provided in remote config", func(t *testing.T) { + baseConfig := ` +server: + log_level: debug +metrics: + global: + external_labels: + foo: bar` + rc := RemoteConfig{ + BaseConfig: BaseConfigContent(baseConfig), + Snippets: allSnippets, + } + c, err := rc.BuildAgentConfig() + require.NoError(t, err) + require.Equal(t, 1, len(c.Logs.Configs)) + require.Equal(t, 1, len(c.Metrics.Global.Prometheus.ExternalLabels)) + require.Contains(t, c.Metrics.Global.Prometheus.ExternalLabels, labels.Label{Name: "foo", Value: "bar"}) + }) + + t.Run("external labels provided", func(t *testing.T) { + rc := RemoteConfig{ + BaseConfig: BaseConfigContent(baseConfig), + Snippets: allSnippets, + AgentMetadata: AgentMetadata{ + ExternalLabels: map[string]string{ + "foo": "bar", + }, + }, + } + c, err := rc.BuildAgentConfig() + require.NoError(t, err) + require.Equal(t, 1, len(c.Logs.Configs)) + require.Equal(t, 1, len(c.Metrics.Configs)) + require.Contains(t, c.Metrics.Global.Prometheus.ExternalLabels, labels.Label{Name: "foo", Value: "bar"}) + }) + + t.Run("external labels don't override base config", func(t *testing.T) { + baseConfig := ` +server: + log_level: debug +metrics: + global: + external_labels: + foo: bar +` + rc := RemoteConfig{ + BaseConfig: BaseConfigContent(baseConfig), + Snippets: allSnippets, + AgentMetadata: AgentMetadata{ + ExternalLabels: map[string]string{ + "foo": "baz", + }, + }, + } + c, err := rc.BuildAgentConfig() + require.NoError(t, err) + require.Equal(t, 1, len(c.Logs.Configs)) + require.Equal(t, 1, len(c.Metrics.Configs)) + require.Contains(t, c.Metrics.Global.Prometheus.ExternalLabels, labels.Label{Name: "foo", Value: "bar"}) + require.NotContains(t, c.Metrics.Global.Prometheus.ExternalLabels, labels.Label{Name: "foo", Value: "baz"}) + }) } diff --git a/pkg/config/agentmanagement_remote_config.go b/pkg/config/agentmanagement_remote_config.go index d40b70551da1..f5deeed9a47c 100644 --- a/pkg/config/agentmanagement_remote_config.go +++ b/pkg/config/agentmanagement_remote_config.go @@ -6,13 +6,15 @@ import ( "github.com/grafana/agent/pkg/metrics/instance" "github.com/grafana/loki/clients/pkg/promtail/scrapeconfig" pc "github.com/prometheus/prometheus/config" + "github.com/prometheus/prometheus/model/labels" "gopkg.in/yaml.v2" ) type ( RemoteConfig struct { - BaseConfig BaseConfigContent `json:"base_config" yaml:"base_config"` - Snippets []Snippet `json:"snippets" yaml:"snippets"` + BaseConfig BaseConfigContent `json:"base_config" yaml:"base_config"` + Snippets []Snippet `json:"snippets" yaml:"snippets"` + AgentMetadata AgentMetadata `json:"agent_metadata,omitempty" yaml:"agent_metadata,omitempty"` } // BaseConfigContent is the content of a base config @@ -24,6 +26,10 @@ type ( Config string `json:"config" yaml:"config"` } + AgentMetadata struct { + ExternalLabels map[string]string `json:"external_labels,omitempty" yaml:"external_labels,omitempty"` + } + // SnippetContent defines the internal structure of a snippet configuration. SnippetContent struct { // MetricsScrapeConfigs is a YAML containing list of metrics scrape configs. @@ -63,6 +69,7 @@ func (rc *RemoteConfig) BuildAgentConfig() (*Config, error) { if err != nil { return nil, err } + appendExternalLabels(&c, rc.AgentMetadata.ExternalLabels) return &c, nil } @@ -116,3 +123,18 @@ func appendSnippets(c *Config, snippets []Snippet) error { c.Integrations.ConfigV1.Integrations = append(c.Integrations.ConfigV1.Integrations, integrationConfigs.Integrations...) return nil } + +func appendExternalLabels(c *Config, externalLabels map[string]string) { + // Avoid doing anything if there are no external labels + if len(externalLabels) == 0 { + return + } + // Start off with the existing external labels, which will only be added to (not replaced) + newExternalLabels := c.Metrics.Global.Prometheus.ExternalLabels.Map() + for k, v := range externalLabels { + if _, ok := newExternalLabels[k]; !ok { + newExternalLabels[k] = v + } + } + c.Metrics.Global.Prometheus.ExternalLabels = labels.FromMap(newExternalLabels) +}