Skip to content

Commit

Permalink
Move semver parsing code
Browse files Browse the repository at this point in the history
  • Loading branch information
bjee19 committed Mar 11, 2024
1 parent 0da2c7b commit 44a8e9d
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 149 deletions.
66 changes: 30 additions & 36 deletions internal/mode/static/telemetry/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
97 changes: 0 additions & 97 deletions internal/mode/static/telemetry/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"reflect"
"runtime"
"testing"

tel "github.com/nginxinc/telemetry-exporter/pkg/telemetry"
. "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -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())
}
})
}
}
57 changes: 57 additions & 0 deletions internal/mode/static/telemetry/parse_semver.go
Original file line number Diff line number Diff line change
@@ -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
}
104 changes: 104 additions & 0 deletions internal/mode/static/telemetry/parse_semver_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
})
}
}
Loading

0 comments on commit 44a8e9d

Please sign in to comment.