From d092614a1c3b05ad6e567bd5c042241d4b4e7de0 Mon Sep 17 00:00:00 2001 From: Nitin Goyal Date: Thu, 8 Sep 2022 15:14:23 +0530 Subject: [PATCH 1/5] fix: update boilerplate text Signed-off-by: Nitin Goyal --- hack/boilerplate.go.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 45dbbbbcf..7914acc2f 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2021. +Copyright 2022 Red Hat Openshift Data Foundation. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ 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. -*/ \ No newline at end of file +*/ From 73ccbe1b563a3f127abfe8934bf0e846d0e20700 Mon Sep 17 00:00:00 2001 From: Nitin Goyal Date: Thu, 8 Sep 2022 10:50:15 +0530 Subject: [PATCH 2/5] feat: create LVMCluster webhook scaffolding Output of: ./bin/operator-sdk create webhook --group lvm --version v1alpha1 --kind LVMCluster --programmatic-validation Signed-off-by: Nitin Goyal --- PROJECT | 3 + api/v1alpha1/lvmcluster_webhook.go | 58 ++++++++ api/v1alpha1/webhook_suite_test.go | 135 +++++++++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 4 +- config/certmanager/certificate.yaml | 25 ++++ config/certmanager/kustomization.yaml | 5 + config/certmanager/kustomizeconfig.yaml | 16 +++ config/default/manager_webhook_patch.yaml | 23 ++++ config/default/webhookcainjection_patch.yaml | 15 +++ config/webhook/kustomization.yaml | 6 + config/webhook/kustomizeconfig.yaml | 25 ++++ config/webhook/manifests.yaml | 28 ++++ config/webhook/service.yaml | 13 ++ main.go | 5 + 14 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 api/v1alpha1/lvmcluster_webhook.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/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 3d789566a..a46588799 100644 --- a/PROJECT +++ b/PROJECT @@ -16,6 +16,9 @@ resources: kind: LVMCluster path: github.com/red-hat-storage/lvm-operator/api/v1alpha1 version: v1alpha1 + webhooks: + validation: true + webhookVersion: v1 - api: crdVersion: v1 namespaced: true diff --git a/api/v1alpha1/lvmcluster_webhook.go b/api/v1alpha1/lvmcluster_webhook.go new file mode 100644 index 000000000..25bdd28fc --- /dev/null +++ b/api/v1alpha1/lvmcluster_webhook.go @@ -0,0 +1,58 @@ +/* +Copyright 2022 Red Hat Openshift Data Foundation. + +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/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// log is for logging in this package. +var lvmclusterlog = logf.Log.WithName("lvmcluster-webhook") + +var _ webhook.Validator = &LVMCluster{} + +//+kubebuilder:webhook:path=/validate-lvm-topolvm-io-v1alpha1-lvmcluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=lvm.topolvm.io,resources=lvmclusters,verbs=create;update,versions=v1alpha1,name=vlvmcluster.kb.io,admissionReviewVersions=v1 + +func (l *LVMCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(l). + Complete() +} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (l *LVMCluster) ValidateCreate() error { + lvmclusterlog.Info("validate create", "name", l.Name) + + return nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (l *LVMCluster) ValidateUpdate(old runtime.Object) error { + lvmclusterlog.Info("validate update", "name", l.Name) + + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (l *LVMCluster) ValidateDelete() error { + lvmclusterlog.Info("validate delete", "name", l.Name) + + return nil +} diff --git a/api/v1alpha1/webhook_suite_test.go b/api/v1alpha1/webhook_suite_test.go new file mode 100644 index 000000000..32f12f2e2 --- /dev/null +++ b/api/v1alpha1/webhook_suite_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2022 Red Hat Openshift Data Foundation. + +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" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + //+kubebuilder:scaffold:imports + "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" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// 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) + + RunSpecsWithDefaultAndCustomReporters(t, + "Webhook Suite", + []Reporter{printer.NewlineReporter{}}) +} + +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, + 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 := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.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, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&LVMCluster{}).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 + } + conn.Close() + return nil + }).Should(Succeed()) + +}, 60) + +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 74dd50ac8..372e7ec51 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2021 Red Hat Openshift Data Foundation. +Copyright 2022 Red Hat Openshift Data Foundation. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ package v1alpha1 import ( conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1" "k8s.io/api/core/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/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml new file mode 100644 index 000000000..52d866183 --- /dev/null +++ b/config/certmanager/certificate.yaml @@ -0,0 +1,25 @@ +# 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: + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + 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 000000000..bebea5a59 --- /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 000000000..e631f7773 --- /dev/null +++ b/config/certmanager/kustomizeconfig.yaml @@ -0,0 +1,16 @@ +# This configuration is for teaching kustomize how to update name ref and var substitution +nameReference: +- kind: Issuer + group: cert-manager.io + fieldSpecs: + - kind: Certificate + group: cert-manager.io + path: spec/issuerRef/name + +varReference: +- kind: Certificate + group: cert-manager.io + path: spec/commonName +- kind: Certificate + group: cert-manager.io + path: spec/dnsNames diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml new file mode 100644 index 000000000..738de350b --- /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 000000000..02ab515d4 --- /dev/null +++ b/config/default/webhookcainjection_patch.yaml @@ -0,0 +1,15 @@ +# This patch add annotation to admission webhook config and +# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + 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 000000000..9cf26134e --- /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 000000000..25e21e3c9 --- /dev/null +++ b/config/webhook/kustomizeconfig.yaml @@ -0,0 +1,25 @@ +# the following config is for teaching kustomize where to look at when substituting vars. +# 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 + +varReference: +- path: metadata/annotations diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml new file mode 100644 index 000000000..90d43e4bc --- /dev/null +++ b/config/webhook/manifests.yaml @@ -0,0 +1,28 @@ + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-lvm-topolvm-io-v1alpha1-lvmcluster + failurePolicy: Fail + name: vlvmcluster.kb.io + rules: + - apiGroups: + - lvm.topolvm.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - lvmclusters + sideEffects: None diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml new file mode 100644 index 000000000..3f638bd9c --- /dev/null +++ b/config/webhook/service.yaml @@ -0,0 +1,13 @@ + +apiVersion: v1 +kind: Service +metadata: + name: webhook-service + namespace: system +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + control-plane: controller-manager diff --git a/main.go b/main.go index bc1dde721..b62d8cd35 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ import ( snapapi "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" secv1client "github.com/openshift/client-go/security/clientset/versioned/typed/security/v1" + lvmv1alpha1 "github.com/red-hat-storage/lvm-operator/api/v1alpha1" "github.com/red-hat-storage/lvm-operator/controllers" topolvmv1 "github.com/topolvm/topolvm/api/v1" @@ -102,6 +103,10 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "LVMCluster") os.Exit(1) } + if err = (&lvmv1alpha1.LVMCluster{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "LVMCluster") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { From 758169dd8d933ecd719712dd4e027b0d2520e46d Mon Sep 17 00:00:00 2001 From: Nitin Goyal Date: Thu, 8 Sep 2022 12:06:20 +0530 Subject: [PATCH 3/5] feat: implement webhook logic Signed-off-by: Nitin Goyal --- api/v1alpha1/lvmcluster_webhook.go | 208 +++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/api/v1alpha1/lvmcluster_webhook.go b/api/v1alpha1/lvmcluster_webhook.go index 25bdd28fc..58f23f539 100644 --- a/api/v1alpha1/lvmcluster_webhook.go +++ b/api/v1alpha1/lvmcluster_webhook.go @@ -17,6 +17,9 @@ limitations under the License. package v1alpha1 import ( + "fmt" + "strings" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -28,6 +31,11 @@ var lvmclusterlog = logf.Log.WithName("lvmcluster-webhook") var _ webhook.Validator = &LVMCluster{} +var ( + ErrDeviceClassNotFound = fmt.Errorf("DeviceClass not found in the LVMCluster") + ErrThinPoolConfigNotSet = fmt.Errorf("ThinPoolConfig is not set for the DeviceClass") +) + //+kubebuilder:webhook:path=/validate-lvm-topolvm-io-v1alpha1-lvmcluster,mutating=false,failurePolicy=fail,sideEffects=None,groups=lvm.topolvm.io,resources=lvmclusters,verbs=create;update,versions=v1alpha1,name=vlvmcluster.kb.io,admissionReviewVersions=v1 func (l *LVMCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { @@ -40,6 +48,25 @@ func (l *LVMCluster) SetupWebhookWithManager(mgr ctrl.Manager) error { func (l *LVMCluster) ValidateCreate() error { lvmclusterlog.Info("validate create", "name", l.Name) + if len(l.Spec.Storage.DeviceClasses) != 1 { + return fmt.Errorf("Exactly one deviceClass is allowed") + } + + err := l.verifyPathsAreNotEmpty() + if err != nil { + return err + } + + err = l.verifyAbsolutePath() + if err != nil { + return err + } + + err = l.verifyNoDeviceOverlap() + if err != nil { + return err + } + return nil } @@ -47,6 +74,86 @@ func (l *LVMCluster) ValidateCreate() error { func (l *LVMCluster) ValidateUpdate(old runtime.Object) error { lvmclusterlog.Info("validate update", "name", l.Name) + if len(l.Spec.Storage.DeviceClasses) != 1 { + return fmt.Errorf("Exactly one deviceClass is allowed") + } + + err := l.verifyPathsAreNotEmpty() + if err != nil { + return err + } + + err = l.verifyAbsolutePath() + if err != nil { + return err + } + + err = l.verifyNoDeviceOverlap() + if err != nil { + return err + } + + oldLVMCluster, ok := old.(*LVMCluster) + if !ok { + return fmt.Errorf("Failed to parse LVMCluster.") + } + + for _, deviceClass := range l.Spec.Storage.DeviceClasses { + var newThinPoolConfig, oldThinPoolConfig *ThinPoolConfig + var newDevices, oldDevices []string + + newThinPoolConfig = deviceClass.ThinPoolConfig + oldThinPoolConfig, err = oldLVMCluster.getThinPoolsConfigOfDeviceClass(deviceClass.Name) + + if (newThinPoolConfig != nil && oldThinPoolConfig == nil && err != ErrDeviceClassNotFound) || + (newThinPoolConfig == nil && oldThinPoolConfig != nil) { + return fmt.Errorf("ThinPoolConfig can not be changed") + } + + if newThinPoolConfig != nil && oldThinPoolConfig != nil { + if newThinPoolConfig.Name != oldThinPoolConfig.Name { + return fmt.Errorf("ThinPoolConfig.Name can not be changed") + } else if newThinPoolConfig.SizePercent != oldThinPoolConfig.SizePercent { + return fmt.Errorf("ThinPoolConfig.SizePercent can not be changed") + } else if newThinPoolConfig.OverprovisionRatio != oldThinPoolConfig.OverprovisionRatio { + return fmt.Errorf("ThinPoolConfig.OverprovisionRatio can not be changed") + } + } + + if deviceClass.DeviceSelector != nil { + newDevices = deviceClass.DeviceSelector.Paths + } + + oldDevices, err = oldLVMCluster.getPathsOfDeviceClass(deviceClass.Name) + + // if devices are removed now + if len(oldDevices) > len(newDevices) { + return fmt.Errorf("Invalid:devices can not be removed from the LVMCluster once added.") + } + + // if devices are added now + if len(oldDevices) == 0 && len(newDevices) > 0 && err != ErrDeviceClassNotFound { + return fmt.Errorf("Invalid:devices can not be added in the LVMCluster once created without devices.") + } + + deviceMap := make(map[string]bool) + + for _, device := range oldDevices { + deviceMap[device] = true + } + + for _, device := range newDevices { + delete(deviceMap, device) + } + + // if any old device is removed now + if len(deviceMap) != 0 { + return fmt.Errorf("Invalid:some of devices are deleted from the LVMCluster. "+ + "Device can not be removed from the LVMCluster once added. "+ + "oldDevices:%s, newDevices:%s", oldDevices, newDevices) + } + } + return nil } @@ -56,3 +163,104 @@ func (l *LVMCluster) ValidateDelete() error { return nil } + +func (l *LVMCluster) verifyPathsAreNotEmpty() error { + + for _, deviceClass := range l.Spec.Storage.DeviceClasses { + if deviceClass.DeviceSelector != nil { + if len(deviceClass.DeviceSelector.Paths) == 0 { + return fmt.Errorf("Path list should not be empty when DeviceSelector is specified.") + } + } + } + + return nil +} + +func (l *LVMCluster) verifyAbsolutePath() error { + + for _, deviceClass := range l.Spec.Storage.DeviceClasses { + if deviceClass.DeviceSelector != nil { + for _, path := range deviceClass.DeviceSelector.Paths { + if !strings.HasPrefix(path, "/dev/") { + return fmt.Errorf("Given path %s is not an absolute path. "+ + "Please provide the absolute path to the device", path) + } + } + } + } + + return nil +} + +func (l *LVMCluster) verifyNoDeviceOverlap() error { + + // make sure no device overlap with another VGs + // use map to find the duplicate entries for paths + /* + { + "nodeSelector1": { + "/dev/sda": "vg1", + "/dev/sdb": "vg1" + }, + "nodeSelector2": { + "/dev/sda": "vg1", + "/dev/sdb": "vg1" + } + } + */ + devices := make(map[string]map[string]string) + + for _, deviceClass := range l.Spec.Storage.DeviceClasses { + if deviceClass.DeviceSelector != nil { + nodeSelector := deviceClass.NodeSelector.String() + for _, path := range deviceClass.DeviceSelector.Paths { + if val, ok := devices[nodeSelector][path]; ok { + var err error + if val != deviceClass.Name { + err = fmt.Errorf("Error: device path %s overlaps in two different deviceClasss %s and %s", path, val, deviceClass.Name) + } else { + err = fmt.Errorf("Error: device path %s is specified at multiple places in deviceClass %s", path, val) + } + return err + } + + if devices[nodeSelector] == nil { + devices[nodeSelector] = make(map[string]string) + } + + devices[nodeSelector][path] = deviceClass.Name + } + } + } + + return nil +} + +func (l *LVMCluster) getPathsOfDeviceClass(deviceClassName string) ([]string, error) { + + for _, deviceClass := range l.Spec.Storage.DeviceClasses { + if deviceClass.Name == deviceClassName { + if deviceClass.DeviceSelector != nil { + return deviceClass.DeviceSelector.Paths, nil + } + return []string{}, nil + } + } + + return []string{}, ErrDeviceClassNotFound +} + +func (l *LVMCluster) getThinPoolsConfigOfDeviceClass(deviceClassName string) (*ThinPoolConfig, error) { + + for _, deviceClass := range l.Spec.Storage.DeviceClasses { + if deviceClass.Name == deviceClassName { + if deviceClass.ThinPoolConfig != nil { + return deviceClass.ThinPoolConfig, nil + } + return nil, ErrThinPoolConfigNotSet + } + } + + return nil, ErrDeviceClassNotFound +} From 0424a550aab8b5f2dcc5822f6aa64bdfe68f4808 Mon Sep 17 00:00:00 2001 From: Nitin Goyal Date: Fri, 9 Sep 2022 13:00:09 +0530 Subject: [PATCH 4/5] feat: change config to generate webhook manifests Signed-off-by: Nitin Goyal --- Makefile | 2 ++ config/default/kustomization.yaml | 3 +++ config/default/manager_webhook_patch.yaml | 2 +- config/default/webhookcainjection_patch.yaml | 16 +++++++++------- config/webhook/kustomization.yaml | 5 +++++ config/webhook/service.yaml | 4 +++- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index d7fdd3004..a73ec0260 100644 --- a/Makefile +++ b/Makefile @@ -183,6 +183,7 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified deploy: update-mgr-env manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} && $(KUSTOMIZE) edit set nameprefix ${MANAGER_NAME_PREFIX} cd config/default && $(KUSTOMIZE) edit set image rbac-proxy=$(RBAC_PROXY_IMG) + cd config/webhook && $(KUSTOMIZE) edit set nameprefix ${MANAGER_NAME_PREFIX} $(KUSTOMIZE) build config/default | kubectl apply -f - undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. @@ -232,6 +233,7 @@ bundle: update-mgr-env manifests kustomize operator-sdk rename-csv build-prometh rm -rf bundle # $(OPERATOR_SDK) generate kustomize manifests --package $(BUNDLE_PACKAGE) -q cd config/default && $(KUSTOMIZE) edit set namespace $(OPERATOR_NAMESPACE) + cd config/webhook && $(KUSTOMIZE) edit set nameprefix ${MANAGER_NAME_PREFIX} cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} && $(KUSTOMIZE) edit set nameprefix ${MANAGER_NAME_PREFIX} cd config/default && $(KUSTOMIZE) edit set image rbac-proxy=$(RBAC_PROXY_IMG) $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle -q --package $(BUNDLE_PACKAGE) --version $(VERSION) $(BUNDLE_METADATA_OPTS) diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 9dfa92b46..114a3afb7 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -24,6 +24,8 @@ namespace: lvm-operator-system patchesStrategicMerge: - manager_auth_proxy_patch.yaml - manager_custom_env.yaml +- manager_webhook_patch.yaml +- webhookcainjection_patch.yaml # Mount the controller config file for loading manager configurations # through a ComponentConfig type @@ -46,6 +48,7 @@ resources: - ../rbac - ../manager - ../prometheus +- ../webhook images: - name: rbac-proxy newName: gcr.io/kubebuilder/kube-rbac-proxy diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml index 738de350b..88b6b5924 100644 --- a/config/default/manager_webhook_patch.yaml +++ b/config/default/manager_webhook_patch.yaml @@ -20,4 +20,4 @@ spec: - name: cert secret: defaultMode: 420 - secretName: webhook-server-cert + secretName: lvm-operator-webhook-server-cert diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml index 02ab515d4..161c21967 100644 --- a/config/default/webhookcainjection_patch.yaml +++ b/config/default/webhookcainjection_patch.yaml @@ -1,15 +1,17 @@ # This patch add annotation to admission webhook config and # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - name: mutating-webhook-configuration - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + +#apiVersion: admissionregistration.k8s.io/v1 +#kind: MutatingWebhookConfiguration +#metadata: +# name: mutating-webhook-configuration +# annotations: +# cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + #cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + service.beta.openshift.io/inject-cabundle: "true" diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml index 9cf26134e..40b9a143f 100644 --- a/config/webhook/kustomization.yaml +++ b/config/webhook/kustomization.yaml @@ -1,3 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namePrefix: lvm-operator- + resources: - manifests.yaml - service.yaml diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml index 3f638bd9c..d045722af 100644 --- a/config/webhook/service.yaml +++ b/config/webhook/service.yaml @@ -2,6 +2,8 @@ apiVersion: v1 kind: Service metadata: + annotations: + service.beta.openshift.io/serving-cert-secret-name: lvm-operator-webhook-server-cert name: webhook-service namespace: system spec: @@ -10,4 +12,4 @@ spec: protocol: TCP targetPort: 9443 selector: - control-plane: controller-manager + app.kubernetes.io/name: lvm-operator From 08188c40fea587ff077e3f9bb0224be72eff04d4 Mon Sep 17 00:00:00 2001 From: Nitin Goyal Date: Fri, 9 Sep 2022 14:59:17 +0530 Subject: [PATCH 5/5] build: generate bundle changes Signed-off-by: Nitin Goyal --- ...m-operator-webhook-service_v1_service.yaml | 16 +++++ .../lvm-operator.clusterserviceversion.yaml | 58 +++++++++++++++---- 2 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 bundle/manifests/lvm-operator-webhook-service_v1_service.yaml diff --git a/bundle/manifests/lvm-operator-webhook-service_v1_service.yaml b/bundle/manifests/lvm-operator-webhook-service_v1_service.yaml new file mode 100644 index 000000000..d5148f996 --- /dev/null +++ b/bundle/manifests/lvm-operator-webhook-service_v1_service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + service.beta.openshift.io/serving-cert-secret-name: lvm-operator-webhook-server-cert + creationTimestamp: null + name: lvm-operator-webhook-service +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + app.kubernetes.io/name: lvm-operator +status: + loadBalancer: {} diff --git a/bundle/manifests/lvm-operator.clusterserviceversion.yaml b/bundle/manifests/lvm-operator.clusterserviceversion.yaml index 59acde771..e1c0dfc96 100644 --- a/bundle/manifests/lvm-operator.clusterserviceversion.yaml +++ b/bundle/manifests/lvm-operator.clusterserviceversion.yaml @@ -276,18 +276,6 @@ spec: exporter: lvm-operator spec: containers: - - args: - - --secure-listen-address=0.0.0.0:8443 - - --upstream=http://127.0.0.1:8080/ - - --logtostderr=true - - --v=10 - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 - name: kube-rbac-proxy - ports: - - containerPort: 8443 - name: https - protocol: TCP - resources: {} - args: - --health-probe-bind-address=:8081 - --metrics-bind-address=127.0.0.1:8080 @@ -323,6 +311,10 @@ spec: initialDelaySeconds: 15 periodSeconds: 20 name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP readinessProbe: httpGet: path: /readyz @@ -338,6 +330,22 @@ spec: memory: 50Mi securityContext: allowPrivilegeEscalation: false + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=10 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + protocol: TCP + resources: {} - command: - /metricsexporter image: quay.io/ocs-dev/lvm-operator:latest @@ -353,6 +361,11 @@ spec: runAsNonRoot: true serviceAccountName: lvm-controller-manager terminationGracePeriodSeconds: 10 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: lvm-operator-webhook-server-cert permissions: - rules: - apiGroups: @@ -411,3 +424,24 @@ spec: provider: name: Red Hat version: 0.0.1 + webhookdefinitions: + - admissionReviewVersions: + - v1 + containerPort: 443 + deploymentName: lvm-operator-controller-manager + failurePolicy: Fail + generateName: vlvmcluster.kb.io + rules: + - apiGroups: + - lvm.topolvm.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - lvmclusters + sideEffects: None + targetPort: 9443 + type: ValidatingAdmissionWebhook + webhookPath: /validate-lvm-topolvm-io-v1alpha1-lvmcluster