Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions internal/cnpgi/operator/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0
package config

import (
"cmp"
"strings"

cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
Expand Down Expand Up @@ -71,6 +72,10 @@ type PluginConfiguration struct {
BarmanObjectName string
ServerName string

// SidecarImage overrides the default sidecar image. It is the first set
// of: spec.plugins[], the recovery source, the replica source.
SidecarImage string

RecoveryBarmanObjectName string
RecoveryServerName string

Expand Down Expand Up @@ -186,6 +191,11 @@ func NewFromCluster(cluster *cnpgv1.Cluster) *PluginConfiguration {
// used for the backup/archive
BarmanObjectName: helper.Parameters["barmanObjectName"],
ServerName: serverName,
SidecarImage: cmp.Or(
helper.Parameters["sidecarImage"],
getRecoveryParameters(cluster)["sidecarImage"],
getReplicaSourceParameters(cluster)["sidecarImage"],
),
// used for restore and wal_restore during backup recovery
RecoveryServerName: recoveryServerName,
RecoveryBarmanObjectName: recoveryBarmanObjectName,
Expand Down
70 changes: 70 additions & 0 deletions internal/cnpgi/operator/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,73 @@ var _ = Describe("NewFromCluster", func() {
Expect(cfg.Validate()).NotTo(Succeed())
})
})

var _ = Describe("sidecar image resolution", func() {
newCluster := func(pluginImage, recoveryImage string) *cnpgv1.Cluster {
cluster := &cnpgv1.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "test-ns"},
Spec: cnpgv1.ClusterSpec{
Bootstrap: &cnpgv1.BootstrapConfiguration{
Recovery: &cnpgv1.BootstrapRecovery{Source: "source"},
},
ExternalClusters: []cnpgv1.ExternalCluster{{
Name: "source",
PluginConfiguration: &cnpgv1.PluginConfiguration{
Name: metadata.PluginName,
Parameters: map[string]string{
"barmanObjectName": "object-store",
"sidecarImage": recoveryImage,
},
},
}},
},
}
if pluginImage != "" {
cluster.Spec.Plugins = []cnpgv1.PluginConfiguration{{
Name: metadata.PluginName,
Parameters: map[string]string{"sidecarImage": pluginImage},
}}
}
return cluster
}

It("is empty when no image is requested", func() {
cfg := NewFromCluster(newCluster("", ""))
Expect(cfg.SidecarImage).To(BeEmpty())
})

It("uses the spec.plugins[] image", func() {
cfg := NewFromCluster(newCluster("plugin:1", ""))
Expect(cfg.SidecarImage).To(Equal("plugin:1"))
})

It("falls back to the recovery source image", func() {
cfg := NewFromCluster(newCluster("", "recovery:1"))
Expect(cfg.SidecarImage).To(Equal("recovery:1"))
})

It("prefers the spec.plugins[] image over the recovery source image", func() {
cfg := NewFromCluster(newCluster("plugin:1", "recovery:1"))
Expect(cfg.SidecarImage).To(Equal("plugin:1"))
})

It("falls back to the replica source image", func() {
cluster := &cnpgv1.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "test-ns"},
Spec: cnpgv1.ClusterSpec{
ReplicaCluster: &cnpgv1.ReplicaClusterConfiguration{Source: "source"},
ExternalClusters: []cnpgv1.ExternalCluster{{
Name: "source",
PluginConfiguration: &cnpgv1.PluginConfiguration{
Name: metadata.PluginName,
Parameters: map[string]string{
"barmanObjectName": "object-store",
"sidecarImage": "replica:1",
},
},
}},
},
}
Expect(NewFromCluster(cluster).SidecarImage).To(Equal("replica:1"))
})
})
6 changes: 5 additions & 1 deletion internal/cnpgi/operator/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0
package operator

import (
"cmp"
"context"
"errors"
"fmt"
Expand Down Expand Up @@ -151,13 +152,15 @@ func (impl LifecycleImplementation) reconcileJob(
}

return reconcileJob(ctx, cluster, request, sidecarConfiguration{
image: cmp.Or(pluginConfiguration.SidecarImage, viper.GetString("sidecar-image")),
env: env,
certificates: certificates,
resources: resources,
})
}

