From f59c7d6260ef64cee74885cd8ba33a31a45ff4b1 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Thu, 14 Mar 2024 17:09:59 +0500 Subject: [PATCH 1/4] Add validating and defaulting webhooks and tests --- PROJECT | 4 + api/v1alpha1/etcdcluster_webhook.go | 81 ++++++++++ api/v1alpha1/etcdcluster_webhook_test.go | 67 ++++++++ api/v1alpha1/webhook_suite_test.go | 145 ++++++++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 2 +- cmd/main.go | 6 + config/certmanager/certificate.yaml | 39 +++++ config/certmanager/kustomization.yaml | 5 + config/certmanager/kustomizeconfig.yaml | 8 + config/crd/kustomization.yaml | 6 +- .../patches/cainjection_in_etcdclusters.yaml | 7 + .../crd/patches/webhook_in_etcdclusters.yaml | 16 ++ config/default/kustomization.yaml | 4 +- config/default/manager_webhook_patch.yaml | 23 +++ config/default/webhookcainjection_patch.yaml | 29 ++++ config/webhook/kustomization.yaml | 6 + config/webhook/kustomizeconfig.yaml | 22 +++ config/webhook/manifests.yaml | 52 +++++++ config/webhook/service.yaml | 19 +++ go.mod | 2 +- 20 files changed, 536 insertions(+), 7 deletions(-) create mode 100644 api/v1alpha1/etcdcluster_webhook.go create mode 100644 api/v1alpha1/etcdcluster_webhook_test.go create mode 100644 api/v1alpha1/webhook_suite_test.go create mode 100644 config/certmanager/certificate.yaml create mode 100644 config/certmanager/kustomization.yaml create mode 100644 config/certmanager/kustomizeconfig.yaml create mode 100644 config/crd/patches/cainjection_in_etcdclusters.yaml create mode 100644 config/crd/patches/webhook_in_etcdclusters.yaml create mode 100644 config/default/manager_webhook_patch.yaml create mode 100644 config/default/webhookcainjection_patch.yaml create mode 100644 config/webhook/kustomization.yaml create mode 100644 config/webhook/kustomizeconfig.yaml create mode 100644 config/webhook/manifests.yaml create mode 100644 config/webhook/service.yaml diff --git a/PROJECT b/PROJECT index 9395d603..7c3c1467 100644 --- a/PROJECT +++ b/PROJECT @@ -17,4 +17,8 @@ resources: kind: EtcdCluster path: github.com/aenix-io/etcd-operator/api/v1alpha1 version: v1alpha1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 version: "3" diff --git a/api/v1alpha1/etcdcluster_webhook.go b/api/v1alpha1/etcdcluster_webhook.go new file mode 100644 index 00000000..233531a9 --- /dev/null +++ b/api/v1alpha1/etcdcluster_webhook.go @@ -0,0 +1,81 @@ +/* +Copyright 2024. + +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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var etcdclusterlog = logf.Log.WithName("etcdcluster-resource") + +// SetupWebhookWithManager will setup the manager to manage the webhooks +func (r *EtcdCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/mutate-etcd-aenix-io-v1alpha1-etcdcluster,mutating=true,failurePolicy=fail,sideEffects=None,groups=etcd.aenix.io,resources=etcdclusters,verbs=create;update,versions=v1alpha1,name=metcdcluster.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &EtcdCluster{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *EtcdCluster) Default() { + etcdclusterlog.Info("default", "name", r.Name) + if r.Spec.Replicas == 0 { + r.Spec.Replicas = 3 + } + if r.Spec.Storage.Size.IsZero() { + r.Spec.Storage.Size = resource.MustParse("4Gi") + } +} + +//+kubebuilder:webhook:path=/validate-etcd-aenix-io-v1alpha1-etcdcluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=etcd.aenix.io,resources=etcdclusters,verbs=create;update,versions=v1alpha1,name=vetcdcluster.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &EtcdCluster{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *EtcdCluster) ValidateCreate() (admission.Warnings, error) { + etcdclusterlog.Info("validate create", "name", r.Name) + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *EtcdCluster) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + etcdclusterlog.Info("validate update", "name", r.Name) + var warnings admission.Warnings + if old.(*EtcdCluster).Spec.Replicas != r.Spec.Replicas { + warnings = append(warnings, "cluster resize is not currently supported") + } + + if len(warnings) > 0 { + return warnings, nil + } + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *EtcdCluster) ValidateDelete() (admission.Warnings, error) { + etcdclusterlog.Info("validate delete", "name", r.Name) + return nil, nil +} diff --git a/api/v1alpha1/etcdcluster_webhook_test.go b/api/v1alpha1/etcdcluster_webhook_test.go new file mode 100644 index 00000000..b6a88ccd --- /dev/null +++ b/api/v1alpha1/etcdcluster_webhook_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2024. + +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 v1alpha1 + +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/resource" +) + +var _ = Describe("EtcdCluster Webhook", func() { + + Context("When creating EtcdCluster under Defaulting Webhook", func() { + It("Should fill in the default value if a required field is empty", func() { + etcdCluster := &EtcdCluster{} + etcdCluster.Default() + gomega.Expect(etcdCluster.Spec.Replicas).To(gomega.Equal(uint(3))) + gomega.Expect(etcdCluster.Spec.Storage.Size).To(gomega.Equal(resource.MustParse("4Gi"))) + }) + + It("Should not override fields with default values if not empty", func() { + etcdCluster := &EtcdCluster{ + Spec: EtcdClusterSpec{ + Replicas: 5, + Storage: Storage{ + StorageClass: "local-path", + Size: resource.MustParse("10Gi"), + }, + }, + } + etcdCluster.Default() + gomega.Expect(etcdCluster.Spec.Replicas).To(gomega.Equal(uint(5))) + gomega.Expect(etcdCluster.Spec.Storage.Size).To(gomega.Equal(resource.MustParse("10Gi"))) + }) + }) + + // Not yet applicable as currently all fields are optional. + + //Context("When creating EtcdCluster under Validating Webhook", func() { + // It("Should deny if a required field is empty", func() { + // + // // TODO(user): Add your logic here + // + // }) + // + // It("Should admit if all required fields are provided", func() { + // + // // TODO(user): Add your logic here + // + // }) + //}) + +}) diff --git a/api/v1alpha1/webhook_suite_test.go b/api/v1alpha1/webhook_suite_test.go new file mode 100644 index 00000000..ef93bc64 --- /dev/null +++ b/api/v1alpha1/webhook_suite_test.go @@ -0,0 +1,145 @@ +/* +Copyright 2024. + +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 v1alpha1 + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "runtime" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + admissionv1 "k8s.io/api/admission/v1" + //+kubebuilder:scaffold:imports + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := apimachineryruntime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + WebhookServer: webhook.NewServer(webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), + LeaderElection: false, + Metrics: metricsserver.Options{BindAddress: "0"}, + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&EtcdCluster{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:webhook + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + return conn.Close() + }).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index da6b8b19..e19f556d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1alpha1 import ( "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/cmd/main.go b/cmd/main.go index 3f9f949e..304013aa 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -129,6 +129,12 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "EtcdCluster") os.Exit(1) } + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err = (&etcdaenixiov1alpha1.EtcdCluster{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "EtcdCluster") + os.Exit(1) + } + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml new file mode 100644 index 00000000..7863c4f9 --- /dev/null +++ b/config/certmanager/certificate.yaml @@ -0,0 +1,39 @@ +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/name: certificate + app.kubernetes.io/instance: serving-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: etcd-operator + app.kubernetes.io/part-of: etcd-operator + app.kubernetes.io/managed-by: kustomize + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: certificate + app.kubernetes.io/instance: serving-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: etcd-operator + app.kubernetes.io/part-of: etcd-operator + app.kubernetes.io/managed-by: kustomize + name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize diff --git a/config/certmanager/kustomization.yaml b/config/certmanager/kustomization.yaml new file mode 100644 index 00000000..bebea5a5 --- /dev/null +++ b/config/certmanager/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- certificate.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml new file mode 100644 index 00000000..cf6f89e8 --- /dev/null +++ b/config/certmanager/kustomizeconfig.yaml @@ -0,0 +1,8 @@ +# This configuration is for teaching kustomize how to update name ref substitution +nameReference: +- kind: Issuer + group: cert-manager.io + fieldSpecs: + - kind: Certificate + group: cert-manager.io + path: spec/issuerRef/name diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 50a428d2..ae21dc31 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -8,7 +8,7 @@ resources: patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD -#- path: patches/webhook_in_etcdclusters.yaml +- path: patches/webhook_in_etcdclusters.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -19,5 +19,5 @@ patches: # [WEBHOOK] To enable webhook, uncomment the following section # the following config is for teaching kustomize how to do kustomization for CRDs. -#configurations: -#- kustomizeconfig.yaml +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/patches/cainjection_in_etcdclusters.yaml b/config/crd/patches/cainjection_in_etcdclusters.yaml new file mode 100644 index 00000000..017d790b --- /dev/null +++ b/config/crd/patches/cainjection_in_etcdclusters.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: etcdclusters.etcd.aenix.io diff --git a/config/crd/patches/webhook_in_etcdclusters.yaml b/config/crd/patches/webhook_in_etcdclusters.yaml new file mode 100644 index 00000000..f21e035d --- /dev/null +++ b/config/crd/patches/webhook_in_etcdclusters.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: etcdclusters.etcd.aenix.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 8ee8b3c7..fee37300 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -20,7 +20,7 @@ resources: - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- ../webhook +- ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. #- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. @@ -34,7 +34,7 @@ patches: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- path: manager_webhook_patch.yaml +- path: manager_webhook_patch.yaml # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml new file mode 100644 index 00000000..738de350 --- /dev/null +++ b/config/default/manager_webhook_patch.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml new file mode 100644 index 00000000..381a126e --- /dev/null +++ b/config/default/webhookcainjection_patch.yaml @@ -0,0 +1,29 @@ +# This patch add annotation to admission webhook config and +# CERTIFICATE_NAMESPACE and CERTIFICATE_NAME will be substituted by kustomize +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/name: mutatingwebhookconfiguration + app.kubernetes.io/instance: mutating-webhook-configuration + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: etcd-operator + app.kubernetes.io/part-of: etcd-operator + app.kubernetes.io/managed-by: kustomize + name: mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/name: validatingwebhookconfiguration + app.kubernetes.io/instance: validating-webhook-configuration + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: etcd-operator + app.kubernetes.io/part-of: etcd-operator + app.kubernetes.io/managed-by: kustomize + name: validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml new file mode 100644 index 00000000..9cf26134 --- /dev/null +++ b/config/webhook/kustomization.yaml @@ -0,0 +1,6 @@ +resources: +- manifests.yaml +- service.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml new file mode 100644 index 00000000..206316e5 --- /dev/null +++ b/config/webhook/kustomizeconfig.yaml @@ -0,0 +1,22 @@ +# the following config is for teaching kustomize where to look at when substituting nameReference. +# It requires kustomize v2.1.0 or newer to work properly. +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + - kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + +namespace: +- kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true +- kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml new file mode 100644 index 00000000..27e93e79 --- /dev/null +++ b/config/webhook/manifests.yaml @@ -0,0 +1,52 @@ +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-etcd-aenix-io-v1alpha1-etcdcluster + failurePolicy: Fail + name: metcdcluster.kb.io + rules: + - apiGroups: + - etcd.aenix.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - etcdclusters + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-etcd-aenix-io-v1alpha1-etcdcluster + failurePolicy: Fail + name: vetcdcluster.kb.io + rules: + - apiGroups: + - etcd.aenix.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - etcdclusters + sideEffects: None diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml new file mode 100644 index 00000000..5fdbf068 --- /dev/null +++ b/config/webhook/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: service + app.kubernetes.io/instance: webhook-service + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: etcd-operator + app.kubernetes.io/part-of: etcd-operator + app.kubernetes.io/managed-by: kustomize + name: webhook-service + namespace: system +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + control-plane: controller-manager diff --git a/go.mod b/go.mod index 83947e80..24a8f98a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/onsi/ginkgo/v2 v2.14.0 github.com/onsi/gomega v1.30.0 + k8s.io/api v0.29.0 k8s.io/apimachinery v0.29.0 k8s.io/client-go v0.29.0 sigs.k8s.io/controller-runtime v0.17.0 @@ -61,7 +62,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.29.0 // indirect k8s.io/apiextensions-apiserver v0.29.0 // indirect k8s.io/component-base v0.29.0 // indirect k8s.io/klog/v2 v2.110.1 // indirect From 7bc0ba15759ce3c0fb8e93011588480c9700e0b6 Mon Sep 17 00:00:00 2001 From: Kirill Garbar Date: Sat, 16 Mar 2024 22:50:44 +0000 Subject: [PATCH 2/4] add how-to-start-development; fix tg link --- HOW-TO-START-DEVELOPMENT.md | 44 +++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 HOW-TO-START-DEVELOPMENT.md diff --git a/HOW-TO-START-DEVELOPMENT.md b/HOW-TO-START-DEVELOPMENT.md new file mode 100644 index 00000000..9e803dc9 --- /dev/null +++ b/HOW-TO-START-DEVELOPMENT.md @@ -0,0 +1,44 @@ + +# How to start contributing +## Build your develop environment +### Easy way +1. Download and install [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster). +2. Create lind cluster `kind create cluster --name etcd-operator-kind`. +3. Switch kubectl context to kind `kubectl cluster-info --context kind-etcd-operator-kind`. Be attentive to avoid damaging your production environment. +4. Install cert-manager (it creates certificate k8s secrets). Refer to the [docs](https://cert-manager.io/docs/installation/helm/#4-install-cert-manager). +5. Build docker image *controller:latest* `make docker-build`. +6. Retag image to upload to kind cluster with correct name `docker tag controller:latest ghcr.io/aenix-io/etcd-operator:latest`. +7. Load image to kind cluster `kind load docker-image ghcr.io/aenix-io/etcd-operator:latest`. +8. Install CRDs `make install`. +9. Deploy operator, RBAC, webhook certs `make deploy`. + +To deploy your code changes +1. Rebuild the image `make docker-build`. +2. Retag image to upload to kind cluster with correct name `docker tag controller:latest ghcr.io/aenix-io/etcd-operator:latest`. +3. Load image to kind cluster `kind load docker-image ghcr.io/aenix-io/etcd-operator:latest`. +4. Redeploy yaml manifests if necessary `make deploy`. +5. Restart etcd-operator `kubectl rollout restart -n etcd-operator-system deploy/etcd-operator-controller-manager`. + +### Advanced way +Using *easy way* you will not be able to debug golang code locally and every change will be necessary to build as an image, upload to the cluster and then restart operator. +If you use VSCode, you can connect your IDE to a pod in kubernetes cluster and use `go run cmd/main.go` from the pod. It will allow you to debug easily and faster integrate code changes to you develop environment. + +General steps. +1. Install VSCode Kubernetes extension. +2. Install VSCode Dev Containers extension. +3. Create pvc to store operator code, go modules and your files. +4. Build Dockerfile to create image for your develop environment (git, golang , etc...). Take base image that you love most. +5. Patch operator deployment to + * Attach PVC from step 3. + * Change operator image to your image. + * Change command and args to endless sleep loop. + * Change rolling update strategy to recreate if you use RWO storage class. + * Change security context RunAsNonRoot to false. + * Increase requests and limits. + * Remove readiness and liveness probes. +6. Attach VSCode to a running container through Kubernetes extension. +7. Install necessary extensions to the container. They will be preserved after container restart if you attached pvc to home directory. +8. Run `go run cmd/main.go`. + +## Read kubebuilder documentation +[Docs](https://book.kubebuilder.io/introduction) diff --git a/README.md b/README.md index 8e83ff03..741ebdfb 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Open Source](https://img.shields.io/badge/Open-Source-brightgreen)](https://opensource.org/) [![Apache-2.0 License](https://img.shields.io/github/license/aenix-io/etcd-operator)](https://opensource.org/licenses/) -[![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=flat&logo=telegram&logoColor=white)](https://t.me/etcd-operator) +[![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=flat&logo=telegram&logoColor=white)](https://t.me/etcd_operator) [![Active](http://img.shields.io/badge/Status-Active-green.svg)](https://aenix.io/etcd-operator/) [![GitHub Release](https://img.shields.io/github/release/aenix-io/etcd-operator.svg?style=flat)](https://github.com/aenix-io/etcd-operator) [![GitHub Commit](https://img.shields.io/github/commit-activity/y/aenix-io/etcd-operator)](https://github.com/aenix-io/etcd-operator) From 23a19904ec17672375bda01168331dec578da123 Mon Sep 17 00:00:00 2001 From: Kirill Garbar Date: Sat, 16 Mar 2024 22:56:21 +0000 Subject: [PATCH 3/4] fix minor stuff --- HOW-TO-START-DEVELOPMENT.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/HOW-TO-START-DEVELOPMENT.md b/HOW-TO-START-DEVELOPMENT.md index 9e803dc9..e0868d61 100644 --- a/HOW-TO-START-DEVELOPMENT.md +++ b/HOW-TO-START-DEVELOPMENT.md @@ -1,6 +1,10 @@ - # How to start contributing + +## Read kubebuilder documentation +[Docs](https://book.kubebuilder.io/introduction) + ## Build your develop environment + ### Easy way 1. Download and install [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster). 2. Create lind cluster `kind create cluster --name etcd-operator-kind`. @@ -23,7 +27,7 @@ To deploy your code changes Using *easy way* you will not be able to debug golang code locally and every change will be necessary to build as an image, upload to the cluster and then restart operator. If you use VSCode, you can connect your IDE to a pod in kubernetes cluster and use `go run cmd/main.go` from the pod. It will allow you to debug easily and faster integrate code changes to you develop environment. -General steps. +**General steps** 1. Install VSCode Kubernetes extension. 2. Install VSCode Dev Containers extension. 3. Create pvc to store operator code, go modules and your files. @@ -39,6 +43,3 @@ General steps. 6. Attach VSCode to a running container through Kubernetes extension. 7. Install necessary extensions to the container. They will be preserved after container restart if you attached pvc to home directory. 8. Run `go run cmd/main.go`. - -## Read kubebuilder documentation -[Docs](https://book.kubebuilder.io/introduction) From d381bc8c3f0db4093a49e2b8c541c03e4e320bfb Mon Sep 17 00:00:00 2001 From: Kirill Garbar Date: Sun, 17 Mar 2024 21:51:55 +0000 Subject: [PATCH 4/4] fix line that switches context --- HOW-TO-START-DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HOW-TO-START-DEVELOPMENT.md b/HOW-TO-START-DEVELOPMENT.md index e0868d61..c7429bb1 100644 --- a/HOW-TO-START-DEVELOPMENT.md +++ b/HOW-TO-START-DEVELOPMENT.md @@ -8,7 +8,7 @@ ### Easy way 1. Download and install [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster). 2. Create lind cluster `kind create cluster --name etcd-operator-kind`. -3. Switch kubectl context to kind `kubectl cluster-info --context kind-etcd-operator-kind`. Be attentive to avoid damaging your production environment. +3. Switch kubectl context to kind `kubectl config use-context kind-etcd-operator-kind`. Be attentive to avoid damaging your production environment. 4. Install cert-manager (it creates certificate k8s secrets). Refer to the [docs](https://cert-manager.io/docs/installation/helm/#4-install-cert-manager). 5. Build docker image *controller:latest* `make docker-build`. 6. Retag image to upload to kind cluster with correct name `docker tag controller:latest ghcr.io/aenix-io/etcd-operator:latest`.