feat: additional environment variables (#81)

Signed-off-by: Leonardo Cecchi <leonardo.cecchi@enterprisedb.com>
Signed-off-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
Co-authored-by: Francesco Canovai <francesco.canovai@enterprisedb.com>
This commit is contained in:
Leonardo Cecchi 2024-12-05 13:18:18 +01:00 committed by GitHub
parent e30edd2318
commit be4037529c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 237 additions and 12 deletions

View File

@ -18,6 +18,7 @@ package v1
import ( import (
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api" barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -29,6 +30,10 @@ type InstanceSidecarConfiguration struct {
// +kubebuilder:validation:Maximum=3600 // +kubebuilder:validation:Maximum=3600
// +kubebuilder:default=180 // +kubebuilder:default=180
CacheTTL *int `json:"cacheTTL,omitempty"` CacheTTL *int `json:"cacheTTL,omitempty"`
// The environment to be explicitly passed to the sidecar
// +optional
Env []corev1.EnvVar `json:"env,omitempty"`
} }
// GetCacheTTL returns the cache TTL value, defaulting to 180 seconds if not set. // GetCacheTTL returns the cache TTL value, defaulting to 180 seconds if not set.

View File

@ -389,6 +389,126 @@ spec:
maximum: 3600 maximum: 3600
minimum: 0 minimum: 0
type: integer type: integer
env:
description: The environment to be explicitly passed to the sidecar
items:
description: EnvVar represents an environment variable present
in a Container.
properties:
name:
description: Name of the environment variable. Must be a
C_IDENTIFIER.
type: string
value:
description: |-
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 "".
type: string
valueFrom:
description: Source for the environment variable's value.
Cannot be used if value is not empty.
properties:
configMapKeyRef:
description: Selects a key of a ConfigMap.
properties:
key:
description: The key to select.
type: string
name:
default: ""
description: |-
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.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the ConfigMap or its
key must be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
fieldRef:
description: |-
Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['<KEY>']`, `metadata.annotations['<KEY>']`,
spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.
properties:
apiVersion:
description: Version of the schema the FieldPath
is written in terms of, defaults to "v1".
type: string
fieldPath:
description: Path of the field to select in the
specified API version.
type: string
required:
- fieldPath
type: object
x-kubernetes-map-type: atomic
resourceFieldRef:
description: |-
Selects a resource of the container: only resources limits and requests
(limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.
properties:
containerName:
description: 'Container name: required for volumes,
optional for env vars'
type: string
divisor:
anyOf:
- type: integer
- type: string
description: Specifies the output format of the
exposed resources, defaults to "1"
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
resource:
description: 'Required: resource to select'
type: string
required:
- resource
type: object
x-kubernetes-map-type: atomic
secretKeyRef:
description: Selects a key of a secret in the pod's
namespace
properties:
key:
description: The key of the secret to select from. Must
be a valid secret key.
type: string
name:
default: ""
description: |-
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.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
optional:
description: Specify whether the Secret or its key
must be defined
type: boolean
required:
- key
type: object
x-kubernetes-map-type: atomic
type: object
required:
- name
type: object
type: array
type: object type: object
required: required:
- configuration - configuration
@ -396,6 +516,9 @@ spec:
status: status:
description: ObjectStoreStatus defines the observed state of ObjectStore. description: ObjectStoreStatus defines the observed state of ObjectStore.
type: object type: object
required:
- metadata
- spec
type: object type: object
served: true served: true
storage: true storage: true

View File

@ -14,8 +14,11 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
) )
@ -23,6 +26,7 @@ import (
// LifecycleImplementation is the implementation of the lifecycle handler // LifecycleImplementation is the implementation of the lifecycle handler
type LifecycleImplementation struct { type LifecycleImplementation struct {
lifecycle.UnimplementedOperatorLifecycleServer lifecycle.UnimplementedOperatorLifecycleServer
Client client.Client
} }
// GetCapabilities exposes the lifecycle capabilities // GetCapabilities exposes the lifecycle capabilities
@ -94,20 +98,85 @@ func (impl LifecycleImplementation) LifecycleHook(
switch kind { switch kind {
case "Pod": case "Pod":
contextLogger.Info("Reconciling pod") contextLogger.Info("Reconciling pod")
return reconcilePod(ctx, &cluster, request, pluginConfiguration) return impl.reconcilePod(ctx, &cluster, request, pluginConfiguration)
case "Job": case "Job":
contextLogger.Info("Reconciling job") contextLogger.Info("Reconciling job")
return reconcileJob(ctx, &cluster, request, pluginConfiguration) return impl.reconcileJob(ctx, &cluster, request, pluginConfiguration)
default: default:
return nil, fmt.Errorf("unsupported kind: %s", kind) return nil, fmt.Errorf("unsupported kind: %s", kind)
} }
} }
func (impl LifecycleImplementation) collectAdditionalEnvs(
ctx context.Context,
namespace string,
pluginConfiguration *config.PluginConfiguration,
) ([]corev1.EnvVar, error) {
var result []corev1.EnvVar
if len(pluginConfiguration.BarmanObjectName) > 0 {
envs, err := impl.collectObjectStoreEnvs(
ctx,
types.NamespacedName{
Name: pluginConfiguration.BarmanObjectName,
Namespace: namespace,
},
)
if err != nil {
return nil, err
}
result = append(result, envs...)
}
if len(pluginConfiguration.RecoveryBarmanObjectName) > 0 {
envs, err := impl.collectObjectStoreEnvs(
ctx,
types.NamespacedName{
Name: pluginConfiguration.RecoveryBarmanObjectName,
Namespace: namespace,
},
)
if err != nil {
return nil, err
}
result = append(result, envs...)
}
return result, nil
}
func (impl LifecycleImplementation) collectObjectStoreEnvs(
ctx context.Context,
barmanObjectKey types.NamespacedName,
) ([]corev1.EnvVar, error) {
var objectStore barmancloudv1.ObjectStore
if err := impl.Client.Get(ctx, barmanObjectKey, &objectStore); err != nil {
return nil, err
}
return objectStore.Spec.InstanceSidecarConfiguration.Env, nil
}
func (impl LifecycleImplementation) reconcileJob(
ctx context.Context,
cluster *cnpgv1.Cluster,
request *lifecycle.OperatorLifecycleRequest,
pluginConfiguration *config.PluginConfiguration,
) (*lifecycle.OperatorLifecycleResponse, error) {
env, err := impl.collectAdditionalEnvs(ctx, cluster.Namespace, pluginConfiguration)
if err != nil {
return nil, nil
}
return reconcileJob(ctx, cluster, request, pluginConfiguration, env)
}
func reconcileJob( func reconcileJob(
ctx context.Context, ctx context.Context,
cluster *cnpgv1.Cluster, cluster *cnpgv1.Cluster,
request *lifecycle.OperatorLifecycleRequest, request *lifecycle.OperatorLifecycleRequest,
pluginConfiguration *config.PluginConfiguration, pluginConfiguration *config.PluginConfiguration,
env []corev1.EnvVar,
) (*lifecycle.OperatorLifecycleResponse, error) { ) (*lifecycle.OperatorLifecycleResponse, error) {
contextLogger := log.FromContext(ctx).WithName("lifecycle") contextLogger := log.FromContext(ctx).WithName("lifecycle")
if pluginConfig := cluster.GetRecoverySourcePlugin(); pluginConfig == nil || pluginConfig.Name != metadata.PluginName { if pluginConfig := cluster.GetRecoverySourcePlugin(); pluginConfig == nil || pluginConfig.Name != metadata.PluginName {
@ -144,6 +213,7 @@ func reconcileJob(
corev1.Container{ corev1.Container{
Args: []string{"restore"}, Args: []string{"restore"},
}, },
env,
); err != nil { ); err != nil {
return nil, fmt.Errorf("while reconciling pod spec for job: %w", err) return nil, fmt.Errorf("while reconciling pod spec for job: %w", err)
} }
@ -159,11 +229,26 @@ func reconcileJob(
}, nil }, nil
} }
func (impl LifecycleImplementation) reconcilePod(
ctx context.Context,
cluster *cnpgv1.Cluster,
request *lifecycle.OperatorLifecycleRequest,
pluginConfiguration *config.PluginConfiguration,
) (*lifecycle.OperatorLifecycleResponse, error) {
env, err := impl.collectAdditionalEnvs(ctx, cluster.Namespace, pluginConfiguration)
if err != nil {
return nil, nil
}
return reconcilePod(ctx, cluster, request, pluginConfiguration, env)
}
func reconcilePod( func reconcilePod(
ctx context.Context, ctx context.Context,
cluster *cnpgv1.Cluster, cluster *cnpgv1.Cluster,
request *lifecycle.OperatorLifecycleRequest, request *lifecycle.OperatorLifecycleRequest,
pluginConfiguration *config.PluginConfiguration, pluginConfiguration *config.PluginConfiguration,
env []corev1.EnvVar,
) (*lifecycle.OperatorLifecycleResponse, error) { ) (*lifecycle.OperatorLifecycleResponse, error) {
pod, err := decoder.DecodePodJSON(request.GetObjectDefinition()) pod, err := decoder.DecodePodJSON(request.GetObjectDefinition())
if err != nil { if err != nil {
@ -176,9 +261,16 @@ func reconcilePod(
mutatedPod := pod.DeepCopy() mutatedPod := pod.DeepCopy()
if len(pluginConfiguration.BarmanObjectName) != 0 { if len(pluginConfiguration.BarmanObjectName) != 0 {
if err := reconcilePodSpec(pluginConfiguration, cluster, &mutatedPod.Spec, "postgres", corev1.Container{ if err := reconcilePodSpec(
Args: []string{"instance"}, pluginConfiguration,
}); err != nil { cluster,
&mutatedPod.Spec,
"postgres",
corev1.Container{
Args: []string{"instance"},
},
env,
); err != nil {
return nil, fmt.Errorf("while reconciling pod spec for pod: %w", err) return nil, fmt.Errorf("while reconciling pod spec for pod: %w", err)
} }
} else { } else {
@ -202,6 +294,7 @@ func reconcilePodSpec(
spec *corev1.PodSpec, spec *corev1.PodSpec,
mainContainerName string, mainContainerName string,
sidecarConfig corev1.Container, sidecarConfig corev1.Container,
additionalEnvs []corev1.EnvVar,
) error { ) error {
envs := []corev1.EnvVar{ envs := []corev1.EnvVar{
{ {
@ -246,6 +339,8 @@ func reconcilePodSpec(
) )
} }
envs = append(envs, additionalEnvs...)
baseProbe := &corev1.Probe{ baseProbe := &corev1.Probe{
FailureThreshold: 3, FailureThreshold: 3,
ProbeHandler: corev1.ProbeHandler{ ProbeHandler: corev1.ProbeHandler{

View File

@ -107,7 +107,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: jobJSON, ObjectDefinition: jobJSON,
} }
response, err := reconcileJob(ctx, cluster, request, pluginConfiguration) response, err := reconcileJob(ctx, cluster, request, pluginConfiguration, nil)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).NotTo(BeNil()) Expect(response).NotTo(BeNil())
Expect(response.JsonPatch).NotTo(BeEmpty()) Expect(response.JsonPatch).NotTo(BeEmpty())
@ -128,7 +128,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: jobJSON, ObjectDefinition: jobJSON,
} }
response, err := reconcileJob(ctx, cluster, request, pluginConfiguration) response, err := reconcileJob(ctx, cluster, request, pluginConfiguration, nil)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })
@ -138,7 +138,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: []byte("invalid-json"), ObjectDefinition: []byte("invalid-json"),
} }
response, err := reconcileJob(ctx, cluster, request, pluginConfiguration) response, err := reconcileJob(ctx, cluster, request, pluginConfiguration, nil)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })
@ -165,7 +165,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: jobJSON, ObjectDefinition: jobJSON,
} }
response, err := reconcileJob(ctx, cluster, request, pluginConfiguration) response, err := reconcileJob(ctx, cluster, request, pluginConfiguration, nil)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })
@ -185,7 +185,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: podJSON, ObjectDefinition: podJSON,
} }
response, err := reconcilePod(ctx, cluster, request, pluginConfiguration) response, err := reconcilePod(ctx, cluster, request, pluginConfiguration, nil)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(response).NotTo(BeNil()) Expect(response).NotTo(BeNil())
Expect(response.JsonPatch).NotTo(BeEmpty()) Expect(response.JsonPatch).NotTo(BeEmpty())
@ -203,7 +203,7 @@ var _ = Describe("LifecycleImplementation", func() {
ObjectDefinition: []byte("invalid-json"), ObjectDefinition: []byte("invalid-json"),
} }
response, err := reconcilePod(ctx, cluster, request, pluginConfiguration) response, err := reconcilePod(ctx, cluster, request, pluginConfiguration, nil)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(response).To(BeNil()) Expect(response).To(BeNil())
}) })

View File

@ -27,7 +27,9 @@ func (c *CNPGI) Start(ctx context.Context) error {
reconciler.RegisterReconcilerHooksServer(server, ReconcilerImplementation{ reconciler.RegisterReconcilerHooksServer(server, ReconcilerImplementation{
Client: c.Client, Client: c.Client,
}) })
lifecycle.RegisterOperatorLifecycleServer(server, LifecycleImplementation{}) lifecycle.RegisterOperatorLifecycleServer(server, LifecycleImplementation{
Client: c.Client,
})
return nil return nil
} }