Compare commits

...

5 Commits

Author SHA1 Message Date
Ildar-fix
08dd96da31
Merge 946c704bee into 31b25538bf 2026-09-02 18:15:56 +02:00
renovate[bot]
31b25538bf
fix(deps): update all non-major go dependencies (#1086)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-02 18:13:49 +02:00
Tao Li
1bb6bab640
chore(deps): bump barman version to 3.20.0 (#1090)
Bump barman to 3.20.0 and regenerate the sidecar's pip lockfile with the
same Python 3.13 used by the sidecar's venv.

barman 3.20.0 dropped fake-gcs-server support
(EnterpriseDB/barman#1218), so the GCS backup/restore and
replica-cluster e2e specs are skipped at runtime until that's fixed
upstream.

Also bundles tar into the sidecar image: barman 3.20.0's restore path
now shells out to it, and the distroless base doesn't ship it.

Signed-off-by: Tao Li <tao.li@enterprisedb.com>
Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
Co-authored-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
2026-09-02 17:07:32 +02:00
Ildar Gilyazev
946c704bee feat: let a Cluster declare the object stores a Backup may request
The Role bound to the instance service account is built from the object
stores the Cluster refers to, so a Backup naming any other store fails
with a forbidden error on objectstores. The object store selection of a
Backup is therefore unusable on its own.

Add the additionalBarmanObjectNames parameter, a comma separated list of
stores that take part in the RBAC and in the certificates of the
instances while nothing is routed to them, so that a Backup can name one
of them.

Closes #611

Assisted-by: Claude Opus 5
Signed-off-by: Ildar Gilyazev <horiganmikle@gmail.com>
2026-08-26 10:32:38 +03:00
Ildar Gilyazev
ec238c4d42 fix: honour the object store requested by a Backup resource
The operator relays the parameters of a Backup resource to the plugin in
the BackupRequest, but the plugin builds its configuration from the
Cluster alone. A Backup asking for a different object store is therefore
written to the cluster one, and it completes without any warning, so the
mistake surfaces only when the archive turns out to be empty.

Apply the parameters of the Backup on top of the configuration derived
from the Cluster, so that barmanObjectName and serverName select the
destination of that single backup.

Closes #611

Assisted-by: Claude Opus 5
Signed-off-by: Ildar Gilyazev <horiganmikle@gmail.com>
2026-08-26 10:32:05 +03:00
10 changed files with 160 additions and 20 deletions

View File

@ -63,6 +63,9 @@ RUN python3 -m venv /venv && \
# already present in the distroless base image.
# Distroless package list from: https://github.com/GoogleContainerTools/distroless/blob/main/base/config.bzl
# and https://github.com/GoogleContainerTools/distroless/blob/main/python3/config.bzl
# libselinux1/libpcre2-8-0 are pulled in transitively by python3.13-venv's apt metadata,
# but the actual distroless base does not ship them, so they are excluded from the
# "already present" set below to force them to be bundled (tar needs them at runtime).
RUN mkdir -p /dependencies /build/downloads && \
cd /build/downloads && \
DISTROLESS_PACKAGES="libc6 libssl3t64 libzstd1 zlib1g libgcc-s1 libstdc++6 \
@ -73,10 +76,11 @@ RUN mkdir -p /dependencies /build/downloads && \
libpython3.13-stdlib python3.13-minimal python3.13-venv" && \
apt-cache depends --recurse --no-recommends --no-suggests \
--no-conflicts --no-breaks --no-replaces --no-enhances \
$DISTROLESS_PACKAGES 2>/dev/null | grep "^\w" | sort -u > /tmp/distroless.txt && \
$DISTROLESS_PACKAGES 2>/dev/null | grep "^\w" | sort -u | \
grep -v -x -E 'libselinux1|libpcre2-8-0' > /tmp/distroless.txt && \
apt-cache depends --recurse --no-recommends --no-suggests \
--no-conflicts --no-breaks --no-replaces --no-enhances \
libpq5 liblz4-1 libsnappy1v5 2>/dev/null | grep "^\w" | sort -u | \
libpq5 liblz4-1 libsnappy1v5 tar 2>/dev/null | grep "^\w" | sort -u | \
grep -v -F -x -f /tmp/distroless.txt > /tmp/packages.txt && \
apt-get download $(cat /tmp/packages.txt) && \
for deb in *.deb; do \
@ -102,6 +106,7 @@ LABEL summary="$SUMMARY" \
COPY --from=pythonbuilder /venv /venv
COPY --from=pythonbuilder /dependencies/usr/lib /usr/lib
COPY --from=pythonbuilder /dependencies/usr/bin/tar /usr/bin/tar
COPY --from=gobuilder /workspace/manager /manager
# Compile all Python bytecode as root to avoid runtime compilation

View File

@ -1,2 +1,2 @@
barman[azure,cloud,google,snappy,zstandard,lz4]==3.19.1
barman[azure,cloud,google,snappy,zstandard,lz4]==3.20.0
zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability

View File

@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --no-index --output-file=sidecar-requirements.txt --strip-extras sidecar-requirements.in
# pip-compile --allow-unsafe --generate-hashes --output-file=sidecar-requirements.txt --strip-extras sidecar-requirements.in
#
azure-core==1.41.0 \
--hash=sha256:522b4011e8180b1a3dcd2024396a4e7fe9ac37fb8597db47163d230b5efe892d \
@ -18,9 +18,9 @@ azure-storage-blob==12.30.0 \
--hash=sha256:2cd74d4d5731e5eb6b8d5c5056ee115a5e88f8fdf22517b739836fda685018be \
--hash=sha256:d415ac50b67a8da6b3ae7e9f1014b1b55cd7aafa0b8d4ca9b380568dc7360423
# via barman
barman==3.19.1 \
--hash=sha256:0a6a9e1babf97687732d8b2a3eb79ea95d55246a5257b9433865cb6e755221c0 \
--hash=sha256:2f71c4a1f1ba53f694cbdf838bb9906d8ba02b97d1fd3041196e8999bec7a1ee
barman==3.20.0 \
--hash=sha256:02dd8936e62c1829c78597eefedfcab0aa820f5618da2871f38b5bc684891a54 \
--hash=sha256:1aa92df452f39c357d6547fd0abd3885a8c243ea95002e6fc0b7f66f8e8c24d5
# via -r sidecar-requirements.in
boto3==1.43.81 \
--hash=sha256:62ecf695088e06f37500d6cc49a240dc1331379bd5ae992d185fef212038ca29 \

8
go.mod
View File

@ -5,13 +5,13 @@ go 1.26.4
require (
github.com/cert-manager/cert-manager v1.21.1
github.com/cloudnative-pg/api v1.30.0
github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260806065336-5aa56cd49543
github.com/cloudnative-pg/barman-cloud v0.6.0
github.com/cloudnative-pg/cloudnative-pg v1.30.0
github.com/cloudnative-pg/cnpg-i v0.6.0
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2
github.com/cloudnative-pg/machinery v0.5.0
github.com/cloudnative-pg/machinery v0.6.0
github.com/onsi/ginkgo/v2 v2.32.1
github.com/onsi/gomega v1.42.1
github.com/onsi/gomega v1.43.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
google.golang.org/grpc v1.83.2
@ -42,7 +42,7 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v1.0.0 // indirect

16
go.sum
View File

@ -20,16 +20,16 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudnative-pg/api v1.30.0 h1:L8hnvV/tPEQA1xYEi41FUBFA7FUNVGju8+SlgFlDDjI=
github.com/cloudnative-pg/api v1.30.0/go.mod h1:XrKBbOWObL33si0FNuwX4uHNf5JShiZyOUqd6LxbJQo=
github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260806065336-5aa56cd49543 h1:QNddaYyvyw14Ky/oxeI3cBiMMg7h6J0LI8VLCaxTUCY=
github.com/cloudnative-pg/barman-cloud v0.5.2-0.20260806065336-5aa56cd49543/go.mod h1:ZQLkdpk44FW5/BGWzABTOEcV9qPbwC+rbdscg2I8mBI=
github.com/cloudnative-pg/barman-cloud v0.6.0 h1:OtBFmCDyVUAcgFa++FIoCCJwPfd5TtqK3PH6DGPcpkA=
github.com/cloudnative-pg/barman-cloud v0.6.0/go.mod h1:eqSPRGz/s8M0Mea8mkqiVQUTkoSquAhvJF49feh+Ks4=
github.com/cloudnative-pg/cloudnative-pg v1.30.0 h1:fnhVq44xXx97MNiuvJsPrX1vSjYbgdyBK5MSGfdHdp0=
github.com/cloudnative-pg/cloudnative-pg v1.30.0/go.mod h1:QkolwBOWZ+GvAiJt6KpDSymwkpf0K19/p4Q6MQlTM8U=
github.com/cloudnative-pg/cnpg-i v0.6.0 h1:LA//DLkFOLIjU0ASOpFkydZhGir9IAIDfgSsTTX9IpU=
github.com/cloudnative-pg/cnpg-i v0.6.0/go.mod h1:4kcpLAj+feMTnh0TVadXRASdVnEhBytNSm/BvktLgmI=
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2 h1:0reS9MtyLYINHXQ/MfxJ9jp39hhBf8e3Qdj+T5Nsq6I=
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2/go.mod h1:gvrKabgxXq0zGthXGucemDdsxakLEQDMxn43M4HLW30=
github.com/cloudnative-pg/machinery v0.5.0 h1:hhTnkzn+AiN3NmbjCQ6RXj5rfqV3K6arzq6kdXAzcnQ=
github.com/cloudnative-pg/machinery v0.5.0/go.mod h1:uuFjqBUjWn0a9uvAk1ixTSzPM0PrjaS+QiKLOIBqLm4=
github.com/cloudnative-pg/machinery v0.6.0 h1:faHxS1AK75XkzppOcAKdOexKmzX7iGK6jX2b9itssb4=
github.com/cloudnative-pg/machinery v0.6.0/go.mod h1:wyw9E/0uYGAixp9+0PLsJ6qHiEiFKDkeXb9O2dRi2w0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -60,8 +60,8 @@ github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop
github.com/go-faker/faker/v4 v4.4.1 h1:LY1jDgjVkBZWIhATCt+gkl0x9i/7wC61gZx73GTFb+Q=
github.com/go-faker/faker/v4 v4.4.1/go.mod h1:HRLrjis+tYsbFtIHufEPTAIzcZiRu0rS9EYl2Ccwme4=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
@ -163,8 +163,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw=
github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU=
github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

View File

@ -81,6 +81,8 @@ func (b BackupServiceImplementation) Backup(
return nil, err
}
configuration.ApplyBackupParameters(request.GetParameters())
var objectStore barmancloudv1.ObjectStore
if err := b.Client.Get(ctx, configuration.GetBarmanObjectKey(), &objectStore); err != nil {
contextLogger.Error(err, "while getting object store", "key", configuration.GetRecoveryBarmanObjectKey())

View File

@ -76,6 +76,12 @@ type PluginConfiguration struct {
ReplicaSourceBarmanObjectName string
ReplicaSourceServerName string
// AdditionalBarmanObjectNames lists the object stores that a Backup
// resource is allowed to request on top of the ones used by the cluster
// itself. Nothing is written to them unless a Backup asks for one, but
// they take part in the RBAC and in the certificates of the instances.
AdditionalBarmanObjectNames []string
}
// GetBarmanObjectKey gets the namespaced name of the barman object
@ -86,6 +92,24 @@ func (config *PluginConfiguration) GetBarmanObjectKey() types.NamespacedName {
}
}
// ApplyBackupParameters overrides the object store selection with the
// parameters of the Backup resource. The operator relays them in the
// BackupRequest, and without this a Backup asking for a different object
// store is silently written to the cluster one.
func (config *PluginConfiguration) ApplyBackupParameters(parameters map[string]string) {
if len(parameters) == 0 {
return
}
if value := parameters["barmanObjectName"]; len(value) > 0 {
config.BarmanObjectName = value
}
if value := parameters["serverName"]; len(value) > 0 {
config.ServerName = value
}
}
// GetRecoveryBarmanObjectKey gets the namespaced name of the recovery barman object
func (config *PluginConfiguration) GetRecoveryBarmanObjectKey() types.NamespacedName {
return types.NamespacedName{
@ -122,8 +146,11 @@ func (config *PluginConfiguration) GetReferredBarmanObjectsKey() []types.Namespa
if len(config.ReplicaSourceBarmanObjectName) > 0 {
objectNames.Put(config.ReplicaSourceBarmanObjectName)
}
for _, name := range config.AdditionalBarmanObjectNames {
objectNames.Put(name)
}
result := make([]types.NamespacedName, 0, 3)
result := make([]types.NamespacedName, 0, 4)
for _, name := range objectNames.ToSortedList() {
result = append(result, types.NamespacedName{
Name: name,
@ -186,6 +213,8 @@ func NewFromCluster(cluster *cnpgv1.Cluster) *PluginConfiguration {
// used for the backup/archive
BarmanObjectName: helper.Parameters["barmanObjectName"],
ServerName: serverName,
// reachable by a Backup resource requesting them explicitly
AdditionalBarmanObjectNames: parseObjectNameList(helper.Parameters["additionalBarmanObjectNames"]),
// used for restore and wal_restore during backup recovery
RecoveryServerName: recoveryServerName,
RecoveryBarmanObjectName: recoveryBarmanObjectName,
@ -197,6 +226,23 @@ func NewFromCluster(cluster *cnpgv1.Cluster) *PluginConfiguration {
return result
}
// parseObjectNameList splits a comma separated list of object store names,
// dropping the empty entries
func parseObjectNameList(value string) []string {
if len(value) == 0 {
return nil
}
var result []string
for _, name := range strings.Split(value, ",") {
if name = strings.TrimSpace(name); len(name) > 0 {
result = append(result, name)
}
}
return result
}
func getRecoveryParameters(cluster *cnpgv1.Cluster) map[string]string {
recoveryPluginConfiguration := getRecoverySourcePlugin(cluster)
if recoveryPluginConfiguration == nil {

View File

@ -124,3 +124,79 @@ var _ = Describe("NewFromCluster", func() {
Expect(cfg.Validate()).NotTo(Succeed())
})
})
var _ = Describe("PluginConfiguration.ApplyBackupParameters", func() {
newConfiguration := func(parameters map[string]string) *PluginConfiguration {
return NewFromCluster(&cnpgv1.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: "cluster-example", Namespace: "test-ns"},
Spec: cnpgv1.ClusterSpec{
Plugins: []cnpgv1.PluginConfiguration{
{Name: metadata.PluginName, Parameters: parameters},
},
},
})
}
It("sends the backup to the object store requested by the Backup resource", func() {
cfg := newConfiguration(map[string]string{"barmanObjectName": "minio-store"})
cfg.ApplyBackupParameters(map[string]string{"barmanObjectName": "archive-store"})
Expect(cfg.GetBarmanObjectKey().Name).To(Equal("archive-store"))
})
It("overrides the server name too", func() {
cfg := newConfiguration(map[string]string{"barmanObjectName": "minio-store"})
cfg.ApplyBackupParameters(map[string]string{"serverName": "another-name"})
Expect(cfg.ServerName).To(Equal("another-name"))
Expect(cfg.GetBarmanObjectKey().Name).To(Equal("minio-store"))
})
It("keeps the cluster object store when the Backup carries no parameters", func() {
cfg := newConfiguration(map[string]string{"barmanObjectName": "minio-store"})
cfg.ApplyBackupParameters(nil)
Expect(cfg.GetBarmanObjectKey().Name).To(Equal("minio-store"))
Expect(cfg.ServerName).To(Equal("cluster-example"))
})
})
var _ = Describe("Additional object stores", func() {
newConfiguration := func(parameters map[string]string) *PluginConfiguration {
return NewFromCluster(&cnpgv1.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: "cluster-example", Namespace: "test-ns"},
Spec: cnpgv1.ClusterSpec{
Plugins: []cnpgv1.PluginConfiguration{
{Name: metadata.PluginName, Parameters: parameters},
},
},
})
}
It("are referred to, so that they are covered by RBAC and certificates", func() {
cfg := newConfiguration(map[string]string{
"barmanObjectName": "minio-store",
"additionalBarmanObjectNames": "archive-store, monthly-store ,",
})
names := make([]string, 0, 3)
for _, key := range cfg.GetReferredBarmanObjectsKey() {
Expect(key.Namespace).To(Equal("test-ns"))
names = append(names, key.Name)
}
Expect(names).To(ConsistOf("minio-store", "archive-store", "monthly-store"))
})
It("do not receive anything on their own", func() {
cfg := newConfiguration(map[string]string{
"barmanObjectName": "minio-store",
"additionalBarmanObjectNames": "archive-store",
})
Expect(cfg.GetBarmanObjectKey().Name).To(Equal("minio-store"))
})
})

View File

@ -56,6 +56,12 @@ var _ = Describe("Backup and restore", func() {
ctx SpecContext,
factory testCaseFactory,
) {
switch factory.(type) {
case *gcsBackupPluginBackupPluginRestore, *gcsBackupPluginBackupInTreeRestore,
*gcsBackupPluginInTreeBackupPluginRestore:
Skip("GCS e2e tests are disabled until https://github.com/EnterpriseDB/barman/issues/1218 is fixed")
}
testResources := factory.createBackupRestoreTestResources(namespace.Name)
By("starting the ObjectStore deployment")

View File

@ -57,6 +57,11 @@ var _ = Describe("Replica cluster", func() {
ctx SpecContext,
factory testCaseFactory,
) {
switch factory.(type) {
case gcsReplicaClusterFactory:
Skip("GCS e2e tests are disabled until https://github.com/EnterpriseDB/barman/issues/1218 is fixed")
}
testResources := factory.createReplicaClusterTestResources(namespace.Name)
By("starting the ObjectStore deployments")