diff --git a/docs/manifest.json b/docs/manifest.json index a5fcb0b..fdcf1bc 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1514,6 +1514,346 @@ } ] }, + "BudgetAdjustment": { + "name": "v2alpha.BudgetAdjustment", + "properties": [ + { + "path": "$", + "typeInfo": { + "name": "BudgetAdjustment", + "kind": "struct", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo/v2alpha" + }, + "childrenPaths": [ + "$.apiVersion", + "$.kind", + "$.metadata", + "$.spec" + ] + }, + { + "path": "$.apiVersion", + "typeInfo": { + "name": "Version", + "kind": "string", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo" + }, + "values": [ + "openslo.com/v2alpha" + ], + "rules": [ + { + "description": "property is required", + "errorCode": "required" + }, + { + "description": "must be equal to 'openslo.com/v2alpha'", + "errorCode": "equal_to" + } + ], + "typeDoc": "Version represents a version of the OpenSLO specification." + }, + { + "path": "$.kind", + "typeInfo": { + "name": "Kind", + "kind": "string", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo" + }, + "values": [ + "BudgetAdjustment" + ], + "rules": [ + { + "description": "property is required", + "errorCode": "required" + }, + { + "description": "must be equal to 'BudgetAdjustment'", + "errorCode": "equal_to" + } + ], + "typeDoc": "Kind represents all the object kinds defined by OpenSLO specification. Keep in mind not all specification versions support every [Kind](https://pkg.go.dev/github.com/OpenSLO/go-sdk/pkg/openslo#Kind)." + }, + { + "path": "$.metadata", + "typeInfo": { + "name": "Metadata", + "kind": "struct", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo/v2alpha" + }, + "rules": [ + { + "description": "property is required", + "errorCode": "required" + } + ], + "childrenPaths": [ + "$.metadata.name", + "$.metadata.labels", + "$.metadata.annotations" + ] + }, + { + "path": "$.metadata.name", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "property is required", + "errorCode": "required" + }, + { + "description": "length must be between 1 and 63", + "errorCode": "string_dns_label:string_length" + }, + { + "description": "string must match regular expression: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'", + "details": "an RFC-1123 compliant label name must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character", + "errorCode": "string_dns_label:string_match_regexp", + "examples": [ + "my-name", + "123-abc" + ] + } + ] + }, + { + "path": "$.metadata.labels", + "typeInfo": { + "name": "Labels", + "kind": "map[string]string", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo/v2alpha" + }, + "childrenPaths": [ + "$.metadata.labels.*~", + "$.metadata.labels.*" + ] + }, + { + "path": "$.metadata.labels.*~", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "length must be between 1 and 317", + "errorCode": "string_kubernetes_qualified_name:string_length" + }, + { + "description": "string must be a Kubernetes Qualified Name", + "details": "Kubernetes Qualified Name must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character with an optional DNS subdomain prefix and '/'", + "errorCode": "string_kubernetes_qualified_name", + "examples": [ + "my.domain/MyName", + "MyName", + "my.name", + "123-abc" + ] + } + ] + }, + { + "path": "$.metadata.labels.*", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "string must match regular expression: '^([a-z0-9]([-._a-z0-9]{0,61}[a-z0-9])?)?$'", + "errorCode": "string_match_regexp", + "examples": [ + "my-label", + "my.domain_123-label" + ] + } + ] + }, + { + "path": "$.metadata.annotations", + "typeInfo": { + "name": "Annotations", + "kind": "map[string]string", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo/v2alpha" + }, + "childrenPaths": [ + "$.metadata.annotations.*~", + "$.metadata.annotations.*" + ] + }, + { + "path": "$.metadata.annotations.*~", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "length must be between 1 and 317", + "errorCode": "string_kubernetes_qualified_name:string_length" + }, + { + "description": "string must be a Kubernetes Qualified Name", + "details": "Kubernetes Qualified Name must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character with an optional DNS subdomain prefix and '/'", + "errorCode": "string_kubernetes_qualified_name", + "examples": [ + "my.domain/MyName", + "MyName", + "my.name", + "123-abc" + ] + } + ] + }, + { + "path": "$.metadata.annotations.*", + "typeInfo": { + "name": "string", + "kind": "string" + } + }, + { + "path": "$.spec", + "typeInfo": { + "name": "BudgetAdjustmentSpec", + "kind": "struct", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo/v2alpha" + }, + "childrenPaths": [ + "$.spec.description", + "$.spec.service", + "$.spec.indicatorRef", + "$.spec.startTime", + "$.spec.endTime", + "$.spec.duration" + ] + }, + { + "path": "$.spec.description", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "property is required", + "errorCode": "required" + }, + { + "description": "length must be less than or equal to 1050", + "errorCode": "string_max_length" + } + ] + }, + { + "path": "$.spec.service", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "property is required", + "errorCode": "required" + }, + { + "description": "length must be between 1 and 63", + "errorCode": "string_dns_label:string_length" + }, + { + "description": "string must match regular expression: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'", + "details": "an RFC-1123 compliant label name must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character", + "errorCode": "string_dns_label:string_match_regexp", + "examples": [ + "my-name", + "123-abc" + ] + } + ] + }, + { + "path": "$.spec.indicatorRef", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "property is required", + "errorCode": "required" + }, + { + "description": "length must be between 1 and 63", + "errorCode": "string_dns_label:string_length" + }, + { + "description": "string must match regular expression: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'", + "details": "an RFC-1123 compliant label name must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character", + "errorCode": "string_dns_label:string_match_regexp", + "examples": [ + "my-name", + "123-abc" + ] + } + ] + }, + { + "path": "$.spec.startTime", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "property is required", + "errorCode": "required" + }, + { + "description": "string must be a valid date and time in '2006-01-02T15:04:05Z07:00' format", + "details": "date and time format follows Go's time layout, see https://pkg.go.dev/time#Layout for more details", + "errorCode": "string_date_time" + } + ] + }, + { + "path": "$.spec.endTime", + "typeInfo": { + "name": "string", + "kind": "string" + }, + "rules": [ + { + "description": "property is optional", + "errorCode": "optional" + }, + { + "description": "string must be a valid date and time in '2006-01-02T15:04:05Z07:00' format", + "details": "date and time format follows Go's time layout, see https://pkg.go.dev/time#Layout for more details", + "errorCode": "string_date_time" + } + ] + }, + { + "path": "$.spec.duration", + "typeInfo": { + "name": "DurationShorthand", + "kind": "struct", + "package": "github.com/OpenSLO/go-sdk/pkg/openslo/v2alpha" + }, + "rules": [ + { + "description": "property is optional", + "errorCode": "optional" + } + ], + "typeDoc": "DurationShorthand is a shorthand representation of time duration. It consists of a value and unit, e.g. '1m' (1 minute), '10d' (10 days)." + } + ] + }, "DataSource": { "name": "v2alpha.DataSource", "properties": [ diff --git a/go.work.sum b/go.work.sum index 7639051..2ec4f1b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -42,6 +42,7 @@ golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -51,8 +52,6 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -70,6 +69,7 @@ golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXct golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/internal/cmd/objectdoc/main.go b/internal/cmd/objectdoc/main.go index 5295074..97c5f3d 100644 --- a/internal/cmd/objectdoc/main.go +++ b/internal/cmd/objectdoc/main.go @@ -37,6 +37,7 @@ var allDocsGeneratorFuncs = []func() (govydoc.ObjectDoc, error){ }, func() (govydoc.ObjectDoc, error) { return govydoc.Generate(v2alpha.AlertPolicy{}.GetValidator()) }, func() (govydoc.ObjectDoc, error) { return govydoc.Generate(v2alpha.DataSource{}.GetValidator()) }, + func() (govydoc.ObjectDoc, error) { return govydoc.Generate(v2alpha.BudgetAdjustment{}.GetValidator()) }, } var ( diff --git a/pkg/openslo/kind.go b/pkg/openslo/kind.go index 83da089..3bde281 100644 --- a/pkg/openslo/kind.go +++ b/pkg/openslo/kind.go @@ -16,6 +16,7 @@ const ( KindAlertPolicy Kind = "AlertPolicy" KindAlertCondition Kind = "AlertCondition" KindAlertNotificationTarget Kind = "AlertNotificationTarget" + KindBudgetAdjustment Kind = "BudgetAdjustment" ) func ParseKind(s string) (Kind, error) { @@ -38,7 +39,8 @@ func (k Kind) Validate() error { KindService, KindAlertPolicy, KindAlertCondition, - KindAlertNotificationTarget: + KindAlertNotificationTarget, + KindBudgetAdjustment: return nil default: return fmt.Errorf("unsupported %[1]T: %[1]s", k) diff --git a/pkg/openslo/v2alpha/budget_adjustment.go b/pkg/openslo/v2alpha/budget_adjustment.go new file mode 100644 index 0000000..d092cc7 --- /dev/null +++ b/pkg/openslo/v2alpha/budget_adjustment.go @@ -0,0 +1,112 @@ +package v2alpha + +import ( + "time" + + "github.com/nobl9/govy/pkg/govy" + "github.com/nobl9/govy/pkg/rules" + + "github.com/OpenSLO/go-sdk/internal" + "github.com/OpenSLO/go-sdk/pkg/openslo" +) + +var ( + _ = Object(BudgetAdjustment{}) + _ = openslo.ObjectValidator[BudgetAdjustment](BudgetAdjustment{}) +) + +func NewBudgetAdjustment(metadata Metadata, spec BudgetAdjustmentSpec) BudgetAdjustment { + return BudgetAdjustment{ + APIVersion: APIVersion, + Kind: openslo.KindBudgetAdjustment, + Metadata: metadata, + Spec: spec, + } +} + +type BudgetAdjustment struct { + APIVersion openslo.Version `json:"apiVersion"` + Kind openslo.Kind `json:"kind"` + Metadata Metadata `json:"metadata"` + Spec BudgetAdjustmentSpec `json:"spec"` +} + +func (b BudgetAdjustment) GetVersion() openslo.Version { + return APIVersion +} + +func (b BudgetAdjustment) GetKind() openslo.Kind { + return openslo.KindBudgetAdjustment +} + +func (b BudgetAdjustment) GetName() string { + return b.Metadata.Name +} + +func (b BudgetAdjustment) Validate() error { + return budgetAdjustmentValidation.Validate(b) +} + +func (b BudgetAdjustment) String() string { + return internal.GetObjectName(b) +} + +func (b BudgetAdjustment) GetMetadata() Metadata { + return b.Metadata +} + +func (b BudgetAdjustment) GetValidator() govy.Validator[BudgetAdjustment] { + return budgetAdjustmentValidation +} + +type BudgetAdjustmentSpec struct { + Description string `json:"description"` + Service string `json:"service"` + IndicatorRef string `json:"indicatorRef"` + StartTime string `json:"startTime"` + EndTime *string `json:"endTime,omitempty"` + Duration *DurationShorthand `json:"duration,omitempty"` +} + +var budgetAdjustmentValidation = govy.New( + validationRulesAPIVersion(func(b BudgetAdjustment) openslo.Version { return b.APIVersion }), + validationRulesKind(func(b BudgetAdjustment) openslo.Kind { return b.Kind }, openslo.KindBudgetAdjustment), + validationRulesMetadata(func(b BudgetAdjustment) Metadata { return b.Metadata }), + govy.For(func(b BudgetAdjustment) BudgetAdjustmentSpec { return b.Spec }). + WithName("spec"). + Include(govy.New( + govy.For(func(spec BudgetAdjustmentSpec) string { return spec.Description }). + WithName("description"). + Required(). + Rules(rules.StringMaxLength(1050)), + govy.For(func(spec BudgetAdjustmentSpec) string { return spec.Service }). + WithName("service"). + Required(). + Rules(rules.StringDNSLabel()), + govy.For(func(spec BudgetAdjustmentSpec) string { return spec.IndicatorRef }). + WithName("indicatorRef"). + Required(). + Rules(rules.StringDNSLabel()), + govy.For(func(spec BudgetAdjustmentSpec) string { return spec.StartTime }). + WithName("startTime"). + Required(). + Rules(rules.StringDateTime(time.RFC3339)), + govy.ForPointer(func(spec BudgetAdjustmentSpec) *string { return spec.EndTime }). + WithName("endTime"). + Rules(rules.StringDateTime(time.RFC3339)), + govy.ForPointer(func(spec BudgetAdjustmentSpec) *DurationShorthand { return spec.Duration }). + WithName("duration"). + Include(durationShortHandValidation), + govy.For(govy.GetSelf[BudgetAdjustmentSpec]()). + WithName("spec"). + Rules(govy.NewRule(func(spec BudgetAdjustmentSpec) error { + if spec.EndTime == nil && spec.Duration == nil { + return govy.NewRuleError("one of 'endTime' or 'duration' is required") + } + if spec.EndTime != nil && spec.Duration != nil { + return govy.NewRuleError("only one of 'endTime' or 'duration' can be provided") + } + return nil + })), + )), +).WithNameFunc(internal.GetObjectName[BudgetAdjustment]) diff --git a/pkg/openslo/v2alpha/objects.go b/pkg/openslo/v2alpha/objects.go index d5bd3c2..ebd06ec 100644 --- a/pkg/openslo/v2alpha/objects.go +++ b/pkg/openslo/v2alpha/objects.go @@ -20,6 +20,7 @@ var supportedKinds = []openslo.Kind{ openslo.KindAlertPolicy, openslo.KindAlertCondition, openslo.KindAlertNotificationTarget, + openslo.KindBudgetAdjustment, } func GetSupportedKinds() []openslo.Kind { diff --git a/pkg/openslosdk/encoding.go b/pkg/openslosdk/encoding.go index 9908622..5d90253 100644 --- a/pkg/openslosdk/encoding.go +++ b/pkg/openslosdk/encoding.go @@ -211,6 +211,8 @@ func decodeV2alphaObject(generic genericObject) (openslo.Object, error) { return decodeJSONObject[v2alpha.AlertCondition](generic.data) case openslo.KindAlertNotificationTarget: return decodeJSONObject[v2alpha.AlertNotificationTarget](generic.data) + case openslo.KindBudgetAdjustment: + return decodeJSONObject[v2alpha.BudgetAdjustment](generic.data) default: return nil, fmt.Errorf("unsupported %[1]T: %[1]s for version: %[2]s", generic.kind, generic.apiVersion) }