From 3d0bd43be504e464476884ad8c4d88a2381e374f Mon Sep 17 00:00:00 2001 From: Robin Opletal Date: Mon, 24 Jun 2024 13:44:23 +0200 Subject: [PATCH] feat(alertmanagerconfig): statuses and stuff Signed-off-by: Robin Opletal --- api/osko/v1alpha1/alertmanagerconfig_types.go | 8 +- api/osko/v1alpha1/zz_generated.deepcopy.go | 10 ++- .../bases/osko.dev_alertmanagerconfigs.yaml | 86 ++++++++++++++++++- config/samples/config_secret.yaml | 2 - config/samples/openslo_v1_datasource.yaml | 5 +- devel/mimir/alertmanager-default-config.yaml | 1 - devel/mimir/configmap.yaml | 4 +- .../osko/alertmanagerconfig_controller.go | 21 ++++- internal/utils/common_utils.go | 21 ++++- 9 files changed, 142 insertions(+), 16 deletions(-) diff --git a/api/osko/v1alpha1/alertmanagerconfig_types.go b/api/osko/v1alpha1/alertmanagerconfig_types.go index 2fc958a..8388568 100644 --- a/api/osko/v1alpha1/alertmanagerconfig_types.go +++ b/api/osko/v1alpha1/alertmanagerconfig_types.go @@ -11,10 +11,16 @@ type AlertManagerConfigSpec struct { } // AlertManagerConfigStatus defines the observed state of AlertManagerConfig -type AlertManagerConfigStatus struct{} +type AlertManagerConfigStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty"` + LastEvaluationTime metav1.Time `json:"lastEvaluationTime,omitempty"` + Ready string `json:"ready,omitempty"` +} // +kubebuilder:object:root=true // +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=.status.ready,description="The reason for the current status of the AlertmanagerConfig resource" +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=.metadata.creationTimestamp,description="The time when the AlertmanagerConfig resource was created" // AlertManagerConfig is the Schema for the alertmanagerconfigs API type AlertManagerConfig struct { diff --git a/api/osko/v1alpha1/zz_generated.deepcopy.go b/api/osko/v1alpha1/zz_generated.deepcopy.go index b099bc7..2f6f279 100644 --- a/api/osko/v1alpha1/zz_generated.deepcopy.go +++ b/api/osko/v1alpha1/zz_generated.deepcopy.go @@ -16,7 +16,7 @@ func (in *AlertManagerConfig) DeepCopyInto(out *AlertManagerConfig) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = in.Spec - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertManagerConfig. @@ -88,6 +88,14 @@ func (in *AlertManagerConfigSpec) DeepCopy() *AlertManagerConfigSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AlertManagerConfigStatus) DeepCopyInto(out *AlertManagerConfigStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.LastEvaluationTime.DeepCopyInto(&out.LastEvaluationTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertManagerConfigStatus. diff --git a/config/crd/bases/osko.dev_alertmanagerconfigs.yaml b/config/crd/bases/osko.dev_alertmanagerconfigs.yaml index 132e298..cc51ea7 100644 --- a/config/crd/bases/osko.dev_alertmanagerconfigs.yaml +++ b/config/crd/bases/osko.dev_alertmanagerconfigs.yaml @@ -14,7 +14,16 @@ spec: singular: alertmanagerconfig scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - description: The reason for the current status of the AlertmanagerConfig resource + jsonPath: .status.ready + name: Ready + type: string + - description: The time when the AlertmanagerConfig resource was created + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 schema: openAPIV3Schema: description: AlertManagerConfig is the Schema for the alertmanagerconfigs @@ -58,6 +67,81 @@ spec: type: object status: description: AlertManagerConfigStatus defines the observed state of AlertManagerConfig + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + lastEvaluationTime: + format: date-time + type: string + ready: + type: string type: object type: object served: true diff --git a/config/samples/config_secret.yaml b/config/samples/config_secret.yaml index 14517b7..17b12e8 100644 --- a/config/samples/config_secret.yaml +++ b/config/samples/config_secret.yaml @@ -10,7 +10,6 @@ stringData: alertmanager.yaml: |- route: receiver: 'default-receiver' - receivers: - name: 'default-receiver' email_configs: @@ -20,4 +19,3 @@ stringData: auth_username: 'alertmanager' auth_password: 'password' auth_identity: 'alertmanager@domain.com' - diff --git a/config/samples/openslo_v1_datasource.yaml b/config/samples/openslo_v1_datasource.yaml index f3b4a39..e3688f4 100644 --- a/config/samples/openslo_v1_datasource.yaml +++ b/config/samples/openslo_v1_datasource.yaml @@ -1,5 +1,3 @@ -# TODO: heureka-specific sample, should be replaced in the further-future with a more comprehensive dev environment -# ref.: https://github.com/oskoperator/osko/issues/45 apiVersion: openslo.com/v1 kind: Datasource metadata: @@ -10,7 +8,10 @@ spec: description: Mimir Datasource for logging tenant type: mimir connectionDetails: + address: https://mimir.monitoring.dev.heu.group/ address: http://localhost:9009/ sourceTenants: + - gatekeeper-system + targetTenant: gatekeeper-system - monitoring targetTenant: monitoring diff --git a/devel/mimir/alertmanager-default-config.yaml b/devel/mimir/alertmanager-default-config.yaml index 749431c..1df8aac 100644 --- a/devel/mimir/alertmanager-default-config.yaml +++ b/devel/mimir/alertmanager-default-config.yaml @@ -7,7 +7,6 @@ data: route: group_wait: 0s receiver: empty-receiver - receivers: # In this example we're not going to send any notification out of Alertmanager. - name: 'empty-receiver' diff --git a/devel/mimir/configmap.yaml b/devel/mimir/configmap.yaml index 71d2289..b159b13 100644 --- a/devel/mimir/configmap.yaml +++ b/devel/mimir/configmap.yaml @@ -16,12 +16,12 @@ data: dir: /tmp/mimir/data/tsdb tsdb: dir: /tmp/mimir/tsdb - + alertmanager: data_dir: /tmp/mimir/alertmanager enable_api: true fallback_config_file: /tmp/mimir/alertmanager-config/alertmanager-fallback-config.yaml - + alertmanager_storage: backend: filesystem filesystem: diff --git a/internal/controller/osko/alertmanagerconfig_controller.go b/internal/controller/osko/alertmanagerconfig_controller.go index b13ca2b..b00ab8a 100644 --- a/internal/controller/osko/alertmanagerconfig_controller.go +++ b/internal/controller/osko/alertmanagerconfig_controller.go @@ -3,6 +3,8 @@ package osko import ( "context" "fmt" + "github.com/oskoperator/osko/internal/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/handler" "time" @@ -65,6 +67,7 @@ func (r *AlertManagerConfigReconciler) Reconcile(ctx context.Context, req ctrl.R } log.V(1).Info("Getting datasourceRef", "datasourceRef", amc.ObjectMeta.Annotations["osko.dev/datasourceRef"]) + // Get DS from AMC's ref err = r.Get(ctx, client.ObjectKey{Name: amc.ObjectMeta.Annotations["osko.dev/datasourceRef"], Namespace: amc.Namespace}, ds) if err != nil { @@ -89,7 +92,11 @@ func (r *AlertManagerConfigReconciler) Reconcile(ctx context.Context, req ctrl.R err = r.Get(ctx, client.ObjectKey{Namespace: amc.Spec.SecretRef.Namespace, Name: amc.Spec.SecretRef.Name}, secret) if err != nil { if apierrors.IsNotFound(err) { - log.Error(err, "Secret not found") + if err = utils.UpdateStatus(ctx, amc, r.Client, "Ready", metav1.ConditionFalse, "Secret from secretRef not found"); err != nil { + log.Error(err, "Failed to update amc status") + return ctrl.Result{}, err + } + r.Recorder.Event(amc, "Warning", "SecretNotFound", "Secret from secretRef not found") return ctrl.Result{}, nil } log.Error(err, "Failed to get secret") @@ -98,7 +105,11 @@ func (r *AlertManagerConfigReconciler) Reconcile(ctx context.Context, req ctrl.R yamlData, ok := secret.Data["alertmanager.yaml"] if !ok { - log.Error(err, "alertmanager.yaml not found in secret") + if err = utils.UpdateStatus(ctx, amc, r.Client, "Ready", metav1.ConditionFalse, "alertmanager.yaml key not found in secret"); err != nil { + log.Error(err, "Failed to update amc status") + return ctrl.Result{}, err + } + r.Recorder.Event(amc, "Warning", "KeyNotFound", "alertmanager.yaml key not found in secret") return ctrl.Result{}, nil } @@ -114,6 +125,12 @@ func (r *AlertManagerConfigReconciler) Reconcile(ctx context.Context, req ctrl.R return ctrl.Result{}, err } + r.Recorder.Event(amc, "Normal", "AlertManagerConfigCreated", "AlertManagerConfig created successfully") + if err = utils.UpdateStatus(ctx, amc, r.Client, "Ready", metav1.ConditionTrue, "PrometheusRule created"); err != nil { + log.V(1).Error(err, "Failed to update SLO status") + return ctrl.Result{}, err + } + return ctrl.Result{}, nil } diff --git a/internal/utils/common_utils.go b/internal/utils/common_utils.go index 6d52003..d67d5e5 100644 --- a/internal/utils/common_utils.go +++ b/internal/utils/common_utils.go @@ -92,7 +92,7 @@ func updateCondition(conditions []metav1.Condition, newCondition metav1.Conditio return updatedConditions } -func UpdateStatus(ctx context.Context, slo *openslov1.SLO, r client.Client, conditionType string, status metav1.ConditionStatus, message string) error { +func UpdateStatus(ctx context.Context, obj client.Object, r client.Client, conditionType string, status metav1.ConditionStatus, message string) error { // Update the conditions based on provided arguments condition := metav1.Condition{ Type: conditionType, @@ -101,9 +101,22 @@ func UpdateStatus(ctx context.Context, slo *openslov1.SLO, r client.Client, cond Message: message, LastTransitionTime: metav1.NewTime(time.Now()), } - slo.Status.Conditions = updateCondition(slo.Status.Conditions, condition) - slo.Status.Ready = string(status) - return r.Status().Update(ctx, slo) + + statusField := reflect.ValueOf(obj).Elem().FieldByName("Status") + + conditionsField := statusField.FieldByName("Conditions") + if conditionsField.IsValid() && conditionsField.CanSet() { + conditions := conditionsField.Interface().([]metav1.Condition) + updatedConditions := updateCondition(conditions, condition) + conditionsField.Set(reflect.ValueOf(updatedConditions)) + } + + readyField := statusField.FieldByName("Ready") + if readyField.IsValid() && readyField.CanSet() { + readyField.SetString(string(status)) + } + + return r.Status().Update(ctx, obj) } func (m MetricLabel) NewMetricLabelCompiler(rule *monitoringv1.Rule, window string) string {