From 196676f919021b63fa5fa9f6c727bb5283902d3d Mon Sep 17 00:00:00 2001 From: Nikita Vaniasin Date: Mon, 13 Nov 2023 11:03:19 +0100 Subject: [PATCH 1/4] (Feature) Add generator for CRD validation schemas --- CHANGELOG.md | 1 + cmd/cmd.go | 11 +- cmd/crd.go | 53 ++++++++- docs/README.md | 2 +- docs/crds.md | 38 +++++++ docs/how-to/additional_configuration.md | 21 +++- go.mod | 2 +- pkg/crd/apply.go | 58 ++++++---- pkg/crd/apply_test.go | 28 ++++- pkg/crd/arangobackup.go | 4 +- pkg/crd/arangobackuppolicies.go | 4 +- pkg/crd/arangoclustersynchronizations.go | 4 +- pkg/crd/arangodeploymentreplications.go | 4 +- pkg/crd/arangodeployments.go | 4 +- pkg/crd/arangojobs.go | 4 +- pkg/crd/arangolocalstorage.go | 4 +- pkg/crd/arangomembers.go | 4 +- pkg/crd/arangoml.go | 18 ++- pkg/crd/arangotasks.go | 4 +- pkg/crd/crds/apps-job.go | 23 +++- pkg/crd/crds/backups-backup.go | 23 +++- pkg/crd/crds/backups-backuppolicy.go | 23 +++- pkg/crd/crds/crds.go | 70 +++++++++--- pkg/crd/crds/crds_test.go | 105 +++++++++++++++--- .../crds/database-clustersynchronization.go | 23 +++- pkg/crd/crds/database-deployment.go | 23 +++- pkg/crd/crds/database-member.go | 23 +++- pkg/crd/crds/database-task.go | 23 +++- pkg/crd/crds/ml-extension.go | 17 +-- pkg/crd/crds/ml-job-batch.go | 13 ++- pkg/crd/crds/ml-job-cron.go | 17 +-- pkg/crd/crds/ml-storage.go | 17 +-- .../crds/replication-deploymentreplication.go | 23 +++- pkg/crd/crds/storage-localstorage.go | 23 +++- pkg/crd/definitions.go | 49 ++++---- 35 files changed, 583 insertions(+), 180 deletions(-) create mode 100644 docs/crds.md diff --git a/CHANGELOG.md b/CHANGELOG.md index cf38a6eda..b548b4c7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - (Bugfix) Proper handling of --agency.retries argument - (Documentation) Do not use field type name for field URL hash - (Maintenance) Bump Go to 1.20.11 +- (Feature) Add support for CRD validation schemas ## [1.2.35](https://github.com/arangodb/kube-arangodb/tree/1.2.35) (2023-11-06) - (Maintenance) Update go-driver to v1.6.0, update IsNotFound() checks diff --git a/cmd/cmd.go b/cmd/cmd.go index a043f5347..4b4dd85a3 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -137,7 +137,8 @@ var ( timeout time.Duration } crdOptions struct { - install bool + install bool + validationSchema []string } operatorKubernetesOptions struct { maxBatchSize int64 @@ -227,6 +228,7 @@ func init() { f.Float32Var(&operatorKubernetesOptions.qps, "kubernetes.qps", kclient.DefaultQPS, "Number of queries per second for k8s API") f.IntVar(&operatorKubernetesOptions.burst, "kubernetes.burst", kclient.DefaultBurst, "Burst for the k8s API") f.BoolVar(&crdOptions.install, "crd.install", true, "Install missing CRD if access is possible") + f.StringArrayVar(&crdOptions.validationSchema, "crd.validation-schema", defaultValidationSchemaEnabled, "Overrides default set of CRDs which should have validation schema enabled =.") f.IntVar(&operatorBackup.concurrentUploads, "backup-concurrent-uploads", globals.DefaultBackupConcurrentUploads, "Number of concurrent uploads per deployment") f.Uint64Var(&memoryLimit.hardLimit, "memory-limit", 0, "Define memory limit for hard shutdown and the dump of goroutines. Used for testing") f.StringArrayVar(&metricsOptions.excludedMetricPrefixes, "metrics.excluded-prefixes", nil, "List of the excluded metrics prefixes") @@ -369,7 +371,12 @@ func executeMain(cmd *cobra.Command, args []string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - _ = crd.EnsureCRD(ctx, client, true) + crdOpts, err := prepareCRDOptions(crdOptions.validationSchema) + if err != nil { + logger.Fatal("Invalid --crd.validation-schema args: %s", err) + } + + _ = crd.EnsureCRDWithOptions(ctx, client, crdOpts, true) } secrets := client.Kubernetes().CoreV1().Secrets(namespace) diff --git a/cmd/crd.go b/cmd/crd.go index 4a26cc863..bb2425cc6 100644 --- a/cmd/crd.go +++ b/cmd/crd.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany +// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,12 +22,17 @@ package cmd import ( "context" + "fmt" "os" + "strconv" + "strings" "time" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/arangodb/kube-arangodb/pkg/crd" + "github.com/arangodb/kube-arangodb/pkg/crd/crds" "github.com/arangodb/kube-arangodb/pkg/util/kclient" ) @@ -44,23 +49,67 @@ var ( } ) +var ( + crdInstallOptions struct { + validationSchema []string + } +) + +var ( + defaultValidationSchemaEnabled []string +) + func init() { cmdMain.AddCommand(cmdCRD) cmdOps.AddCommand(cmdCRD) + f := cmdCRDInstall.Flags() + f.StringArrayVar(&crdInstallOptions.validationSchema, "crd.validation-schema", defaultValidationSchemaEnabled, "Controls which CRD should have validation schema =.") cmdCRD.AddCommand(cmdCRDInstall) } +func prepareCRDOptions(schemaEnabledArgs []string) (map[string]crds.CRDOptions, error) { + defaultOptions := crd.GetDefaultCRDOptions() + result := make(map[string]crds.CRDOptions) + var err error + for _, arg := range schemaEnabledArgs { + parts := strings.Split(arg, "=") + + crdName := parts[0] + opts, ok := defaultOptions[crdName] + if !ok { + return nil, fmt.Errorf("unknown CRD %s", crdName) + } + + if len(parts) == 2 { + opts.WithSchema, err = strconv.ParseBool(parts[1]) + if err != nil { + return nil, errors.Wrapf(err, "not a bool value: %s", parts[1]) + } + } + + result[crdName] = opts + } + return result, nil +} + func cmdCRDInstallRun(cmd *cobra.Command, args []string) { + crdOpts, err := prepareCRDOptions(crdInstallOptions.validationSchema) + if err != nil { + logger.Fatal("Invalid --crd.validation-schema args: %s", err) + return + } + client, ok := kclient.GetDefaultFactory().Client() if !ok { logger.Fatal("Failed to get client") + return } ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - err := crd.EnsureCRD(ctx, client, false) + err = crd.EnsureCRDWithOptions(ctx, client, crdOpts, false) if err != nil { os.Exit(1) } diff --git a/docs/README.md b/docs/README.md index ef17fcaa5..58d9711ab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ - [Operator Metrics & Alerts](generated/metrics/README.md) - [Operator Actions](generated/actions.md) - [Authentication](authentication.md) -- Custom resources overview: +- [Custom resources overview](crds.md): - [ArangoDeployment](deployment-resource-reference.md) - [ArangoDeploymentReplication](deployment-replication-resource-reference.md) - [ArangoLocalStorage](storage-resource.md) diff --git a/docs/crds.md b/docs/crds.md new file mode 100644 index 000000000..363c2f1f2 --- /dev/null +++ b/docs/crds.md @@ -0,0 +1,38 @@ +# Custom resources overview + +Main CRDs: +- [ArangoDeployment](deployment-resource-reference.md) +- [ArangoDeploymentReplication](deployment-replication-resource-reference.md) +- [ArangoLocalStorage](storage-resource.md) +- [Backup](backup-resource.md) +- [BackupPolicy](backuppolicy-resource.md) + +Operator manages the CustomResources based on CustomResourceDefinitions installed in your cluster. + +There are different options how CustomResourceDefinitions can be created. + +**Deprecated options:** +- Install CRDs directly from `manifests` folder. +- Install `kube-arangodb-crd` helm chart before installing `kube-arangodb` chart. +- Install CRDs using kustomize `all` or `crd` manifests. + +**Recommended:** +Use `kube-arangodb` Helm chart. Chart itself does not contain CRDs. +Instead, operator will try to create the required CRDs on the first start. +Make sure that ServiceAccount for operator has permissions to `create` CustomResourceDefinitions. + +To disable the automatic creation of CRDs, set `enableCRDManagement=false` template parameter, e.g.: +```shell +helm install --generate-name https://github.com/arangodb/kube-arangodb/releases/download/$VER/kube-arangodb-$VER.tgz --set "operator.enableCRDManagement=false" +``` + +## Schema validation + +Starting with v1.2.36, the [schema validation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation) is supported for all CRDs. + +Schema validation can be enabled only on cluster with no CRDs installed or by upgrading your CR from one CRD version to another. + +To enable creation of CRD with validation schema, pass additional args to operator command line, e.g.: +``` +--crd.validation-schema=arangobackuppolicies.backup.arangodb.com=true --crd.validation-schema=arangodeployments.database.arangodb.com=false +``` diff --git a/docs/how-to/additional_configuration.md b/docs/how-to/additional_configuration.md index 48dd98c2b..b0bb35f69 100644 --- a/docs/how-to/additional_configuration.md +++ b/docs/how-to/additional_configuration.md @@ -3,15 +3,28 @@ It is possible to additionally fine-tune operator behavior by providing arguments via `operator.args` chart template value. -For example, you can specify burst size for k8s API requests or how long the operator +The full list of available arguments can be retrieved using +``` +export OPERATOR_IMAGE=arangodb/kube-arangodb:$VER +kubectl run arango-operator-help --image=$OPERATOR_IMAGE -i --rm --restart=Never -- --help +``` + + +### Example 1: kubernetes.burst + +You can specify burst size for k8s API requests or how long the operator should wait for ArangoDeployment termination after receiving interruption signal: ``` operator: args: ["--kubernetes.burst=40", --shutdown.timeout=2m"] ``` -The full list of available arguments can be retrieved using +### Example 2: CRD validation + +You can specify which of installed CRD should have a validation schema enabled: ``` -export OPERATOR_IMAGE=arangodb/kube-arangodb:1.2.9 -kubectl run arango-operator-help --image=$OPERATOR_IMAGE -i --rm --restart=Never -- --help +operator: + args: + - --crd.validation-schema=arangobackuppolicies.backup.arangodb.com=true + - --crd.validation-schema=arangodeployments.database.arangodb.com=false ``` diff --git a/go.mod b/go.mod index d3b6157fd..596212e35 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( k8s.io/apiextensions-apiserver v0.25.13 k8s.io/apimachinery v0.25.13 k8s.io/client-go v0.25.13 + k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 sigs.k8s.io/yaml v1.2.0 ) @@ -126,7 +127,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.70.1 // indirect - k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect diff --git a/pkg/crd/apply.go b/pkg/crd/apply.go index 17e5e47c7..d38f9c018 100644 --- a/pkg/crd/apply.go +++ b/pkg/crd/apply.go @@ -31,24 +31,36 @@ import ( "github.com/arangodb/go-driver" + "github.com/arangodb/kube-arangodb/pkg/crd/crds" "github.com/arangodb/kube-arangodb/pkg/logging" "github.com/arangodb/kube-arangodb/pkg/util/kclient" ) var logger = logging.Global().RegisterAndGetLogger("crd", logging.Info) +// Deprecated: use EnsureCRDWithOptions instead func EnsureCRD(ctx context.Context, client kclient.Client, ignoreErrors bool) error { + return EnsureCRDWithOptions(ctx, client, nil, ignoreErrors) +} + +func EnsureCRDWithOptions(ctx context.Context, client kclient.Client, ensureOpts map[string]crds.CRDOptions, ignoreErrors bool) error { crdsLock.Lock() defer crdsLock.Unlock() - for crd, spec := range registeredCRDs { - getAccess := verifyCRDAccess(ctx, client, crd, "get") + for crdName, crdReg := range registeredCRDs { + getAccess := verifyCRDAccess(ctx, client, crdName, "get") if !getAccess.Allowed { - logger.Str("crd", crd).Info("Get Operations is not allowed. Continue") + logger.Str("crd", crdName).Info("Get Operations is not allowed. Continue") continue } - err := tryApplyCRD(ctx, client, crd, spec) + var opt *crds.CRDOptions + if o, ok := ensureOpts[crdName]; ok { + opt = &o + } + def := crdReg.getter(opt) + + err := tryApplyCRD(ctx, client, def) if !ignoreErrors && err != nil { return err } @@ -56,45 +68,47 @@ func EnsureCRD(ctx context.Context, client kclient.Client, ignoreErrors bool) er return nil } -func tryApplyCRD(ctx context.Context, client kclient.Client, crd string, spec crd) error { +func tryApplyCRD(ctx context.Context, client kclient.Client, def crds.Definition) error { crdDefinitions := client.KubernetesExtensions().ApiextensionsV1().CustomResourceDefinitions() - c, err := crdDefinitions.Get(ctx, crd, meta.GetOptions{}) + crdName := def.CRD.Name + + c, err := crdDefinitions.Get(ctx, crdName, meta.GetOptions{}) if err != nil { if !errors.IsNotFound(err) { - logger.Err(err).Str("crd", crd).Warn("Get Operations is not allowed due to error") + logger.Err(err).Str("crd", crdName).Warn("Get Operations is not allowed due to error") return err } - createAccess := verifyCRDAccess(ctx, client, crd, "create") + createAccess := verifyCRDAccess(ctx, client, crdName, "create") if !createAccess.Allowed { - logger.Str("crd", crd).Info("Create Operations is not allowed but CRD is missing. Continue") + logger.Str("crd", crdName).Info("Create Operations is not allowed but CRD is missing. Continue") return nil } c = &apiextensions.CustomResourceDefinition{ ObjectMeta: meta.ObjectMeta{ - Name: crd, + Name: crdName, Labels: map[string]string{ - Version: string(spec.version), + Version: string(def.Version), }, }, - Spec: spec.spec, + Spec: def.CRD.Spec, } if _, err := crdDefinitions.Create(ctx, c, meta.CreateOptions{}); err != nil { - logger.Err(err).Str("crd", crd).Warn("Create Operations is not allowed due to error") + logger.Err(err).Str("crd", crdName).Warn("Create Operations is not allowed due to error") return err } - logger.Str("crd", crd).Info("CRD Created") + logger.Str("crd", crdName).Info("CRD Created") return nil } - updateAccess := verifyCRDAccess(ctx, client, crd, "update") + updateAccess := verifyCRDAccess(ctx, client, crdName, "update") if !updateAccess.Allowed { - logger.Str("crd", crd).Info("Update Operations is not allowed. Continue") + logger.Str("crd", crdName).Info("Update Operations is not allowed. Continue") return nil } @@ -104,21 +118,21 @@ func tryApplyCRD(ctx context.Context, client kclient.Client, crd string, spec cr if v, ok := c.ObjectMeta.Labels[Version]; ok { if v != "" { - if !isUpdateRequired(spec.version, driver.Version(v)) { - logger.Str("crd", crd).Info("CRD Update not required") + if !isUpdateRequired(def.Version, driver.Version(v)) { + logger.Str("crd", crdName).Info("CRD Update not required") return nil } } } - c.ObjectMeta.Labels[Version] = string(spec.version) - c.Spec = spec.spec + c.ObjectMeta.Labels[Version] = string(def.Version) + c.Spec = def.CRD.Spec if _, err := crdDefinitions.Update(ctx, c, meta.UpdateOptions{}); err != nil { - logger.Err(err).Str("crd", crd).Warn("Create Operations is not allowed due to error") + logger.Err(err).Str("crd", crdName).Warn("Create Operations is not allowed due to error") return err } - logger.Str("crd", crd).Info("CRD Updated") + logger.Str("crd", crdName).Info("CRD Updated") return nil } diff --git a/pkg/crd/apply_test.go b/pkg/crd/apply_test.go index d10b23c43..0ddd447ca 100644 --- a/pkg/crd/apply_test.go +++ b/pkg/crd/apply_test.go @@ -55,6 +55,28 @@ func dropLogMessages(t *testing.T, s tests.LogScanner) map[string]string { } func Test_Apply(t *testing.T) { + t.Run("NoSchema", func(t *testing.T) { + crdValidation := make(map[string]crds.CRDOptions) + for n := range GetDefaultCRDOptions() { + crdValidation[n] = crds.CRDOptions{ + WithSchema: false, + } + } + runApply(t, crdValidation) + }) + t.Run("WithSchema", func(t *testing.T) { + crdValidation := make(map[string]crds.CRDOptions) + for n := range GetDefaultCRDOptions() { + crdValidation[n] = crds.CRDOptions{ + WithSchema: true, + } + } + runApply(t, crdValidation) + }) +} + +func runApply(t *testing.T, crdOpts map[string]crds.CRDOptions) { + t.Helper() verifyCRDAccessForTests = &authorization.SubjectAccessReviewStatus{ Allowed: true, } @@ -66,7 +88,7 @@ func Test_Apply(t *testing.T) { c := kclient.NewFakeClient() t.Run("Ensure", func(t *testing.T) { - require.NoError(t, EnsureCRD(context.Background(), c, false)) + require.NoError(t, EnsureCRDWithOptions(context.Background(), c, crdOpts, false)) for k, v := range dropLogMessages(t, s) { t.Run(k, func(t *testing.T) { @@ -92,7 +114,7 @@ func Test_Apply(t *testing.T) { }) t.Run("Ensure", func(t *testing.T) { - require.NoError(t, EnsureCRD(context.Background(), c, false)) + require.NoError(t, EnsureCRDWithOptions(context.Background(), c, crdOpts, false)) for k, v := range dropLogMessages(t, s) { t.Run(k, func(t *testing.T) { @@ -120,7 +142,7 @@ func Test_Apply(t *testing.T) { }) t.Run("Ensure", func(t *testing.T) { - require.NoError(t, EnsureCRD(context.Background(), c, false)) + require.NoError(t, EnsureCRDWithOptions(context.Background(), c, crdOpts, false)) for k, v := range dropLogMessages(t, s) { t.Run(k, func(t *testing.T) { diff --git a/pkg/crd/arangobackup.go b/pkg/crd/arangobackup.go index 0e89ca90b..db58259df 100644 --- a/pkg/crd/arangobackup.go +++ b/pkg/crd/arangobackup.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.BackupsBackupDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.BackupsBackupDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangobackuppolicies.go b/pkg/crd/arangobackuppolicies.go index 04bb4fd18..bd29acb13 100644 --- a/pkg/crd/arangobackuppolicies.go +++ b/pkg/crd/arangobackuppolicies.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.BackupsBackupPolicyDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.BackupsBackupPolicyDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangoclustersynchronizations.go b/pkg/crd/arangoclustersynchronizations.go index b646de8ea..2cab35fa0 100644 --- a/pkg/crd/arangoclustersynchronizations.go +++ b/pkg/crd/arangoclustersynchronizations.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.DatabaseClusterSynchronizationDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.DatabaseClusterSynchronizationDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangodeploymentreplications.go b/pkg/crd/arangodeploymentreplications.go index 2f3efbbb7..37530d381 100644 --- a/pkg/crd/arangodeploymentreplications.go +++ b/pkg/crd/arangodeploymentreplications.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.ReplicationDeploymentReplicationDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.ReplicationDeploymentReplicationDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangodeployments.go b/pkg/crd/arangodeployments.go index 021be8e96..8915bc31a 100644 --- a/pkg/crd/arangodeployments.go +++ b/pkg/crd/arangodeployments.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.DatabaseDeploymentDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.DatabaseDeploymentDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangojobs.go b/pkg/crd/arangojobs.go index bbc06fed0..11bce351c 100644 --- a/pkg/crd/arangojobs.go +++ b/pkg/crd/arangojobs.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.AppsJobDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.AppsJobDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangolocalstorage.go b/pkg/crd/arangolocalstorage.go index b476e2ec3..8d8e02e82 100644 --- a/pkg/crd/arangolocalstorage.go +++ b/pkg/crd/arangolocalstorage.go @@ -23,5 +23,7 @@ package crd import "github.com/arangodb/kube-arangodb/pkg/crd/crds" func init() { - registerCRDWithPanic(crds.StorageLocalStorageDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.StorageLocalStorageDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangomembers.go b/pkg/crd/arangomembers.go index f87a0e25c..b7fa373f7 100644 --- a/pkg/crd/arangomembers.go +++ b/pkg/crd/arangomembers.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.DatabaseMemberDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.DatabaseMemberDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/arangoml.go b/pkg/crd/arangoml.go index a6fa39dfc..85a1f1deb 100644 --- a/pkg/crd/arangoml.go +++ b/pkg/crd/arangoml.go @@ -25,8 +25,18 @@ import ( ) func init() { - registerCRDWithPanic(crds.MLStorageDefinition()) - registerCRDWithPanic(crds.MLExtensionDefinition()) - registerCRDWithPanic(crds.MLCronJobDefinition()) - registerCRDWithPanic(crds.MLBatchJobDefinition()) + defs := []func(...func(options *crds.CRDOptions)) crds.Definition{ + crds.MLExtensionDefinitionWithOptions, + crds.MLStorageDefinitionWithOptions, + crds.MLCronJobDefinitionWithOptions, + crds.MLBatchJobDefinitionWithOptions, + } + for _, getDef := range defs { + defFn := getDef // bring into scope + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return defFn() + }, &crds.CRDOptions{ + WithSchema: true, + }) + } } diff --git a/pkg/crd/arangotasks.go b/pkg/crd/arangotasks.go index 9cb4d30a2..2be88a264 100644 --- a/pkg/crd/arangotasks.go +++ b/pkg/crd/arangotasks.go @@ -25,5 +25,7 @@ import ( ) func init() { - registerCRDWithPanic(crds.DatabaseTaskDefinition()) + registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { + return crds.DatabaseTaskDefinitionWithOptions() + }, nil) } diff --git a/pkg/crd/crds/apps-job.go b/pkg/crd/crds/apps-job.go index 262acf0e7..25d96e7ad 100644 --- a/pkg/crd/crds/apps-job.go +++ b/pkg/crd/crds/apps-job.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(appsJobs, &appsJobsCRD); err != nil { - panic(err) - } + mustLoadCRD(appsJobs, appsJobsSchemaRaw, &appsJobsCRD, &appsJobsCRDSchemas) } +// Deprecated: use AppsJobWithOptions instead func AppsJob() *apiextensions.CustomResourceDefinition { - return appsJobsCRD.DeepCopy() + return AppsJobWithOptions() +} + +func AppsJobWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(appsJobsCRD, appsJobsCRDSchemas, opts...) } +// Deprecated: use AppsJobDefinitionWithOptions instead func AppsJobDefinition() Definition { + return AppsJobDefinitionWithOptions() +} + +func AppsJobDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: AppsJobVersion, - CRD: appsJobsCRD.DeepCopy(), + CRD: AppsJobWithOptions(opts...), } } var appsJobsCRD apiextensions.CustomResourceDefinition +var appsJobsCRDSchemas crdSchemas //go:embed apps-job.yaml var appsJobs []byte + +//go:embed apps-job.schema.generated.yaml +var appsJobsSchemaRaw []byte diff --git a/pkg/crd/crds/backups-backup.go b/pkg/crd/crds/backups-backup.go index b728d173d..05d5ad9ed 100644 --- a/pkg/crd/crds/backups-backup.go +++ b/pkg/crd/crds/backups-backup.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(backupsBackup, &backupsBackupCRD); err != nil { - panic(err) - } + mustLoadCRD(backupsBackup, backupsBackupSchemaRaw, &backupsBackupCRD, &backupsBackupCRDSchemas) } +// Deprecated: use BackupsBackupWithOptions instead func BackupsBackup() *apiextensions.CustomResourceDefinition { - return backupsBackupCRD.DeepCopy() + return BackupsBackupWithOptions() +} + +func BackupsBackupWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(backupsBackupCRD, backupsBackupCRDSchemas, opts...) } +// Deprecated: use BackupsBackupDefinitionWithOptions instead func BackupsBackupDefinition() Definition { + return BackupsBackupDefinitionWithOptions() +} + +func BackupsBackupDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: BackupsBackupVersion, - CRD: backupsBackupCRD.DeepCopy(), + CRD: BackupsBackupWithOptions(opts...), } } var backupsBackupCRD apiextensions.CustomResourceDefinition +var backupsBackupCRDSchemas crdSchemas //go:embed backups-backup.yaml var backupsBackup []byte + +//go:embed backups-backup.schema.generated.yaml +var backupsBackupSchemaRaw []byte diff --git a/pkg/crd/crds/backups-backuppolicy.go b/pkg/crd/crds/backups-backuppolicy.go index 26eea35f6..afac0b5b7 100644 --- a/pkg/crd/crds/backups-backuppolicy.go +++ b/pkg/crd/crds/backups-backuppolicy.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(backupsBackupPolicy, &backupsBackupPolicyCRD); err != nil { - panic(err) - } + mustLoadCRD(backupsBackupPolicy, backupsBackupPolicySchemaRaw, &backupsBackupPolicyCRD, &backupsBackupPolicyCRDSchemas) } +// Deprecated: use BackupsBackupPolicyPolicyWithOptions instead func BackupsBackupPolicyPolicy() *apiextensions.CustomResourceDefinition { - return backupsBackupPolicyCRD.DeepCopy() + return BackupsBackupPolicyPolicyWithOptions() +} + +func BackupsBackupPolicyPolicyWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(backupsBackupPolicyCRD, backupsBackupPolicyCRDSchemas, opts...) } +// Deprecated: use func BackupsBackupPolicyDefinitionWithOptions instead func BackupsBackupPolicyDefinition() Definition { + return BackupsBackupPolicyDefinitionWithOptions() +} + +func BackupsBackupPolicyDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: BackupsBackupPolicyPolicyVersion, - CRD: backupsBackupPolicyCRD.DeepCopy(), + CRD: BackupsBackupPolicyPolicyWithOptions(opts...), } } var backupsBackupPolicyCRD apiextensions.CustomResourceDefinition +var backupsBackupPolicyCRDSchemas crdSchemas //go:embed backups-backuppolicy.yaml var backupsBackupPolicy []byte + +//go:embed backups-backuppolicy.schema.generated.yaml +var backupsBackupPolicySchemaRaw []byte diff --git a/pkg/crd/crds/crds.go b/pkg/crd/crds/crds.go index 45f0b969b..5f7ce9076 100644 --- a/pkg/crd/crds/crds.go +++ b/pkg/crd/crds/crds.go @@ -21,7 +21,10 @@ package crds import ( + "fmt" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,31 +37,72 @@ type Definition struct { func AllDefinitions() []Definition { return []Definition{ // Deployment - DatabaseDeploymentDefinition(), - DatabaseMemberDefinition(), + DatabaseDeploymentDefinitionWithOptions(), + DatabaseMemberDefinitionWithOptions(), // ACS - DatabaseClusterSynchronizationDefinition(), + DatabaseClusterSynchronizationDefinitionWithOptions(), // ArangoSync - ReplicationDeploymentReplicationDefinition(), + ReplicationDeploymentReplicationDefinitionWithOptions(), // Storage - StorageLocalStorageDefinition(), + StorageLocalStorageDefinitionWithOptions(), // Apps - AppsJobDefinition(), - DatabaseTaskDefinition(), + AppsJobDefinitionWithOptions(), + DatabaseTaskDefinitionWithOptions(), // Backups - BackupsBackupDefinition(), - BackupsBackupPolicyDefinition(), + BackupsBackupDefinitionWithOptions(), + BackupsBackupPolicyDefinitionWithOptions(), // ML - MLExtensionDefinition(), - MLStorageDefinition(), + MLExtensionDefinitionWithOptions(), + MLStorageDefinitionWithOptions(), + MLCronJobDefinitionWithOptions(), + MLBatchJobDefinitionWithOptions(), + } +} + +func mustLoadCRD(crdRaw, crdSchemasRaw []byte, crd *apiextensions.CustomResourceDefinition, schemas *crdSchemas) { + if err := yaml.Unmarshal(crdRaw, crd); err != nil { + panic(err) + } + + if err := yaml.Unmarshal(crdSchemasRaw, schemas); err != nil { + panic(err) + } +} + +type crdSchemas map[string]apiextensions.CustomResourceValidation + +type CRDOptions struct { + WithSchema bool +} + +func WithSchema() func(*CRDOptions) { + return func(o *CRDOptions) { + o.WithSchema = true + } +} + +func getCRD(crd apiextensions.CustomResourceDefinition, schemas crdSchemas, opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + o := &CRDOptions{} + for _, fn := range opts { + fn(o) + } + if o.WithSchema { + crdWithSchema := crd.DeepCopy() + for i, v := range crdWithSchema.Spec.Versions { + schema, ok := schemas[v.Name] + if !ok { + panic(fmt.Sprintf("Validation schema is not defined for version %s of %s", v.Name, crd.Name)) + } + crdWithSchema.Spec.Versions[i].Schema = schema.DeepCopy() + } - MLCronJobDefinition(), - MLBatchJobDefinition(), + return crdWithSchema } + return crd.DeepCopy() } diff --git a/pkg/crd/crds/crds_test.go b/pkg/crd/crds/crds_test.go index 19a214893..f230403f1 100644 --- a/pkg/crd/crds/crds_test.go +++ b/pkg/crd/crds/crds_test.go @@ -21,9 +21,11 @@ package crds import ( + "fmt" "testing" "github.com/stretchr/testify/require" + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "github.com/arangodb/kube-arangodb/pkg/apis/apps" "github.com/arangodb/kube-arangodb/pkg/apis/backup" @@ -33,24 +35,93 @@ import ( "github.com/arangodb/kube-arangodb/pkg/apis/storage" ) -func ensureCRDCompliance(t *testing.T, name string, def Definition) { - t.Run(name, func(t *testing.T) { - require.Equal(t, name, def.CRD.GetName()) - }) +func ensureCRDCompliance(t *testing.T, name string, crdDef *apiextensions.CustomResourceDefinition, schemaExpected bool) { + t.Helper() + + require.NotNil(t, crdDef) + require.Equal(t, name, crdDef.GetName()) + for _, version := range crdDef.Spec.Versions { + t.Run(name+" "+version.Name, func(t *testing.T) { + require.NotNil(t, version.Schema) + require.Equal(t, "object", version.Schema.OpenAPIV3Schema.Type) + require.NotNil(t, version.Schema.OpenAPIV3Schema.XPreserveUnknownFields) + require.True(t, *version.Schema.OpenAPIV3Schema.XPreserveUnknownFields) + if schemaExpected { + require.NotEmpty(t, version.Schema.OpenAPIV3Schema.Properties) + } else { + require.Empty(t, version.Schema.OpenAPIV3Schema.Properties) + } + }) + } } func Test_CRD(t *testing.T) { - ensureCRDCompliance(t, apps.ArangoJobCRDName, AppsJobDefinition()) - ensureCRDCompliance(t, backup.ArangoBackupCRDName, BackupsBackupDefinition()) - ensureCRDCompliance(t, backup.ArangoBackupPolicyCRDName, BackupsBackupPolicyDefinition()) - ensureCRDCompliance(t, deployment.ArangoClusterSynchronizationCRDName, DatabaseClusterSynchronizationDefinition()) - ensureCRDCompliance(t, deployment.ArangoDeploymentCRDName, DatabaseDeploymentDefinition()) - ensureCRDCompliance(t, deployment.ArangoMemberCRDName, DatabaseMemberDefinition()) - ensureCRDCompliance(t, deployment.ArangoTaskCRDName, DatabaseTaskDefinition()) - ensureCRDCompliance(t, replication.ArangoDeploymentReplicationCRDName, ReplicationDeploymentReplicationDefinition()) - ensureCRDCompliance(t, storage.ArangoLocalStorageCRDName, StorageLocalStorageDefinition()) - ensureCRDCompliance(t, ml.ArangoMLExtensionCRDName, MLExtensionDefinition()) - ensureCRDCompliance(t, ml.ArangoMLStorageCRDName, MLStorageDefinition()) - ensureCRDCompliance(t, ml.ArangoMLCronJobCRDName, MLCronJobDefinition()) - ensureCRDCompliance(t, ml.ArangoMLBatchJobCRDName, MLBatchJobDefinition()) + testCases := []struct { + name string + getter func(opts ...func(options *CRDOptions)) Definition + }{ + {apps.ArangoJobCRDName, AppsJobDefinitionWithOptions}, + {backup.ArangoBackupCRDName, BackupsBackupDefinitionWithOptions}, + {backup.ArangoBackupPolicyCRDName, BackupsBackupPolicyDefinitionWithOptions}, + {deployment.ArangoClusterSynchronizationCRDName, DatabaseClusterSynchronizationDefinitionWithOptions}, + {deployment.ArangoDeploymentCRDName, DatabaseDeploymentDefinitionWithOptions}, + {deployment.ArangoMemberCRDName, DatabaseMemberDefinitionWithOptions}, + {deployment.ArangoTaskCRDName, DatabaseTaskDefinitionWithOptions}, + {replication.ArangoDeploymentReplicationCRDName, ReplicationDeploymentReplicationDefinitionWithOptions}, + {storage.ArangoLocalStorageCRDName, StorageLocalStorageDefinitionWithOptions}, + {ml.ArangoMLExtensionCRDName, MLExtensionDefinitionWithOptions}, + {ml.ArangoMLStorageCRDName, MLStorageDefinitionWithOptions}, + {ml.ArangoMLCronJobCRDName, MLCronJobDefinitionWithOptions}, + {ml.ArangoMLBatchJobCRDName, MLBatchJobDefinitionWithOptions}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s-no-schema", tc.name), func(t *testing.T) { + ensureCRDCompliance(t, tc.name, tc.getter().CRD, false) + }) + t.Run(fmt.Sprintf("%s-with-schema", tc.name), func(t *testing.T) { + ensureCRDCompliance(t, tc.name, tc.getter(WithSchema()).CRD, true) + }) + } +} + +func Test_AllDefinitionsDefined(t *testing.T) { + for _, def := range AllDefinitions() { + require.NotEmpty(t, def.Version) + require.NotNil(t, def.CRD) + } +} + +func Test_CRDGetters(t *testing.T) { + // getters are exposed for the usage by customers + getters := []func(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition{ + AppsJobWithOptions, + BackupsBackupWithOptions, + BackupsBackupPolicyPolicyWithOptions, + DatabaseClusterSynchronizationWithOptions, + DatabaseDeploymentWithOptions, + DatabaseMemberWithOptions, + DatabaseTaskWithOptions, + MLExtensionWithOptions, + MLBatchJobWithOptions, + MLCronJobWithOptions, + MLStorageWithOptions, + ReplicationDeploymentReplicationWithOptions, + StorageLocalStorageWithOptions, + } + require.Equal(t, len(AllDefinitions()), len(getters)) + + for _, g := range getters { + t.Run("no-schema", func(t *testing.T) { + crd := g() + require.NotNil(t, crd) + ensureCRDCompliance(t, crd.Spec.Names.Plural+"."+crd.Spec.Group, crd, false) + }) + + t.Run("with-schema", func(t *testing.T) { + crdWithSchema := g(WithSchema()) + require.NotNil(t, crdWithSchema) + ensureCRDCompliance(t, crdWithSchema.Spec.Names.Plural+"."+crdWithSchema.Spec.Group+"", crdWithSchema, true) + }) + } } diff --git a/pkg/crd/crds/database-clustersynchronization.go b/pkg/crd/crds/database-clustersynchronization.go index a512730d6..133e5ee99 100644 --- a/pkg/crd/crds/database-clustersynchronization.go +++ b/pkg/crd/crds/database-clustersynchronization.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(databaseClusterSynchronization, &databaseClusterSynchronizationCRD); err != nil { - panic(err) - } + mustLoadCRD(databaseClusterSynchronization, databaseClusterSynchronizationSchemaRaw, &databaseClusterSynchronizationCRD, &databaseClusterSynchronizationCRDSchemas) } +// Deprecated: use DatabaseClusterSynchronizationWithOptions instead func DatabaseClusterSynchronization() *apiextensions.CustomResourceDefinition { - return databaseClusterSynchronizationCRD.DeepCopy() + return DatabaseClusterSynchronizationWithOptions() +} + +func DatabaseClusterSynchronizationWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(databaseClusterSynchronizationCRD, databaseClusterSynchronizationCRDSchemas, opts...) } +// Deprecated: use DatabaseClusterSynchronizationDefinitionWithOptions instead func DatabaseClusterSynchronizationDefinition() Definition { + return DatabaseClusterSynchronizationDefinitionWithOptions() +} + +func DatabaseClusterSynchronizationDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: DatabaseClusterSynchronizationVersion, - CRD: databaseClusterSynchronizationCRD.DeepCopy(), + CRD: DatabaseClusterSynchronizationWithOptions(opts...), } } var databaseClusterSynchronizationCRD apiextensions.CustomResourceDefinition +var databaseClusterSynchronizationCRDSchemas crdSchemas //go:embed database-clustersynchronization.yaml var databaseClusterSynchronization []byte + +//go:embed database-clustersynchronization.schema.generated.yaml +var databaseClusterSynchronizationSchemaRaw []byte diff --git a/pkg/crd/crds/database-deployment.go b/pkg/crd/crds/database-deployment.go index 1c80bcdfe..4ea4cecb0 100644 --- a/pkg/crd/crds/database-deployment.go +++ b/pkg/crd/crds/database-deployment.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(databaseDeployment, &databaseDeploymentCRD); err != nil { - panic(err) - } + mustLoadCRD(databaseDeployment, databaseDeploymentSchemaRaw, &databaseDeploymentCRD, &databaseDeploymentCRDSchemas) } +// Deprecated: use DatabaseDeploymentWithOptions instead func DatabaseDeployment() *apiextensions.CustomResourceDefinition { - return databaseDeploymentCRD.DeepCopy() + return DatabaseDeploymentWithOptions() +} + +func DatabaseDeploymentWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(databaseDeploymentCRD, databaseDeploymentCRDSchemas, opts...) } +// Deprecated: use DatabaseDeploymentDefinitionWithOptions instead func DatabaseDeploymentDefinition() Definition { + return DatabaseDeploymentDefinitionWithOptions() +} + +func DatabaseDeploymentDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: DatabaseDeploymentVersion, - CRD: databaseDeploymentCRD.DeepCopy(), + CRD: DatabaseDeploymentWithOptions(opts...), } } var databaseDeploymentCRD apiextensions.CustomResourceDefinition +var databaseDeploymentCRDSchemas crdSchemas //go:embed database-deployment.yaml var databaseDeployment []byte + +//go:embed database-deployment.schema.generated.yaml +var databaseDeploymentSchemaRaw []byte diff --git a/pkg/crd/crds/database-member.go b/pkg/crd/crds/database-member.go index c93aaa7c7..439b71b33 100644 --- a/pkg/crd/crds/database-member.go +++ b/pkg/crd/crds/database-member.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(databaseMember, &databaseMemberCRD); err != nil { - panic(err) - } + mustLoadCRD(databaseMember, databaseMemberSchemaRaw, &databaseMemberCRD, &databaseMemberCRDSchemas) } +// Deprecated: use DatabaseMemberWithOptions instead func DatabaseMember() *apiextensions.CustomResourceDefinition { - return databaseMemberCRD.DeepCopy() + return DatabaseMemberWithOptions() +} + +func DatabaseMemberWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(databaseMemberCRD, databaseMemberCRDSchemas, opts...) } +// Deprecated: use DatabaseMemberDefinitionWithOptions instead func DatabaseMemberDefinition() Definition { + return DatabaseMemberDefinitionWithOptions() +} + +func DatabaseMemberDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: DatabaseMemberVersion, - CRD: databaseMemberCRD.DeepCopy(), + CRD: DatabaseMemberWithOptions(opts...), } } var databaseMemberCRD apiextensions.CustomResourceDefinition +var databaseMemberCRDSchemas crdSchemas //go:embed database-member.yaml var databaseMember []byte + +//go:embed database-member.schema.generated.yaml +var databaseMemberSchemaRaw []byte diff --git a/pkg/crd/crds/database-task.go b/pkg/crd/crds/database-task.go index b8133c290..47ee56e19 100644 --- a/pkg/crd/crds/database-task.go +++ b/pkg/crd/crds/database-task.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(databaseTask, &databaseTaskCRD); err != nil { - panic(err) - } + mustLoadCRD(databaseTask, databaseTaskSchemaRaw, &databaseTaskCRD, &databaseTaskCRDSchemas) } +// Deprecated: use DatabaseTaskWithOptions instead func DatabaseTask() *apiextensions.CustomResourceDefinition { - return databaseTaskCRD.DeepCopy() + return DatabaseTaskWithOptions() +} + +func DatabaseTaskWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(databaseTaskCRD, databaseTaskCRDSchemas, opts...) } +// Deprecated: use DatabaseTaskDefinitionWithOptions instead func DatabaseTaskDefinition() Definition { + return DatabaseTaskDefinitionWithOptions() +} + +func DatabaseTaskDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: DatabaseTaskVersion, - CRD: databaseTaskCRD.DeepCopy(), + CRD: DatabaseTaskWithOptions(opts...), } } var databaseTaskCRD apiextensions.CustomResourceDefinition +var databaseTaskCRDSchemas crdSchemas //go:embed database-task.yaml var databaseTask []byte + +//go:embed database-task.schema.generated.yaml +var databaseTaskSchemaRaw []byte diff --git a/pkg/crd/crds/ml-extension.go b/pkg/crd/crds/ml-extension.go index 4c71e3c3a..111afcc88 100644 --- a/pkg/crd/crds/ml-extension.go +++ b/pkg/crd/crds/ml-extension.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,25 @@ const ( ) func init() { - if err := yaml.Unmarshal(mlExtension, &mlExtensionCRD); err != nil { - panic(err) - } + mustLoadCRD(mlExtension, mlExtensionSchemaRaw, &mlExtensionCRD, &mlExtensionCRDSchemas) } -func MLExtension() *apiextensions.CustomResourceDefinition { - return mlExtensionCRD.DeepCopy() +func MLExtensionWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(mlExtensionCRD, mlExtensionCRDSchemas, opts...) } -func MLExtensionDefinition() Definition { +func MLExtensionDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: MLExtensionVersion, - CRD: mlExtensionCRD.DeepCopy(), + CRD: MLExtensionWithOptions(opts...), } } var mlExtensionCRD apiextensions.CustomResourceDefinition +var mlExtensionCRDSchemas crdSchemas //go:embed ml-extension.yaml var mlExtension []byte + +//go:embed ml-extension.schema.generated.yaml +var mlExtensionSchemaRaw []byte diff --git a/pkg/crd/crds/ml-job-batch.go b/pkg/crd/crds/ml-job-batch.go index 26d04edf5..ff495901e 100644 --- a/pkg/crd/crds/ml-job-batch.go +++ b/pkg/crd/crds/ml-job-batch.go @@ -37,20 +37,25 @@ func init() { if err := yaml.Unmarshal(mlBatchJob, &mlBatchJobCRD); err != nil { panic(err) } + mustLoadCRD(mlBatchJob, mlBatchJobSchemaRow, &mlBatchJobCRD, &mlBatchJobCRDSchemas) } -func MLBatchJob() *apiextensions.CustomResourceDefinition { - return mlBatchJobCRD.DeepCopy() +func MLBatchJobWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(mlBatchJobCRD, mlBatchJobCRDSchemas, opts...) } -func MLBatchJobDefinition() Definition { +func MLBatchJobDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: MLBatchJobVersion, - CRD: mlBatchJobCRD.DeepCopy(), + CRD: MLBatchJobWithOptions(opts...), } } var mlBatchJobCRD apiextensions.CustomResourceDefinition +var mlBatchJobCRDSchemas crdSchemas //go:embed ml-job-batch.yaml var mlBatchJob []byte + +//go:embed ml-job-batch.schema.generated.yaml +var mlBatchJobSchemaRow []byte diff --git a/pkg/crd/crds/ml-job-cron.go b/pkg/crd/crds/ml-job-cron.go index 64f19e549..af6b8660a 100644 --- a/pkg/crd/crds/ml-job-cron.go +++ b/pkg/crd/crds/ml-job-cron.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,25 @@ const ( ) func init() { - if err := yaml.Unmarshal(mlCronJob, &mlCronJobCRD); err != nil { - panic(err) - } + mustLoadCRD(mlCronJob, mlCronJobSchemaRaw, &mlCronJobCRD, &mlCronJobCRDSchemas) } -func MLCronJob() *apiextensions.CustomResourceDefinition { - return mlCronJobCRD.DeepCopy() +func MLCronJobWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(mlCronJobCRD, mlCronJobCRDSchemas, opts...) } -func MLCronJobDefinition() Definition { +func MLCronJobDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: MLCronJobVersion, - CRD: mlCronJobCRD.DeepCopy(), + CRD: MLCronJobWithOptions(opts...), } } var mlCronJobCRD apiextensions.CustomResourceDefinition +var mlCronJobCRDSchemas crdSchemas //go:embed ml-job-cron.yaml var mlCronJob []byte + +//go:embed ml-job-cron.schema.generated.yaml +var mlCronJobSchemaRaw []byte diff --git a/pkg/crd/crds/ml-storage.go b/pkg/crd/crds/ml-storage.go index 62c0b84e2..5314ecff2 100644 --- a/pkg/crd/crds/ml-storage.go +++ b/pkg/crd/crds/ml-storage.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,25 @@ const ( ) func init() { - if err := yaml.Unmarshal(mlStorage, &mlStorageCRD); err != nil { - panic(err) - } + mustLoadCRD(mlStorage, mlStorageSchemaRaw, &mlStorageCRD, &mlStorageCRDSchemas) } -func MLStorage() *apiextensions.CustomResourceDefinition { - return mlStorageCRD.DeepCopy() +func MLStorageWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(mlStorageCRD, mlStorageCRDSchemas, opts...) } -func MLStorageDefinition() Definition { +func MLStorageDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: MLStorageVersion, - CRD: mlStorageCRD.DeepCopy(), + CRD: MLStorageWithOptions(opts...), } } var mlStorageCRD apiextensions.CustomResourceDefinition +var mlStorageCRDSchemas crdSchemas //go:embed ml-storage.yaml var mlStorage []byte + +//go:embed ml-storage.schema.generated.yaml +var mlStorageSchemaRaw []byte diff --git a/pkg/crd/crds/replication-deploymentreplication.go b/pkg/crd/crds/replication-deploymentreplication.go index f4926f264..e830051b2 100644 --- a/pkg/crd/crds/replication-deploymentreplication.go +++ b/pkg/crd/crds/replication-deploymentreplication.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(replicationDeploymentReplication, &replicationDeploymentReplicationCRD); err != nil { - panic(err) - } + mustLoadCRD(replicationDeploymentReplication, replicationDeploymentReplicationSchemaRaw, &replicationDeploymentReplicationCRD, &replicationDeploymentReplicationCRDSchemas) } +// Deprecated: use ReplicationDeploymentReplicationWithOptions instead func ReplicationDeploymentReplication() *apiextensions.CustomResourceDefinition { - return replicationDeploymentReplicationCRD.DeepCopy() + return ReplicationDeploymentReplicationWithOptions() +} + +func ReplicationDeploymentReplicationWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(replicationDeploymentReplicationCRD, replicationDeploymentReplicationCRDSchemas, opts...) } +// Deprecated: use ReplicationDeploymentReplicationDefinitionWithOptions instead func ReplicationDeploymentReplicationDefinition() Definition { + return ReplicationDeploymentReplicationDefinitionWithOptions() +} + +func ReplicationDeploymentReplicationDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: ReplicationDeploymentReplicationVersion, - CRD: replicationDeploymentReplicationCRD.DeepCopy(), + CRD: ReplicationDeploymentReplicationWithOptions(opts...), } } var replicationDeploymentReplicationCRD apiextensions.CustomResourceDefinition +var replicationDeploymentReplicationCRDSchemas crdSchemas //go:embed replication-deploymentreplication.yaml var replicationDeploymentReplication []byte + +//go:embed replication-deploymentreplication.schema.generated.yaml +var replicationDeploymentReplicationSchemaRaw []byte diff --git a/pkg/crd/crds/storage-localstorage.go b/pkg/crd/crds/storage-localstorage.go index e5837072e..d164d9a59 100644 --- a/pkg/crd/crds/storage-localstorage.go +++ b/pkg/crd/crds/storage-localstorage.go @@ -24,7 +24,6 @@ import ( _ "embed" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/arangodb/go-driver" ) @@ -34,23 +33,35 @@ const ( ) func init() { - if err := yaml.Unmarshal(storageLocalStorage, &storageLocalStorageCRD); err != nil { - panic(err) - } + mustLoadCRD(storageLocalStorage, storageLocalStorageSchemaRaw, &storageLocalStorageCRD, &storageLocalStorageCRDSchemas) } +// Deprecated: use StorageLocalStorageWithOptions instead func StorageLocalStorage() *apiextensions.CustomResourceDefinition { - return storageLocalStorageCRD.DeepCopy() + return StorageLocalStorageWithOptions() +} + +func StorageLocalStorageWithOptions(opts ...func(*CRDOptions)) *apiextensions.CustomResourceDefinition { + return getCRD(storageLocalStorageCRD, storageLocalStorageCRDSchemas, opts...) } +// Deprecated: use StorageLocalStorageDefinitionWithOptions instead func StorageLocalStorageDefinition() Definition { + return StorageLocalStorageDefinitionWithOptions() +} + +func StorageLocalStorageDefinitionWithOptions(opts ...func(*CRDOptions)) Definition { return Definition{ Version: StorageLocalStorageVersion, - CRD: storageLocalStorageCRD.DeepCopy(), + CRD: StorageLocalStorageWithOptions(opts...), } } var storageLocalStorageCRD apiextensions.CustomResourceDefinition +var storageLocalStorageCRDSchemas crdSchemas //go:embed storage-localstorage.yaml var storageLocalStorage []byte + +//go:embed storage-localstorage.schema.generated.yaml +var storageLocalStorageSchemaRaw []byte diff --git a/pkg/crd/definitions.go b/pkg/crd/definitions.go index 7fb871611..7e8b7f534 100644 --- a/pkg/crd/definitions.go +++ b/pkg/crd/definitions.go @@ -23,45 +23,54 @@ package crd import ( "sync" - apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - - "github.com/arangodb/go-driver" - "github.com/arangodb/kube-arangodb/pkg/crd/crds" "github.com/arangodb/kube-arangodb/pkg/util/errors" ) const Version = "arangodb.com/version" +type crdDefinitionGetter func(opts *crds.CRDOptions) crds.Definition + +type crdRegistration struct { + getter crdDefinitionGetter + defaultOpts crds.CRDOptions +} + var ( - registeredCRDs = map[string]crd{} + registeredCRDs = map[string]crdRegistration{} crdsLock sync.Mutex ) -type crd struct { - version driver.Version - spec apiextensions.CustomResourceDefinitionSpec -} - -func registerCRDWithPanic(c crds.Definition) { - if err := registerCRD(c.CRD.GetName(), crd{ - version: c.Version, - spec: c.CRD.Spec, - }); err != nil { +func registerCRDWithPanic(getter crdDefinitionGetter, defaultOpts *crds.CRDOptions) { + if defaultOpts == nil { + defaultOpts = &crds.CRDOptions{} + } + if err := registerCRD(getter, *defaultOpts); err != nil { panic(err) } } -func registerCRD(name string, crd crd) error { +func registerCRD(getter crdDefinitionGetter, defaultOpts crds.CRDOptions) error { crdsLock.Lock() defer crdsLock.Unlock() - if _, ok := registeredCRDs[name]; ok { - return errors.Newf("CRD %s already exists", name) + def := getter(nil) + if _, ok := registeredCRDs[def.CRD.GetName()]; ok { + return errors.Newf("CRD %s already exists", def.CRD.GetName()) + } + registeredCRDs[def.CRD.GetName()] = crdRegistration{ + getter: getter, + defaultOpts: defaultOpts, } - - registeredCRDs[name] = crd return nil } + +func GetDefaultCRDOptions() map[string]crds.CRDOptions { + ret := make(map[string]crds.CRDOptions) + for n, s := range registeredCRDs { + ret[n] = s.defaultOpts + } + return ret +} From 442e3c00ada2326c0c9d1b47e6e3c9c47c9b5609 Mon Sep 17 00:00:00 2001 From: Nikita Vaniasin Date: Mon, 13 Nov 2023 13:27:43 +0100 Subject: [PATCH 2/4] Improve docs / log output for crd installation --- docs/crds.md | 4 ++-- pkg/crd/apply.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/crds.md b/docs/crds.md index 363c2f1f2..363f19b89 100644 --- a/docs/crds.md +++ b/docs/crds.md @@ -17,8 +17,8 @@ There are different options how CustomResourceDefinitions can be created. - Install CRDs using kustomize `all` or `crd` manifests. **Recommended:** -Use `kube-arangodb` Helm chart. Chart itself does not contain CRDs. -Instead, operator will try to create the required CRDs on the first start. +Use `kube-arangodb` Helm chart. +If you've chosen not to install CRDs automatically (`--skip-crds`), the operator will try to install CRDs automatically. Make sure that ServiceAccount for operator has permissions to `create` CustomResourceDefinitions. To disable the automatic creation of CRDs, set `enableCRDManagement=false` template parameter, e.g.: diff --git a/pkg/crd/apply.go b/pkg/crd/apply.go index d38f9c018..eea9f1f3c 100644 --- a/pkg/crd/apply.go +++ b/pkg/crd/apply.go @@ -129,7 +129,7 @@ func tryApplyCRD(ctx context.Context, client kclient.Client, def crds.Definition c.Spec = def.CRD.Spec if _, err := crdDefinitions.Update(ctx, c, meta.UpdateOptions{}); err != nil { - logger.Err(err).Str("crd", crdName).Warn("Create Operations is not allowed due to error") + logger.Err(err).Str("crd", crdName).Warn("Failed to update CRD definition") return err } logger.Str("crd", crdName).Info("CRD Updated") From 36a1a307d523f3d8bb732e9a2674fc70a5ba2b80 Mon Sep 17 00:00:00 2001 From: Nikita Vaniasin Date: Mon, 13 Nov 2023 15:38:13 +0100 Subject: [PATCH 3/4] Proper getters for CRDs --- pkg/crd/arangobackup.go | 2 +- pkg/crd/arangobackuppolicies.go | 2 +- pkg/crd/arangoclustersynchronizations.go | 2 +- pkg/crd/arangodeploymentreplications.go | 2 +- pkg/crd/arangodeployments.go | 2 +- pkg/crd/arangojobs.go | 2 +- pkg/crd/arangolocalstorage.go | 2 +- pkg/crd/arangomembers.go | 2 +- pkg/crd/arangoml.go | 2 +- pkg/crd/arangotasks.go | 2 +- pkg/crd/crds/crds.go | 10 ++++++++++ 11 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pkg/crd/arangobackup.go b/pkg/crd/arangobackup.go index db58259df..888bc6eff 100644 --- a/pkg/crd/arangobackup.go +++ b/pkg/crd/arangobackup.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.BackupsBackupDefinitionWithOptions() + return crds.BackupsBackupDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangobackuppolicies.go b/pkg/crd/arangobackuppolicies.go index bd29acb13..77f93234c 100644 --- a/pkg/crd/arangobackuppolicies.go +++ b/pkg/crd/arangobackuppolicies.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.BackupsBackupPolicyDefinitionWithOptions() + return crds.BackupsBackupPolicyDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangoclustersynchronizations.go b/pkg/crd/arangoclustersynchronizations.go index 2cab35fa0..8f799bf9c 100644 --- a/pkg/crd/arangoclustersynchronizations.go +++ b/pkg/crd/arangoclustersynchronizations.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.DatabaseClusterSynchronizationDefinitionWithOptions() + return crds.DatabaseClusterSynchronizationDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangodeploymentreplications.go b/pkg/crd/arangodeploymentreplications.go index 37530d381..add51515d 100644 --- a/pkg/crd/arangodeploymentreplications.go +++ b/pkg/crd/arangodeploymentreplications.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.ReplicationDeploymentReplicationDefinitionWithOptions() + return crds.ReplicationDeploymentReplicationDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangodeployments.go b/pkg/crd/arangodeployments.go index 8915bc31a..04b951676 100644 --- a/pkg/crd/arangodeployments.go +++ b/pkg/crd/arangodeployments.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.DatabaseDeploymentDefinitionWithOptions() + return crds.DatabaseDeploymentDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangojobs.go b/pkg/crd/arangojobs.go index 11bce351c..2d92d5b77 100644 --- a/pkg/crd/arangojobs.go +++ b/pkg/crd/arangojobs.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.AppsJobDefinitionWithOptions() + return crds.AppsJobDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangolocalstorage.go b/pkg/crd/arangolocalstorage.go index 8d8e02e82..be7d8786b 100644 --- a/pkg/crd/arangolocalstorage.go +++ b/pkg/crd/arangolocalstorage.go @@ -24,6 +24,6 @@ import "github.com/arangodb/kube-arangodb/pkg/crd/crds" func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.StorageLocalStorageDefinitionWithOptions() + return crds.StorageLocalStorageDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangomembers.go b/pkg/crd/arangomembers.go index b7fa373f7..4b0b33b56 100644 --- a/pkg/crd/arangomembers.go +++ b/pkg/crd/arangomembers.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.DatabaseMemberDefinitionWithOptions() + return crds.DatabaseMemberDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/arangoml.go b/pkg/crd/arangoml.go index 85a1f1deb..1df15ff04 100644 --- a/pkg/crd/arangoml.go +++ b/pkg/crd/arangoml.go @@ -34,7 +34,7 @@ func init() { for _, getDef := range defs { defFn := getDef // bring into scope registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return defFn() + return defFn(opts.AsFunc()) }, &crds.CRDOptions{ WithSchema: true, }) diff --git a/pkg/crd/arangotasks.go b/pkg/crd/arangotasks.go index 2be88a264..b2cbd934f 100644 --- a/pkg/crd/arangotasks.go +++ b/pkg/crd/arangotasks.go @@ -26,6 +26,6 @@ import ( func init() { registerCRDWithPanic(func(opts *crds.CRDOptions) crds.Definition { - return crds.DatabaseTaskDefinitionWithOptions() + return crds.DatabaseTaskDefinitionWithOptions(opts.AsFunc()) }, nil) } diff --git a/pkg/crd/crds/crds.go b/pkg/crd/crds/crds.go index 5f7ce9076..d667de01a 100644 --- a/pkg/crd/crds/crds.go +++ b/pkg/crd/crds/crds.go @@ -81,6 +81,16 @@ type CRDOptions struct { WithSchema bool } +func (o *CRDOptions) AsFunc() func(*CRDOptions) { + return func(opts *CRDOptions) { + if o == nil || opts == nil { + opts = &CRDOptions{} + } else { + opts.WithSchema = o.WithSchema + } + } +} + func WithSchema() func(*CRDOptions) { return func(o *CRDOptions) { o.WithSchema = true From a66cdd0f9925b50911342c503bddd2afd3e4826e Mon Sep 17 00:00:00 2001 From: Nikita Vaniasin Date: Mon, 13 Nov 2023 20:31:33 +0100 Subject: [PATCH 4/4] Add ability to force-update CRDs to allow testing --- cmd/cmd.go | 2 +- cmd/crd.go | 2 +- pkg/crd/apply.go | 33 +++++++++++++++++++++------------ pkg/crd/apply_test.go | 6 +++--- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 4b4dd85a3..ad65d7a59 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -376,7 +376,7 @@ func executeMain(cmd *cobra.Command, args []string) { logger.Fatal("Invalid --crd.validation-schema args: %s", err) } - _ = crd.EnsureCRDWithOptions(ctx, client, crdOpts, true) + _ = crd.EnsureCRDWithOptions(ctx, client, crd.EnsureCRDOptions{IgnoreErrors: true, CRDOptions: crdOpts}) } secrets := client.Kubernetes().CoreV1().Secrets(namespace) diff --git a/cmd/crd.go b/cmd/crd.go index bb2425cc6..6b95b94e7 100644 --- a/cmd/crd.go +++ b/cmd/crd.go @@ -109,7 +109,7 @@ func cmdCRDInstallRun(cmd *cobra.Command, args []string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - err = crd.EnsureCRDWithOptions(ctx, client, crdOpts, false) + err = crd.EnsureCRDWithOptions(ctx, client, crd.EnsureCRDOptions{IgnoreErrors: false, CRDOptions: crdOpts}) if err != nil { os.Exit(1) } diff --git a/pkg/crd/apply.go b/pkg/crd/apply.go index eea9f1f3c..4d37192af 100644 --- a/pkg/crd/apply.go +++ b/pkg/crd/apply.go @@ -40,10 +40,21 @@ var logger = logging.Global().RegisterAndGetLogger("crd", logging.Info) // Deprecated: use EnsureCRDWithOptions instead func EnsureCRD(ctx context.Context, client kclient.Client, ignoreErrors bool) error { - return EnsureCRDWithOptions(ctx, client, nil, ignoreErrors) + return EnsureCRDWithOptions(ctx, client, EnsureCRDOptions{ + IgnoreErrors: ignoreErrors, + }) } -func EnsureCRDWithOptions(ctx context.Context, client kclient.Client, ensureOpts map[string]crds.CRDOptions, ignoreErrors bool) error { +type EnsureCRDOptions struct { + // IgnoreErrors do not return errors if could not apply CRD + IgnoreErrors bool + // ForceUpdate if true, CRD will be updated even if definitions versions are the same + ForceUpdate bool + // CRDOptions defines options per each CRD + CRDOptions map[string]crds.CRDOptions +} + +func EnsureCRDWithOptions(ctx context.Context, client kclient.Client, opts EnsureCRDOptions) error { crdsLock.Lock() defer crdsLock.Unlock() @@ -55,20 +66,20 @@ func EnsureCRDWithOptions(ctx context.Context, client kclient.Client, ensureOpts } var opt *crds.CRDOptions - if o, ok := ensureOpts[crdName]; ok { + if o, ok := opts.CRDOptions[crdName]; ok { opt = &o } def := crdReg.getter(opt) - err := tryApplyCRD(ctx, client, def) - if !ignoreErrors && err != nil { + err := tryApplyCRD(ctx, client, def, opts.ForceUpdate) + if !opts.IgnoreErrors && err != nil { return err } } return nil } -func tryApplyCRD(ctx context.Context, client kclient.Client, def crds.Definition) error { +func tryApplyCRD(ctx context.Context, client kclient.Client, def crds.Definition, forceUpdate bool) error { crdDefinitions := client.KubernetesExtensions().ApiextensionsV1().CustomResourceDefinitions() crdName := def.CRD.Name @@ -116,12 +127,10 @@ func tryApplyCRD(ctx context.Context, client kclient.Client, def crds.Definition c.ObjectMeta.Labels = map[string]string{} } - if v, ok := c.ObjectMeta.Labels[Version]; ok { - if v != "" { - if !isUpdateRequired(def.Version, driver.Version(v)) { - logger.Str("crd", crdName).Info("CRD Update not required") - return nil - } + if v, ok := c.ObjectMeta.Labels[Version]; ok && v != "" { + if !forceUpdate && !isUpdateRequired(def.Version, driver.Version(v)) { + logger.Str("crd", crdName).Info("CRD Update not required") + return nil } } diff --git a/pkg/crd/apply_test.go b/pkg/crd/apply_test.go index 0ddd447ca..cdd9e8b58 100644 --- a/pkg/crd/apply_test.go +++ b/pkg/crd/apply_test.go @@ -88,7 +88,7 @@ func runApply(t *testing.T, crdOpts map[string]crds.CRDOptions) { c := kclient.NewFakeClient() t.Run("Ensure", func(t *testing.T) { - require.NoError(t, EnsureCRDWithOptions(context.Background(), c, crdOpts, false)) + require.NoError(t, EnsureCRDWithOptions(context.Background(), c, EnsureCRDOptions{IgnoreErrors: false, CRDOptions: crdOpts})) for k, v := range dropLogMessages(t, s) { t.Run(k, func(t *testing.T) { @@ -114,7 +114,7 @@ func runApply(t *testing.T, crdOpts map[string]crds.CRDOptions) { }) t.Run("Ensure", func(t *testing.T) { - require.NoError(t, EnsureCRDWithOptions(context.Background(), c, crdOpts, false)) + require.NoError(t, EnsureCRDWithOptions(context.Background(), c, EnsureCRDOptions{IgnoreErrors: false, CRDOptions: crdOpts})) for k, v := range dropLogMessages(t, s) { t.Run(k, func(t *testing.T) { @@ -142,7 +142,7 @@ func runApply(t *testing.T, crdOpts map[string]crds.CRDOptions) { }) t.Run("Ensure", func(t *testing.T) { - require.NoError(t, EnsureCRDWithOptions(context.Background(), c, crdOpts, false)) + require.NoError(t, EnsureCRDWithOptions(context.Background(), c, EnsureCRDOptions{IgnoreErrors: false, CRDOptions: crdOpts})) for k, v := range dropLogMessages(t, s) { t.Run(k, func(t *testing.T) {