From 0180d750872527bc0bf9f8ed793b9095efb41c67 Mon Sep 17 00:00:00 2001 From: Nikhil Thomas Date: Wed, 24 Nov 2021 19:23:48 +0530 Subject: [PATCH] Add mechanism to handle webhook definitons as part of webhook deployment Add mechanism to webhook controller to create validatingWebhookConfigurations and mutatingWebhookConfigurations right before webhoo registration in webhook deployment. This patch ensures that the names used in the webhookConfigurations remain static without risking edits by OLM Signed-off-by: Nikhil Thomas --- .../500-webhooks.yaml | 0 cmd/kubernetes/webhook/main.go | 6 +- .../500_webhooks.yaml | 60 ++++++++++ cmd/openshift/webhook/main.go | 6 +- config/openshift/base/500-webhooks.yaml | 49 -------- config/openshift/base/kustomization.yaml | 1 - config/webhooks/kustomization.yaml | 2 +- config/webhooks/webhook-secret.yaml | 22 ++++ pkg/webhook/webhook.go | 90 +-------------- pkg/webhook/webhook_init.go | 108 ++++++++++++++++++ 10 files changed, 202 insertions(+), 142 deletions(-) rename {config/webhooks => cmd/kubernetes/webhook/kodata/validating-defaulting-webhook}/500-webhooks.yaml (100%) create mode 100644 cmd/openshift/webhook/kodata/validating-defaulting-webhook/500_webhooks.yaml delete mode 100644 config/openshift/base/500-webhooks.yaml create mode 100644 config/webhooks/webhook-secret.yaml create mode 100644 pkg/webhook/webhook_init.go diff --git a/config/webhooks/500-webhooks.yaml b/cmd/kubernetes/webhook/kodata/validating-defaulting-webhook/500-webhooks.yaml similarity index 100% rename from config/webhooks/500-webhooks.yaml rename to cmd/kubernetes/webhook/kodata/validating-defaulting-webhook/500-webhooks.yaml diff --git a/cmd/kubernetes/webhook/main.go b/cmd/kubernetes/webhook/main.go index 498a3d8a69..a7dcdc3f14 100644 --- a/cmd/kubernetes/webhook/main.go +++ b/cmd/kubernetes/webhook/main.go @@ -44,11 +44,13 @@ func main() { Port: 8443, SecretName: secretName, }) - + cfg := injection.ParseAndGetRESTConfigOrDie() + ctx, _ = injection.EnableInjectionOrDie(ctx, cfg) + webhook.CreateWebhookResources(ctx) webhook.SetTypes("kubernetes") sharedmain.WebhookMainWithConfig(ctx, serviceName, - injection.ParseAndGetRESTConfigOrDie(), + cfg, certificates.NewController, webhook.NewDefaultingAdmissionController, webhook.NewValidationAdmissionController, diff --git a/cmd/openshift/webhook/kodata/validating-defaulting-webhook/500_webhooks.yaml b/cmd/openshift/webhook/kodata/validating-defaulting-webhook/500_webhooks.yaml new file mode 100644 index 0000000000..101f2ae234 --- /dev/null +++ b/cmd/openshift/webhook/kodata/validating-defaulting-webhook/500_webhooks.yaml @@ -0,0 +1,60 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + labels: + app: tekton-operator + name: tekton-operator-webhook + name: webhook.operator.tekton.dev +webhooks: +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: tekton-operator-webhook + namespace: openshift-operators + failurePolicy: Fail + name: webhook.operator.tekton.dev + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app: tekton-operator + name: tekton-operator-webhook + name: config.webhook.operator.tekton.dev +webhooks: +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: tekton-operator-webhook + namespace: openshift-operators + failurePolicy: Fail + name: config.webhook.operator.tekton.dev + namespaceSelector: + matchExpressions: + - key: operator.tekton.dev/release + operator: Exists + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app: tekton-operator + name: tekton-operator-webhook + name: validation.webhook.operator.tekton.dev +webhooks: +- admissionReviewVersions: + - v1beta1 + - v1 + clientConfig: + service: + name: tekton-operator-webhook + namespace: openshift-operators + failurePolicy: Fail + name: validation.webhook.operator.tekton.dev + sideEffects: None \ No newline at end of file diff --git a/cmd/openshift/webhook/main.go b/cmd/openshift/webhook/main.go index ea886106a4..bff5dc533d 100644 --- a/cmd/openshift/webhook/main.go +++ b/cmd/openshift/webhook/main.go @@ -44,11 +44,13 @@ func main() { Port: 8443, SecretName: secretName, }) - + cfg := injection.ParseAndGetRESTConfigOrDie() + ctx, _ = injection.EnableInjectionOrDie(ctx, cfg) + webhook.CreateWebhookResources(ctx) webhook.SetTypes("openshift") sharedmain.WebhookMainWithConfig(ctx, serviceName, - injection.ParseAndGetRESTConfigOrDie(), + cfg, certificates.NewController, webhook.NewDefaultingAdmissionController, webhook.NewValidationAdmissionController, diff --git a/config/openshift/base/500-webhooks.yaml b/config/openshift/base/500-webhooks.yaml deleted file mode 100644 index d702bf7fe0..0000000000 --- a/config/openshift/base/500-webhooks.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2021 The Tekton Authors -# -# 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 -# -# https://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. - -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: validation.webhook.operator.tekton.dev -webhooks: - - clientConfig: - service: - name: tekton-operator-webhook - namespace: openshift-operators - name: validation.webhook.operator.tekton.dev - ---- - -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - name: webhook.operator.tekton.dev -webhooks: -- clientConfig: - service: - name: tekton-operator-webhook - namespace: openshift-operators - name: webhook.operator.tekton.dev - ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: config.webhook.operator.tekton.dev -webhooks: -- clientConfig: - service: - name: tekton-operator-webhook - namespace: openshift-operators - name: config.webhook.operator.tekton.dev diff --git a/config/openshift/base/kustomization.yaml b/config/openshift/base/kustomization.yaml index 79a05d9987..9469ef8f3f 100644 --- a/config/openshift/base/kustomization.yaml +++ b/config/openshift/base/kustomization.yaml @@ -38,7 +38,6 @@ patches: name: tekton-operator patchesStrategicMerge: -- 500-webhooks.yaml - 100-namespace.yaml resources: diff --git a/config/webhooks/kustomization.yaml b/config/webhooks/kustomization.yaml index a8ba142828..441048ea89 100644 --- a/config/webhooks/kustomization.yaml +++ b/config/webhooks/kustomization.yaml @@ -14,6 +14,6 @@ namespace: tekton-operator resources: -- 500-webhooks.yaml - webhook.yaml - webhook-service.yaml +- webhook-secret.yaml diff --git a/config/webhooks/webhook-secret.yaml b/config/webhooks/webhook-secret.yaml new file mode 100644 index 0000000000..1110ecd15e --- /dev/null +++ b/config/webhooks/webhook-secret.yaml @@ -0,0 +1,22 @@ +# Copyright 2021 The Tekton Authors +# +# 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 +# +# https://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. + +apiVersion: v1 +kind: Secret +metadata: + name: tekton-operator-webhook-certs + labels: + name: tekton-operator-webhook + app: tekton-operator +# The data is populated at install time. diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index 8b33811d83..3e1a484568 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -18,13 +18,9 @@ package webhook import ( "context" - "strings" "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" - v1 "k8s.io/api/admissionregistration/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - kubeclient "knative.dev/pkg/client/injection/kube/client" "knative.dev/pkg/configmap" "knative.dev/pkg/controller" "knative.dev/pkg/logging" @@ -49,11 +45,10 @@ func SetTypes(platform string) { } func NewDefaultingAdmissionController(ctx context.Context, cmw configmap.Watcher) *controller.Impl { - name := findAndUpdateMutatingWebhookConfigurationNameOrDie(ctx, "webhook.operator.tekton.dev") return defaulting.NewAdmissionController(ctx, // Name of the resource webhook. - name, + "webhook.operator.tekton.dev", // The path on which to serve the webhook. "/defaulting", @@ -71,11 +66,10 @@ func NewDefaultingAdmissionController(ctx context.Context, cmw configmap.Watcher } func NewValidationAdmissionController(ctx context.Context, cmw configmap.Watcher) *controller.Impl { - name := findAndUpdateValidatingWebhookConfigurationNameOrDie(ctx, "validation.webhook.operator.tekton.dev") return validation.NewAdmissionController(ctx, // Name of the resource webhook. - name, + "validation.webhook.operator.tekton.dev", // The path on which to serve the webhook. "/resource-validation", @@ -94,10 +88,9 @@ func NewValidationAdmissionController(ctx context.Context, cmw configmap.Watcher } func NewConfigValidationController(ctx context.Context, cmw configmap.Watcher) *controller.Impl { - name := findAndUpdateValidatingWebhookConfigurationNameOrDie(ctx, "config.webhook.operator.tekton.dev") return configmaps.NewAdmissionController(ctx, // Name of the configmap webhook. - name, + "config.webhook.operator.tekton.dev", // The path on which to serve the webhook. "/config-validation", @@ -107,80 +100,3 @@ func NewConfigValidationController(ctx context.Context, cmw configmap.Watcher) * }, ) } - -func findAndUpdateMutatingWebhookConfigurationNameOrDie(ctx context.Context, namePrefix string) string { - logger := logging.FromContext(ctx) - kubeClientSet := kubeclient.Get(ctx) - - mutatingWebhookConfigurations, err := kubeClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, metav1.ListOptions{}) - if err != nil { - logger.Error(err) - logger.Fatal("MutatingWebhookConfiguration with prefix ", namePrefix, " not found") - return "" - } - - // Find the mutatingWebhookConfiguration with the given generateName prefix - var mutatingWebhookConfiguration *v1.MutatingWebhookConfiguration - for _, item := range mutatingWebhookConfigurations.Items { - if strings.HasPrefix(item.Name, namePrefix) { - mutatingWebhookConfiguration = &item - break - } - } - if mutatingWebhookConfiguration == nil { - logger.Fatal("MutatingWebhookConfiguration with prefix ", namePrefix, " not found") - return "" - } - webhookName := mutatingWebhookConfiguration.Name - - // Update the webhooks[*].name field with the generated Name (metadata.name) of the mutatingWebhookConfiguration - for i := range mutatingWebhookConfiguration.Webhooks { - mutatingWebhookConfiguration.Webhooks[i].Name = webhookName - } - _, err = kubeClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations().Update(ctx, mutatingWebhookConfiguration, metav1.UpdateOptions{}) - if err != nil { - logger.Error(err) - logger.Fatal("Could not update MutatingWebhookConfiguration ", webhookName) - return "" - } - - return webhookName -} - -func findAndUpdateValidatingWebhookConfigurationNameOrDie(ctx context.Context, namePrefix string) string { - logger := logging.FromContext(ctx) - kubeClientSet := kubeclient.Get(ctx) - - validatingWebhookConfigurations, err := kubeClientSet.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, metav1.ListOptions{}) - if err != nil { - logger.Error(err) - logger.Fatal("ValidatingWebhookConfiguration with prefix ", namePrefix, " not found") - return "" - } - - // Find the validatingWebhookConfiguration with the given generateName prefix - var validatingWebhookConfiguration *v1.ValidatingWebhookConfiguration - for _, item := range validatingWebhookConfigurations.Items { - if strings.HasPrefix(item.Name, namePrefix) { - validatingWebhookConfiguration = &item - break - } - } - if validatingWebhookConfiguration == nil { - logger.Fatal("ValidatingWebhookConfiguration with prefix ", namePrefix, " not found") - return "" - } - webhookName := validatingWebhookConfiguration.Name - - // Update the webhooks[*].name field with the generated Name (metadata.name) of the validatingWebhookConfiguration - for i := range validatingWebhookConfiguration.Webhooks { - validatingWebhookConfiguration.Webhooks[i].Name = webhookName - } - _, err = kubeClientSet.AdmissionregistrationV1().ValidatingWebhookConfigurations().Update(ctx, validatingWebhookConfiguration, metav1.UpdateOptions{}) - if err != nil { - logger.Error(err) - logger.Fatal("Could not update ValidatingWebhookConfiguration ", webhookName) - return "" - } - return webhookName -} diff --git a/pkg/webhook/webhook_init.go b/pkg/webhook/webhook_init.go new file mode 100644 index 0000000000..ba92efb63e --- /dev/null +++ b/pkg/webhook/webhook_init.go @@ -0,0 +1,108 @@ +package webhook + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/go-logr/zapr" + mfc "github.com/manifestival/client-go-client" + mf "github.com/manifestival/manifestival" + "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" + clientset "github.com/tektoncd/operator/pkg/client/clientset/versioned" + operatorclient "github.com/tektoncd/operator/pkg/client/injection/client" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/tektoncd/operator/pkg/reconciler/common" + "go.uber.org/zap" + "knative.dev/pkg/injection" + "knative.dev/pkg/logging" +) + +const WEBHOOK_INSTALLERSET_LABEL = "validating-defaulting-webhooks.operator.tekton.dev" + +func CreateWebhookResources(ctx context.Context) { + logger := logging.FromContext(ctx) + + mfclient, err := mfc.NewClient(injection.GetConfig(ctx)) + if err != nil { + logger.Fatalw("error creating client from injected config", zap.Error(err)) + } + mflogger := zapr.NewLogger(logger.Named("manifestival").Desugar()) + manifest, err := mf.ManifestFrom(mf.Slice{}, mf.UseClient(mfclient), mf.UseLogger(mflogger)) + if err != nil { + logger.Fatalw("error creating initial manifest", zap.Error(err)) + } + + // Read manifests + koDataDir := os.Getenv(common.KoEnvKey) + validating_defaulting_webhooks := filepath.Join(koDataDir, "validating-defaulting-webhook") + if err := common.AppendManifest(&manifest, validating_defaulting_webhooks); err != nil { + logger.Fatalw("error creating initial manifest", zap.Error(err)) + } + + client := operatorclient.Get(ctx) + err = checkAndDeleteInstallerSet(ctx, client) + if err != nil { + logger.Fatalw("error creating client from injected config", zap.Error(err)) + } + + if err := createInstallerSet(ctx, client, manifest); err != nil { + logger.Fatalw("error creating client from injected config", zap.Error(err)) + } +} + +func checkAndDeleteInstallerSet(ctx context.Context, oc clientset.Interface) error { + ctIs, err := oc.OperatorV1alpha1().TektonInstallerSets(). + List(ctx, metav1.ListOptions{ + LabelSelector: WEBHOOK_INSTALLERSET_LABEL, + }) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return err + } + if len(ctIs.Items) >= 0 { + for _, item := range ctIs.Items { + err = oc.OperatorV1alpha1().TektonInstallerSets(). + Delete(ctx, item.Name, metav1.DeleteOptions{}) + if err != nil { + return err + } + } + } + return nil +} + +func createInstallerSet(ctx context.Context, oc clientset.Interface, manifest mf.Manifest) error { + is := makeInstallerSet(manifest) + _, err := oc.OperatorV1alpha1().TektonInstallerSets(). + Create(ctx, is, metav1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +func makeInstallerSet(manifest mf.Manifest) *v1alpha1.TektonInstallerSet { + //TODO: find ownerReference of the operator controller deployment and use that as the + // ownerReference for this TektonInstallerSet + return &v1alpha1.TektonInstallerSet{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s-", "validating-mutating-webhoook"), + Labels: map[string]string{ + WEBHOOK_INSTALLERSET_LABEL: "", + }, + Annotations: map[string]string{ + "releaseVersionKey": "v1.6.0", + }, + //OwnerReferences: []metav1.OwnerReference{ownerRef}, + }, + Spec: v1alpha1.TektonInstallerSetSpec{ + Manifests: manifest.Resources(), + }, + } +}