diff --git a/plugins/processors/k8sdecorator/stores/utils.go b/plugins/processors/k8sdecorator/stores/utils.go index eda19acb03..69f58dff8b 100644 --- a/plugins/processors/k8sdecorator/stores/utils.go +++ b/plugins/processors/k8sdecorator/stores/utils.go @@ -4,6 +4,8 @@ package stores import ( + "regexp" + "strconv" "strings" . "github.com/aws/amazon-cloudwatch-agent/internal/containerinsightscommon" @@ -30,11 +32,12 @@ func createContainerKeyFromMetric(tags map[string]string) string { return k8sutil.CreateContainerKey(namespace, podName, containerName) } -const ( - // kubeAllowedStringAlphaNums holds the characters allowed in replicaset names from as parent deployment +var ( + // deploymentAllowedRegExp holds the characters allowed in replicaset names from as parent deployment // https://github.com/kubernetes/apimachinery/blob/master/pkg/util/rand/rand.go#L83 - kubeAllowedStringAlphaNums = "bcdfghjklmnpqrstvwxz2456789" - cronJobAllowedString = "0123456789" + deploymentAllowedRegExp = regexp.MustCompile(`^[b-hj-np-tv-xz24-9]+$`) + // cronJobAllowedRegexp ensures the characters in cron job name are only numbers. + cronJobAllowedRegexp = regexp.MustCompile(`^\d+$`) ) // get the deployment name by stripping the last dash following some rules @@ -46,47 +49,45 @@ func parseDeploymentFromReplicaSet(name string) string { return "" } suffix := name[lastDash+1:] - if len(suffix) < 3 { + if len(suffix) >= 3 && deploymentAllowedRegExp.MatchString(suffix) { // Invalid suffix if it is less than 3 - return "" - } - - if !stringInRuneset(suffix, kubeAllowedStringAlphaNums) { - // Invalid suffix - return "" + return name[:lastDash] } - return name[:lastDash] + return "" } -// get the cronJob name by stripping the last dash following some rules -// return empty if it is not following the rule +// Get the cronJob name by stripping the last dash following by the naming convention: JobName-UnixTime +// based on https://github.com/kubernetes/kubernetes/blob/c4d752765b3bbac2237bf87cf0b1c2e307844666/pkg/controller/cronjob/cronjob_controllerv2.go#L594-L596. +// Before v1.21 CronJob in Kubernetes has used Unix Time in second; after v1.21 is a Unix Time in Minutes. + func parseCronJobFromJob(name string) string { lastDash := strings.LastIndexAny(name, "-") + + //Return empty since the naming convention is: JobName-UnixTime, if it does not have the "-", meanings the job name is empty if lastDash == -1 { - // No dash return "" } + suffix := name[lastDash+1:] - if len(suffix) != 10 { - // Invalid suffix if it is not 10 rune - return "" - } + suffixInt, err := strconv.ParseInt(suffix, 10, 64) - if !stringInRuneset(suffix, cronJobAllowedString) { - // Invalid suffix + if err != nil { return "" } - return name[:lastDash] -} + //Convert Unix Time In Minutes to Unix Time + suffixStringMultiply := strconv.FormatInt(suffixInt*60, 10) + //Checking if the suffix is a unix time by checking: the length and contains character + //Checking for the length: CronJobControllerV2 is Unix Time in Minutes (7-9 characters) while CronJob is Unix Time (10 characters). + //However, multiply by 60 to convert the Unix Time In Minutes back to Unix Time in order to have the same condition as Unix Time + if len(suffix) == 10 && cronJobAllowedRegexp.MatchString(suffix) { //Condition for CronJob before k8s v1.21 + return name[:lastDash] + } -func stringInRuneset(name, subset string) bool { - for _, r := range name { - if !strings.ContainsRune(subset, r) { - // Found an unexpected rune in suffix - return false - } + if len(suffixStringMultiply) == 10 && cronJobAllowedRegexp.MatchString(suffixStringMultiply) { //Condition for CronJobControllerV2 after k8s v1.21 + return name[:lastDash] } - return true + + return "" } diff --git a/plugins/processors/k8sdecorator/stores/utils_test.go b/plugins/processors/k8sdecorator/stores/utils_test.go index 85699b3c58..6dbe65bc66 100644 --- a/plugins/processors/k8sdecorator/stores/utils_test.go +++ b/plugins/processors/k8sdecorator/stores/utils_test.go @@ -4,17 +4,104 @@ package stores import ( - "github.com/docker/docker/pkg/testutil/assert" + "strconv" "testing" + "time" + + "github.com/docker/docker/pkg/testutil/assert" ) func TestUtils_parseDeploymentFromReplicaSet(t *testing.T) { - assert.Equal(t, "", parseDeploymentFromReplicaSet("cloudwatch-agent")) - assert.Equal(t, "cloudwatch-agent", parseDeploymentFromReplicaSet("cloudwatch-agent-42kcz")) + testcases := []struct { + name string + inputString string + expected string + }{ + { + name: "Get ReplicaSet Name with unallowed characters", + inputString: "cloudwatch-ag", + expected: "", + }, + { + name: "Get ReplicaSet Name with allowed characters smaller than 3 characters", + inputString: "cloudwatch-agent-bj", + expected: "", + }, + { + name: "Get ReplicaSet Name with allowed characters", + inputString: "cloudwatch-agent-42kcz", + expected: "cloudwatch-agent", + }, + { + name: "Get ReplicaSet Name with string smaller than 3 characters", + inputString: "cloudwatch-agent-sd", + expected: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, parseDeploymentFromReplicaSet(tc.inputString), tc.expected) + }) + } } func TestUtils_parseCronJobFromJob(t *testing.T) { - assert.Equal(t, "", parseCronJobFromJob("hello-123")) - assert.Equal(t, "hello", parseCronJobFromJob("hello-1234567890")) - assert.Equal(t, "", parseCronJobFromJob("hello-123456789a")) + unixTime := time.Now().Unix() + unixTimeString := strconv.FormatInt(unixTime, 10) + unixTimeMinutesString := strconv.FormatInt(unixTime/60, 10) + + testcases := []struct { + name string + inputString string + expected string + }{ + { + name: "Get CronJobControllerV2 or CronJob's Name with alphabet characters", + inputString: "hello-name", + expected: "", + }, + { + name: "Get CronJobControllerV2 or CronJob's Name with special characters and exact 10 characters", + inputString: "hello-1678995&64", + expected: "", + }, + { + name: "Get CronJobControllerV2 or CronJob's Name with Unix Time not equal to 10 letters", + inputString: "hello-238", + expected: "", + }, + { + name: "Get CronJobControllerV2's Name after k8s v1.21 with correct Unix Time", + inputString: "hello-" + unixTimeMinutesString, + expected: "hello", + }, + { + name: "Get CronJobControllerV2's Name after k8s v1.21 with alphabet Unix Time", + inputString: "hello-" + unixTimeMinutesString + "a28bc", + expected: "", + }, + + { + name: "Get CronJobControllerV2's Name after k8s v1.21 with Unix Time not equal to 10 letters", + inputString: "hello" + unixTimeMinutesString + "523", + expected: "", + }, + { + name: "Get CronJob's Name before k8s v1.21 with correct Unix Time", + inputString: "hello-" + unixTimeString, + expected: "hello", + }, + { + name: "Get CronJob's Name before k8s v1.21 with special characters", + inputString: "hello-" + unixTimeString + "@", + expected: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, parseCronJobFromJob(tc.inputString), tc.expected) + }) + } }