mirror of
https://github.com/cloudnative-pg/plugin-barman-cloud.git
synced 2026-09-06 06:52:21 +02:00
Compare commits
4 Commits
102739803d
...
2608cdc5f6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2608cdc5f6 | ||
|
|
65b028003e | ||
|
|
7bcb74b987 | ||
|
|
1d777435d0 |
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@ -6,12 +6,15 @@ on:
|
||||
|
||||
permissions: read-all
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ${{ vars.CI_RUNNERS || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
packages: write
|
||||
contents: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Cleanup Disk
|
||||
if: vars.CI_RUNNERS == '' || vars.CI_RUNNERS == 'ubuntu-latest'
|
||||
@ -39,8 +42,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Install QEMU static binaries
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v3.0.0
|
||||
- name: Install Dagger
|
||||
@ -52,6 +53,27 @@ jobs:
|
||||
- name: Run CI task
|
||||
run: |
|
||||
task ci
|
||||
|
||||
publish:
|
||||
runs-on: ${{ vars.CI_RUNNERS || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Install QEMU static binaries
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v3.0.0
|
||||
- name: Install Dagger
|
||||
env:
|
||||
# renovate: datasource=github-tags depName=dagger/dagger versioning=semver
|
||||
DAGGER_VERSION: 0.21.7
|
||||
run: |
|
||||
curl -L https://dl.dagger.io/dagger/install.sh | BIN_DIR=$HOME/.local/bin sh
|
||||
- name: Write manifest
|
||||
run: |
|
||||
task manifest
|
||||
|
||||
@ -156,13 +156,17 @@ func (w WALServiceImplementation) Archive(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Step 2: Check if the archive location is safe to perform archiving
|
||||
checkFileExisting, err := fileutils.FileExists(emptyWalArchiveFile)
|
||||
// Step 2: Check if the archive location is safe to perform archiving.
|
||||
checkEmptyWalArchive, err := resolveArchiveEmptyWalArchiveCheck(
|
||||
request.CheckEmptyWalArchive,
|
||||
configuration.Cluster,
|
||||
emptyWalArchiveFile,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while checking for empty wal archive check file %q: %w", emptyWalArchiveFile, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if utils.IsEmptyWalArchiveCheckEnabled(&configuration.Cluster.ObjectMeta) && checkFileExisting {
|
||||
if checkEmptyWalArchive {
|
||||
if err := CheckBackupDestination(
|
||||
ctx,
|
||||
&objectStore.Spec.Configuration,
|
||||
@ -224,6 +228,31 @@ func (w WALServiceImplementation) Archive(
|
||||
return &wal.WALArchiveResult{}, nil
|
||||
}
|
||||
|
||||
// resolveArchiveEmptyWalArchiveCheck reports whether the WAL archive
|
||||
// destination must be verified before archiving this segment.
|
||||
//
|
||||
// The operator owns the marker file's lifecycle, so when it sets the decision
|
||||
// (non-nil) that value already accounts for the marker and is obeyed as-is. A
|
||||
// nil value comes from an operator that predates this field, so we fall back to
|
||||
// the previous logic: the Cluster annotation combined with the on-disk marker
|
||||
// file.
|
||||
func resolveArchiveEmptyWalArchiveCheck(
|
||||
operatorDecision *bool,
|
||||
cluster *cnpgv1.Cluster,
|
||||
markerFilePath string,
|
||||
) (bool, error) {
|
||||
if operatorDecision != nil {
|
||||
return *operatorDecision, nil
|
||||
}
|
||||
|
||||
markerFilePresent, err := fileutils.FileExists(markerFilePath)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("while checking for empty wal archive check file %q: %w", markerFilePath, err)
|
||||
}
|
||||
|
||||
return utils.IsEmptyWalArchiveCheckEnabled(&cluster.ObjectMeta) && markerFilePresent, nil
|
||||
}
|
||||
|
||||
// Restore implements the WALService interface
|
||||
func (w WALServiceImplementation) Restore(
|
||||
ctx context.Context,
|
||||
|
||||
@ -21,15 +21,20 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
|
||||
barmanRestorer "github.com/cloudnative-pg/barman-cloud/pkg/restorer"
|
||||
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
|
||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
|
||||
)
|
||||
|
||||
var _ = Describe("resolveRestoreObjectStore", func() {
|
||||
@ -177,3 +182,65 @@ var _ = Describe("clearEndOfWALStreamFlag", func() {
|
||||
Expect(isEOS).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("resolveArchiveEmptyWalArchiveCheck", func() {
|
||||
// skipAnnotation mirrors the unexported constant in cloudnative-pg's
|
||||
// pkg/utils; hard-coding the literal makes a divergence surface as a
|
||||
// failing test rather than silently disabling the check.
|
||||
const skipAnnotation = "cnpg.io/skipEmptyWalArchiveCheck"
|
||||
|
||||
clusterWith := func(annotationValue *string) *cnpgv1.Cluster {
|
||||
cluster := &cnpgv1.Cluster{}
|
||||
if annotationValue != nil {
|
||||
cluster.Annotations = map[string]string{skipAnnotation: *annotationValue}
|
||||
}
|
||||
return cluster
|
||||
}
|
||||
|
||||
// markerPath returns the marker file path inside a fresh temp dir,
|
||||
// creating the file there when present is true.
|
||||
markerPath := func(present bool) string {
|
||||
filePath := filepath.Join(GinkgoT().TempDir(), metadata.CheckEmptyWalArchiveFile)
|
||||
if present {
|
||||
Expect(os.WriteFile(filePath, []byte{}, 0o600)).To(Succeed())
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
When("the operator sets the decision", func() {
|
||||
It("obeys true, ignoring the annotation and the marker file", func() {
|
||||
// annotation would skip the check and the marker is absent, yet the
|
||||
// operator's explicit true must still win.
|
||||
got, err := resolveArchiveEmptyWalArchiveCheck(
|
||||
ptr.To(true), clusterWith(ptr.To("enabled")), markerPath(false))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(got).To(BeTrue())
|
||||
})
|
||||
|
||||
It("obeys false, ignoring the annotation and the marker file", func() {
|
||||
// annotation would keep the check on and the marker is present, yet the
|
||||
// operator's explicit false must still win.
|
||||
got, err := resolveArchiveEmptyWalArchiveCheck(
|
||||
ptr.To(false), clusterWith(nil), markerPath(true))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(got).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
When("the operator predates the field (nil decision)", func() {
|
||||
DescribeTable(
|
||||
"falls back to the annotation combined with the marker file",
|
||||
func(annotationValue *string, markerPresent bool, expected bool) {
|
||||
got, err := resolveArchiveEmptyWalArchiveCheck(
|
||||
nil, clusterWith(annotationValue), markerPath(markerPresent))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(got).To(Equal(expected))
|
||||
},
|
||||
Entry("no annotation and marker present: check runs", nil, true, true),
|
||||
Entry("no annotation and marker absent: check skipped", nil, false, false),
|
||||
Entry("opt-out annotation and marker present: check skipped", ptr.To("enabled"), true, false),
|
||||
Entry("unrelated annotation value and marker present: check runs", ptr.To("something-else"), true, true),
|
||||
Entry("empty annotation value and marker present: check runs", ptr.To(""), true, true),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -113,6 +113,7 @@ func (impl JobHookImpl) Restore(
|
||||
configuration.Cluster,
|
||||
&targetObjectStore.Spec.Configuration,
|
||||
targetObjectStore.Name,
|
||||
req.CheckEmptyWalArchive,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -250,6 +251,7 @@ func (impl *JobHookImpl) checkBackupDestination(
|
||||
cluster *cnpgv1.Cluster,
|
||||
barmanConfiguration *cnpgv1.BarmanObjectStoreConfiguration,
|
||||
objectStoreName string,
|
||||
operatorCheckEmptyWalArchive *bool,
|
||||
) error {
|
||||
// Get environment from cache
|
||||
env, err := barmanCredentials.EnvSetCloudCredentialsAndCertificates(ctx,
|
||||
@ -288,14 +290,26 @@ func (impl *JobHookImpl) checkBackupDestination(
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're ok to archive in the desired destination
|
||||
if utils.IsEmptyWalArchiveCheckEnabled(&cluster.ObjectMeta) {
|
||||
if resolveRestoreEmptyWalArchiveCheck(operatorCheckEmptyWalArchive, cluster) {
|
||||
return common.CheckBackupDestination(ctx, barmanConfiguration, walArchiver, serverName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveRestoreEmptyWalArchiveCheck reports whether the destination must be
|
||||
// verified before restoring. When the operator sets the decision (non-nil) it
|
||||
// is obeyed as-is; a nil value comes from an operator that predates this field,
|
||||
// so we fall back to the Cluster annotation. Unlike archiving, restore is a
|
||||
// one-shot operation that has never been gated on the first-archive marker
|
||||
// file, so the annotation is the only fallback needed.
|
||||
func resolveRestoreEmptyWalArchiveCheck(operatorDecision *bool, cluster *cnpgv1.Cluster) bool {
|
||||
if operatorDecision != nil {
|
||||
return *operatorDecision
|
||||
}
|
||||
return utils.IsEmptyWalArchiveCheckEnabled(&cluster.ObjectMeta)
|
||||
}
|
||||
|
||||
// restoreCustomWalDir moves the current pg_wal data to the specified custom wal dir and applies the symlink
|
||||
// returns indicating if any changes were made and any error encountered in the process
|
||||
func (impl JobHookImpl) restoreCustomWalDir(ctx context.Context) (bool, error) {
|
||||
|
||||
66
internal/cnpgi/restore/restore_test.go
Normal file
66
internal/cnpgi/restore/restore_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright © contributors to CloudNativePG, established as
|
||||
CloudNativePG a Series of LF Projects, LLC.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package restore
|
||||
|
||||
import (
|
||||
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("resolveRestoreEmptyWalArchiveCheck", func() {
|
||||
// skipAnnotation mirrors the unexported constant in cloudnative-pg's
|
||||
// pkg/utils; hard-coding the literal makes a divergence surface as a
|
||||
// failing test rather than silently disabling the check.
|
||||
const skipAnnotation = "cnpg.io/skipEmptyWalArchiveCheck"
|
||||
|
||||
clusterWith := func(annotationValue *string) *cnpgv1.Cluster {
|
||||
cluster := &cnpgv1.Cluster{}
|
||||
if annotationValue != nil {
|
||||
cluster.Annotations = map[string]string{skipAnnotation: *annotationValue}
|
||||
}
|
||||
return cluster
|
||||
}
|
||||
|
||||
When("the operator sets the decision", func() {
|
||||
It("obeys true even when the annotation would skip the check", func() {
|
||||
Expect(resolveRestoreEmptyWalArchiveCheck(ptr.To(true), clusterWith(ptr.To("enabled")))).To(BeTrue())
|
||||
})
|
||||
|
||||
It("obeys false even when the annotation would keep the check on", func() {
|
||||
Expect(resolveRestoreEmptyWalArchiveCheck(ptr.To(false), clusterWith(nil))).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
When("the operator predates the field (nil decision)", func() {
|
||||
DescribeTable(
|
||||
"falls back to the Cluster annotation, never to a marker file",
|
||||
func(annotationValue *string, expected bool) {
|
||||
Expect(resolveRestoreEmptyWalArchiveCheck(nil, clusterWith(annotationValue))).To(Equal(expected))
|
||||
},
|
||||
Entry("no annotation: check runs", nil, true),
|
||||
Entry("opt-out annotation: check skipped", ptr.To("enabled"), false),
|
||||
Entry("unrelated annotation value: check runs", ptr.To("something-else"), true),
|
||||
Entry("empty annotation value: check runs", ptr.To(""), true),
|
||||
)
|
||||
})
|
||||
})
|
||||
32
internal/cnpgi/restore/suite_test.go
Normal file
32
internal/cnpgi/restore/suite_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright © contributors to CloudNativePG, established as
|
||||
CloudNativePG a Series of LF Projects, LLC.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package restore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Restore job hook test suite")
|
||||
}
|
||||
@ -7970,9 +7970,9 @@ rc@1.2.8:
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-dom@^19.0.0:
|
||||
version "19.2.7"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.7.tgz#0450dc9ae9ddbff76ef196401cd8b8c7fb466ccc"
|
||||
integrity sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==
|
||||
version "19.2.8"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.8.tgz#3b46b9eeda877cdff2cf13d2770fff4ae36c2ec2"
|
||||
integrity sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==
|
||||
dependencies:
|
||||
scheduler "^0.27.0"
|
||||
|
||||
@ -8052,9 +8052,9 @@ react-router@5.3.4, react-router@^5.3.4:
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react@^19.0.0:
|
||||
version "19.2.7"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-19.2.7.tgz#1f47a1bfc06f8ec885752c6f4af14369a9f8260b"
|
||||
integrity sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==
|
||||
version "19.2.8"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-19.2.8.tgz#a80663dbb58d69c6fe3fd291d3cb324e8a7dff2d"
|
||||
integrity sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==
|
||||
|
||||
readable-stream@^2.0.1:
|
||||
version "2.3.8"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user