diff --git a/e2e-test/api/tests/test_monitor_types.py b/e2e-test/api/tests/test_monitor_types.py index ffa6623..e3dfcac 100644 --- a/e2e-test/api/tests/test_monitor_types.py +++ b/e2e-test/api/tests/test_monitor_types.py @@ -55,7 +55,7 @@ def test_no_monitors_with_uid_names(): with assume: # We want the monitor names NOT to be UUIDs. If they are a UUID, # that means we encountered a bug with the monitor creation somehow - # and defaultName was not set. + # and Name was not set. with pytest.raises(ValueError): uuid.UUID(monitor['name']) @@ -134,5 +134,3 @@ def test_same_id_should_result_one_monitor(): assert len(pings[monitor_key]) > 0 pings = cronitor_wrapper.get_ping_history_by_monitor(monitor_key=monitor_key, env='env2') assert len(pings[monitor_key]) > 0 - - diff --git a/pkg/annotations.go b/pkg/annotations.go index 0f9d1e1..74f8e1e 100644 --- a/pkg/annotations.go +++ b/pkg/annotations.go @@ -1,6 +1,8 @@ package pkg import ( + "crypto/sha1" + "encoding/hex" "fmt" "os" "strconv" @@ -48,7 +50,7 @@ const ( // via manual instrumentation, and you'd like to use the same Monitor object. AnnotationCronitorID CronitorAnnotation = "k8s.cronitor.io/cronitor-id" - // AnnotationCronitorName lets you override the defaultName created by the agent to + // AnnotationCronitorName lets you override the Name created by the agent to // create the monitor in Cronitor with a custom specified name. This is especially useful // if you are attaching the same CronJob across multiple namespaces/clusters to a single // Cronitor Monitor across multiple environments. @@ -64,6 +66,14 @@ const ( // AnnotationCronitorGraceSeconds lets you provide the number of seconds to wait before sending a failure alert. AnnotationCronitorGraceSeconds CronitorAnnotation = "k8s.cronitor.io/cronitor-grace-seconds" + + // AnnotationIDInference lets you decide how the Cronitor ID is determined. + // The only valid values are "k8s" and "name". Default is "k8s". + AnnotationIDInference CronitorAnnotation = "k8s.cronitor.io/id-inference" + + // AnnotationNamePrefix lets you control the prefix of the name. + // Valid options include "none", "namespace" (to prepend namespace/), or any other string, which will be prepended as-is. + AnnotationNamePrefix CronitorAnnotation = "k8s.cronitor.io/name-prefix" ) type CronitorConfigParser struct { @@ -120,13 +130,29 @@ func (cronitorParser CronitorConfigParser) GetSpecifiedCronitorID() string { return "" } -// GetCronitorID returns the correct Cronitor monitor ID for the CronJob, defaulting to the CronJob's -// Kubernetes UID if no pre-specified monitor ID is provided by the user. +// GetCronitorID returns the correct Cronitor monitor ID for the CronJob. +// Defaults to the CronJob's Kubernetes UID if no pre-specified monitor ID is provided by the user. func (cronitorParser CronitorConfigParser) GetCronitorID() string { + // Default behavior + inference := "k8s" + + // Check if a specific Cronitor ID is assigned and return it if present if specifiedId := cronitorParser.GetSpecifiedCronitorID(); specifiedId != "" { return specifiedId } - return string(cronitorParser.cronjob.GetUID()) + + // Override default inference if an annotation is provided + if annotation, ok := cronitorParser.cronjob.Annotations[string(AnnotationIDInference)]; ok && annotation != "" { + inference = annotation + } + + // Return the appropriate ID based on the inference + switch inference { + case "name": + return generateHashFromName(cronitorParser.GetCronitorName()) + default: + return string(cronitorParser.cronjob.GetUID()) + } } // GetSpecifiedCronitorName returns the pre-specified Cronitor monitor name, if provided as an annotation @@ -139,6 +165,34 @@ func (cronitorParser CronitorConfigParser) GetSpecifiedCronitorName() string { return "" } +// GetCronitorName returns the name to be used by Cronitor monitor. +// Allows the namespace to be prepended or not, and allows arbitrary strings as a prefix. +// Defaults to prepending the namespace if no pre-specified monitor name is provided by the user. +func (cronitorParser CronitorConfigParser) GetCronitorName() string { + // Default behavior + prefix := "namespace" + + // Check if a specific Cronitor Name is assigned and return it if present + if specifiedName := cronitorParser.GetSpecifiedCronitorName(); specifiedName != "" { + return specifiedName + } + + // Check if a prefix annotation exists and override the default if present + if annotation, ok := cronitorParser.cronjob.Annotations[string(AnnotationNamePrefix)]; ok && annotation != "" { + prefix = annotation + } + + // Construct the name based on the prefix + switch prefix { + case "namespace": + return fmt.Sprintf("%s/%s", cronitorParser.cronjob.Namespace, cronitorParser.cronjob.Name) + case "none": + return cronitorParser.cronjob.Name + default: + return fmt.Sprintf("%s%s", prefix, cronitorParser.cronjob.Name) + } +} + // Inclusion/exclusion behavior func (cronitorParser CronitorConfigParser) getDefaultBehavior() defaultBehaviorValue { @@ -202,3 +256,10 @@ func (cronitorParser CronitorConfigParser) GetGraceSeconds() int { } return -1 } + +func generateHashFromName(name string) string { + h := sha1.New() + h.Write([]byte(name)) + bs := h.Sum(nil) + return hex.EncodeToString(bs) +} diff --git a/pkg/api/jobs.go b/pkg/api/jobs.go index d797ba3..cbe9122 100644 --- a/pkg/api/jobs.go +++ b/pkg/api/jobs.go @@ -19,7 +19,6 @@ import ( // https://docs.google.com/document/d/1erh-fvTkF14jyJGv3DYuN2UalWe6AN49XOUsWHJccso/edit#heading=h.ylm2gai335jy type CronitorJob struct { Key string `json:"key"` - DefaultName string `json:"defaultName"` Name string `json:"name,omitempty"` DefaultNote string `json:"defaultNote,omitempty"` Metadata string `json:"metadata"` // This is actually a string rather than a map @@ -45,7 +44,7 @@ func (cronitorJob CronitorJob) GetEnvironment() string { return "" } -func truncateDefaultName(name string) string { +func truncateName(name string) string { if len(name) > 100 { name = truncate.Truncator(name, 100, truncate.EllipsisMiddleStrategy{}) } @@ -53,11 +52,6 @@ func truncateDefaultName(name string) string { return name } -func GenerateDefaultName(cronJob *v1.CronJob) string { - name := fmt.Sprintf("%s/%s", cronJob.Namespace, cronJob.Name) - return truncateDefaultName(name) -} - func ValidateTagName(tagName string) string { name := tagName if len(tagName) > 100 { @@ -70,8 +64,6 @@ func ValidateTagName(tagName string) string { func convertCronJobToCronitorJob(cronJob *v1.CronJob) CronitorJob { configParser := pkg.NewCronitorConfigParser(cronJob) - name := GenerateDefaultName(cronJob) - metadata := make(map[string]string) if cronJob.Spec.ConcurrencyPolicy != "" { metadata["concurrencyPolicy"] = string(cronJob.Spec.ConcurrencyPolicy) @@ -91,7 +83,7 @@ func convertCronJobToCronitorJob(cronJob *v1.CronJob) CronitorJob { cronitorJob := CronitorJob{ Key: configParser.GetCronitorID(), - DefaultName: name, + Name: truncateName(configParser.GetCronitorName()), Schedule: cronJob.Spec.Schedule, Metadata: string(metadataJson), Type_: "job", @@ -100,10 +92,6 @@ func convertCronJobToCronitorJob(cronJob *v1.CronJob) CronitorJob { Group: configParser.GetGroup(), } - if name := configParser.GetSpecifiedCronitorName(); name != "" { - cronitorJob.Name = name - } - if graceSeconds := configParser.GetGraceSeconds(); graceSeconds != -1 { cronitorJob.GraceSeconds = graceSeconds } diff --git a/pkg/api/jobs_test.go b/pkg/api/jobs_test.go index 0ba10f5..14ce7da 100644 --- a/pkg/api/jobs_test.go +++ b/pkg/api/jobs_test.go @@ -134,15 +134,15 @@ func TestCronitorGraceSecondsAnnotation(t *testing.T) { } } -func TestTruncateDefaultName(t *testing.T) { +func TestTruncateName(t *testing.T) { shortName := "abcefgh" - if newName := truncateDefaultName(shortName); newName != shortName { + if newName := truncateName(shortName); newName != shortName { t.Errorf("expected truncated name for '%s' to be '%s', got '%s'", shortName, shortName, newName) } longName := "this-is-a-very-long-namespace-name-lets-make-it-really-freaking-long/and-a-very-long-job-name-very-very-long-abcdef12345" expectedNewName := "this-is-a-very-long-namespace-name-lets-make-it-re…d-a-very-long-job-name-very-very-long-abcdef12345" - if newName := truncateDefaultName(longName); newName != expectedNewName { + if newName := truncateName(longName); newName != expectedNewName { t.Errorf("expected truncated name for '%s' to be '%s', got '%s'", longName, expectedNewName, newName) } }