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 22927919d58..a4d55f34b06 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)
}
@@ -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..78f8be38107 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"`
+}
+
+// ConfigMapKeySelector 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/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{
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/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/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 61%
rename from pkg/configuration/tenant_configuration.go
rename to sidecar/pkg/configuration/tenant_configuration.go
index 2900a6a19c6..ddbea480603 100644
--- a/pkg/configuration/tenant_configuration.go
+++ b/sidecar/pkg/configuration/tenant_configuration.go
@@ -17,6 +17,7 @@
package configuration
import (
+ "context"
"fmt"
"sort"
"strings"
@@ -31,36 +32,64 @@ const (
bucketDNSEnv = "MINIO_DNS_WEBHOOK_ENDPOINT"
)
+type (
+ 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
+// 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 {
+ secrets[env.ValueFrom.SecretKeyRef.Name] = sf(ctx, env.ValueFrom.SecretKeyRef.Name)
+ }
+ if env.ValueFrom.ConfigMapKeyRef != nil {
+ configMaps[env.ValueFrom.ConfigMapKeyRef.Name] = cf(ctx, env.ValueFrom.ConfigMapKeyRef.Name)
+ }
+ }
+ }
+
+ 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
+
+ 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" {
- rootUserFound = true
- }
- if env.Name == "MINIO_ACCESS_KEY" {
+ if env.Name == "MINIO_ROOT_USER" || 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
}
-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 {
@@ -70,23 +99,21 @@ 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{
+ envMap[name] = miniov2.EnvVar{
Name: name,
- Value: value,
+ Value: unquote(strings.TrimSpace(parts[1])),
}
- envMap[name] = 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",
@@ -104,7 +131,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: "/",
}
@@ -119,7 +146,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,
}
@@ -130,7 +157,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, ","),
}
@@ -151,7 +178,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,
}
@@ -167,33 +194,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,
}
@@ -201,7 +228,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,
}
@@ -211,7 +238,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 {
@@ -234,10 +261,53 @@ func buildTenantEnvs(tenant *miniov2.Tenant, cfgEnvExisting map[string]corev1.En
return envVars
}
-func envVarsToFileContent(envVars []corev1.EnvVar) string {
- content := ""
+func envVarsToFileContent(envVars []miniov2.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)
+ var value *string
+ if env.ValueFrom != nil {
+ if env.ValueFrom.ConfigMapKeyRef != nil {
+ 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 {
+ 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=%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 content
+ return sb.String()
}
diff --git a/pkg/configuration/tenant_configuration_test.go b/sidecar/pkg/configuration/tenant_configuration_test.go
similarity index 62%
rename from pkg/configuration/tenant_configuration_test.go
rename to sidecar/pkg/configuration/tenant_configuration_test.go
index 9393d888744..3d9560e7edb 100644
--- a/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",
@@ -67,7 +69,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)
}
})
@@ -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,13 +478,57 @@ 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"
+`,
+ },
+ {
+ 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"
`,
},
}
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 {
+
+ if got, _, _ := GetFullTenantConfig(tt.args.tenant, tt.args.configMaps, tt.args.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 1b2778bb166..8b80c924a62 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,9 +47,23 @@ 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")
- 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)
}
@@ -76,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)
@@ -120,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{
@@ -161,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 {
@@ -172,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 {
@@ -192,43 +218,62 @@ 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 {
+ retValue, _ := c.secretInformer.Lister().Secrets(c.tenant.Namespace).Get(name)
+ return retValue
+}
- err = os.WriteFile(v2.CfgFile, []byte(fileContents), 0o644)
- if err != nil {
- log.Println(err)
- }
+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) 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.")
@@ -236,7 +281,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)
}
@@ -249,8 +301,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..ea1e087cac1 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,21 @@ 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 {
+ 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)
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.")