type sidecarConfiguration struct {
image string
env []corev1.EnvVar
certificates []corev1.VolumeProjection
resources corev1.ResourceRequirements
Expand Down Expand Up @@ -249,6 +252,7 @@ func (impl LifecycleImplementation) reconcilePod(
}

return reconcileInstancePod(ctx, cluster, request, pluginConfiguration, sidecarConfiguration{
image: cmp.Or(pluginConfiguration.SidecarImage, viper.GetString("sidecar-image")),
env: env,
certificates: certificates,
resources: resources,
Expand Down Expand Up @@ -450,7 +454,7 @@ func reconcilePodSpec(

// fixed values
sidecarTemplate.Name = "plugin-barman-cloud"
sidecarTemplate.Image = viper.GetString("sidecar-image")
sidecarTemplate.Image = config.image
sidecarTemplate.ImagePullPolicy = cluster.Spec.ImagePullPolicy
sidecarTemplate.StartupProbe = baseProbe.DeepCopy()
sidecarTemplate.SecurityContext = &corev1.SecurityContext{
Expand Down
34 changes: 34 additions & 0 deletions internal/cnpgi/operator/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@ var _ = Describe("LifecycleImplementation", func() {
Expect(response.JsonPatch).NotTo(BeEmpty())
})

It("uses the requested sidecar image", func(ctx SpecContext) {
job := &batchv1.Job{
TypeMeta: jobTypeMeta,
ObjectMeta: metav1.ObjectMeta{Name: "test-job", Labels: map[string]string{}},
Spec: batchv1.JobSpec{Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{utils.JobRoleLabelName: fullRecoveryJobName},
},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: fullRecoveryJobName}}},
}},
}
jobJSON, _ := json.Marshal(job)
request := &lifecycle.OperatorLifecycleRequest{ObjectDefinition: jobJSON}

response, err := reconcileJob(ctx, cluster, request, sidecarConfiguration{image: "custom:1"})
Expect(err).NotTo(HaveOccurred())
Expect(string(response.JsonPatch)).To(ContainSubstring(`"image":"custom:1"`))
})

It("skips non-recovery jobs", func(ctx SpecContext) {
job := &batchv1.Job{
TypeMeta: jobTypeMeta,
Expand Down Expand Up @@ -242,6 +261,21 @@ var _ = Describe("LifecycleImplementation", func() {
HaveKey("value")))
})

It("uses the requested sidecar image", func(ctx SpecContext) {
pod := &corev1.Pod{
TypeMeta: podTypeMeta,
ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "postgres"}}},
}
podJSON, _ := json.Marshal(pod)
request := &lifecycle.OperatorLifecycleRequest{ObjectDefinition: podJSON}

response, err := reconcileInstancePod(
ctx, cluster, request, pluginConfiguration, sidecarConfiguration{image: "custom:1"})
Expect(err).NotTo(HaveOccurred())
Expect(string(response.JsonPatch)).To(ContainSubstring(`"image":"custom:1"`))
})

It("injects the sidecar for a recovery-only cluster", func(ctx SpecContext) {
recoveryOnlyConfig := &config.PluginConfiguration{
RecoveryBarmanObjectName: "object-store-recovery",
Expand Down
9 changes: 9 additions & 0 deletions web/docs/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ built from the
These sidecar images are designed to work seamlessly with the
[`minimal` PostgreSQL container images](https://github.com/cloudnative-pg/postgres-containers?tab=readme-ov-file#minimal-images)
maintained by the CloudNativePG Community.

The image can be overridden per cluster with the
[`sidecarImage` parameter](parameters.md).

:::warning
This override is meant for debugging and testing. Running a sidecar whose
version differs from the operator's is not recommended. Leave it unset in
production.
:::
9 changes: 9 additions & 0 deletions web/docs/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ The following parameters are available for the Barman Cloud Plugin:
- `barmanObjectName`: references the `ObjectStore` resource to be used by the
plugin.
- `serverName`: Specifies the server name in the object store.
- `sidecarImage`: overrides the [sidecar container image](images.md#sidecar-container-image)
injected into the instance pods and the recovery job pods of the cluster.
It can also be set on a recovery or replica source in `externalClusters[]`.

:::warning
`sidecarImage` is meant for debugging and testing. Running a sidecar whose
version differs from the operator's is not recommended. Leave it unset in
production.
:::

:::important
The `serverName` parameter in the `ObjectStore` resource is retained solely for
Expand Down
Loading