Skip to content

Commit

Permalink
Allow annotations to control naming behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
dudo committed May 13, 2024
1 parent 853054b commit 91e15c2
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 24 deletions.
4 changes: 1 addition & 3 deletions e2e-test/api/tests/test_monitor_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])

Expand Down Expand Up @@ -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


69 changes: 65 additions & 4 deletions pkg/annotations.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package pkg

import (
"crypto/sha1"
"encoding/hex"
"fmt"
"os"
"strconv"
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
16 changes: 2 additions & 14 deletions pkg/api/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,19 +44,14 @@ 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{})
}

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 {
Expand All @@ -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)
Expand All @@ -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",
Expand All @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/api/jobs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down

0 comments on commit 91e15c2

Please sign in to comment.