From 321058915b26ea70f9d5a3a19109de0accef6357 Mon Sep 17 00:00:00 2001 From: Justin Kulikauskas Date: Wed, 8 May 2024 17:02:06 -0400 Subject: [PATCH] WIP: next --- api/v1beta1/policycore_types.go | 24 +++++++ pkg/compliance/k8sEventEmitter.go | 103 ++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 pkg/compliance/k8sEventEmitter.go diff --git a/api/v1beta1/policycore_types.go b/api/v1beta1/policycore_types.go index 2acde7a..5f04edd 100644 --- a/api/v1beta1/policycore_types.go +++ b/api/v1beta1/policycore_types.go @@ -193,3 +193,27 @@ type PolicyCore struct { Spec PolicyCoreSpec `json:"spec,omitempty"` Status PolicyCoreStatus `json:"status,omitempty"` } + +//+kubebuilder:object:generate=false + +// PolicyLike is an interface that policies should implement so that they can be worked with +// generically, without worrying about the specific kind of policy. +type PolicyLike interface { + client.Object + + // The ComplianceState (Compliant/NonCompliant) of the specific policy. + ComplianceState() ComplianceState + + // A human-readable string describing the current state of the policy, and why it is either + // Compliant or NonCompliant. + ComplianceMessage() string + + // The "parent" object on this cluster for the specific policy. Generally a Policy, in the API + // GroupVersion `policy.open-cluster-management.io/v1`. For namespaced kinds of policies, this + // will usually be the owner of the policy. For cluster-scoped policies, this must be stored + // some other way. + Parent() metav1.OwnerReference + + // The namespace of the "parent" object. + ParentNamespace() string +} diff --git a/pkg/compliance/k8sEventEmitter.go b/pkg/compliance/k8sEventEmitter.go new file mode 100644 index 0000000..130aa8a --- /dev/null +++ b/pkg/compliance/k8sEventEmitter.go @@ -0,0 +1,103 @@ +// Copyright Contributors to the Open Cluster Management project + +package compliance + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + nucleusv1beta1 "open-cluster-management.io/governance-policy-nucleus/api/v1beta1" +) + +// Can't be an interface because then Emit couldn't be a method +type K8sEmitter struct { + Client client.Client // required + Source corev1.EventSource // optional? + + // debatable for inclusion; allows tweaks of the event like adding/removing labels + // if not included, we could skip creating the event, just build it for the user to send + // but if it is included, we can build bigger things on top of this, and still have extensibility + Mutators []func(corev1.Event) (corev1.Event, error) +} + +func (e K8sEmitter) Emit(ctx context.Context, pl nucleusv1beta1.PolicyLike) (*corev1.Event, error) { + plGVK := pl.GetObjectKind().GroupVersionKind() + time := time.Now() + + // This event name matches the convention of recorders from client-go + name := fmt.Sprintf("%v.%x", pl.Parent().Name, time.UnixNano()) + + // The reason must match a pattern looked for by the policy framework + var reason string + if ns := pl.GetNamespace(); ns != "" { + reason = "policy: " + ns + "/" + pl.GetName() + } else { + reason = "policy: " + pl.GetName() + } + + // The message must begin with the compliance, then go into the descriptive message. + message := string(pl.ComplianceState()) + "; " + pl.ComplianceMessage() + + evType := "Normal" + if pl.ComplianceState() != nucleusv1beta1.Compliant { + evType = "Warning" + } + + event := corev1.Event{ + TypeMeta: metav1.TypeMeta{ + Kind: "Event", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: pl.ParentNamespace(), + Labels: pl.GetLabels(), + Annotations: pl.GetAnnotations(), + }, + InvolvedObject: corev1.ObjectReference{ + Kind: pl.Parent().Kind, + Namespace: pl.ParentNamespace(), + Name: pl.Parent().Name, + UID: pl.Parent().UID, + APIVersion: pl.Parent().APIVersion, + }, + Reason: reason, + Message: message, + Source: e.Source, + FirstTimestamp: metav1.NewTime(time), + LastTimestamp: metav1.NewTime(time), + Count: 1, + Type: evType, + EventTime: metav1.NewMicroTime(time), // does this break anything? + Series: nil, + Action: "ComplianceStateUpdate", + Related: &corev1.ObjectReference{ + Kind: plGVK.Kind, + Namespace: pl.GetNamespace(), + Name: pl.GetName(), + UID: pl.GetUID(), + APIVersion: plGVK.GroupVersion().String(), + ResourceVersion: pl.GetResourceVersion(), + }, + ReportingController: e.Source.Component, + ReportingInstance: e.Source.Host, + } + + for _, mutatorFunc := range e.Mutators { + var err error + + event, err = mutatorFunc(event) + if err != nil { + return nil, err + } + } + + err := e.Client.Create(ctx, &event) + + return &event, err +}