From 502d0f454ffc83fc2ab007b166eac94afaa7383e Mon Sep 17 00:00:00 2001 From: Florian Bacher Date: Wed, 4 Jan 2023 15:34:06 +0100 Subject: [PATCH] add missing files Signed-off-by: Florian Bacher --- .../v1alpha2/keptnappcreationrequest_types.go | 65 ++++++ ...cle.keptn.sh_keptnappcreationrequests.yaml | 57 +++++ ...injection_in_keptnappcreationrequests.yaml | 7 + .../webhook_in_keptnappcreationrequests.yaml | 16 ++ .../keptnappcreationrequest_editor_role.yaml | 24 +++ .../keptnappcreationrequest_viewer_role.yaml | 20 ++ ...ycle_v1alpha2_keptnappcreationrequest.yaml | 8 + .../keptnappcreationrequest/controller.go | 200 ++++++++++++++++++ 8 files changed, 397 insertions(+) create mode 100644 operator/api/v1alpha2/keptnappcreationrequest_types.go create mode 100644 operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml create mode 100644 operator/config/crd/patches/cainjection_in_keptnappcreationrequests.yaml create mode 100644 operator/config/crd/patches/webhook_in_keptnappcreationrequests.yaml create mode 100644 operator/config/rbac/keptnappcreationrequest_editor_role.yaml create mode 100644 operator/config/rbac/keptnappcreationrequest_viewer_role.yaml create mode 100644 operator/config/samples/lifecycle_v1alpha2_keptnappcreationrequest.yaml create mode 100644 operator/controllers/keptnappcreationrequest/controller.go diff --git a/operator/api/v1alpha2/keptnappcreationrequest_types.go b/operator/api/v1alpha2/keptnappcreationrequest_types.go new file mode 100644 index 0000000000..a6714cd21a --- /dev/null +++ b/operator/api/v1alpha2/keptnappcreationrequest_types.go @@ -0,0 +1,65 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// KeptnAppCreationRequestSpec defines the desired state of KeptnAppCreationRequest +type KeptnAppCreationRequestSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + AppName string `json:"appName"` + // +kubebuilder:default=30 + DiscoveryDeadlineSeconds *int64 `json:"discoveryDeadlineSeconds,omitempty"` +} + +// KeptnAppCreationRequestStatus defines the observed state of KeptnAppCreationRequest +type KeptnAppCreationRequestStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// KeptnAppCreationRequest is the Schema for the keptnappcreationrequests API +type KeptnAppCreationRequest struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec KeptnAppCreationRequestSpec `json:"spec,omitempty"` + Status KeptnAppCreationRequestStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// KeptnAppCreationRequestList contains a list of KeptnAppCreationRequest +type KeptnAppCreationRequestList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []KeptnAppCreationRequest `json:"items"` +} + +func init() { + SchemeBuilder.Register(&KeptnAppCreationRequest{}, &KeptnAppCreationRequestList{}) +} diff --git a/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml new file mode 100644 index 0000000000..6393154baa --- /dev/null +++ b/operator/config/crd/bases/lifecycle.keptn.sh_keptnappcreationrequests.yaml @@ -0,0 +1,57 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: keptnappcreationrequests.lifecycle.keptn.sh +spec: + group: lifecycle.keptn.sh + names: + kind: KeptnAppCreationRequest + listKind: KeptnAppCreationRequestList + plural: keptnappcreationrequests + singular: keptnappcreationrequest + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: KeptnAppCreationRequest is the Schema for the keptnappcreationrequests + 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: KeptnAppCreationRequestSpec defines the desired state of + KeptnAppCreationRequest + properties: + appName: + type: string + discoveryDeadlineSeconds: + default: 30 + format: int64 + type: integer + required: + - appName + type: object + status: + description: KeptnAppCreationRequestStatus defines the observed state + of KeptnAppCreationRequest + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/operator/config/crd/patches/cainjection_in_keptnappcreationrequests.yaml b/operator/config/crd/patches/cainjection_in_keptnappcreationrequests.yaml new file mode 100644 index 0000000000..f8023edd07 --- /dev/null +++ b/operator/config/crd/patches/cainjection_in_keptnappcreationrequests.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: keptnappcreationrequests.lifecycle.keptn.sh diff --git a/operator/config/crd/patches/webhook_in_keptnappcreationrequests.yaml b/operator/config/crd/patches/webhook_in_keptnappcreationrequests.yaml new file mode 100644 index 0000000000..fb68a5908b --- /dev/null +++ b/operator/config/crd/patches/webhook_in_keptnappcreationrequests.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: keptnappcreationrequests.lifecycle.keptn.sh +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/operator/config/rbac/keptnappcreationrequest_editor_role.yaml b/operator/config/rbac/keptnappcreationrequest_editor_role.yaml new file mode 100644 index 0000000000..b7fdf0bec3 --- /dev/null +++ b/operator/config/rbac/keptnappcreationrequest_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit keptnappcreationrequests. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: keptnappcreationrequest-editor-role +rules: +- apiGroups: + - lifecycle.keptn.sh + resources: + - keptnappcreationrequests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - lifecycle.keptn.sh + resources: + - keptnappcreationrequests/status + verbs: + - get diff --git a/operator/config/rbac/keptnappcreationrequest_viewer_role.yaml b/operator/config/rbac/keptnappcreationrequest_viewer_role.yaml new file mode 100644 index 0000000000..bec284a954 --- /dev/null +++ b/operator/config/rbac/keptnappcreationrequest_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view keptnappcreationrequests. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: keptnappcreationrequest-viewer-role +rules: +- apiGroups: + - lifecycle.keptn.sh + resources: + - keptnappcreationrequests + verbs: + - get + - list + - watch +- apiGroups: + - lifecycle.keptn.sh + resources: + - keptnappcreationrequests/status + verbs: + - get diff --git a/operator/config/samples/lifecycle_v1alpha2_keptnappcreationrequest.yaml b/operator/config/samples/lifecycle_v1alpha2_keptnappcreationrequest.yaml new file mode 100644 index 0000000000..f467018c18 --- /dev/null +++ b/operator/config/samples/lifecycle_v1alpha2_keptnappcreationrequest.yaml @@ -0,0 +1,8 @@ +apiVersion: lifecycle.keptn.sh/v1alpha2 +kind: KeptnAppCreationRequest +metadata: + name: keptnappcreationrequest-sample + namespace: podtato-kubectl +spec: + appName: "my-app" + discoveryDeadlineSeconds: 10 diff --git a/operator/controllers/keptnappcreationrequest/controller.go b/operator/controllers/keptnappcreationrequest/controller.go new file mode 100644 index 0000000000..267a42379f --- /dev/null +++ b/operator/controllers/keptnappcreationrequest/controller.go @@ -0,0 +1,200 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package keptnappcreationrequest + +import ( + "context" + "fmt" + "github.com/go-logr/logr" + "github.com/hashicorp/go-version" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "strings" + "time" + + klcv1alpha2 "github.com/keptn/lifecycle-toolkit/operator/api/v1alpha2" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// KeptnAppCreationRequestReconciler reconciles a KeptnAppCreationRequest object +type KeptnAppCreationRequestReconciler struct { + client.Client + Scheme *runtime.Scheme + Log logr.Logger +} + +//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnappcreationrequests,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnappcreationrequests/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnappcreationrequests/finalizers,verbs=update +//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnapp,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnworkload,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the KeptnAppCreationRequest object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *KeptnAppCreationRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + + creationRequest := &klcv1alpha2.KeptnAppCreationRequest{} + + if err := r.Get(ctx, req.NamespacedName, creationRequest); err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("could not retrieve KeptnAppCreationRequest: %w", err) + } + + // check if we already have an app that has not been created by this controller + + appFound := false + keptnApp := &klcv1alpha2.KeptnApp{} + name := req.NamespacedName + name.Name = creationRequest.Spec.AppName + if err := r.Get(ctx, name, keptnApp); err != nil { + if errors.IsNotFound(err) { + r.Log.Info("No KeptnApp found for KeptnAppCreationRequest", "KeptnAppCreationRequest", creationRequest) + } else { + return ctrl.Result{}, fmt.Errorf("could not retrieve KeptnApp %w", err) + } + } else { + appFound = true + } + + // if the found app has not been created by this controller, we are done at this point - we don't want to mess with what the user has created + if appFound && len(keptnApp.OwnerReferences) == 0 { + r.Log.Info("User defined KeptnApp found for KeptnAppCreationRequest", "KeptnAppCreationRequest", creationRequest) + if err := r.Delete(ctx, creationRequest); err != nil { + r.Log.Error(err, "Could not delete KeptnAppCreationRequest", "KeptnAppCreationRequest", creationRequest) + } + return ctrl.Result{}, nil + } + + // check if discovery deadline has expired + discoveryDeadlineSeconds := time.Duration(*creationRequest.Spec.DiscoveryDeadlineSeconds) + if !time.Now().After(creationRequest.CreationTimestamp.Add(discoveryDeadlineSeconds * time.Second)) { + r.Log.Info("Discovery deadline not expired yet", "KeptnAppCreationRequest", creationRequest) + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + + // look up all the KeptnWorkloads referencing the KeptnApp + + workloads := &klcv1alpha2.KeptnWorkloadList{} + if err := r.Client.List(ctx, workloads, client.InNamespace(creationRequest.Namespace), client.MatchingFields{ + "spec.app": creationRequest.Spec.AppName, + }); err != nil { + return ctrl.Result{RequeueAfter: 10 * time.Second}, fmt.Errorf("could not retrieve KeptnWorkloads: %w", err) + } + + var err error + if !appFound { + err = r.createKeptnApp(ctx, creationRequest, workloads) + } else { + err = r.updateKeptnApp(ctx, keptnApp, workloads) + } + + if err != nil { + return ctrl.Result{}, fmt.Errorf("could not update: %w", err) + } + + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *KeptnAppCreationRequestReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&klcv1alpha2.KeptnAppCreationRequest{}). + Complete(r) +} + +func (r *KeptnAppCreationRequestReconciler) updateKeptnApp(ctx context.Context, keptnApp *klcv1alpha2.KeptnApp, workloads *klcv1alpha2.KeptnWorkloadList) error { + + updatedVersion := false + addedWorkload := false + for _, workload := range workloads.Items { + foundWorkload := false + workloadName := strings.TrimPrefix(workload.Name, fmt.Sprintf("%s-", keptnApp.Name)) + for index, appWorkload := range keptnApp.Spec.Workloads { + if appWorkload.Name == workloadName { + // make sure the version matches the current version of the workload + if keptnApp.Spec.Workloads[index].Version != workload.Spec.Version { + keptnApp.Spec.Workloads[index].Version = workload.Spec.Version + // we may also want to increase the version of the app if any version has been changed + updatedVersion = true + } + foundWorkload = true + break + } + } + + if !foundWorkload { + keptnApp.Spec.Workloads = append(keptnApp.Spec.Workloads, klcv1alpha2.KeptnWorkloadRef{ + Name: workloadName, + Version: workload.Spec.Version, + }) + addedWorkload = true + } + } + + if !updatedVersion && !addedWorkload { + return nil + } + + if updatedVersion { + oldVersion, _ := version.NewVersion(keptnApp.Spec.Version) + keptnApp.Spec.Version = fmt.Sprintf("%d.0.0", oldVersion.Segments()[0]+1) + } + + return r.Update(ctx, keptnApp) +} + +func (r *KeptnAppCreationRequestReconciler) createKeptnApp(ctx context.Context, creationRequest *klcv1alpha2.KeptnAppCreationRequest, workloads *klcv1alpha2.KeptnWorkloadList) error { + keptnApp := &klcv1alpha2.KeptnApp{ + ObjectMeta: metav1.ObjectMeta{ + Name: creationRequest.Spec.AppName, + Namespace: creationRequest.Namespace, + }, + Spec: klcv1alpha2.KeptnAppSpec{ + Version: "1.0.0", + PreDeploymentTasks: []string{}, + PostDeploymentTasks: []string{}, + PreDeploymentEvaluations: []string{}, + PostDeploymentEvaluations: []string{}, + Workloads: []klcv1alpha2.KeptnWorkloadRef{}, + }, + } + + if err := controllerutil.SetControllerReference(creationRequest, keptnApp, r.Scheme); err != nil { + r.Log.Error(err, "could not set controller reference for KeptnApp: "+keptnApp.Name) + } + + for _, workload := range workloads.Items { + keptnApp.Spec.Workloads = append(keptnApp.Spec.Workloads, klcv1alpha2.KeptnWorkloadRef{ + Name: strings.TrimPrefix(workload.Name, fmt.Sprintf("%s-", creationRequest.Spec.AppName)), + Version: workload.Spec.Version, + }) + } + + return r.Create(ctx, keptnApp) +}