Path: blob/main/pkg/operator/config/logs_templates_test.go
4095 views
package config12import (3"fmt"4"strings"5"testing"67jsonnet "github.com/google/go-jsonnet"8prom "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"9prom_v1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"10"github.com/stretchr/testify/require"11v1 "k8s.io/api/core/v1"12meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"13metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"1415gragent "github.com/grafana/agent/pkg/operator/apis/monitoring/v1alpha1"16"github.com/grafana/agent/pkg/operator/assets"17"github.com/grafana/agent/pkg/util"18)1920func TestLogsClientConfig(t *testing.T) {21agent := &gragent.GrafanaAgent{22ObjectMeta: metav1.ObjectMeta{23Namespace: "telemetry",24Name: "grafana-agent",25},26}2728tt := []struct {29name string30input map[string]interface{}31expect string32}{33{34name: "all-in-one URL",35input: map[string]interface{}{36"agent": agent,37"namespace": "operator",38"spec": &gragent.LogsClientSpec{39URL: "http://username:password@localhost:3100/loki/api/v1/push",40},41},42expect: util.Untab(`43url: http://username:password@localhost:3100/loki/api/v1/push44external_labels:45cluster: telemetry/grafana-agent46`),47},48{49name: "full basic config",50input: map[string]interface{}{51"agent": agent,52"namespace": "operator",53"spec": &gragent.LogsClientSpec{54URL: "http://localhost:3100/loki/api/v1/push",55TenantID: "tenant",56BatchWait: "5m",57BatchSize: 500,58Timeout: "5m",59ExternalLabels: map[string]string{60"foo": "bar",61"fizz": "buzz",62},63ProxyURL: "http://proxy:3100/",64BackoffConfig: &gragent.LogsBackoffConfigSpec{65MinPeriod: "500ms",66MaxPeriod: "5m",67MaxRetries: 100,68},69},70},71expect: util.Untab(`72url: http://localhost:3100/loki/api/v1/push73tenant_id: tenant74batchwait: 5m75batchsize: 50076proxy_url: http://proxy:3100/77backoff_config:78min_period: 500ms79max_period: 5m80max_retries: 10081external_labels:82cluster: telemetry/grafana-agent83foo: bar84fizz: buzz85timeout: 5m86`),87},88{89name: "tls config",90input: map[string]interface{}{91"agent": agent,92"namespace": "operator",93"spec": &gragent.LogsClientSpec{94URL: "http://localhost:3100/loki/api/v1/push",95TLSConfig: &prom.TLSConfig{96CAFile: "ca",97KeyFile: "key",98CertFile: "cert",99},100},101},102expect: util.Untab(`103url: http://localhost:3100/loki/api/v1/push104tls_config:105ca_file: ca106key_file: key107cert_file: cert108external_labels:109cluster: telemetry/grafana-agent110`),111},112{113name: "bearer tokens",114input: map[string]interface{}{115"agent": agent,116"namespace": "operator",117"spec": &gragent.LogsClientSpec{118URL: "http://localhost:3100/loki/api/v1/push",119BearerToken: "tok",120BearerTokenFile: "tokfile",121},122},123expect: util.Untab(`124url: http://localhost:3100/loki/api/v1/push125bearer_token: tok126bearer_token_file: tokfile127external_labels:128cluster: telemetry/grafana-agent129`),130},131{132name: "basic auth",133input: map[string]interface{}{134"agent": agent,135"namespace": "operator",136"spec": &gragent.LogsClientSpec{137URL: "http://localhost:3100/loki/api/v1/push",138BasicAuth: &prom.BasicAuth{139Username: v1.SecretKeySelector{140LocalObjectReference: v1.LocalObjectReference{Name: "obj"},141Key: "key",142},143Password: v1.SecretKeySelector{144LocalObjectReference: v1.LocalObjectReference{Name: "obj"},145Key: "key",146},147},148},149},150expect: util.Untab(`151url: http://localhost:3100/loki/api/v1/push152basic_auth:153username: secretkey154password: secretkey155external_labels:156cluster: telemetry/grafana-agent157`),158},159}160161for _, tc := range tt {162t.Run(tc.name, func(t *testing.T) {163vm, err := createVM(testStore())164require.NoError(t, err)165166actual, err := runSnippetTLA(t, vm, "./component/logs/client.libsonnet", tc.input)167require.NoError(t, err)168require.YAMLEq(t, tc.expect, actual)169})170}171}172173func TestLogsStages(t *testing.T) {174tt := []struct {175name string176input map[string]interface{}177expect string178}{179{180name: "docker",181input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{182Docker: &gragent.DockerStageSpec{},183}},184expect: util.Untab(`docker: {}`),185},186{187name: "cri",188input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{189CRI: &gragent.CRIStageSpec{},190}},191expect: util.Untab(`cri: {}`),192},193{194name: "regex",195input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{196Regex: &gragent.RegexStageSpec{197Source: "time",198Expression: "^(?P<year>\\d+)",199},200}},201expect: util.Untab(`202regex:203expression: '^(?P<year>\d+)'204source: time205`),206},207{208name: "json",209input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{210JSON: &gragent.JSONStageSpec{211Expressions: map[string]string{"user": ""},212Source: "extra",213},214}},215expect: util.Untab(`216json:217expressions:218user: ""219source: extra220`),221},222{223name: "labelallow",224input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{225LabelAllow: []string{"foo", "bar"},226}},227expect: util.Untab(`228labelallow: [foo, bar]229`),230},231{232name: "labeldrop",233input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{234LabelDrop: []string{"foo", "bar"},235}},236expect: util.Untab(`237labeldrop: [foo, bar]238`),239},240{241name: "labels",242input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{243Labels: map[string]string{244"foo": "",245"fizz": "buzz",246},247}},248expect: util.Untab(`249labels:250foo: ""251fizz: buzz252`),253},254{255name: "limit",256input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{257Limit: &gragent.LimitStageSpec{258Rate: 10,259Burst: 20,260Drop: false,261},262}},263expect: util.Untab(`264limit:265rate: 10266burst: 20267drop: false268`),269},270{271name: "match",272input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{273Match: &gragent.MatchStageSpec{274PipelineName: "app2",275Selector: `{app="pokey"}`,276Action: "keep",277DropCounterReason: "no_pokey",278Stages: util.Untab(`279- json:280expressions:281msg: msg282`),283},284}},285expect: util.Untab(`286match:287pipeline_name: app2288selector: '{app="pokey"}'289action: keep290drop_counter_reason: no_pokey291stages:292- json:293expressions:294msg: msg295`),296},297{298name: "metrics",299input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{300Metrics: map[string]gragent.MetricsStageSpec{301"logs_line_total": {302Type: "counter",303Description: "total number of log lines",304Prefix: "my_promtail_custom_",305MaxIdleDuration: "24h",306MatchAll: boolPtr(true),307Action: "inc",308},309"queue_elements": {310Type: "gauge",311Description: "elements in queue",312Action: "add",313},314"http_response_time_seconds": {315Type: "histogram",316Source: "response_time",317Action: "inc",318Buckets: []string{"0.001", "0.0025", "0.050"},319},320},321}},322expect: util.Untab(`323metrics:324logs_line_total:325type: Counter326description: total number of log lines327prefix: my_promtail_custom_328max_idle_duration: 24h329config:330match_all: true331action: inc332queue_elements:333type: Gauge334description: elements in queue335config:336action: add337http_response_time_seconds:338type: Histogram339source: response_time340config:341action: inc342buckets: [0.001, 0.0025, 0.050]343`),344},345{346name: "multiline",347input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{348Multiline: &gragent.MultilineStageSpec{349FirstLine: "first",350MaxWaitTime: "5m",351MaxLines: 5,352},353}},354expect: util.Untab(`355multiline:356firstline: first357max_wait_time: 5m358max_lines: 5359`),360},361{362name: "output",363input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{364Output: &gragent.OutputStageSpec{Source: "message"},365}},366expect: util.Untab(`367output:368source: message369`),370},371{372name: "pack",373input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{374Pack: &gragent.PackStageSpec{375Labels: []string{"foo", "bar"},376IngestTimestamp: true,377},378}},379expect: util.Untab(`380pack:381labels: [foo, bar]382ingest_timestamp: true383`),384},385{386name: "regex",387input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{388Regex: &gragent.RegexStageSpec{389Source: "msg",390Expression: "some regex",391},392}},393expect: util.Untab(`394regex:395source: msg396expression: some regex397`),398},399{400name: "replace",401input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{402Replace: &gragent.ReplaceStageSpec{403Expression: "password (\\S+)",404Replace: "****",405Source: "msg",406},407}},408expect: util.Untab(`409replace:410expression: 'password (\S+)'411replace: '****'412source: msg413`),414},415{416name: "template",417input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{418Template: &gragent.TemplateStageSpec{419Source: "new_key",420Template: "hello world!",421},422}},423expect: util.Untab(`424template:425source: new_key426template: "hello world!"427`),428},429{430name: "tenant",431input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{432Tenant: &gragent.TenantStageSpec{433Label: "__meta_kubernetes_pod_label_fake",434Source: "customer_id",435Value: "fake",436},437}},438expect: util.Untab(`439tenant:440label: __meta_kubernetes_pod_label_fake441source: customer_id442value: fake443`),444},445{446name: "timestamp",447input: map[string]interface{}{"spec": &gragent.PipelineStageSpec{448Timestamp: &gragent.TimestampStageSpec{449Source: "time",450Format: "RFC3339Nano",451FallbackFormats: []string{"UnixNs"},452Location: "America/New_York",453ActionOnFailure: "fudge",454},455}},456expect: util.Untab(`457timestamp:458source: time459format: RFC3339Nano460fallback_formats: [UnixNs]461location: America/New_York462action_on_failure: fudge463`),464},465}466467for _, tc := range tt {468t.Run(tc.name, func(t *testing.T) {469vm, err := createVM(testStore())470require.NoError(t, err)471472actual, err := runSnippetTLA(t, vm, "./component/logs/stages.libsonnet", tc.input)473require.NoError(t, err)474require.YAMLEq(t, tc.expect, actual)475})476}477}478479func TestPodLogsConfig(t *testing.T) {480tt := []struct {481name string482input map[string]interface{}483expect string484}{485{486name: "default",487input: map[string]interface{}{488"agentNamespace": "operator",489"podLogs": gragent.PodLogs{490ObjectMeta: meta_v1.ObjectMeta{491Namespace: "operator",492Name: "podlogs",493},494Spec: gragent.PodLogsSpec{495RelabelConfigs: []*prom_v1.RelabelConfig{{496SourceLabels: []prom.LabelName{"input_a", "input_b"},497Separator: ";",498TargetLabel: "target_a",499Regex: "regex",500Modulus: 1234,501Replacement: "foobar",502Action: "replace",503}},504},505},506"apiServer": prom_v1.APIServerConfig{},507"ignoreNamespaceSelectors": false,508"enforcedNamespaceLabel": "",509},510expect: util.Untab(`511job_name: podLogs/operator/podlogs512kubernetes_sd_configs:513- role: pod514namespaces:515names: [operator]516relabel_configs:517- source_labels: [job]518target_label: __tmp_prometheus_job_name519- source_labels: [__meta_kubernetes_namespace]520target_label: namespace521- source_labels: [__meta_kubernetes_service_name]522target_label: service523- source_labels: [__meta_kubernetes_pod_name]524target_label: pod525- source_labels: [__meta_kubernetes_pod_container_name]526target_label: container527- target_label: job528replacement: operator/podlogs529- source_labels: ['__meta_kubernetes_pod_uid', '__meta_kubernetes_pod_container_name']530target_label: __path__531separator: /532replacement: /var/log/pods/*$1/*.log533- source_labels: ["input_a", "input_b"]534separator: ";"535target_label: "target_a"536regex: regex537modulus: 1234538replacement: foobar539action: replace540`),541},542}543544for _, tc := range tt {545t.Run(tc.name, func(t *testing.T) {546vm, err := createVM(testStore())547require.NoError(t, err)548549actual, err := runSnippetTLA(t, vm, "./component/logs/pod_logs.libsonnet", tc.input)550require.NoError(t, err)551require.YAMLEq(t, tc.expect, actual)552})553}554}555556func TestLogsConfig(t *testing.T) {557agent := &gragent.GrafanaAgent{558ObjectMeta: metav1.ObjectMeta{559Namespace: "operator",560Name: "grafana-agent",561},562}563564tt := []struct {565name string566input map[string]interface{}567expect string568}{569{570name: "global clients",571input: map[string]interface{}{572"agent": agent,573"global": &gragent.LogsSubsystemSpec{574Clients: []gragent.LogsClientSpec{{URL: "global"}},575},576"instance": &gragent.LogsDeployment{577Instance: &gragent.LogsInstance{578ObjectMeta: metav1.ObjectMeta{579Namespace: "inst",580Name: "default",581},582Spec: gragent.LogsInstanceSpec{},583},584},585"apiServer": &prom.APIServerConfig{},586587"ignoreNamespaceSelectors": false,588"enforcedNamespaceLabel": "",589},590expect: util.Untab(`591name: inst/default592clients:593- url: global594external_labels:595cluster: operator/grafana-agent596`),597},598{599name: "local clients",600input: map[string]interface{}{601"agent": agent,602"global": &gragent.LogsSubsystemSpec{603Clients: []gragent.LogsClientSpec{{URL: "global"}},604},605"instance": &gragent.LogsDeployment{606Instance: &gragent.LogsInstance{607ObjectMeta: metav1.ObjectMeta{608Namespace: "inst",609Name: "default",610},611Spec: gragent.LogsInstanceSpec{612Clients: []gragent.LogsClientSpec{{URL: "local"}},613},614},615},616"apiServer": &prom.APIServerConfig{},617618"ignoreNamespaceSelectors": false,619"enforcedNamespaceLabel": "",620},621expect: util.Untab(`622name: inst/default623clients:624- url: local625external_labels:626cluster: operator/grafana-agent627`),628},629{630name: "pod logs",631input: map[string]interface{}{632"agent": agent,633"global": &gragent.LogsSubsystemSpec{},634"instance": &gragent.LogsDeployment{635Instance: &gragent.LogsInstance{636ObjectMeta: metav1.ObjectMeta{Namespace: "inst", Name: "default"},637},638PodLogs: []*gragent.PodLogs{{639ObjectMeta: metav1.ObjectMeta{Namespace: "app", Name: "pod"},640}},641},642"apiServer": &prom.APIServerConfig{},643644"ignoreNamespaceSelectors": false,645"enforcedNamespaceLabel": "",646},647expect: util.Untab(`648name: inst/default649scrape_configs:650- job_name: podLogs/app/pod651kubernetes_sd_configs:652- namespaces:653names:654- app655role: pod656relabel_configs:657- source_labels:658- job659target_label: __tmp_prometheus_job_name660- source_labels:661- __meta_kubernetes_namespace662target_label: namespace663- source_labels:664- __meta_kubernetes_service_name665target_label: service666- source_labels:667- __meta_kubernetes_pod_name668target_label: pod669- source_labels:670- __meta_kubernetes_pod_container_name671target_label: container672- replacement: app/pod673target_label: job674- source_labels: ['__meta_kubernetes_pod_uid', '__meta_kubernetes_pod_container_name']675target_label: __path__676separator: /677replacement: /var/log/pods/*$1/*.log678`),679},680{681name: "additional scrape configs",682input: map[string]interface{}{683"agent": agent,684"global": &gragent.LogsSubsystemSpec{},685"instance": &gragent.LogsDeployment{686Instance: &gragent.LogsInstance{687ObjectMeta: metav1.ObjectMeta{Namespace: "inst", Name: "default"},688Spec: gragent.LogsInstanceSpec{689AdditionalScrapeConfigs: &v1.SecretKeySelector{690LocalObjectReference: v1.LocalObjectReference{Name: "additional"},691Key: "configs",692},693},694},695},696"apiServer": &prom.APIServerConfig{},697698"ignoreNamespaceSelectors": false,699"enforcedNamespaceLabel": "",700},701expect: util.Untab(`702name: inst/default703scrape_configs:704- job_name: extra705`),706},707}708709for _, tc := range tt {710t.Run(tc.name, func(t *testing.T) {711s := testStore()712713s[assets.KeyForSecret("inst", &v1.SecretKeySelector{714LocalObjectReference: v1.LocalObjectReference{715Name: "additional",716},717Key: "configs",718})] = `[{ "job_name": "extra" }]`719720vm, err := createVM(s)721require.NoError(t, err)722723actual, err := runSnippetTLA(t, vm, "./logs.libsonnet", tc.input)724require.NoError(t, err)725require.YAMLEq(t, tc.expect, actual)726})727}728}729730func runSnippetTLA(t *testing.T, vm *jsonnet.VM, filename string, tla map[string]interface{}) (string, error) {731t.Helper()732733args := make([]string, 0, len(tla))734for arg := range tla {735args = append(args, arg)736}737738boundArgs := make([]string, len(args))739for i := range args {740boundArgs[i] = fmt.Sprintf("%[1]s=%[1]s", args[i])741}742743// Bind argument to TLA.744for arg, value := range tla {745bb, err := jsonnetMarshal(value)746require.NoError(t, err)747vm.TLACode(arg, string(bb))748}749750return vm.EvaluateAnonymousSnippet(751filename,752fmt.Sprintf(`753local marshal = import './ext/marshal.libsonnet';754local optionals = import './ext/optionals.libsonnet';755local eval = import '%s';756function(%s) marshal.YAML(optionals.trim(eval(%s)))757`, filename, strings.Join(args, ","), strings.Join(boundArgs, ",")),758)759}760761func boolPtr(v bool) *bool { return &v }762763764