Repository: influxdata/telegraf-operator
Pull request #83 (2022-02-16 04:35:18)

feat: Add telegraf.influxdata.com/global-tag-literal-
Implements main form of the #82 proposal.

The implementation is quite quick&dirty; there are a few yaks to shave in order to make this more clean; in particular we should avoid fiddling with TOML as a string, but we should parse it and manipulate the AST or some intermediate representation. Unfortunately the TOML library we're using doesn't support marshaling back the AST back.

mkmik: > LGTM, but I'm wondering - why not just inject global tags section as raw configuration for telegraf? Is your concern that this has to be updated for other global tags?

Yes. The class defined some global tags already

Patch:
diff --git a/sidecar.go b/sidecar.go
index 9d8cb53..3fb148c 100644
--- a/sidecar.go
+++ b/sidecar.go
@@ -53,6 +53,8 @@ const (
 	TelegrafEnvSecretKeyRefPrefix = "telegraf.influxdata.com/env-secretkeyref-"
 	// TelegrafEnvLiteralPrefix allows adding a literal to the telegraf sidecar in the form of an environment variable
 	TelegrafEnvLiteralPrefix = "telegraf.influxdata.com/env-literal-"
+	// TelegrafGlobalTagLiteralPrefix allows adding a literal global tag to the telegraf sidecar config
+	TelegrafGlobalTagLiteralPrefix = "telegraf.influxdata.com/global-tag-literal-"
 	// TelegrafImage allows specifying a custom telegraf image to be used in the sidecar container
 	TelegrafImage = "telegraf.influxdata.com/image"
 	// TelegrafRequestsCPU allows specifying custom CPU resource requests
@@ -273,6 +275,33 @@ func (h *sidecarHandler) assembleConf(pod *corev1.Pod, className string) (telegr
 	}
 	telegrafConf = fmt.Sprintf("%s\n%s", telegrafConf, classData)
 
+	type keyValue struct{ key, value string }
+	var globalTags []keyValue
+	for key, value := range pod.Annotations {
+		if strings.HasPrefix(key, TelegrafGlobalTagLiteralPrefix) {
+			globalTags = append(globalTags, keyValue{strings.TrimPrefix(key, TelegrafGlobalTagLiteralPrefix), value})
+		}
+	}
+	// Go maps aren't ordered; we want a stable config output, to simplify tests among other things
+	sort.Slice(globalTags, func(i, j int) bool { return globalTags[i].key < globalTags[j].key })
+
+	if len(globalTags) > 0 {
+		globalTagsText := "[global_tags]\n"
+		for _, i := range globalTags {
+			globalTagsText = fmt.Sprintf("%s  %s = %q\n", globalTagsText, i.key, i.value)
+		}
+
+		// inject globalTagsText at the top of an existing "[global_tags]" section
+		// or create one.
+		// Edge case / caveat: This doesn't handle when the class config file starts with "[global_tags]
+		// TODO(mkm): yak shave: change this whole method to manipulate a real toml instead of fiddling with strings.
+		//            currently blocked on inability of github.com/influxdata/toml to render the AST back to string.
+		if !strings.Contains(telegrafConf, "[global_tags]\n") {
+			telegrafConf = fmt.Sprintf("%s\n%s", telegrafConf, "[global_tags]\n")
+		}
+		telegrafConf = strings.ReplaceAll(telegrafConf, "[global_tags]\n", globalTagsText)
+	}
+
 	if _, err := toml.Parse([]byte(telegrafConf)); err != nil {
 		return "", fmt.Errorf("resulting Telegraf is not a valid file: %v", err)
 	}


Test patch:
diff --git a/sidecar_test.go b/sidecar_test.go
index d091a7a..af82181 100644
--- a/sidecar_test.go
+++ b/sidecar_test.go
@@ -277,6 +277,46 @@ func Test_assembleConf(t *testing.T) {
 [[inputs.internal]]
 `,
 		},
+		{
+			name: "handle global_tags",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						TelegrafGlobalTagLiteralPrefix + "foo": "bar",
+					},
+				},
+			},
+			wantConfig: `[global_tags]
+  foo = "bar"`,
+		},
+		{
+			name: "handle global_tags multiple",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						TelegrafGlobalTagLiteralPrefix + "foo": "bar",
+						TelegrafGlobalTagLiteralPrefix + "baz": "quz",
+					},
+				},
+			},
+			wantConfig: `[global_tags]
+  baz = "quz"
+  foo = "bar"`,
+		},
+		{
+			name: "handle global_tags existing",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						TelegrafRawInput:                       "[global_tags]\n  a = \"b\"",
+						TelegrafGlobalTagLiteralPrefix + "foo": "bar",
+					},
+				},
+			},
+			wantConfig: `[global_tags]
+  foo = "bar"
+  a = "b"`,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {