Repository: influxdata/telegraf-operator (go)

Issue:
Support `telegraf.influxdata.com/port` annotation
It's would be cool to support `telegraf.influxdata.com/port` in addition to `telegraf.influxdata.com/ports`

Pull request description:
Add telegraf.influxdata.com/port annotation for port
Closes #5 

This is an ergonomics annotation supplementing
telegraf.influxdata.com/ports (plural).  If both annotations
are specified, the list is merged preserving just the unique
ports.

- [x] Signed the [InfluxData CLA](https://www.influxdata.com/legal/cla/)
  ([if necessary](https://github.com/influxdata/telegraf-operator/blob/master/CONTRIBUTING.md#sign-the-influxdata-cla))
- [x] Tests pass (no build errors)
- [x] Rebased/mergeable


Patch:
diff --git a/sidecar.go b/sidecar.go
index 696347d..d87bd9f 100644
--- a/sidecar.go
+++ b/sidecar.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"sort"
 	"strconv"
 	"strings"
 
@@ -11,7 +12,11 @@ import (
 )
 
 const (
+	// TelegrafAnnotationCommon is the shared prefix for all annotations.
 	TelegrafAnnotationCommon = "telegraf.influxdata.com"
+	// TelegrafMetricsPort is used to configure a port telegraf should scrape;
+	// Equivalent to TelegrafMetricsPorts: "6060"
+	TelegrafMetricsPort = "telegraf.influxdata.com/port"
 	// TelegrafMetricsPorts is used to configure which port telegraf should scrape, comma separated list of ports to scrape
 	TelegrafMetricsPorts = "telegraf.influxdata.com/ports"
 	// TelegrafMetricsPath is used to configure at which path to configure scraping to (a port must be configured also), will apply to all ports if multiple are configured
@@ -54,9 +59,10 @@ func addSidecar(pod *corev1.Pod, name, namespace, classData string) (*corev1.Sec
 	return newSecret(pod, name, namespace, telegrafConf)
 }
 
+// Assembling prometheus input
 func assembleConf(pod *corev1.Pod, classData string) (config string, err error) {
-	// Assembling prometheus input
-	if ports, ok := pod.Annotations[TelegrafMetricsPorts]; ok {
+	ports := ports(pod)
+	if len(ports) != 0 {
 		path := "/metrics"
 		if extPath, ok := pod.Annotations[TelegrafMetricsPath]; ok {
 			path = extPath
@@ -70,9 +76,8 @@ func assembleConf(pod *corev1.Pod, classData string) (config string, err error)
 		if ok {
 			intervalConfig = fmt.Sprintf("interval = \"%s\"", intervalRaw)
 		}
-		portList := strings.Split(fmt.Sprint(ports), ",")
 		urls := []string{}
-		for _, port := range portList {
+		for _, port := range ports {
 			urls = append(urls, fmt.Sprintf("%s://127.0.0.1:%s%s", scheme, port, path))
 		}
 		if len(urls) != 0 {
@@ -155,3 +160,26 @@ func newContainer() corev1.Container {
 		},
 	}
 }
+
+// ports gathers and merges unique ports from both TelegrafMetricsPort and TelegrafMetricsPorts.
+func ports(pod *corev1.Pod) []string {
+	uniquePorts := map[string]struct{}{}
+	if p, ok := pod.Annotations[TelegrafMetricsPort]; ok {
+		uniquePorts[p] = struct{}{}
+	}
+	if ports, ok := pod.Annotations[TelegrafMetricsPorts]; ok {
+		for _, p := range strings.Split(ports, ",") {
+			uniquePorts[p] = struct{}{}
+		}
+	}
+	if len(uniquePorts) == 0 {
+		return nil
+	}
+
+	ps := make([]string, 0, len(uniquePorts))
+	for p := range uniquePorts {
+		ps = append(ps, p)
+	}
+	 sort.Strings(ps)
+	 return ps
+}


Test patch:
diff --git a/sidecar_test.go b/sidecar_test.go
index 4c1735a..64cc486 100644
--- a/sidecar_test.go
+++ b/sidecar_test.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"bytes"
+	"reflect"
 	"testing"
 
 	corev1 "k8s.io/api/core/v1"
@@ -48,7 +49,7 @@ func Test_assembleConf(t *testing.T) {
 			pod: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
 					Annotations: map[string]string{
-						TelegrafMetricsPorts: "6060",
+						TelegrafMetricsPort: "6060",
 					},
 				},
 			},
@@ -57,6 +58,22 @@ func Test_assembleConf(t *testing.T) {
   urls = ["http://127.0.0.1:6060/metrics"]
   
 
+`,
+		},
+		{
+			name: "default prometheus settings with multiple ports",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						TelegrafMetricsPorts: "6060,9999",
+					},
+				},
+			},
+			wantConfig: `
+[[inputs.prometheus]]
+  urls = ["http://127.0.0.1:6060/metrics", "http://127.0.0.1:9999/metrics"]
+  
+
 `,
 		},
 		{
@@ -182,6 +199,64 @@ status: {}
 	}
 }
 
+func Test_ports(t *testing.T) {
+	tests := []struct {
+		name string
+		pod  *corev1.Pod
+		want []string
+	}{
+		{
+			name: "ports merges ports for both annotations",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						TelegrafMetricsPort:  "6060",
+						TelegrafMetricsPorts: "6060,8080,8888",
+					},
+				},
+			},
+			want: []string{"6060", "8080", "8888"},
+		},
+		{
+			name: "no annotation returns no ports",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{},
+				},
+			},
+		},
+		{
+			name: "ports are unique and returned in order",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						TelegrafMetricsPorts: "9999,6060,6060",
+					},
+				},
+			},
+			want: []string{"6060", "9999"},
+		},
+		{
+			name: "single port from TelegrafMetricsPorts",
+			pod: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						TelegrafMetricsPorts: "6060",
+					},
+				},
+			},
+			want: []string{"6060"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := ports(tt.pod); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ports() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
 func toYAML(t *testing.T, o runtime.Object) string {
 	t.Helper()