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(), + }, + } +}