diff --git a/internal/mode/static/telemetry/collector.go b/internal/mode/static/telemetry/collector.go index 2b191e21b0..c31d77add1 100644 --- a/internal/mode/static/telemetry/collector.go +++ b/internal/mode/static/telemetry/collector.go @@ -5,9 +5,7 @@ import ( "errors" "fmt" "runtime" - "strconv" "strings" - "unicode" tel "github.com/nginxinc/telemetry-exporter/pkg/telemetry" appsv1 "k8s.io/api/apps/v1" @@ -298,51 +296,47 @@ func CollectNodeList(ctx context.Context, k8sClient client.Reader) (v1.NodeList, return nodes, nil } -// ParseKubeletVersion takes a string and turns it into a semver format. -func ParseKubeletVersion(s string) (string, error) { - s = strings.TrimSpace(s) - s = strings.TrimPrefix(s, "v") - - if s == "" { - return "", errors.New("string cannot be empty") - } +type clusterInformation struct { + Platform string + Version string + ClusterID string + Nodes v1.NodeList +} - parts := strings.SplitN(s, ".", 3) +func collectClusterInformation(ctx context.Context, k8sClient client.Reader) (clusterInformation, error) { + var clusterInfo clusterInformation - if _, err := strconv.Atoi(parts[0]); err != nil { - return "", errors.New("string must have a number as the major version") + nodes, err := CollectNodeList(ctx, k8sClient) + if err != nil { + return clusterInformation{}, fmt.Errorf("failed to collect cluster information: %w", err) } - - if len(parts) == 1 || parts[1] == "" { - return "", errors.New("string must have at least a major and minor version specified") + if len(nodes.Items) == 0 { + return clusterInformation{}, errors.New("failed to collect cluster information: NodeList length is zero") } - // in the edge case where the kubeletVersion is missing the patch version and has trailing characters which include - // '.' e.g. "v1.27-gke.1067004" - if _, err := strconv.Atoi(parts[1]); err != nil && len(parts) == 3 { - parts[1] = parts[1] + parts[2] - parts = parts[:len(parts)-1] + clusterInfo.Nodes = nodes + + var clusterID string + if clusterID, err = CollectClusterID(ctx, k8sClient); err != nil { + return clusterInformation{}, fmt.Errorf("failed to collect cluster information: %w", err) } + clusterInfo.ClusterID = clusterID - lastString := parts[len(parts)-1] + node := nodes.Items[0] - for index := range lastString { - // cut off trailing characters after the patch version. - // e.g. if kubeletVersion = "1.27.4+500050039", will return "4" - if !unicode.IsDigit(rune(lastString[index])) { - parts[len(parts)-1] = lastString[:index] - break - } - } + clusterInfo.Version = "unknown" + kubeletVersion := node.Status.NodeInfo.KubeletVersion - if len(parts) == 2 { - parts = append(parts, "0") + if version, err := parseSemver(kubeletVersion); err == nil { + clusterInfo.Version = version } - // in the case where lastString was "" - if parts[2] == "" { - parts[2] = "0" + var namespaces v1.NamespaceList + if err = k8sClient.List(ctx, &namespaces); err != nil { + return clusterInformation{}, fmt.Errorf("failed to collect cluster information: %w", err) } - return strings.Join(parts, "."), nil + clusterInfo.Platform = collectK8sPlatform(node, namespaces) + + return clusterInfo, nil } diff --git a/internal/mode/static/telemetry/collector_test.go b/internal/mode/static/telemetry/collector_test.go index a51318569b..b488c8ec20 100644 --- a/internal/mode/static/telemetry/collector_test.go +++ b/internal/mode/static/telemetry/collector_test.go @@ -5,7 +5,6 @@ import ( "errors" "reflect" "runtime" - "testing" tel "github.com/nginxinc/telemetry-exporter/pkg/telemetry" . "github.com/onsi/ginkgo/v2" @@ -939,99 +938,3 @@ var _ = Describe("Collector", Ordered, func() { }) }) }) - -func TestParseKubeletVersion(t *testing.T) { - tests := []struct { - expError error - input string - expected string - name string - }{ - { - input: "v1.27.9", - expected: "1.27.9", - name: "normal case", - expError: nil, - }, - { - input: " v1.27.9 ", - expected: "1.27.9", - name: "removes added whitespace", - expError: nil, - }, - { - input: "v1.27", - expected: "1.27.0", - name: "adds appended 0's if missing semver patch number", - expError: nil, - }, - { - input: "v1.27.8-gke.1067004", - expected: "1.27.8", - name: "removes trailing characters from semver version", - expError: nil, - }, - { - input: "v1.27.9+", - expected: "1.27.9", - name: "removes trailing characters from semver version no following characters", - expError: nil, - }, - { - input: "v1.27-gke.1067004", - expected: "1.27.0", - name: "removes trailing characters from semver version no patch version", - expError: nil, - }, - { - input: "v1.27.", - expected: "1.27.0", - name: "edge case where patch version is missing but separating period is", - expError: nil, - }, - { - input: "v1.27.gke+323", - expected: "1.27.0", - name: "edge case where patch version is missing but additional characters are present", - expError: nil, - }, - { - input: "", - expected: "", - name: "error on empty string", - expError: errors.New("string cannot be empty"), - }, - { - input: "1", - expected: "", - name: "errors when major and minor version are not present", - expError: errors.New("string must have at least a major and minor version specified"), - }, - { - input: "1.", - expected: "", - name: "errors when major and minor version are not present", - expError: errors.New("string must have at least a major and minor version specified"), - }, - { - input: "123gke", - expected: "", - name: "errors when string does not contain a number as the major version", - expError: errors.New("string must have a number as the major version"), - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - g := NewWithT(t) - - result, err := telemetry.ParseKubeletVersion(test.input) - g.Expect(result).To(Equal(test.expected)) - - if test.expError != nil { - g.Expect(err).To(MatchError(test.expError)) - } else { - g.Expect(err).To(BeNil()) - } - }) - } -} diff --git a/internal/mode/static/telemetry/parse_semver.go b/internal/mode/static/telemetry/parse_semver.go new file mode 100644 index 0000000000..41521415c8 --- /dev/null +++ b/internal/mode/static/telemetry/parse_semver.go @@ -0,0 +1,57 @@ +package telemetry + +import ( + "errors" + "strconv" + "strings" + "unicode" +) + +// parseSemver takes a string and turns it into a semver format. +func parseSemver(s string) (string, error) { + s = strings.TrimSpace(s) + s = strings.TrimPrefix(s, "v") + + if s == "" { + return "", errors.New("string cannot be empty") + } + + parts := strings.SplitN(s, ".", 3) + + if _, err := strconv.Atoi(parts[0]); err != nil { + return "", errors.New("string must have a number as the major version") + } + + if len(parts) == 1 || parts[1] == "" { + return "", errors.New("string must have at least a major and minor version specified") + } + + // in the edge case where the kubeletVersion is missing the patch version and has trailing characters which include + // '.' e.g. "v1.27-gke.1067004" + if _, err := strconv.Atoi(parts[1]); err != nil && len(parts) == 3 { + parts[1] = parts[1] + parts[2] + parts = parts[:len(parts)-1] + } + + lastString := parts[len(parts)-1] + + for index := range lastString { + // cut off trailing characters after the patch version. + // e.g. if kubeletVersion = "1.27.4+500050039", will return "4" + if !unicode.IsDigit(rune(lastString[index])) { + parts[len(parts)-1] = lastString[:index] + break + } + } + + if len(parts) == 2 { + parts = append(parts, "0") + } + + // in the case where lastString was "" + if parts[2] == "" { + parts[2] = "0" + } + + return strings.Join(parts, "."), nil +} diff --git a/internal/mode/static/telemetry/parse_semver_test.go b/internal/mode/static/telemetry/parse_semver_test.go new file mode 100644 index 0000000000..345e5646db --- /dev/null +++ b/internal/mode/static/telemetry/parse_semver_test.go @@ -0,0 +1,104 @@ +package telemetry + +import ( + "errors" + "testing" + + . "github.com/onsi/gomega" +) + +func TestParseKubeletVersion(t *testing.T) { + tests := []struct { + expError error + input string + expected string + name string + }{ + { + input: "v1.27.9", + expected: "1.27.9", + name: "normal case", + expError: nil, + }, + { + input: " v1.27.9 ", + expected: "1.27.9", + name: "removes added whitespace", + expError: nil, + }, + { + input: "v1.27", + expected: "1.27.0", + name: "adds appended 0's if missing semver patch number", + expError: nil, + }, + { + input: "v1.27.8-gke.1067004", + expected: "1.27.8", + name: "removes trailing characters from semver version", + expError: nil, + }, + { + input: "v1.27.9+", + expected: "1.27.9", + name: "removes trailing characters from semver version no following characters", + expError: nil, + }, + { + input: "v1.27-gke.1067004", + expected: "1.27.0", + name: "removes trailing characters from semver version no patch version", + expError: nil, + }, + { + input: "v1.27.", + expected: "1.27.0", + name: "edge case where patch version is missing but separating period is", + expError: nil, + }, + { + input: "v1.27.gke+323", + expected: "1.27.0", + name: "edge case where patch version is missing but additional characters are present", + expError: nil, + }, + { + input: "", + expected: "", + name: "error on empty string", + expError: errors.New("string cannot be empty"), + }, + { + input: "1", + expected: "", + name: "errors when major and minor version are not present", + expError: errors.New("string must have at least a major and minor version specified"), + }, + { + input: "1.", + expected: "", + name: "errors when major and minor version are not present", + expError: errors.New("string must have at least a major and minor version specified"), + }, + { + input: "123gke", + expected: "", + name: "errors when string does not contain a number as the major version", + expError: errors.New("string must have a number as the major version"), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + + result, err := parseSemver(test.input) + g.Expect(result).To(Equal(test.expected)) + + if test.expError != nil { + g.Expect(err).To(MatchError(test.expError)) + } else { + g.Expect(err).To(BeNil()) + } + }) + } +} diff --git a/internal/mode/static/telemetry/platform.go b/internal/mode/static/telemetry/platform.go index aea388e198..8d9afe92d8 100644 --- a/internal/mode/static/telemetry/platform.go +++ b/internal/mode/static/telemetry/platform.go @@ -15,14 +15,14 @@ const ( kindIdentifier = "kind" rancherIdentifier = "cattle-system" - clusterPlatformGKE = "gke" - clusterPlatformAWS = "eks" - clusterPlatformAzure = "aks" - clusterPlatformKind = "kind" - clusterPlatformK3S = "k3s" - clusterPlatformOpenShift = "openshift" - clusterPlatformRancher = "rancher" - clusterPlatformOther = "other" + platformGKE = "gke" + platformAWS = "eks" + platformAzure = "aks" + platformKind = "kind" + platformK3S = "k3s" + platformOpenShift = "openshift" + platformRancher = "rancher" + platformOther = "other" ) func collectK8sPlatform(node v1.Node, namespaces v1.NamespaceList) string { @@ -31,32 +31,32 @@ func collectK8sPlatform(node v1.Node, namespaces v1.NamespaceList) string { } if isAWSPlatform(node) { - return clusterPlatformAWS + return platformAWS } if isGKEPlatform(node) { - return clusterPlatformGKE + return platformGKE } if isAzurePlatform(node) { - return clusterPlatformAzure + return platformAzure } if isKindPlatform(node) { - return clusterPlatformKind + return platformKind } if isK3SPlatform(node) { - return clusterPlatformK3S + return platformK3S } - return clusterPlatformOther + return platformOther } // isMultiplePlatforms checks for platforms that run on other platforms. e.g. Rancher on K3s. func isMultiplePlatforms(node v1.Node, namespaces v1.NamespaceList) string { if isRancherPlatform(namespaces) { - return clusterPlatformRancher + return platformRancher } if isOpenshiftPlatform(node) { - return clusterPlatformOpenShift + return platformOpenShift } return ""