From 76f0dd285eecbb3c3dce17f60caa0be55060eda0 Mon Sep 17 00:00:00 2001 From: Ramon de Klein Date: Mon, 26 Aug 2024 19:53:00 +0200 Subject: [PATCH 1/6] Allow running locally --- pkg/apis/minio.min.io/v2/helper.go | 2 +- sidecar/pkg/sidecar/sidecar_utils.go | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/apis/minio.min.io/v2/helper.go b/pkg/apis/minio.min.io/v2/helper.go index 22927919d58..e8579fad8c8 100644 --- a/pkg/apis/minio.min.io/v2/helper.go +++ b/pkg/apis/minio.min.io/v2/helper.go @@ -128,7 +128,7 @@ func GetPrivateKeyFilePath(serviceName string) string { func GetNSFromFile() string { namespace, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") if err != nil { - return "minio-operator" + return os.Getenv("DEV_NAMESPACE") } return string(namespace) } diff --git a/sidecar/pkg/sidecar/sidecar_utils.go b/sidecar/pkg/sidecar/sidecar_utils.go index 1b2778bb166..dcbaa86377e 100644 --- a/sidecar/pkg/sidecar/sidecar_utils.go +++ b/sidecar/pkg/sidecar/sidecar_utils.go @@ -48,7 +48,21 @@ func init() { // StartSideCar instantiates kube clients and starts the side-car controller func StartSideCar(tenantName string, secretName string) { log.Println("Starting Sidecar") - cfg, err := rest.InClusterConfig() + var cfg *rest.Config + var err error + + if os.Getenv("DEV_NAMESPACE") != "" { + klog.Info("DEV_NAMESPACE present, running dev mode") + cfg = &rest.Config{ + Host: "http://localhost:8001", + TLSClientConfig: rest.TLSClientConfig{Insecure: true}, + APIPath: "/", + } + } else { + // Look for incluster config by default + cfg, err = rest.InClusterConfig() + } + if err != nil { panic(err) } From 77208c49bf2fae4b202b62b373d7924c018d8cca Mon Sep 17 00:00:00 2001 From: Ramon de Klein Date: Mon, 26 Aug 2024 22:28:47 +0200 Subject: [PATCH 2/6] support `envFrom` in sidecar (#2279) --- .../statefulsets/minio-statefulset.go | 2 - sidecar/cmd/sidecar/sidecar.go | 12 +- .../configuration/tenant_configuration.go | 92 +++++++++--- .../tenant_configuration_test.go | 9 +- sidecar/pkg/sidecar/sidecar_utils.go | 137 ++++++++++++------ sidecar/pkg/validator/validator.go | 14 +- 6 files changed, 182 insertions(+), 84 deletions(-) rename {pkg => sidecar/pkg}/configuration/tenant_configuration.go (72%) rename {pkg => sidecar/pkg}/configuration/tenant_configuration_test.go (96%) diff --git a/pkg/resources/statefulsets/minio-statefulset.go b/pkg/resources/statefulsets/minio-statefulset.go index 7124e351a9a..4ab8d20ae0e 100644 --- a/pkg/resources/statefulsets/minio-statefulset.go +++ b/pkg/resources/statefulsets/minio-statefulset.go @@ -786,8 +786,6 @@ func getSideCarContainer(t *miniov2.Tenant, pool *miniov2.Pool) corev1.Container "sidecar", "--tenant", t.Name, - "--config-name", - t.Spec.Configuration.Name, }, Env: []corev1.EnvVar{ { diff --git a/sidecar/cmd/sidecar/sidecar.go b/sidecar/cmd/sidecar/sidecar.go index 0f59bf2ce78..f2c1ac04ce5 100644 --- a/sidecar/cmd/sidecar/sidecar.go +++ b/sidecar/cmd/sidecar/sidecar.go @@ -36,11 +36,6 @@ var sidecarCmd = cli.Command{ Value: "", Usage: "name of tenant being validated", }, - cli.StringFlag{ - Name: "config-name", - Value: "", - Usage: "secret being watched", - }, }, } @@ -50,10 +45,5 @@ func startSideCar(ctx *cli.Context) { log.Println("Must pass --tenant flag") os.Exit(1) } - configName := ctx.String("config-name") - if configName == "" { - log.Println("Must pass --config-name flag") - os.Exit(1) - } - sidecar.StartSideCar(tenantName, configName) + sidecar.StartSideCar(tenantName) } diff --git a/pkg/configuration/tenant_configuration.go b/sidecar/pkg/configuration/tenant_configuration.go similarity index 72% rename from pkg/configuration/tenant_configuration.go rename to sidecar/pkg/configuration/tenant_configuration.go index 2900a6a19c6..c00c24aad0e 100644 --- a/pkg/configuration/tenant_configuration.go +++ b/sidecar/pkg/configuration/tenant_configuration.go @@ -17,8 +17,12 @@ package configuration import ( + "context" + "errors" "fmt" + "log" "sort" + "strconv" "strings" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" @@ -31,27 +35,69 @@ const ( bucketDNSEnv = "MINIO_DNS_WEBHOOK_ENDPOINT" ) +type ( + secretFunc func(ctx context.Context, name string) (*corev1.Secret, error) + configFunc func(ctx context.Context, name string) (*corev1.ConfigMap, error) +) + +// TenantResources returns maps for all configmap/secret resources that +// are used in the tenant specification +func TenantResources(ctx context.Context, tenant *miniov2.Tenant, cf configFunc, sf secretFunc) (map[string]*corev1.ConfigMap, map[string]*corev1.Secret, error) { + configMaps := make(map[string]*corev1.ConfigMap) + secrets := make(map[string]*corev1.Secret) + + for _, env := range tenant.Spec.Env { + if env.ValueFrom != nil { + if env.ValueFrom.SecretKeyRef != nil { + secret, err := sf(ctx, env.ValueFrom.SecretKeyRef.Name) + if err != nil { + return nil, nil, err + } + secrets[env.ValueFrom.SecretKeyRef.Name] = secret + } + if env.ValueFrom.ConfigMapKeyRef != nil { + configmap, err := cf(ctx, env.ValueFrom.ConfigMapKeyRef.Name) + if err != nil { + return nil, nil, err + } + configMaps[env.ValueFrom.ConfigMapKeyRef.Name] = configmap + } + if env.ValueFrom.FieldRef != nil { + return nil, nil, errors.New("mapping fields is not supported") + } + if env.ValueFrom.ResourceFieldRef != nil { + return nil, nil, errors.New("mapping resource fields is not supported") + } + } + } + + secret, err := sf(ctx, tenant.Spec.Configuration.Name) + if err != nil { + return nil, nil, err + } + secrets[tenant.Spec.Configuration.Name] = secret + + return configMaps, secrets, nil +} + // GetFullTenantConfig returns the full configuration for the tenant considering the secret and the tenant spec -func GetFullTenantConfig(tenant *miniov2.Tenant, configSecret *corev1.Secret) (string, bool, bool) { +func GetFullTenantConfig(tenant *miniov2.Tenant, configMaps map[string]*corev1.ConfigMap, secrets map[string]*corev1.Secret) (string, bool, bool) { + configSecret := secrets[tenant.Spec.Configuration.Name] + seededVars := parseConfEnvSecret(configSecret) rootUserFound := false rootPwdFound := false for _, env := range seededVars { - if env.Name == "MINIO_ROOT_USER" { + if env.Name == "MINIO_ROOT_USER" || env.Name == "MINIO_ACCESS_KEY" { rootUserFound = true } - if env.Name == "MINIO_ACCESS_KEY" { - rootUserFound = true - } - if env.Name == "MINIO_ROOT_PASSWORD" { - rootPwdFound = true - } - if env.Name == "MINIO_SECRET_KEY" { + if env.Name == "MINIO_ROOT_PASSWORD" || env.Name == "MINIO_SECRET_KEY" { rootPwdFound = true } } + compiledConfig := buildTenantEnvs(tenant, seededVars) - configurationFileContent := envVarsToFileContent(compiledConfig) + configurationFileContent := envVarsToFileContent(compiledConfig, configMaps, secrets) return configurationFileContent, rootUserFound, rootPwdFound } @@ -70,12 +116,15 @@ func parseConfEnvSecret(secret *corev1.Secret) map[string]corev1.EnvVar { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { name := strings.TrimSpace(parts[0]) - value := strings.Trim(strings.TrimSpace(parts[1]), "\"") - envVar := corev1.EnvVar{ + value, err := strconv.Unquote(strings.TrimSpace(parts[1])) + if err != nil { + log.Printf("Syntax error for variable %s (skipped): %s", name, err) + continue + } + envMap[name] = corev1.EnvVar{ Name: name, Value: value, } - envMap[name] = envVar } } } @@ -234,10 +283,19 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En return envVars } -func envVarsToFileContent(envVars []corev1.EnvVar) string { - content := "" +func envVarsToFileContent(envVars []corev1.EnvVar, configMaps map[string]*corev1.ConfigMap, secrets map[string]*corev1.Secret) string { + var sb strings.Builder for _, env := range envVars { - content += fmt.Sprintf("export %s=\"%s\"\n", env.Name, env.Value) + value := env.Value + if env.ValueFrom != nil { + if env.ValueFrom.ConfigMapKeyRef != nil { + value = configMaps[env.ValueFrom.ConfigMapKeyRef.Name].Data[env.ValueFrom.ConfigMapKeyRef.Key] + } + if env.ValueFrom.SecretKeyRef != nil { + value = string(secrets[env.ValueFrom.SecretKeyRef.Name].Data[env.ValueFrom.SecretKeyRef.Key]) + } + } + sb.WriteString(fmt.Sprintf("export %s=\"%s\"\n", env.Name, value)) } - return content + return sb.String() } diff --git a/pkg/configuration/tenant_configuration_test.go b/sidecar/pkg/configuration/tenant_configuration_test.go similarity index 96% rename from pkg/configuration/tenant_configuration_test.go rename to sidecar/pkg/configuration/tenant_configuration_test.go index 9393d888744..042351730a5 100644 --- a/pkg/configuration/tenant_configuration_test.go +++ b/sidecar/pkg/configuration/tenant_configuration_test.go @@ -67,7 +67,7 @@ export MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRy } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := envVarsToFileContent(tt.args.envVars); got != tt.want { + if got := envVarsToFileContent(tt.args.envVars, nil, nil); got != tt.want { t.Errorf("envVarsToFileContent() = `%v`, want `%v`", got, tt.want) } }) @@ -377,7 +377,12 @@ export TEST="value" for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.args.tenant.EnsureDefaults() - if got, _, _ := GetFullTenantConfig(tt.args.tenant, tt.args.configSecret); got != tt.want { + + var configMaps map[string]*corev1.ConfigMap + secrets := map[string]*corev1.Secret{ + tt.args.tenant.ConfigurationSecretName(): tt.args.configSecret, + } + if got, _, _ := GetFullTenantConfig(tt.args.tenant, configMaps, secrets); got != tt.want { t.Errorf("GetFullTenantConfig() = `%v`, want `%v`", got, tt.want) } }) diff --git a/sidecar/pkg/sidecar/sidecar_utils.go b/sidecar/pkg/sidecar/sidecar_utils.go index dcbaa86377e..bf37cccc88a 100644 --- a/sidecar/pkg/sidecar/sidecar_utils.go +++ b/sidecar/pkg/sidecar/sidecar_utils.go @@ -22,9 +22,10 @@ import ( "log" "net/http" "os" + "sync" "time" - "github.com/minio/operator/pkg/configuration" + "github.com/minio/operator/sidecar/pkg/configuration" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -46,7 +47,7 @@ func init() { } // StartSideCar instantiates kube clients and starts the side-car controller -func StartSideCar(tenantName string, secretName string) { +func StartSideCar(tenantName string) { log.Println("Starting Sidecar") var cfg *rest.Config var err error @@ -90,7 +91,7 @@ func StartSideCar(tenantName string, secretName string) { tenant.EnsureDefaults() - controller := NewSideCarController(kubeClient, controllerClient, namespace, tenantName, secretName) + controller := NewSideCarController(kubeClient, controllerClient, tenant) controller.ws = configureWebhookServer(controller) controller.probeServer = configureProbesServer(tenant) controller.sidecar = configureSidecarServer(controller) @@ -134,36 +135,38 @@ func StartSideCar(tenantName string, secretName string) { type Controller struct { kubeClient *kubernetes.Clientset controllerClient *clientset.Clientset - tenantName string - secretName string minInformerFactory minioInformers.SharedInformerFactory secretInformer coreinformers.SecretInformer + configMapInformer coreinformers.ConfigMapInformer tenantInformer v22.TenantInformer - namespace string informerFactory informers.SharedInformerFactory + lock sync.Mutex + tenant *v2.Tenant + configMaps map[string]*corev1.ConfigMap + secrets map[string]*corev1.Secret ws *http.Server probeServer *http.Server sidecar *http.Server } // NewSideCarController returns an instance of Controller with the provided clients -func NewSideCarController(kubeClient *kubernetes.Clientset, controllerClient *clientset.Clientset, namespace string, tenantName string, secretName string) *Controller { - factory := informers.NewSharedInformerFactoryWithOptions(kubeClient, time.Hour*1, informers.WithNamespace(namespace)) +func NewSideCarController(kubeClient *kubernetes.Clientset, controllerClient *clientset.Clientset, tenant *v2.Tenant) *Controller { + factory := informers.NewSharedInformerFactoryWithOptions(kubeClient, time.Hour*1, informers.WithNamespace(tenant.Namespace)) secretInformer := factory.Core().V1().Secrets() + configMapInformer := factory.Core().V1().ConfigMaps() - minioInformerFactory := minioInformers.NewSharedInformerFactoryWithOptions(controllerClient, time.Hour*1, minioInformers.WithNamespace(namespace)) + minioInformerFactory := minioInformers.NewSharedInformerFactoryWithOptions(controllerClient, time.Hour*1, minioInformers.WithNamespace(tenant.Namespace)) tenantInformer := minioInformerFactory.Minio().V2().Tenants() c := &Controller{ kubeClient: kubeClient, controllerClient: controllerClient, - tenantName: tenantName, - namespace: namespace, - secretName: secretName, minInformerFactory: minioInformerFactory, informerFactory: factory, tenantInformer: tenantInformer, secretInformer: secretInformer, + configMapInformer: configMapInformer, + tenant: tenant, } _, err := tenantInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -175,7 +178,13 @@ func NewSideCarController(kubeClient *kubernetes.Clientset, controllerClient *cl // Two different versions of the same Tenant will always have different RVs. return } - c.regenCfgWithTenant(newTenant) + c.lock.Lock() + defer c.lock.Unlock() + + log.Println("tenant was updated, regenerating configuration") + + c.tenant = newTenant + c.regenCfg() }, }) if err != nil { @@ -186,19 +195,22 @@ func NewSideCarController(kubeClient *kubernetes.Clientset, controllerClient *cl _, err = secretInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ UpdateFunc: func(old, new interface{}) { oldSecret := old.(*corev1.Secret) - // ignore anything that is not what we want - if oldSecret.Name != secretName { - return - } - log.Printf("Config secret '%s' sync", secretName) newSecret := new.(*corev1.Secret) - if newSecret.ResourceVersion == oldSecret.ResourceVersion { - // Periodic resync will send update events for all known Tenants. - // Two different versions of the same Tenant will always have different RVs. + if oldSecret.ResourceVersion == newSecret.ResourceVersion { + // Periodic resync will send update events for all known secrets. + // Two different versions of the same secret will always have different RVs. return } + c.lock.Lock() + defer c.lock.Unlock() - c.regenCfgWithSecret(newSecret) + log.Printf("secret %s was updated, regenerating configuration", newSecret.Name) + + if _, ok := c.secrets[oldSecret.Name]; !ok { + // Not interested in secrets that we don't use + return + } + c.regenCfg() }, }) if err != nil { @@ -206,43 +218,60 @@ func NewSideCarController(kubeClient *kubernetes.Clientset, controllerClient *cl return nil } - return c -} + _, err = configMapInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(old, new interface{}) { + oldConfigMap := old.(*corev1.ConfigMap) + newConfigMap := new.(*corev1.ConfigMap) + if oldConfigMap.ResourceVersion == newConfigMap.ResourceVersion { + // Periodic resync will send update events for all known config maps. + // Two different versions of the same config map will always have different RVs. + return + } + c.lock.Lock() + defer c.lock.Unlock() -func (c *Controller) regenCfgWithTenant(tenant *v2.Tenant) { - // get the tenant secret - tenant.EnsureDefaults() + log.Printf("configmap %s was updated, regenerating configuration", newConfigMap.Name) - configSecret, err := c.secretInformer.Lister().Secrets(c.namespace).Get(tenant.Spec.Configuration.Name) + if _, ok := c.configMaps[oldConfigMap.Name]; !ok { + // Not interested in configmaps that we don't use + return + } + c.regenCfg() + }, + }) if err != nil { - log.Println("could not get secret", err) - return + log.Println("could not add event handler for secret informer", err) + return nil } - fileContents, rootUserFound, rootPwdFound := configuration.GetFullTenantConfig(tenant, configSecret) + return c +} - if !rootUserFound || !rootPwdFound { - log.Println("Missing root credentials in the configuration.") - log.Println("MinIO won't start") - os.Exit(1) - } +func (c *Controller) getSecret(_ context.Context, name string) (*corev1.Secret, error) { + return c.secretInformer.Lister().Secrets(c.tenant.Namespace).Get(name) +} - err = os.WriteFile(v2.CfgFile, []byte(fileContents), 0o644) - if err != nil { - log.Println(err) - } +func (c *Controller) getConfigMap(_ context.Context, name string) (*corev1.ConfigMap, error) { + return c.configMapInformer.Lister().ConfigMaps(c.tenant.Namespace).Get(name) } -func (c *Controller) regenCfgWithSecret(configSecret *corev1.Secret) { - // get the tenant - tenant, err := c.tenantInformer.Lister().Tenants(c.namespace).Get(c.tenantName) +func (c *Controller) regenCfg() { + // get the tenant secret + c.tenant.EnsureDefaults() + + // determine the configmaps and secrets to watch + configMaps, secrets, err := configuration.TenantResources(context.Background(), c.tenant, c.getConfigMap, c.getSecret) if err != nil { - log.Println("could not get secret", err) + log.Println(err) return } - tenant.EnsureDefaults() - fileContents, rootUserFound, rootPwdFound := configuration.GetFullTenantConfig(tenant, configSecret) + // update secrets and configmaps that should be watched + c.secrets = secrets + c.configMaps = configMaps + + // obtain the full tenant configuration + fileContents, rootUserFound, rootPwdFound := configuration.GetFullTenantConfig(c.tenant, c.configMaps, c.secrets) if !rootUserFound || !rootPwdFound { log.Println("Missing root credentials in the configuration.") @@ -250,7 +279,14 @@ func (c *Controller) regenCfgWithSecret(configSecret *corev1.Secret) { os.Exit(1) } - err = os.WriteFile(v2.CfgFile, []byte(fileContents), 0o644) + tmpFile := v2.CfgFile + ".tmp" + defer os.Remove(tmpFile) + + err = os.WriteFile(tmpFile, []byte(fileContents), 0o644) + if err != nil { + log.Println(err) + } + err = os.Rename(tmpFile, v2.CfgFile) if err != nil { log.Println(err) } @@ -263,8 +299,13 @@ func (c *Controller) Run(stopCh chan struct{}) error { go c.informerFactory.Start(stopCh) // wait for the initial synchronization of the local cache. - if !cache.WaitForCacheSync(stopCh, c.tenantInformer.Informer().HasSynced, c.secretInformer.Informer().HasSynced) { + if !cache.WaitForCacheSync(stopCh, c.tenantInformer.Informer().HasSynced, c.configMapInformer.Informer().HasSynced, c.secretInformer.Informer().HasSynced) { return fmt.Errorf("failed to sync") } + + c.lock.Lock() + defer c.lock.Unlock() + + c.regenCfg() return nil } diff --git a/sidecar/pkg/validator/validator.go b/sidecar/pkg/validator/validator.go index 94671a29f33..df8c7670c58 100644 --- a/sidecar/pkg/validator/validator.go +++ b/sidecar/pkg/validator/validator.go @@ -23,11 +23,12 @@ import ( "os" "strings" - "github.com/minio/operator/pkg/configuration" + "github.com/minio/operator/sidecar/pkg/configuration" "k8s.io/client-go/kubernetes" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" operatorClientset "github.com/minio/operator/pkg/client/clientset/versioned" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" "k8s.io/klog/v2" @@ -66,14 +67,19 @@ func Validate(tenantName string) { panic(err) } tenant.EnsureDefaults() - // get tenant config secret - configSecret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, tenant.Spec.Configuration.Name, metav1.GetOptions{}) + + // determine the configmaps and secrets to watch + configMaps, secrets, err := configuration.TenantResources(context.Background(), tenant, func(ctx context.Context, name string) (*corev1.ConfigMap, error) { + return kubeClient.CoreV1().ConfigMaps(tenant.Namespace).Get(ctx, name, metav1.GetOptions{}) + }, func(ctx context.Context, name string) (*corev1.Secret, error) { + return kubeClient.CoreV1().Secrets(tenant.Namespace).Get(ctx, name, metav1.GetOptions{}) + }) if err != nil { log.Println(err) panic(err) } - fileContents, rootUserFound, rootPwdFound := configuration.GetFullTenantConfig(tenant, configSecret) + fileContents, rootUserFound, rootPwdFound := configuration.GetFullTenantConfig(tenant, configMaps, secrets) if !rootUserFound || !rootPwdFound { log.Println("Missing root credentials in the configuration.") From 498d10e5a35c87d77fe9a76392b5fa01bc42fcb6 Mon Sep 17 00:00:00 2001 From: Ramon de Klein Date: Tue, 27 Aug 2024 13:05:46 +0200 Subject: [PATCH 3/6] remove unsupported fields --- docs/tenant_crd.adoc | 117 +++++++++++++++++- .../templates/minio.min.io_tenants.yaml | 30 ----- pkg/apis/minio.min.io/v2/helper.go | 2 +- pkg/apis/minio.min.io/v2/helper_test.go | 4 +- pkg/apis/minio.min.io/v2/types.go | 53 +++++++- .../minio.min.io/v2/zz_generated.deepcopy.go | 83 ++++++++++++- .../minio.min.io/v2/configmapkeyselector.go | 52 ++++++++ .../minio.min.io/v2/envvar.go | 57 +++++++++ .../minio.min.io/v2/envvarsource.go | 48 +++++++ .../minio.min.io/v2/secretkeyselector.go | 52 ++++++++ .../minio.min.io/v2/tenantspec.go | 9 +- pkg/client/applyconfiguration/utils.go | 8 ++ resources/base/crds/minio.min.io_tenants.yaml | 30 ----- .../pkg/configuration/tenant_configuration.go | 88 +++++++------ sidecar/pkg/sidecar/sidecar_utils.go | 10 +- sidecar/pkg/validator/validator.go | 10 +- 16 files changed, 531 insertions(+), 122 deletions(-) create mode 100644 pkg/client/applyconfiguration/minio.min.io/v2/configmapkeyselector.go create mode 100644 pkg/client/applyconfiguration/minio.min.io/v2/envvar.go create mode 100644 pkg/client/applyconfiguration/minio.min.io/v2/envvarsource.go create mode 100644 pkg/client/applyconfiguration/minio.min.io/v2/secretkeyselector.go diff --git a/docs/tenant_crd.adoc b/docs/tenant_crd.adoc index 7fd269b8bb6..7394ed25b84 100644 --- a/docs/tenant_crd.adoc +++ b/docs/tenant_crd.adoc @@ -108,6 +108,35 @@ CertificateStatus keeps track of all the certificates managed by the operator |=== +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-configmapkeyselector"] +==== ConfigMapKeySelector + +Selects a key from a ConfigMap. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-envvarsource[$$EnvVarSource$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`name`* __string__ +|Name of the referent. +This field is effectively required, but due to backwards compatibility is +allowed to be empty. Instances of this type with an empty value here are +almost certainly wrong. +TODO: Add other useful fields. apiVersion, kind, uid? +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + +|*`key`* __string__ +|The key to select. + +|=== + + [id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificateconfig"] ==== CustomCertificateConfig @@ -190,6 +219,63 @@ Certificate Authorities |=== +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-envvar"] +==== EnvVar + +EnvVar represents an environment variable present in a Container. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-tenantspec[$$TenantSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`name`* __string__ +|Name of the environment variable. Must be a C_IDENTIFIER. + +|*`value`* __string__ +|Variable references $(VAR_NAME) are expanded +using the previously defined environment variables in the container and +any service environment variables. If a variable cannot be resolved, +the reference in the input string will be unchanged. Double $$ are reduced +to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. +"$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". +Escaped references will never be expanded, regardless of whether the variable +exists or not. +Defaults to "". + +|*`valueFrom`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-envvarsource[$$EnvVarSource$$]__ +|Source for the environment variable's value. Cannot be used if value is not empty. + +|=== + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-envvarsource"] +==== EnvVarSource + +EnvVarSource represents a source for the value of an EnvVar. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-envvar[$$EnvVar$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`configMapKeyRef`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-configmapkeyselector[$$ConfigMapKeySelector$$]__ +|Selects a key of a ConfigMap. + +|*`secretKeyRef`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-secretkeyselector[$$SecretKeySelector$$]__ +|Selects a key of a secret in the pod's namespace + +|=== + + [id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-exposeservices"] ==== ExposeServices @@ -689,6 +775,35 @@ Security Context |=== +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-secretkeyselector"] +==== SecretKeySelector + +SecretKeySelector selects a key of a Secret. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-envvarsource[$$EnvVarSource$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`name`* __string__ +|Name of the referent. +This field is effectively required, but due to backwards compatibility is +allowed to be empty. Instances of this type with an empty value here are +almost certainly wrong. +TODO: Add other useful fields. apiVersion, kind, uid? +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. + +|*`key`* __string__ +|The key of the secret to select from. Must be a valid secret key. + +|=== + + [id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-servicemetadata"] ==== ServiceMetadata @@ -915,7 +1030,7 @@ Specify the secret key to use for pulling images from a private Docker repositor Pod Management Policy for pod created by StatefulSet -|*`env`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#envvar-v1-core[$$EnvVar$$] array__ +|*`env`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-envvar[$$EnvVar$$] array__ |*Optional* + diff --git a/helm/operator/templates/minio.min.io_tenants.yaml b/helm/operator/templates/minio.min.io_tenants.yaml index b55a9f736bd..1b13e257665 100644 --- a/helm/operator/templates/minio.min.io_tenants.yaml +++ b/helm/operator/templates/minio.min.io_tenants.yaml @@ -901,38 +901,10 @@ spec: name: default: "" type: string - optional: - type: boolean required: - key type: object x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic secretKeyRef: properties: key: @@ -940,8 +912,6 @@ spec: name: default: "" type: string - optional: - type: boolean required: - key type: object diff --git a/pkg/apis/minio.min.io/v2/helper.go b/pkg/apis/minio.min.io/v2/helper.go index e8579fad8c8..a4d55f34b06 100644 --- a/pkg/apis/minio.min.io/v2/helper.go +++ b/pkg/apis/minio.min.io/v2/helper.go @@ -523,7 +523,7 @@ func (t *Tenant) HasPrometheusOperatorEnabled() bool { } // GetEnvVars returns the environment variables for tenant deployment. -func (t *Tenant) GetEnvVars() (env []corev1.EnvVar) { +func (t *Tenant) GetEnvVars() (env []EnvVar) { return t.Spec.Env } diff --git a/pkg/apis/minio.min.io/v2/helper_test.go b/pkg/apis/minio.min.io/v2/helper_test.go index d8bc4cccda0..18d22fb720a 100644 --- a/pkg/apis/minio.min.io/v2/helper_test.go +++ b/pkg/apis/minio.min.io/v2/helper_test.go @@ -395,7 +395,7 @@ func TestTenant_HasEnv(t1 *testing.T) { name: "Contains env", fields: fields{ Spec: TenantSpec{ - Env: []corev1.EnvVar{ + Env: []EnvVar{ { Name: "ENV1", Value: "whatever", @@ -412,7 +412,7 @@ func TestTenant_HasEnv(t1 *testing.T) { name: "Does not Contains env", fields: fields{ Spec: TenantSpec{ - Env: []corev1.EnvVar{ + Env: []EnvVar{ { Name: "ENV1", Value: "whatever", diff --git a/pkg/apis/minio.min.io/v2/types.go b/pkg/apis/minio.min.io/v2/types.go index fd03c608b88..eb0ed685ef4 100644 --- a/pkg/apis/minio.min.io/v2/types.go +++ b/pkg/apis/minio.min.io/v2/types.go @@ -128,7 +128,7 @@ type TenantSpec struct { // // If provided, the MinIO Operator adds the specified environment variables when deploying the Tenant resource. // +optional - Env []corev1.EnvVar `json:"env,omitempty"` + Env []EnvVar `json:"env,omitempty"` // *Optional* + // @@ -910,3 +910,54 @@ type SideCars struct { // +optional Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } + +// EnvVar represents an environment variable present in a Container. +type EnvVar struct { + // Name of the environment variable. Must be a C_IDENTIFIER. + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + + // Optional: no more than one of the following may be specified. + + // Variable references $(VAR_NAME) are expanded + // using the previously defined environment variables in the container and + // any service environment variables. If a variable cannot be resolved, + // the reference in the input string will be unchanged. Double $$ are reduced + // to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + // "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + // Escaped references will never be expanded, regardless of whether the variable + // exists or not. + // Defaults to "". + // +optional + Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"` + // Source for the environment variable's value. Cannot be used if value is not empty. + // +optional + ValueFrom *EnvVarSource `json:"valueFrom,omitempty" protobuf:"bytes,3,opt,name=valueFrom"` +} + +// EnvVarSource represents a source for the value of an EnvVar. +type EnvVarSource struct { + // Selects a key of a ConfigMap. + // +optional + ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty" protobuf:"bytes,3,opt,name=configMapKeyRef"` + // Selects a key of a secret in the pod's namespace + // +optional + SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty" protobuf:"bytes,4,opt,name=secretKeyRef"` +} + +// Selects a key from a ConfigMap. +// +structType=atomic +type ConfigMapKeySelector struct { + // The ConfigMap to select from. + corev1.LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` + // The key to select. + Key string `json:"key" protobuf:"bytes,2,opt,name=key"` +} + +// SecretKeySelector selects a key of a Secret. +// +structType=atomic +type SecretKeySelector struct { + // The name of the secret in the pod's namespace to select from. + corev1.LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key" protobuf:"bytes,2,opt,name=key"` +} diff --git a/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go b/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go index f126817ac5a..a08e022b0d2 100644 --- a/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go +++ b/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go @@ -115,6 +115,23 @@ func (in *CertificateStatus) DeepCopy() *CertificateStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapKeySelector) DeepCopyInto(out *ConfigMapKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapKeySelector. +func (in *ConfigMapKeySelector) DeepCopy() *ConfigMapKeySelector { + if in == nil { + return nil + } + out := new(ConfigMapKeySelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomCertificateConfig) DeepCopyInto(out *CustomCertificateConfig) { *out = *in @@ -185,6 +202,53 @@ func (in *CustomCertificates) DeepCopy() *CustomCertificates { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvVar) DeepCopyInto(out *EnvVar) { + *out = *in + if in.ValueFrom != nil { + in, out := &in.ValueFrom, &out.ValueFrom + *out = new(EnvVarSource) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVar. +func (in *EnvVar) DeepCopy() *EnvVar { + if in == nil { + return nil + } + out := new(EnvVar) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvVarSource) DeepCopyInto(out *EnvVarSource) { + *out = *in + if in.ConfigMapKeyRef != nil { + in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef + *out = new(ConfigMapKeySelector) + **out = **in + } + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(SecretKeySelector) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvVarSource. +func (in *EnvVarSource) DeepCopy() *EnvVarSource { + if in == nil { + return nil + } + out := new(EnvVarSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExposeServices) DeepCopyInto(out *ExposeServices) { *out = *in @@ -441,6 +505,23 @@ func (in *PoolStatus) DeepCopy() *PoolStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. +func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { + if in == nil { + return nil + } + out := new(SecretKeySelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceMetadata) DeepCopyInto(out *ServiceMetadata) { *out = *in @@ -639,7 +720,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { out.ImagePullSecret = in.ImagePullSecret if in.Env != nil { in, out := &in.Env, &out.Env - *out = make([]v1.EnvVar, len(*in)) + *out = make([]EnvVar, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/client/applyconfiguration/minio.min.io/v2/configmapkeyselector.go b/pkg/client/applyconfiguration/minio.min.io/v2/configmapkeyselector.go new file mode 100644 index 00000000000..be88c8cf400 --- /dev/null +++ b/pkg/client/applyconfiguration/minio.min.io/v2/configmapkeyselector.go @@ -0,0 +1,52 @@ +// This file is part of MinIO Operator +// Copyright (c) 2024 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v2 + +import ( + v1 "k8s.io/api/core/v1" +) + +// ConfigMapKeySelectorApplyConfiguration represents an declarative configuration of the ConfigMapKeySelector type for use +// with apply. +type ConfigMapKeySelectorApplyConfiguration struct { + v1.LocalObjectReference `json:",inline"` + Key *string `json:"key,omitempty"` +} + +// ConfigMapKeySelectorApplyConfiguration constructs an declarative configuration of the ConfigMapKeySelector type for use with +// apply. +func ConfigMapKeySelector() *ConfigMapKeySelectorApplyConfiguration { + return &ConfigMapKeySelectorApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ConfigMapKeySelectorApplyConfiguration) WithName(value string) *ConfigMapKeySelectorApplyConfiguration { + b.Name = value + return b +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *ConfigMapKeySelectorApplyConfiguration) WithKey(value string) *ConfigMapKeySelectorApplyConfiguration { + b.Key = &value + return b +} diff --git a/pkg/client/applyconfiguration/minio.min.io/v2/envvar.go b/pkg/client/applyconfiguration/minio.min.io/v2/envvar.go new file mode 100644 index 00000000000..7d2bd6d5de7 --- /dev/null +++ b/pkg/client/applyconfiguration/minio.min.io/v2/envvar.go @@ -0,0 +1,57 @@ +// This file is part of MinIO Operator +// Copyright (c) 2024 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v2 + +// EnvVarApplyConfiguration represents an declarative configuration of the EnvVar type for use +// with apply. +type EnvVarApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Value *string `json:"value,omitempty"` + ValueFrom *EnvVarSourceApplyConfiguration `json:"valueFrom,omitempty"` +} + +// EnvVarApplyConfiguration constructs an declarative configuration of the EnvVar type for use with +// apply. +func EnvVar() *EnvVarApplyConfiguration { + return &EnvVarApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *EnvVarApplyConfiguration) WithName(value string) *EnvVarApplyConfiguration { + b.Name = &value + return b +} + +// WithValue sets the Value field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Value field is set to the value of the last call. +func (b *EnvVarApplyConfiguration) WithValue(value string) *EnvVarApplyConfiguration { + b.Value = &value + return b +} + +// WithValueFrom sets the ValueFrom field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ValueFrom field is set to the value of the last call. +func (b *EnvVarApplyConfiguration) WithValueFrom(value *EnvVarSourceApplyConfiguration) *EnvVarApplyConfiguration { + b.ValueFrom = value + return b +} diff --git a/pkg/client/applyconfiguration/minio.min.io/v2/envvarsource.go b/pkg/client/applyconfiguration/minio.min.io/v2/envvarsource.go new file mode 100644 index 00000000000..184a1ba5809 --- /dev/null +++ b/pkg/client/applyconfiguration/minio.min.io/v2/envvarsource.go @@ -0,0 +1,48 @@ +// This file is part of MinIO Operator +// Copyright (c) 2024 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v2 + +// EnvVarSourceApplyConfiguration represents an declarative configuration of the EnvVarSource type for use +// with apply. +type EnvVarSourceApplyConfiguration struct { + ConfigMapKeyRef *ConfigMapKeySelectorApplyConfiguration `json:"configMapKeyRef,omitempty"` + SecretKeyRef *SecretKeySelectorApplyConfiguration `json:"secretKeyRef,omitempty"` +} + +// EnvVarSourceApplyConfiguration constructs an declarative configuration of the EnvVarSource type for use with +// apply. +func EnvVarSource() *EnvVarSourceApplyConfiguration { + return &EnvVarSourceApplyConfiguration{} +} + +// WithConfigMapKeyRef sets the ConfigMapKeyRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConfigMapKeyRef field is set to the value of the last call. +func (b *EnvVarSourceApplyConfiguration) WithConfigMapKeyRef(value *ConfigMapKeySelectorApplyConfiguration) *EnvVarSourceApplyConfiguration { + b.ConfigMapKeyRef = value + return b +} + +// WithSecretKeyRef sets the SecretKeyRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretKeyRef field is set to the value of the last call. +func (b *EnvVarSourceApplyConfiguration) WithSecretKeyRef(value *SecretKeySelectorApplyConfiguration) *EnvVarSourceApplyConfiguration { + b.SecretKeyRef = value + return b +} diff --git a/pkg/client/applyconfiguration/minio.min.io/v2/secretkeyselector.go b/pkg/client/applyconfiguration/minio.min.io/v2/secretkeyselector.go new file mode 100644 index 00000000000..5b771262942 --- /dev/null +++ b/pkg/client/applyconfiguration/minio.min.io/v2/secretkeyselector.go @@ -0,0 +1,52 @@ +// This file is part of MinIO Operator +// Copyright (c) 2024 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v2 + +import ( + v1 "k8s.io/api/core/v1" +) + +// SecretKeySelectorApplyConfiguration represents an declarative configuration of the SecretKeySelector type for use +// with apply. +type SecretKeySelectorApplyConfiguration struct { + v1.LocalObjectReference `json:",inline"` + Key *string `json:"key,omitempty"` +} + +// SecretKeySelectorApplyConfiguration constructs an declarative configuration of the SecretKeySelector type for use with +// apply. +func SecretKeySelector() *SecretKeySelectorApplyConfiguration { + return &SecretKeySelectorApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SecretKeySelectorApplyConfiguration) WithName(value string) *SecretKeySelectorApplyConfiguration { + b.Name = value + return b +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *SecretKeySelectorApplyConfiguration) WithKey(value string) *SecretKeySelectorApplyConfiguration { + b.Key = &value + return b +} diff --git a/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go b/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go index abc30b5c57d..45899e76785 100644 --- a/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go +++ b/pkg/client/applyconfiguration/minio.min.io/v2/tenantspec.go @@ -31,7 +31,7 @@ type TenantSpecApplyConfiguration struct { Image *string `json:"image,omitempty"` ImagePullSecret *v1.LocalObjectReference `json:"imagePullSecret,omitempty"` PodManagementPolicy *appsv1.PodManagementPolicyType `json:"podManagementPolicy,omitempty"` - Env []v1.EnvVar `json:"env,omitempty"` + Env []EnvVarApplyConfiguration `json:"env,omitempty"` ExternalCertSecret []*miniominiov2.LocalCertificateReference `json:"externalCertSecret,omitempty"` ExternalCaCertSecret []*miniominiov2.LocalCertificateReference `json:"externalCaCertSecret,omitempty"` ExternalClientCertSecret *LocalCertificateReferenceApplyConfiguration `json:"externalClientCertSecret,omitempty"` @@ -109,9 +109,12 @@ func (b *TenantSpecApplyConfiguration) WithPodManagementPolicy(value appsv1.PodM // WithEnv adds the given value to the Env field in the declarative configuration // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, values provided by each call will be appended to the Env field. -func (b *TenantSpecApplyConfiguration) WithEnv(values ...v1.EnvVar) *TenantSpecApplyConfiguration { +func (b *TenantSpecApplyConfiguration) WithEnv(values ...*EnvVarApplyConfiguration) *TenantSpecApplyConfiguration { for i := range values { - b.Env = append(b.Env, values[i]) + if values[i] == nil { + panic("nil value passed to WithEnv") + } + b.Env = append(b.Env, *values[i]) } return b } diff --git a/pkg/client/applyconfiguration/utils.go b/pkg/client/applyconfiguration/utils.go index 1dd2f545ea7..5d4a278c1b1 100644 --- a/pkg/client/applyconfiguration/utils.go +++ b/pkg/client/applyconfiguration/utils.go @@ -55,10 +55,16 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &miniominiov2.CertificateConfigApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("CertificateStatus"): return &miniominiov2.CertificateStatusApplyConfiguration{} + case v2.SchemeGroupVersion.WithKind("ConfigMapKeySelector"): + return &miniominiov2.ConfigMapKeySelectorApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("CustomCertificateConfig"): return &miniominiov2.CustomCertificateConfigApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("CustomCertificates"): return &miniominiov2.CustomCertificatesApplyConfiguration{} + case v2.SchemeGroupVersion.WithKind("EnvVar"): + return &miniominiov2.EnvVarApplyConfiguration{} + case v2.SchemeGroupVersion.WithKind("EnvVarSource"): + return &miniominiov2.EnvVarSourceApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("ExposeServices"): return &miniominiov2.ExposeServicesApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("Features"): @@ -73,6 +79,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &miniominiov2.PoolApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("PoolStatus"): return &miniominiov2.PoolStatusApplyConfiguration{} + case v2.SchemeGroupVersion.WithKind("SecretKeySelector"): + return &miniominiov2.SecretKeySelectorApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("ServiceMetadata"): return &miniominiov2.ServiceMetadataApplyConfiguration{} case v2.SchemeGroupVersion.WithKind("SideCars"): diff --git a/resources/base/crds/minio.min.io_tenants.yaml b/resources/base/crds/minio.min.io_tenants.yaml index b55a9f736bd..1b13e257665 100644 --- a/resources/base/crds/minio.min.io_tenants.yaml +++ b/resources/base/crds/minio.min.io_tenants.yaml @@ -901,38 +901,10 @@ spec: name: default: "" type: string - optional: - type: boolean required: - key type: object x-kubernetes-map-type: atomic - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - required: - - fieldPath - type: object - x-kubernetes-map-type: atomic - resourceFieldRef: - properties: - containerName: - type: string - divisor: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - resource: - type: string - required: - - resource - type: object - x-kubernetes-map-type: atomic secretKeyRef: properties: key: @@ -940,8 +912,6 @@ spec: name: default: "" type: string - optional: - type: boolean required: - key type: object diff --git a/sidecar/pkg/configuration/tenant_configuration.go b/sidecar/pkg/configuration/tenant_configuration.go index c00c24aad0e..60584b66d82 100644 --- a/sidecar/pkg/configuration/tenant_configuration.go +++ b/sidecar/pkg/configuration/tenant_configuration.go @@ -18,7 +18,6 @@ package configuration import ( "context" - "errors" "fmt" "log" "sort" @@ -36,8 +35,8 @@ const ( ) type ( - secretFunc func(ctx context.Context, name string) (*corev1.Secret, error) - configFunc func(ctx context.Context, name string) (*corev1.ConfigMap, error) + secretFunc func(ctx context.Context, name string) *corev1.Secret + configFunc func(ctx context.Context, name string) *corev1.ConfigMap ) // TenantResources returns maps for all configmap/secret resources that @@ -49,31 +48,17 @@ func TenantResources(ctx context.Context, tenant *miniov2.Tenant, cf configFunc, for _, env := range tenant.Spec.Env { if env.ValueFrom != nil { if env.ValueFrom.SecretKeyRef != nil { - secret, err := sf(ctx, env.ValueFrom.SecretKeyRef.Name) - if err != nil { - return nil, nil, err - } - secrets[env.ValueFrom.SecretKeyRef.Name] = secret + secrets[env.ValueFrom.SecretKeyRef.Name] = sf(ctx, env.ValueFrom.SecretKeyRef.Name) } if env.ValueFrom.ConfigMapKeyRef != nil { - configmap, err := cf(ctx, env.ValueFrom.ConfigMapKeyRef.Name) - if err != nil { - return nil, nil, err - } - configMaps[env.ValueFrom.ConfigMapKeyRef.Name] = configmap - } - if env.ValueFrom.FieldRef != nil { - return nil, nil, errors.New("mapping fields is not supported") - } - if env.ValueFrom.ResourceFieldRef != nil { - return nil, nil, errors.New("mapping resource fields is not supported") + configMaps[env.ValueFrom.ConfigMapKeyRef.Name] = cf(ctx, env.ValueFrom.ConfigMapKeyRef.Name) } } } - secret, err := sf(ctx, tenant.Spec.Configuration.Name) - if err != nil { - return nil, nil, err + secret := sf(ctx, tenant.Spec.Configuration.Name) + if secret == nil { + return nil, nil, fmt.Errorf("cannot find configurations secret %s", tenant.Spec.Configuration.Name) } secrets[tenant.Spec.Configuration.Name] = secret @@ -101,12 +86,12 @@ func GetFullTenantConfig(tenant *miniov2.Tenant, configMaps map[string]*corev1.C return configurationFileContent, rootUserFound, rootPwdFound } -func parseConfEnvSecret(secret *corev1.Secret) map[string]corev1.EnvVar { +func parseConfEnvSecret(secret *corev1.Secret) map[string]miniov2.EnvVar { if secret == nil { return nil } data := secret.Data["config.env"] - envMap := make(map[string]corev1.EnvVar) + envMap := make(map[string]miniov2.EnvVar) lines := strings.Split(string(data), "\n") for _, line := range lines { @@ -121,7 +106,7 @@ func parseConfEnvSecret(secret *corev1.Secret) map[string]corev1.EnvVar { log.Printf("Syntax error for variable %s (skipped): %s", name, err) continue } - envMap[name] = corev1.EnvVar{ + envMap[name] = miniov2.EnvVar{ Name: name, Value: value, } @@ -131,11 +116,11 @@ func parseConfEnvSecret(secret *corev1.Secret) map[string]corev1.EnvVar { return envMap } -func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.EnvVar) []corev1.EnvVar { +func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]miniov2.EnvVar) []miniov2.EnvVar { // Enable `mc admin update` style updates to MinIO binaries // within the container, only operator is supposed to perform // these operations. - envVarsMap := map[string]corev1.EnvVar{ + envVarsMap := map[string]miniov2.EnvVar{ "MINIO_UPDATE": { Name: "MINIO_UPDATE", Value: "on", @@ -153,7 +138,7 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En for _, pool := range tenant.Spec.Pools { if pool.RuntimeClassName != nil && *pool.RuntimeClassName == "crun" { // Set HOME to / - envVarsMap["HOME"] = corev1.EnvVar{ + envVarsMap["HOME"] = miniov2.EnvVar{ Name: "HOME", Value: "/", } @@ -168,7 +153,7 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En common.WebhookAPIBucketService, tenant.Namespace, tenant.Name) - envVarsMap[bucketDNSEnv] = corev1.EnvVar{ + envVarsMap[bucketDNSEnv] = miniov2.EnvVar{ Name: bucketDNSEnv, Value: sidecarBucketURL, } @@ -179,7 +164,7 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En } // tell MinIO about all the domains meant to hit it if they are not passed manually via .spec.env if len(domains) > 0 { - envVarsMap[miniov2.MinIODomain] = corev1.EnvVar{ + envVarsMap[miniov2.MinIODomain] = miniov2.EnvVar{ Name: miniov2.MinIODomain, Value: strings.Join(domains, ","), } @@ -200,7 +185,7 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En serverURL = tenant.Spec.Features.Domains.Minio[0] } } - envVarsMap[miniov2.MinIOServerURL] = corev1.EnvVar{ + envVarsMap[miniov2.MinIOServerURL] = miniov2.EnvVar{ Name: miniov2.MinIOServerURL, Value: serverURL, } @@ -216,33 +201,33 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En } consoleDomain = fmt.Sprintf("%s://%s", useSchema, consoleDomain) } - envVarsMap[miniov2.MinIOBrowserRedirectURL] = corev1.EnvVar{ + envVarsMap[miniov2.MinIOBrowserRedirectURL] = miniov2.EnvVar{ Name: miniov2.MinIOBrowserRedirectURL, Value: consoleDomain, } } if tenant.HasKESEnabled() { - envVarsMap["MINIO_KMS_KES_ENDPOINT"] = corev1.EnvVar{ + envVarsMap["MINIO_KMS_KES_ENDPOINT"] = miniov2.EnvVar{ Name: "MINIO_KMS_KES_ENDPOINT", Value: tenant.KESServiceEndpoint(), } - envVarsMap["MINIO_KMS_KES_CERT_FILE"] = corev1.EnvVar{ + envVarsMap["MINIO_KMS_KES_CERT_FILE"] = miniov2.EnvVar{ Name: "MINIO_KMS_KES_CERT_FILE", Value: miniov2.MinIOCertPath + "/client.crt", } - envVarsMap["MINIO_KMS_KES_KEY_FILE"] = corev1.EnvVar{ + envVarsMap["MINIO_KMS_KES_KEY_FILE"] = miniov2.EnvVar{ Name: "MINIO_KMS_KES_KEY_FILE", Value: miniov2.MinIOCertPath + "/client.key", } - envVarsMap["MINIO_KMS_KES_CA_PATH"] = corev1.EnvVar{ + envVarsMap["MINIO_KMS_KES_CA_PATH"] = miniov2.EnvVar{ Name: "MINIO_KMS_KES_CA_PATH", Value: miniov2.MinIOCertPath + "/CAs/kes.crt", } - envVarsMap["MINIO_KMS_KES_CAPATH"] = corev1.EnvVar{ + envVarsMap["MINIO_KMS_KES_CAPATH"] = miniov2.EnvVar{ Name: "MINIO_KMS_KES_CAPATH", Value: miniov2.MinIOCertPath + "/CAs/kes.crt", } - envVarsMap["MINIO_KMS_KES_KEY_NAME"] = corev1.EnvVar{ + envVarsMap["MINIO_KMS_KES_KEY_NAME"] = miniov2.EnvVar{ Name: "MINIO_KMS_KES_KEY_NAME", Value: tenant.Spec.KES.KeyName, } @@ -250,7 +235,7 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En // attach tenant args args := strings.Join(statefulsets.GetContainerArgs(tenant, ""), " ") - envVarsMap["MINIO_ARGS"] = corev1.EnvVar{ + envVarsMap["MINIO_ARGS"] = miniov2.EnvVar{ Name: "MINIO_ARGS", Value: args, } @@ -260,7 +245,7 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En for _, env := range tenant.GetEnvVars() { envVarsMap[env.Name] = env } - var envVars []corev1.EnvVar + var envVars []miniov2.EnvVar // transform map to array and skip configurations from config.env for _, env := range envVarsMap { if cfgEnvExisting != nil { @@ -283,19 +268,32 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En return envVars } -func envVarsToFileContent(envVars []corev1.EnvVar, configMaps map[string]*corev1.ConfigMap, secrets map[string]*corev1.Secret) string { +func envVarsToFileContent(envVars []miniov2.EnvVar, configMaps map[string]*corev1.ConfigMap, secrets map[string]*corev1.Secret) string { var sb strings.Builder for _, env := range envVars { - value := env.Value + var value *string if env.ValueFrom != nil { if env.ValueFrom.ConfigMapKeyRef != nil { - value = configMaps[env.ValueFrom.ConfigMapKeyRef.Name].Data[env.ValueFrom.ConfigMapKeyRef.Key] + if configMap, ok := configMaps[env.ValueFrom.ConfigMapKeyRef.Name]; ok { + if configMapValue, ok := configMap.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok { + value = &configMapValue + } + } } if env.ValueFrom.SecretKeyRef != nil { - value = string(secrets[env.ValueFrom.SecretKeyRef.Name].Data[env.ValueFrom.SecretKeyRef.Key]) + if secret, ok := secrets[env.ValueFrom.SecretKeyRef.Name]; ok { + if secretValue, ok := secret.Data[env.ValueFrom.SecretKeyRef.Key]; ok { + textValue := string(secretValue) + value = &textValue + } + } } + } else { + value = &env.Value + } + if value != nil { + sb.WriteString(fmt.Sprintf("export %s=\"%s\"\n", env.Name, *value)) } - sb.WriteString(fmt.Sprintf("export %s=\"%s\"\n", env.Name, value)) } return sb.String() } diff --git a/sidecar/pkg/sidecar/sidecar_utils.go b/sidecar/pkg/sidecar/sidecar_utils.go index bf37cccc88a..8b80c924a62 100644 --- a/sidecar/pkg/sidecar/sidecar_utils.go +++ b/sidecar/pkg/sidecar/sidecar_utils.go @@ -247,12 +247,14 @@ func NewSideCarController(kubeClient *kubernetes.Clientset, controllerClient *cl return c } -func (c *Controller) getSecret(_ context.Context, name string) (*corev1.Secret, error) { - return c.secretInformer.Lister().Secrets(c.tenant.Namespace).Get(name) +func (c *Controller) getSecret(_ context.Context, name string) *corev1.Secret { + retValue, _ := c.secretInformer.Lister().Secrets(c.tenant.Namespace).Get(name) + return retValue } -func (c *Controller) getConfigMap(_ context.Context, name string) (*corev1.ConfigMap, error) { - return c.configMapInformer.Lister().ConfigMaps(c.tenant.Namespace).Get(name) +func (c *Controller) getConfigMap(_ context.Context, name string) *corev1.ConfigMap { + retValue, _ := c.configMapInformer.Lister().ConfigMaps(c.tenant.Namespace).Get(name) + return retValue } func (c *Controller) regenCfg() { diff --git a/sidecar/pkg/validator/validator.go b/sidecar/pkg/validator/validator.go index df8c7670c58..ea1e087cac1 100644 --- a/sidecar/pkg/validator/validator.go +++ b/sidecar/pkg/validator/validator.go @@ -69,10 +69,12 @@ func Validate(tenantName string) { tenant.EnsureDefaults() // determine the configmaps and secrets to watch - configMaps, secrets, err := configuration.TenantResources(context.Background(), tenant, func(ctx context.Context, name string) (*corev1.ConfigMap, error) { - return kubeClient.CoreV1().ConfigMaps(tenant.Namespace).Get(ctx, name, metav1.GetOptions{}) - }, func(ctx context.Context, name string) (*corev1.Secret, error) { - return kubeClient.CoreV1().Secrets(tenant.Namespace).Get(ctx, name, metav1.GetOptions{}) + configMaps, secrets, err := configuration.TenantResources(context.Background(), tenant, func(ctx context.Context, name string) *corev1.ConfigMap { + retValue, _ := kubeClient.CoreV1().ConfigMaps(tenant.Namespace).Get(ctx, name, metav1.GetOptions{}) + return retValue + }, func(ctx context.Context, name string) *corev1.Secret { + retValue, _ := kubeClient.CoreV1().Secrets(tenant.Namespace).Get(ctx, name, metav1.GetOptions{}) + return retValue }) if err != nil { log.Println(err) From dcb4c57b6fd4835a5c1c471c5ad1d2fec82c84cb Mon Sep 17 00:00:00 2001 From: Ramon de Klein Date: Tue, 27 Aug 2024 13:30:31 +0200 Subject: [PATCH 4/6] update and add unit tests --- pkg/apis/minio.min.io/v2/types.go | 2 +- .../tenant_configuration_test.go | 159 +++++++++++++++--- 2 files changed, 133 insertions(+), 28 deletions(-) diff --git a/pkg/apis/minio.min.io/v2/types.go b/pkg/apis/minio.min.io/v2/types.go index eb0ed685ef4..78f8be38107 100644 --- a/pkg/apis/minio.min.io/v2/types.go +++ b/pkg/apis/minio.min.io/v2/types.go @@ -944,7 +944,7 @@ type EnvVarSource struct { SecretKeyRef *SecretKeySelector `json:"secretKeyRef,omitempty" protobuf:"bytes,4,opt,name=secretKeyRef"` } -// Selects a key from a ConfigMap. +// ConfigMapKeySelector selects a key from a ConfigMap. // +structType=atomic type ConfigMapKeySelector struct { // The ConfigMap to select from. diff --git a/sidecar/pkg/configuration/tenant_configuration_test.go b/sidecar/pkg/configuration/tenant_configuration_test.go index 042351730a5..daa6afd1a9b 100644 --- a/sidecar/pkg/configuration/tenant_configuration_test.go +++ b/sidecar/pkg/configuration/tenant_configuration_test.go @@ -25,9 +25,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const configSecretName = "config-secret" + func TestEnvVarsToFileContent(t *testing.T) { type args struct { - envVars []corev1.EnvVar + envVars []miniov2.EnvVar } tests := []struct { name string @@ -37,7 +39,7 @@ func TestEnvVarsToFileContent(t *testing.T) { { name: "Basic test case", args: args{ - envVars: []corev1.EnvVar{ + envVars: []miniov2.EnvVar{ { Name: "MINIO_UPDATE", Value: "on", @@ -49,7 +51,7 @@ func TestEnvVarsToFileContent(t *testing.T) { { name: "Two Vars test case", args: args{ - envVars: []corev1.EnvVar{ + envVars: []miniov2.EnvVar{ { Name: "MINIO_UPDATE", Value: "on", @@ -77,12 +79,12 @@ export MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRy func TestGetTenantConfiguration(t *testing.T) { type args struct { tenant *miniov2.Tenant - cfgEnvExisting map[string]corev1.EnvVar + cfgEnvExisting map[string]miniov2.EnvVar } tests := []struct { name string args args - want []corev1.EnvVar + want []miniov2.EnvVar }{ { name: "Defaulted Values", @@ -90,7 +92,7 @@ func TestGetTenantConfiguration(t *testing.T) { tenant: &miniov2.Tenant{}, cfgEnvExisting: nil, }, - want: []corev1.EnvVar{ + want: []miniov2.EnvVar{ { Name: "MINIO_ARGS", Value: "", @@ -118,7 +120,7 @@ func TestGetTenantConfiguration(t *testing.T) { args: args{ tenant: &miniov2.Tenant{ Spec: miniov2.TenantSpec{ - Env: []corev1.EnvVar{ + Env: []miniov2.EnvVar{ { Name: "TEST", Value: "value", @@ -128,7 +130,7 @@ func TestGetTenantConfiguration(t *testing.T) { }, cfgEnvExisting: nil, }, - want: []corev1.EnvVar{ + want: []miniov2.EnvVar{ { Name: "MINIO_ARGS", Value: "", @@ -173,7 +175,7 @@ func TestParseConfEnvSecret(t *testing.T) { tests := []struct { name string args args - want map[string]corev1.EnvVar + want map[string]miniov2.EnvVar }{ { name: "Basic case", @@ -185,7 +187,7 @@ export MINIO_STORAGE_CLASS_STANDARD="EC:2" export MINIO_BROWSER="on"`)}, }, }, - want: map[string]corev1.EnvVar{ + want: map[string]miniov2.EnvVar{ "MINIO_ROOT_USER": { Name: "MINIO_ROOT_USER", Value: "minio", @@ -214,7 +216,7 @@ export MINIO_BROWSER="on"`)}, export MINIO_BROWSER="on"`)}, }, }, - want: map[string]corev1.EnvVar{ + want: map[string]miniov2.EnvVar{ "MINIO_ROOT_USER": { Name: "MINIO_ROOT_USER", Value: "minio", @@ -245,8 +247,9 @@ export MINIO_BROWSER="on"`)}, func TestGetFullTenantConfig(t *testing.T) { type args struct { - tenant *miniov2.Tenant - configSecret *corev1.Secret + tenant *miniov2.Tenant + secrets map[string]*corev1.Secret + configMaps map[string]*corev1.ConfigMap } tests := []struct { name string @@ -258,7 +261,10 @@ func TestGetFullTenantConfig(t *testing.T) { args: args{ tenant: &miniov2.Tenant{ Spec: miniov2.TenantSpec{ - Env: []corev1.EnvVar{ + Configuration: &corev1.LocalObjectReference{ + Name: configSecretName, + }, + Env: []miniov2.EnvVar{ { Name: "TEST", Value: "value", @@ -266,11 +272,13 @@ func TestGetFullTenantConfig(t *testing.T) { }, }, }, - configSecret: &corev1.Secret{ - Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" + secrets: map[string]*corev1.Secret{ + configSecretName: { + Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" export MINIO_ROOT_PASSWORD="minio123" export MINIO_STORAGE_CLASS_STANDARD="EC:2" export MINIO_BROWSER="on"`)}, + }, }, }, want: `export MINIO_ARGS="" @@ -290,7 +298,10 @@ export TEST="value" args: args{ tenant: &miniov2.Tenant{ Spec: miniov2.TenantSpec{ - Env: []corev1.EnvVar{ + Configuration: &corev1.LocalObjectReference{ + Name: configSecretName, + }, + Env: []miniov2.EnvVar{ { Name: "TEST", Value: "value", @@ -303,11 +314,13 @@ export TEST="value" }, }, }, - configSecret: &corev1.Secret{ - Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" + secrets: map[string]*corev1.Secret{ + configSecretName: { + Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" export MINIO_ROOT_PASSWORD="minio123" export MINIO_STORAGE_CLASS_STANDARD="EC:2" export MINIO_BROWSER="on"`)}, + }, }, }, want: `export MINIO_ARGS="" @@ -332,7 +345,10 @@ export TEST="value" Namespace: "ns-x", }, Spec: miniov2.TenantSpec{ - Env: []corev1.EnvVar{ + Configuration: &corev1.LocalObjectReference{ + Name: configSecretName, + }, + Env: []miniov2.EnvVar{ { Name: "TEST", Value: "value", @@ -353,11 +369,102 @@ export TEST="value" }, }, }, - configSecret: &corev1.Secret{ - Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" + secrets: map[string]*corev1.Secret{ + configSecretName: { + Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" +export MINIO_ROOT_PASSWORD="minio123" +export MINIO_STORAGE_CLASS_STANDARD="EC:2" +export MINIO_BROWSER="on"`)}, + }, + }, + }, + want: `export MINIO_ARGS="https://tenant-pool-0-{0...3}.tenant-hl.ns-x.svc.cluster.local/export{0...3}" +export MINIO_BROWSER="on" +export MINIO_BROWSER_REDIRECT_URL="http://console.minio" +export MINIO_PROMETHEUS_JOB_ID="minio-job" +export MINIO_ROOT_PASSWORD="minio123" +export MINIO_ROOT_USER="minio" +export MINIO_SERVER_URL="https://minio.ns-x.svc.cluster.local:443" +export MINIO_STORAGE_CLASS_STANDARD="EC:2" +export MINIO_UPDATE="on" +export MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" +export TEST="value" +`, + }, + { + name: "Default with both a config-map and secret reference", + args: args{ + tenant: &miniov2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tenant", + Namespace: "ns-x", + }, + Spec: miniov2.TenantSpec{ + Configuration: &corev1.LocalObjectReference{ + Name: configSecretName, + }, + Env: []miniov2.EnvVar{ + { + Name: "TEST", + Value: "value", + }, + { + Name: "TEST_CONFIGMAP", + ValueFrom: &miniov2.EnvVarSource{ + ConfigMapKeyRef: &miniov2.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-configmap", + }, + Key: "test-configmap-key", + }, + }, + }, + { + Name: "TEST_SECRET", + ValueFrom: &miniov2.EnvVarSource{ + SecretKeyRef: &miniov2.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-secret", + }, + Key: "test-secret-key", + }, + }, + }, + }, + Features: &miniov2.Features{ + Domains: &miniov2.TenantDomains{ + Console: "http://console.minio", + }, + }, + Pools: []miniov2.Pool{ + { + Name: "pool-0", + Servers: 4, + VolumesPerServer: 4, + VolumeClaimTemplate: nil, + }, + }, + }, + }, + secrets: map[string]*corev1.Secret{ + configSecretName: { + Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" export MINIO_ROOT_PASSWORD="minio123" export MINIO_STORAGE_CLASS_STANDARD="EC:2" export MINIO_BROWSER="on"`)}, + }, + "test-secret": { + Data: map[string][]byte{ + "test-secret-key": []byte("test-secret-value"), + }, + }, + }, + configMaps: map[string]*corev1.ConfigMap{ + "test-configmap": { + Data: map[string]string{ + "test-configmap-key": "test-configmap-value", + }, + }, }, }, want: `export MINIO_ARGS="https://tenant-pool-0-{0...3}.tenant-hl.ns-x.svc.cluster.local/export{0...3}" @@ -371,6 +478,8 @@ export MINIO_STORAGE_CLASS_STANDARD="EC:2" export MINIO_UPDATE="on" export MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" export TEST="value" +export TEST_CONFIGMAP="test-configmap-value" +export TEST_SECRET="test-secret-value" `, }, } @@ -378,11 +487,7 @@ export TEST="value" t.Run(tt.name, func(t *testing.T) { tt.args.tenant.EnsureDefaults() - var configMaps map[string]*corev1.ConfigMap - secrets := map[string]*corev1.Secret{ - tt.args.tenant.ConfigurationSecretName(): tt.args.configSecret, - } - if got, _, _ := GetFullTenantConfig(tt.args.tenant, configMaps, secrets); got != tt.want { + if got, _, _ := GetFullTenantConfig(tt.args.tenant, tt.args.configMaps, tt.args.secrets); got != tt.want { t.Errorf("GetFullTenantConfig() = `%v`, want `%v`", got, tt.want) } }) From 6517b7325253ddce13bb5b73e490d204c76da04a Mon Sep 17 00:00:00 2001 From: Ramon de Klein Date: Tue, 27 Aug 2024 14:51:19 +0200 Subject: [PATCH 5/6] fixed quoting --- .../pkg/configuration/tenant_configuration.go | 32 +++++++++++---- .../tenant_configuration_test.go | 41 +++++++++++++++++++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/sidecar/pkg/configuration/tenant_configuration.go b/sidecar/pkg/configuration/tenant_configuration.go index 60584b66d82..ddbea480603 100644 --- a/sidecar/pkg/configuration/tenant_configuration.go +++ b/sidecar/pkg/configuration/tenant_configuration.go @@ -19,9 +19,7 @@ package configuration import ( "context" "fmt" - "log" "sort" - "strconv" "strings" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" @@ -101,14 +99,9 @@ func parseConfEnvSecret(secret *corev1.Secret) map[string]miniov2.EnvVar { parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { name := strings.TrimSpace(parts[0]) - value, err := strconv.Unquote(strings.TrimSpace(parts[1])) - if err != nil { - log.Printf("Syntax error for variable %s (skipped): %s", name, err) - continue - } envMap[name] = miniov2.EnvVar{ Name: name, - Value: value, + Value: unquote(strings.TrimSpace(parts[1])), } } } @@ -292,7 +285,28 @@ func envVarsToFileContent(envVars []miniov2.EnvVar, configMaps map[string]*corev value = &env.Value } if value != nil { - sb.WriteString(fmt.Sprintf("export %s=\"%s\"\n", env.Name, *value)) + sb.WriteString(fmt.Sprintf("export %s=%q\n", env.Name, *value)) + } + } + return sb.String() +} + +func unquote(value string) string { + if len(value) < 2 { + return value + } + firstCh := value[0] + lastCh := value[len(value)-1] + if firstCh != lastCh || (firstCh != '\'' && firstCh != '"') { + return value + } + var sb strings.Builder + for i, ch := range value { + if i == 0 || i == len(value)-1 { + continue + } + if ch != '\\' { + sb.WriteRune(ch) } } return sb.String() diff --git a/sidecar/pkg/configuration/tenant_configuration_test.go b/sidecar/pkg/configuration/tenant_configuration_test.go index daa6afd1a9b..3d9560e7edb 100644 --- a/sidecar/pkg/configuration/tenant_configuration_test.go +++ b/sidecar/pkg/configuration/tenant_configuration_test.go @@ -480,6 +480,47 @@ export MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRy export TEST="value" export TEST_CONFIGMAP="test-configmap-value" export TEST_SECRET="test-secret-value" +`, + }, + { + name: "Quoted variables", + args: args{ + tenant: &miniov2.Tenant{ + Spec: miniov2.TenantSpec{ + Configuration: &corev1.LocalObjectReference{ + Name: configSecretName, + }, + }, + }, + secrets: map[string]*corev1.Secret{ + configSecretName: { + Data: map[string][]byte{"config.env": []byte(`export MINIO_ROOT_USER="minio" +export MINIO_ROOT_PASSWORD="minio123" +export MINIO_STORAGE_CLASS_STANDARD="EC:2" +export MINIO_BROWSER="on" +export MINIO_TEST1a="on" +export MINIO_TEST1b="\"'on'\"" +export MINIO_TEST2a='on' +export MINIO_TEST2b='"\'on\'"' +export MINIO_TEST3=on +`)}, + }, + }, + }, + want: `export MINIO_ARGS="" +export MINIO_BROWSER="on" +export MINIO_PROMETHEUS_JOB_ID="minio-job" +export MINIO_ROOT_PASSWORD="minio123" +export MINIO_ROOT_USER="minio" +export MINIO_SERVER_URL="https://minio..svc.cluster.local:443" +export MINIO_STORAGE_CLASS_STANDARD="EC:2" +export MINIO_TEST1a="on" +export MINIO_TEST1b="\"'on'\"" +export MINIO_TEST2a="on" +export MINIO_TEST2b="\"'on'\"" +export MINIO_TEST3="on" +export MINIO_UPDATE="on" +export MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" `, }, } From 9225dc8b4c0c37de1d240d658477836f285d5c16 Mon Sep 17 00:00:00 2001 From: Ramon de Klein Date: Thu, 29 Aug 2024 19:52:48 +0200 Subject: [PATCH 6/6] allow sidecar to access configmaps --- pkg/controller/service-account.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/controller/service-account.go b/pkg/controller/service-account.go index 0f86fa95e6f..8929c104e7c 100644 --- a/pkg/controller/service-account.go +++ b/pkg/controller/service-account.go @@ -99,6 +99,7 @@ func getTenantRole(tenant *miniov2.Tenant) *rbacv1.Role { "", }, Resources: []string{ + "configmaps", "secrets", }, Verbs: []string{