diff --git a/Makefile b/Makefile index ed25ba1..3114a7b 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,14 @@ OPENAPI ?= docker run --rm \ -v ${PWD}:${PWD} \ openapitools/openapi-generator-cli:v5.1.1 +gojsontoyaml: +ifeq (, $(shell which gojsontoyaml)) + go install github.com/brancz/gojsontoyaml@latest +GOJSONTOYAML=$(GOBIN)/gojsontoyaml +else +GOJSONTOYAML=$(shell which gojsontoyaml) +endif + all: ui/build build lint .PHONY: install @@ -39,8 +47,9 @@ deploy: manifests config/api.yaml config/kubernetes.yaml # Generate manifests e.g. CRD, RBAC etc. .PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. +manifests: controller-gen gojsontoyaml ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=pyrra-kubernetes crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + find config/crd/bases -name '*.yaml' -print0 | xargs -0 -I{} sh -c '$(GOJSONTOYAML) -yamltojson < "$$1" | jq > "$(PWD)/config/crd/bases/$$(basename -s .yaml $$1).json"' -- {} config: config/api.yaml config/kubernetes.yaml diff --git a/config/crd/bases/pyrra.dev_servicelevelobjectives.json b/config/crd/bases/pyrra.dev_servicelevelobjectives.json new file mode 100644 index 0000000..4859a07 --- /dev/null +++ b/config/crd/bases/pyrra.dev_servicelevelobjectives.json @@ -0,0 +1,173 @@ +{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": { + "annotations": { + "controller-gen.kubebuilder.io/version": "v0.8.0" + }, + "creationTimestamp": null, + "name": "servicelevelobjectives.pyrra.dev" + }, + "spec": { + "group": "pyrra.dev", + "names": { + "kind": "ServiceLevelObjective", + "listKind": "ServiceLevelObjectiveList", + "plural": "servicelevelobjectives", + "shortNames": [ + "slo" + ], + "singular": "servicelevelobjective" + }, + "scope": "Namespaced", + "versions": [ + { + "name": "v1alpha1", + "schema": { + "openAPIV3Schema": { + "description": "ServiceLevelObjective is the Schema for the ServiceLevelObjectives API.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "ServiceLevelObjectiveSpec defines the desired state of ServiceLevelObjective.", + "properties": { + "description": { + "description": "Description describes the ServiceLevelObjective in more detail and gives extra context for engineers that might not directly work on the service.", + "type": "string" + }, + "indicator": { + "description": "ServiceLevelIndicator is the underlying data source that indicates how the service is doing. This will be a Prometheus metric with specific selectors for your service.", + "properties": { + "latency": { + "description": "Latency is the indicator that measures a certain percentage to be fast than.", + "properties": { + "grouping": { + "description": "Grouping allows an SLO to be defined for many SLI at once, like HTTP handlers for example.", + "items": { + "type": "string" + }, + "type": "array" + }, + "success": { + "description": "Success is the metric that returns how many errors there are.", + "properties": { + "metric": { + "type": "string" + } + }, + "required": [ + "metric" + ], + "type": "object" + }, + "total": { + "description": "Total is the metric that returns how many requests there are in total.", + "properties": { + "metric": { + "type": "string" + } + }, + "required": [ + "metric" + ], + "type": "object" + } + }, + "required": [ + "success", + "total" + ], + "type": "object" + }, + "ratio": { + "description": "Ratio is the indicator that measures against errors / total events.", + "properties": { + "errors": { + "description": "Errors is the metric that returns how many errors there are.", + "properties": { + "metric": { + "type": "string" + } + }, + "required": [ + "metric" + ], + "type": "object" + }, + "grouping": { + "description": "Grouping allows an SLO to be defined for many SLI at once, like HTTP handlers for example.", + "items": { + "type": "string" + }, + "type": "array" + }, + "total": { + "description": "Total is the metric that returns how many requests there are in total.", + "properties": { + "metric": { + "type": "string" + } + }, + "required": [ + "metric" + ], + "type": "object" + } + }, + "required": [ + "errors", + "total" + ], + "type": "object" + } + }, + "type": "object" + }, + "target": { + "description": "Target is a string that's casted to a float64 between 0 - 100. It represents the desired availability of the service in the given window. float64 are not supported: https://github.com/kubernetes-sigs/controller-tools/issues/245", + "type": "string" + }, + "window": { + "description": "Window within which the Target is supposed to be kept. Usually something like 1d, 7d or 28d.", + "type": "string" + } + }, + "required": [ + "indicator", + "target", + "window" + ], + "type": "object" + }, + "status": { + "description": "ServiceLevelObjectiveStatus defines the observed state of ServiceLevelObjective.", + "type": "object" + } + }, + "type": "object" + } + }, + "served": true, + "storage": true + } + ] + }, + "status": { + "acceptedNames": { + "kind": "", + "plural": "" + }, + "conditions": [], + "storedVersions": [] + } +} diff --git a/config/crd/bases/pyrra.dev_servicelevelobjectives.yaml b/config/crd/bases/pyrra.dev_servicelevelobjectives.yaml index 45abaf4..35069ab 100644 --- a/config/crd/bases/pyrra.dev_servicelevelobjectives.yaml +++ b/config/crd/bases/pyrra.dev_servicelevelobjectives.yaml @@ -21,7 +21,7 @@ spec: schema: openAPIV3Schema: description: ServiceLevelObjective is the Schema for the ServiceLevelObjectives - API + API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -36,7 +36,7 @@ spec: metadata: type: object spec: - description: ServiceLevelObjectiveSpec defines the desired state of ServiceLevelObjective + description: ServiceLevelObjectiveSpec defines the desired state of ServiceLevelObjective. properties: description: description: Description describes the ServiceLevelObjective in more @@ -129,7 +129,7 @@ spec: type: object status: description: ServiceLevelObjectiveStatus defines the observed state of - ServiceLevelObjective + ServiceLevelObjective. type: object type: object served: true diff --git a/kubernetes/api/v1alpha1/servicelevelobjective_types.go b/kubernetes/api/v1alpha1/servicelevelobjective_types.go index b29f8ae..2381ae8 100644 --- a/kubernetes/api/v1alpha1/servicelevelobjective_types.go +++ b/kubernetes/api/v1alpha1/servicelevelobjective_types.go @@ -70,7 +70,7 @@ type ServiceLevelObjectiveSpec struct { Target string `json:"target"` // Window within which the Target is supposed to be kept. Usually something like 1d, 7d or 28d. - Window model.Duration `json:"window"` + Window string `json:"window"` // ServiceLevelIndicator is the underlying data source that indicates how the service is doing. // This will be a Prometheus metric with specific selectors for your service. @@ -119,7 +119,12 @@ type ServiceLevelObjectiveStatus struct{} func (in ServiceLevelObjective) Internal() (slo.Objective, error) { target, err := strconv.ParseFloat(in.Spec.Target, 64) if err != nil { - return slo.Objective{}, err + return slo.Objective{}, fmt.Errorf("failed to parse objective target: %w", err) + } + + window, err := model.ParseDuration(in.Spec.Window) + if err != nil { + return slo.Objective{}, fmt.Errorf("failed to parse objective window: %w", err) } if in.Spec.ServiceLevelIndicator.Ratio != nil && in.Spec.ServiceLevelIndicator.Latency != nil { @@ -236,7 +241,7 @@ func (in ServiceLevelObjective) Internal() (slo.Objective, error) { Labels: ls, Description: in.Spec.Description, Target: target / 100, - Window: in.Spec.Window, + Window: window, Config: string(config), Indicator: slo.Indicator{ Ratio: ratio,