From f617199749351344b01d4e66bf31b9e9e292e3ec Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 2 Jul 2024 09:49:43 -0500 Subject: [PATCH 01/45] Use Postgres 16 in integration tests --- testing/kuttl/e2e/backup/00--cluster.yaml | 2 +- testing/kuttl/e2e/create/00--create_cluster.yaml | 2 +- testing/kuttl/e2e/create/00-assert.yaml | 2 +- testing/kuttl/e2e/delete-cluster/00--cluster.yaml | 2 +- testing/kuttl/e2e/restore/00--cluster.yaml | 2 +- testing/kuttl/e2e/show/00--cluster.yaml | 2 +- testing/kuttl/e2e/start-stop/00--cluster.yaml | 2 +- testing/kuttl/e2e/support-export/00--create_cluster.yaml | 2 +- testing/kuttl/e2e/support-export/00-assert.yaml | 2 +- .../e2e/support-export/30--create_cluster_with_monitoring.yaml | 2 +- testing/kuttl/e2e/support-export/30-assert.yaml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/testing/kuttl/e2e/backup/00--cluster.yaml b/testing/kuttl/e2e/backup/00--cluster.yaml index 72b60fb0..cf731e8e 100644 --- a/testing/kuttl/e2e/backup/00--cluster.yaml +++ b/testing/kuttl/e2e/backup/00--cluster.yaml @@ -3,7 +3,7 @@ kind: PostgresCluster metadata: name: backup-cluster spec: - postgresVersion: 14 + postgresVersion: 16 instances: - name: instance1 dataVolumeClaimSpec: diff --git a/testing/kuttl/e2e/create/00--create_cluster.yaml b/testing/kuttl/e2e/create/00--create_cluster.yaml index e1b1c6d9..41e83518 100644 --- a/testing/kuttl/e2e/create/00--create_cluster.yaml +++ b/testing/kuttl/e2e/create/00--create_cluster.yaml @@ -2,4 +2,4 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: -- script: kubectl-pgo --namespace $NAMESPACE create postgrescluster --pg-major-version 14 kuttl-create-cluster +- script: kubectl-pgo --namespace $NAMESPACE create postgrescluster --pg-major-version 16 kuttl-create-cluster diff --git a/testing/kuttl/e2e/create/00-assert.yaml b/testing/kuttl/e2e/create/00-assert.yaml index 40b260c0..32bf3b3c 100644 --- a/testing/kuttl/e2e/create/00-assert.yaml +++ b/testing/kuttl/e2e/create/00-assert.yaml @@ -22,7 +22,7 @@ spec: requests: storage: 1Gi replicas: 1 - postgresVersion: 14 + postgresVersion: 16 status: instances: - name: "00" diff --git a/testing/kuttl/e2e/delete-cluster/00--cluster.yaml b/testing/kuttl/e2e/delete-cluster/00--cluster.yaml index 6b6c7edd..34688cb0 100644 --- a/testing/kuttl/e2e/delete-cluster/00--cluster.yaml +++ b/testing/kuttl/e2e/delete-cluster/00--cluster.yaml @@ -3,7 +3,7 @@ kind: PostgresCluster metadata: name: delete-cluster spec: - postgresVersion: 14 + postgresVersion: 16 instances: - name: instance1 dataVolumeClaimSpec: diff --git a/testing/kuttl/e2e/restore/00--cluster.yaml b/testing/kuttl/e2e/restore/00--cluster.yaml index 873d8cd8..e1d5ec3c 100644 --- a/testing/kuttl/e2e/restore/00--cluster.yaml +++ b/testing/kuttl/e2e/restore/00--cluster.yaml @@ -4,7 +4,7 @@ kind: PostgresCluster metadata: name: restore-cluster spec: - postgresVersion: 14 + postgresVersion: 16 instances: - name: instance1 dataVolumeClaimSpec: diff --git a/testing/kuttl/e2e/show/00--cluster.yaml b/testing/kuttl/e2e/show/00--cluster.yaml index fa20b575..c1377673 100644 --- a/testing/kuttl/e2e/show/00--cluster.yaml +++ b/testing/kuttl/e2e/show/00--cluster.yaml @@ -3,7 +3,7 @@ kind: PostgresCluster metadata: name: show-cluster spec: - postgresVersion: 14 + postgresVersion: 16 instances: - name: instance1 dataVolumeClaimSpec: diff --git a/testing/kuttl/e2e/start-stop/00--cluster.yaml b/testing/kuttl/e2e/start-stop/00--cluster.yaml index bd95640a..ac0a3baf 100644 --- a/testing/kuttl/e2e/start-stop/00--cluster.yaml +++ b/testing/kuttl/e2e/start-stop/00--cluster.yaml @@ -4,7 +4,7 @@ metadata: name: start-stop-cluster spec: shutdown: false - postgresVersion: 14 + postgresVersion: 16 instances: - name: instance1 dataVolumeClaimSpec: diff --git a/testing/kuttl/e2e/support-export/00--create_cluster.yaml b/testing/kuttl/e2e/support-export/00--create_cluster.yaml index f9ce1f6d..36365efd 100644 --- a/testing/kuttl/e2e/support-export/00--create_cluster.yaml +++ b/testing/kuttl/e2e/support-export/00--create_cluster.yaml @@ -2,4 +2,4 @@ apiVersion: kuttl.dev/v1beta1 kind: TestStep commands: -- script: kubectl-pgo --namespace $NAMESPACE create postgrescluster --pg-major-version 14 kuttl-support-cluster +- script: kubectl-pgo --namespace $NAMESPACE create postgrescluster --pg-major-version 16 kuttl-support-cluster diff --git a/testing/kuttl/e2e/support-export/00-assert.yaml b/testing/kuttl/e2e/support-export/00-assert.yaml index a0c3f91c..09b14410 100644 --- a/testing/kuttl/e2e/support-export/00-assert.yaml +++ b/testing/kuttl/e2e/support-export/00-assert.yaml @@ -22,7 +22,7 @@ spec: requests: storage: 1Gi replicas: 1 - postgresVersion: 14 + postgresVersion: 16 status: instances: - name: "00" diff --git a/testing/kuttl/e2e/support-export/30--create_cluster_with_monitoring.yaml b/testing/kuttl/e2e/support-export/30--create_cluster_with_monitoring.yaml index d16b9b2d..f782d013 100644 --- a/testing/kuttl/e2e/support-export/30--create_cluster_with_monitoring.yaml +++ b/testing/kuttl/e2e/support-export/30--create_cluster_with_monitoring.yaml @@ -3,7 +3,7 @@ kind: PostgresCluster metadata: name: kuttl-support-monitoring-cluster spec: - postgresVersion: 14 + postgresVersion: 16 instances: - dataVolumeClaimSpec: accessModes: [ReadWriteOnce] diff --git a/testing/kuttl/e2e/support-export/30-assert.yaml b/testing/kuttl/e2e/support-export/30-assert.yaml index 83f1f5fd..51aa48a9 100644 --- a/testing/kuttl/e2e/support-export/30-assert.yaml +++ b/testing/kuttl/e2e/support-export/30-assert.yaml @@ -22,7 +22,7 @@ spec: requests: storage: 1Gi replicas: 1 - postgresVersion: 14 + postgresVersion: 16 status: instances: - name: "00" From 6670195dc396a1141dc9508693e305ca44aa2ea5 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Tue, 2 Jul 2024 12:02:10 -0500 Subject: [PATCH 02/45] Accept longer validation error messages --- testing/kuttl/e2e/restore/01--validation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/kuttl/e2e/restore/01--validation.yaml b/testing/kuttl/e2e/restore/01--validation.yaml index 54df24fa..ea44869e 100644 --- a/testing/kuttl/e2e/restore/01--validation.yaml +++ b/testing/kuttl/e2e/restore/01--validation.yaml @@ -15,7 +15,7 @@ commands: } case "${RESULT}" in - *'Error:'*'repoName: Required value') + *'Error:'*'repoName: Required value'*) ;; *) echo "Expected an error message, got:" From e26a831b3c46a5496db76d185dc842f6b32fdecc Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Mon, 1 Jul 2024 17:47:11 -0500 Subject: [PATCH 03/45] Explicitly ignore errors when writing to stdout --- internal/cmd/restore.go | 8 ++++---- internal/util/interactions.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/cmd/restore.go b/internal/cmd/restore.go index fc4b8bd9..4465430e 100644 --- a/internal/cmd/restore.go +++ b/internal/cmd/restore.go @@ -196,12 +196,12 @@ func (config pgBackRestRestore) Run(ctx context.Context) error { config.Patch.PatchOptions(patchOptions)) if err != nil { if apierrors.IsConflict(err) { - fmt.Fprintf(config.Out, "SUGGESTION: The --force-conflicts flag may help in performing this operation.\n") + _, _ = fmt.Fprintf(config.Out, "SUGGESTION: The --force-conflicts flag may help in performing this operation.\n") } return err } - fmt.Fprintf(config.Out, + _, _ = fmt.Fprintf(config.Out, "WARNING: You are about to restore from pgBackRest with %+v\n"+ "WARNING: This action is destructive and PostgreSQL will be"+ " unavailable while its data is restored.\n\n"+ @@ -224,7 +224,7 @@ func (config pgBackRestRestore) Run(ctx context.Context) error { config.Patch.PatchOptions(patchOptions)) if err == nil { - fmt.Fprintf(config.Out, "%s/%s patched\n", + _, _ = fmt.Fprintf(config.Out, "%s/%s patched\n", mapping.Resource.Resource, config.PostgresCluster) } @@ -318,7 +318,7 @@ func (config pgBackRestRestoreDisable) Run(ctx context.Context) error { } if err == nil { - fmt.Fprintf(config.Out, "%s/%s patched\n", + _, _ = fmt.Fprintf(config.Out, "%s/%s patched\n", mapping.Resource.Resource, config.PostgresCluster) } diff --git a/internal/util/interactions.go b/internal/util/interactions.go index ffc4c1b8..be29127d 100644 --- a/internal/util/interactions.go +++ b/internal/util/interactions.go @@ -37,7 +37,7 @@ func Confirm(reader io.Reader, writer io.Writer) *bool { } if scanner.Err() != nil || response == "" { - fmt.Fprint(writer, "Please type yes or no and then press enter: ") + _, _ = fmt.Fprint(writer, "Please type yes or no and then press enter: ") return nil } @@ -49,7 +49,7 @@ func Confirm(reader io.Reader, writer io.Writer) *bool { } else if slices.Contains(noResponses, response) { return &boolVar } else { - fmt.Fprint(writer, "Please type yes or no and then press enter: ") + _, _ = fmt.Fprint(writer, "Please type yes or no and then press enter: ") return nil } } From 827821a2a12509b7406710f52d0d00161734c50a Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Mon, 1 Jul 2024 17:47:11 -0500 Subject: [PATCH 04/45] Return errors when writing to tabwriter --- internal/cmd/export.go | 61 ++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index f17aeabc..7113ac83 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -669,9 +669,12 @@ func gatherNodes(ctx context.Context, var buf bytes.Buffer w := tabwriter.NewWriter(&buf, 10, 1, 1, ' ', tabwriter.Debug) - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "NAME", - "STATUS", "ROLES", "AGE", "VERSION", "INTERNAL-IP", "EXTERNAL-IP", - "OS-IMAGE", "KERNEL-VERSION", "CONTAINER-RUNTIME") + if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + "NAME", "STATUS", "ROLES", "AGE", "VERSION", "INTERNAL-IP", "EXTERNAL-IP", + "OS-IMAGE", "KERNEL-VERSION", "CONTAINER-RUNTIME", + ); err != nil { + return err + } for _, item := range list.Items { @@ -685,38 +688,28 @@ func gatherNodes(ctx context.Context, return err } - // NAME - fmt.Fprintf(w, "%s\t", item.GetName()) - - // STATUS + var status string for _, c := range item.Status.Conditions { if c.Type == "Ready" { if c.Status == "True" { - fmt.Fprintf(w, "%s\t", "Ready") + status = "Ready" } else { - fmt.Fprintf(w, "%s\t", "Not Ready") + status = "Not Ready" } } } - // ROLES rolePrefix := "node-role.kubernetes.io/" + var roles string for k := range item.Labels { if strings.Contains(k, rolePrefix) { sa := strings.Split(k, rolePrefix) if len(sa) > 1 { - fmt.Fprintf(w, "%s\t", sa[1]) + roles = sa[1] } } } - // AGE - fmt.Fprintf(w, "%s\t", translateTimestampSince(item.CreationTimestamp)) - - // VERSION - fmt.Fprintf(w, "%s\t", item.Status.NodeInfo.KubeletVersion) - - // INTERNAL-IP and EXTERNAL-IP var internalIP = "" var externalIP = "" for _, a := range item.Status.Addresses { @@ -727,18 +720,18 @@ func gatherNodes(ctx context.Context, externalIP = a.Address } } - fmt.Fprintf(w, "%s\t", internalIP) - fmt.Fprintf(w, "%s\t", externalIP) - - // OS-IMAGE - fmt.Fprintf(w, "%s\t", item.Status.NodeInfo.OSImage) - - // KERNEL-VERSION - fmt.Fprintf(w, "%s\t", item.Status.NodeInfo.KernelVersion) - - // CONTAINER-RUNTIME - fmt.Fprintf(w, "%s\t\n", item.Status.NodeInfo.ContainerRuntimeVersion) + if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + item.GetName(), status, roles, + translateTimestampSince(item.CreationTimestamp), + item.Status.NodeInfo.KubeletVersion, + internalIP, externalIP, + item.Status.NodeInfo.OSImage, + item.Status.NodeInfo.KernelVersion, + item.Status.NodeInfo.ContainerRuntimeVersion, + ); err != nil { + return err + } } if err := w.Flush(); err != nil { return err @@ -907,7 +900,9 @@ func gatherEvents(ctx context.Context, // https://github.com/kubernetes/kubectl/blob/release-1.24/pkg/cmd/events/events.go#L262-L292 var buf bytes.Buffer p := printers.GetNewTabWriter(&buf) - fmt.Fprintf(p, "Last Seen\tTYPE\tREASON\tOBJECT\tMESSAGE\n") + if _, err := fmt.Fprintf(p, "Last Seen\tTYPE\tREASON\tOBJECT\tMESSAGE\n"); err != nil { + return err + } for _, event := range list.Items { var interval string firstTimestampSince := translateMicroTimestampSince(event.EventTime) @@ -920,13 +915,15 @@ func gatherEvents(ctx context.Context, interval = firstTimestampSince } - fmt.Fprintf(p, "%s\t%s\t%s\t%s/%s\t%v\n", + if _, err := fmt.Fprintf(p, "%s\t%s\t%s\t%s/%s\t%v\n", interval, event.Type, event.Reason, event.InvolvedObject.Kind, event.InvolvedObject.Name, strings.TrimSpace(event.Message), - ) + ); err != nil { + return err + } } if err := p.Flush(); err != nil { return err From acf6c75b59cfaf850364709f8ec3c6272693aeb4 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Mon, 1 Jul 2024 17:47:11 -0500 Subject: [PATCH 05/45] Bump GitHub action versions Issue: PGO-1283 See: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/ --- .github/workflows/lint.yaml | 6 +++--- .github/workflows/test.yaml | 12 ++++++------ .github/workflows/trivy-scan.yaml | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 009b53d0..18a9757b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -9,9 +9,9 @@ jobs: golangci-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: { go-version: 1.x } + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: { go-version: stable } - uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d2c7f5e5..b32858c0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,9 +12,9 @@ jobs: go-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: { go-version: 1.x } + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: { go-version: stable } - run: make check - run: make check-cli-docs @@ -27,9 +27,9 @@ jobs: matrix: kubernetes: [v1.27, v1.24, v1.21] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: { go-version: 1.x } + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: { go-version: stable } - name: Start k3s uses: ./.github/actions/k3d diff --git a/.github/workflows/trivy-scan.yaml b/.github/workflows/trivy-scan.yaml index ea3991ae..9ea21fff 100644 --- a/.github/workflows/trivy-scan.yaml +++ b/.github/workflows/trivy-scan.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Run trivy and log detected and fixed vulnerabilities # This report should match the uploaded code scan report below @@ -48,6 +48,6 @@ jobs: output: 'trivy-results.sarif' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' From 1d4e453affe4f5a4de74bcade6362173c66d718d Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Mon, 1 Jul 2024 17:47:11 -0500 Subject: [PATCH 06/45] Bump golangci/golangci-lint-action to v6 Issue: PGO-1283 --- .github/workflows/lint.yaml | 5 ++++- .golangci.yaml | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 18a9757b..b6d3a537 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,12 +8,15 @@ on: jobs: golangci-lint: runs-on: ubuntu-latest + permissions: + contents: read + checks: write steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: { go-version: stable } - - uses: golangci/golangci-lint-action@v3 + - uses: golangci/golangci-lint-action@v6 with: version: latest args: --timeout=5m diff --git a/.golangci.yaml b/.golangci.yaml index b00ad895..2a277b28 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,7 +3,6 @@ linters: disable: - gofumpt - - scopelint enable: - depguard - goheader From 9782c9b2d75a5cfbbc7992197bbd3199cb5437c9 Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Wed, 3 Jul 2024 17:25:23 -0400 Subject: [PATCH 07/45] Address Dependabot code scan alerts Issue: PGO-1284 --- go.mod | 10 +++++----- go.sum | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index c2155043..b6e40e57 100644 --- a/go.mod +++ b/go.mod @@ -56,14 +56,14 @@ require ( github.com/stretchr/testify v1.7.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/term v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f4e87dd8..84f651e0 100644 --- a/go.sum +++ b/go.sum @@ -611,8 +611,8 @@ golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -704,12 +704,12 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -719,8 +719,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -900,8 +900,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 78e0830a8a1eb51cb598c6aaf7222431adef8f33 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Tue, 10 Sep 2024 16:29:40 -0400 Subject: [PATCH 08/45] More support export logs (#102) * Get Postgres logs from Primary and Replicas * Get Postgres configuration files from PGDATA directory * Get pgBackRest logs from DB host(s) and Repo hosts * Store files in `pods/pod-name` directory * Rename PGO log from `cli` to `cli.log` --------- Co-authored-by: Philip Hurst --- internal/cmd/exec.go | 33 ++ internal/cmd/export.go | 374 ++++++++++++++++-- internal/cmd/show.go | 8 +- internal/cmd/stop.go | 2 +- internal/util/naming.go | 24 ++ .../support-export/01--support_export.yaml | 2 +- .../support-export/31--support_export.yaml | 4 +- 7 files changed, 404 insertions(+), 43 deletions(-) diff --git a/internal/cmd/exec.go b/internal/cmd/exec.go index 7ed15cd6..1d7d6efd 100644 --- a/internal/cmd/exec.go +++ b/internal/cmd/exec.go @@ -49,6 +49,39 @@ func (exec Executor) listPGLogFiles(numLogs int) (string, string, error) { return stdout.String(), stderr.String(), err } +// listPGConfFiles returns the full path of Postgres conf files. +// These are the *.conf stored on the Postgres instance +func (exec Executor) listPGConfFiles() (string, string, error) { + var stdout, stderr bytes.Buffer + + command := "ls -1dt pgdata/pg[0-9][0-9]/*.conf" + err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) + + return stdout.String(), stderr.String(), err +} + +// listBackrestLogFiles returns the full path of pgBackRest log files. +// These are the pgBackRest logs stored on the Postgres instance +func (exec Executor) listBackrestLogFiles() (string, string, error) { + var stdout, stderr bytes.Buffer + + command := "ls -1dt pgdata/pgbackrest/log/*" + err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) + + return stdout.String(), stderr.String(), err +} + +// listBackrestRepoHostLogFiles returns the full path of pgBackRest log files. +// These are the pgBackRest logs stored on the repo host +func (exec Executor) listBackrestRepoHostLogFiles() (string, string, error) { + var stdout, stderr bytes.Buffer + + command := "ls -1dt pgbackrest/*/log/*" + err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) + + return stdout.String(), stderr.String(), err +} + // catFile takes the full path of a file and returns the contents // of that file func (exec Executor) catFile(filePath string) (string, string, error) { diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 7113ac83..b27b6f48 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -446,12 +446,23 @@ Collecting PGO CLI logs... } // Logs + // All Postgres Logs on the Postgres Instances (primary and replicas) if numLogs > 0 { if err == nil { - err = gatherPostgresqlLogs(ctx, clientset, restConfig, namespace, clusterName, numLogs, tw, cmd) + err = gatherPostgresLogsAndConfigs(ctx, clientset, restConfig, namespace, clusterName, numLogs, tw, cmd) } } + // All pgBackRest Logs on the Postgres Instances + if err == nil { + err = gatherDbBackrestLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + } + + // All pgBackRest Logs on the Repo Host + if err == nil { + err = gatherRepoHostLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + } + // get PostgresCluster Pod logs if err == nil { writeInfo(cmd, "Collecting PostgresCluster pod logs...") @@ -516,7 +527,7 @@ Collecting PGO CLI logs... // Print cli output writeInfo(cmd, "Collecting PGO CLI logs...") - path := clusterName + "/logs/cli" + path := clusterName + "/cli.log" if logErr := writeTar(tw, cliOutput.Bytes(), path, cmd); logErr != nil { return logErr } @@ -937,8 +948,9 @@ func gatherEvents(ctx context.Context, return nil } -// gatherLogs takes a client and buffer to write logs to a buffer -func gatherPostgresqlLogs(ctx context.Context, +// gatherPostgresLogsAndConfigs take a client and writes logs and configs +// from primary and replicas to a buffer +func gatherPostgresLogsAndConfigs(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, namespace string, @@ -948,11 +960,11 @@ func gatherPostgresqlLogs(ctx context.Context, cmd *cobra.Command, ) error { writeInfo(cmd, "Collecting Postgres logs...") - // Get the primary instance Pod by its labels - pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ - // TODO(jmckulk): should we be getting replica logs? - LabelSelector: util.PrimaryInstanceLabels(clusterName), + + dbPods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: util.DBInstanceLabels(clusterName), }) + if err != nil { if apierrors.IsForbidden(err) { writeInfo(cmd, err.Error()) @@ -960,23 +972,161 @@ func gatherPostgresqlLogs(ctx context.Context, } return err } - if len(pods.Items) != 1 { - writeInfo(cmd, "No primary instance pod found for gathering logs") + + if len(dbPods.Items) == 0 { + writeInfo(cmd, "No database instance pod found for gathering logs and config") return nil } + writeDebug(cmd, fmt.Sprintf("Found %d Pods\n", len(dbPods.Items))) + podExec, err := util.NewPodExecutor(config) if err != nil { return err } - exec := func(stdin io.Reader, stdout, stderr io.Writer, command ...string, - ) error { - return podExec(namespace, pods.Items[0].GetName(), util.ContainerDatabase, - stdin, stdout, stderr, command...) + for _, pod := range dbPods.Items { + writeDebug(cmd, fmt.Sprintf("Pod Name is %s\n", pod.Name)) + + exec := func(stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + return podExec(namespace, pod.Name, util.ContainerDatabase, + stdin, stdout, stderr, command...) + } + + // Get Postgres Log Files + stdout, stderr, err := Executor(exec).listPGLogFiles(numLogs) + + // Depending upon the list* function above: + // An error may happen when err is non-nil or stderr is non-empty. + // In both cases, we want to print helpful information and continue to the + // next iteration. + if err != nil || stderr != "" { + + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + + writeDebug(cmd, "Error getting PG logs\n") + + if err != nil { + writeDebug(cmd, fmt.Sprintf("%s\n", err.Error())) + } + if stderr != "" { + writeDebug(cmd, stderr) + } + + if strings.Contains(stderr, "No such file or directory") { + writeDebug(cmd, "Cannot find any Postgres log files. This is acceptable in some configurations.\n") + } + continue + } + + logFiles := strings.Split(strings.TrimSpace(stdout), "\n") + for _, logFile := range logFiles { + writeDebug(cmd, fmt.Sprintf("LOG FILE: %s\n", logFile)) + var buf bytes.Buffer + + stdout, stderr, err := Executor(exec).catFile(logFile) + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + // Continue and output errors for each log file + // Allow the user to see and address all issues at once + continue + } + return err + } + + buf.Write([]byte(stdout)) + if stderr != "" { + str := fmt.Sprintf("\nError returned: %s\n", stderr) + buf.Write([]byte(str)) + } + + path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } + } + + // Get Postgres Conf Files + stdout, stderr, err = Executor(exec).listPGConfFiles() + + // Depending upon the list* function above: + // An error may happen when err is non-nil or stderr is non-empty. + // In both cases, we want to print helpful information and continue to the + // next iteration. + if err != nil || stderr != "" { + + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + + writeDebug(cmd, "Error getting PG Conf files\n") + + if err != nil { + writeDebug(cmd, fmt.Sprintf("%s\n", err.Error())) + } + if stderr != "" { + writeDebug(cmd, stderr) + } + + if strings.Contains(stderr, "No such file or directory") { + writeDebug(cmd, "Cannot find any PG Conf files. This is acceptable in some configurations.\n") + } + continue + } + + logFiles = strings.Split(strings.TrimSpace(stdout), "\n") + for _, logFile := range logFiles { + var buf bytes.Buffer + + stdout, stderr, err := Executor(exec).catFile(logFile) + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + // Continue and output errors for each log file + // Allow the user to see and address all issues at once + continue + } + return err + } + + buf.Write([]byte(stdout)) + if stderr != "" { + str := fmt.Sprintf("\nError returned: %s\n", stderr) + buf.Write([]byte(str)) + } + + path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } + } + } + return nil +} + +// gatherDbBackrestLogs gathers all the file-based pgBackRest logs on the DB instance. +// There may not be any logs depending upon pgBackRest's log-level-file. +func gatherDbBackrestLogs(ctx context.Context, + clientset *kubernetes.Clientset, + config *rest.Config, + namespace string, + clusterName string, + tw *tar.Writer, + cmd *cobra.Command, +) error { + writeInfo(cmd, "Collecting pgBackRest logs...") + + dbPods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: util.DBInstanceLabels(clusterName), + }) - stdout, stderr, err := Executor(exec).listPGLogFiles(numLogs) if err != nil { if apierrors.IsForbidden(err) { writeInfo(cmd, err.Error()) @@ -984,37 +1134,191 @@ func gatherPostgresqlLogs(ctx context.Context, } return err } - if stderr != "" { - writeInfo(cmd, stderr) + + if len(dbPods.Items) == 0 { + writeInfo(cmd, "No database instance pod found for gathering logs") + return nil } - logFiles := strings.Split(strings.TrimSpace(stdout), "\n") - for _, logFile := range logFiles { - var buf bytes.Buffer + writeDebug(cmd, fmt.Sprintf("Found %d Pods\n", len(dbPods.Items))) + + podExec, err := util.NewPodExecutor(config) + if err != nil { + return err + } + + for _, pod := range dbPods.Items { + writeDebug(cmd, fmt.Sprintf("Pod Name is %s\n", pod.Name)) + + exec := func(stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + return podExec(namespace, pod.Name, util.ContainerDatabase, + stdin, stdout, stderr, command...) + } + + // Get pgBackRest Log Files + stdout, stderr, err := Executor(exec).listBackrestLogFiles() + + // Depending upon the list* function above: + // An error may happen when err is non-nil or stderr is non-empty. + // In both cases, we want to print helpful information and continue to the + // next iteration. + if err != nil || stderr != "" { - stdout, stderr, err := Executor(exec).catFile(logFile) - if err != nil { if apierrors.IsForbidden(err) { writeInfo(cmd, err.Error()) - // Continue and output errors for each log file - // Allow the user to see and address all issues at once - continue + return nil } - return err + + writeDebug(cmd, "Error getting pgBackRest logs\n") + + if err != nil { + writeDebug(cmd, fmt.Sprintf("%s\n", err.Error())) + } + if stderr != "" { + writeDebug(cmd, stderr) + } + + if strings.Contains(stderr, "No such file or directory") { + writeDebug(cmd, "Cannot find any pgBackRest log files. This is acceptable in some configurations.\n") + } + continue } - buf.Write([]byte(stdout)) - if stderr != "" { - str := fmt.Sprintf("\nError returned: %s\n", stderr) - buf.Write([]byte(str)) + logFiles := strings.Split(strings.TrimSpace(stdout), "\n") + for _, logFile := range logFiles { + writeDebug(cmd, fmt.Sprintf("LOG FILE: %s\n", logFile)) + var buf bytes.Buffer + + stdout, stderr, err := Executor(exec).catFile(logFile) + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + // Continue and output errors for each log file + // Allow the user to see and address all issues at once + continue + } + return err + } + + buf.Write([]byte(stdout)) + if stderr != "" { + str := fmt.Sprintf("\nError returned: %s\n", stderr) + buf.Write([]byte(str)) + } + + path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } } - path := clusterName + "/logs/postgresql/" + logFile - if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { - return err + } + return nil +} + +// gatherRepoHostLogs gathers all the file-based pgBackRest logs on the repo host. +// There may not be any logs depending upon pgBackRest's log-level-file. +func gatherRepoHostLogs(ctx context.Context, + clientset *kubernetes.Clientset, + config *rest.Config, + namespace string, + clusterName string, + tw *tar.Writer, + cmd *cobra.Command, +) error { + writeInfo(cmd, "Collecting pgBackRest Repo Host logs...") + + repoHostPods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: util.RepoHostInstanceLabels(clusterName), + }) + + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil } + return err + } + + if len(repoHostPods.Items) == 0 { + writeInfo(cmd, "No Repo Host pod found for gathering logs") + } + + writeDebug(cmd, fmt.Sprintf("Found %d Repo Host Pod\n", len(repoHostPods.Items))) + + podExec, err := util.NewPodExecutor(config) + if err != nil { + return err } + for _, pod := range repoHostPods.Items { + writeDebug(cmd, fmt.Sprintf("Pod Name is %s\n", pod.Name)) + + exec := func(stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + return podExec(namespace, pod.Name, util.ContainerPGBackrest, + stdin, stdout, stderr, command...) + } + + // Get BackRest Repo Host Log Files + stdout, stderr, err := Executor(exec).listBackrestRepoHostLogFiles() + + // Depending upon the list* function above: + // An error may happen when err is non-nil or stderr is non-empty. + // In both cases, we want to print helpful information and continue to the + // next iteration. + if err != nil || stderr != "" { + + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + + writeDebug(cmd, "Error getting pgBackRest logs\n") + + if err != nil { + writeDebug(cmd, fmt.Sprintf("%s\n", err.Error())) + } + if stderr != "" { + writeDebug(cmd, stderr) + } + + if strings.Contains(stderr, "No such file or directory") { + writeDebug(cmd, "Cannot find any pgBackRest log files. This is acceptable in some configurations.\n") + } + continue + } + + logFiles := strings.Split(strings.TrimSpace(stdout), "\n") + for _, logFile := range logFiles { + writeDebug(cmd, fmt.Sprintf("LOG FILE: %s\n", logFile)) + var buf bytes.Buffer + + stdout, stderr, err := Executor(exec).catFile(logFile) + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + // Continue and output errors for each log file + // Allow the user to see and address all issues at once + continue + } + return err + } + + buf.Write([]byte(stdout)) + if stderr != "" { + str := fmt.Sprintf("\nError returned: %s\n", stderr) + buf.Write([]byte(str)) + } + + path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } + } + + } return nil } @@ -1070,8 +1374,8 @@ func gatherPodLogs(ctx context.Context, return err } - path := rootDir + "/logs/" + - pod.GetName() + "_" + container.Name + ".log" + path := rootDir + "/pods/" + + pod.GetName() + "/containers/" + container.Name + ".log" if err := writeTar(tw, b, path, cmd); err != nil { return err } diff --git a/internal/cmd/show.go b/internal/cmd/show.go index 3536004e..f8d015e9 100644 --- a/internal/cmd/show.go +++ b/internal/cmd/show.go @@ -95,7 +95,7 @@ HA if stdout, stderr, err := getBackup(config, args, "text", ""); err != nil { return err } else { - cmd.Printf(stdout) + cmd.Printf("%s", stdout) if stderr != "" { cmd.Printf("\nError returned: %s\n", stderr) } @@ -106,7 +106,7 @@ HA if stdout, stderr, err := getHA(config, args, "pretty"); err != nil { return err } else { - cmd.Printf(stdout) + cmd.Printf("%s", stdout) if stderr != "" { cmd.Printf("\nError returned: %s\n", stderr) } @@ -185,7 +185,7 @@ stanza: db stdout, stderr, err := getBackup(config, args, outputEnum.String(), repoNum) if err == nil { - cmd.Printf(stdout) + cmd.Printf("%s", stdout) if stderr != "" { cmd.Printf("\nError returned: %s\n", stderr) } @@ -257,7 +257,7 @@ pgo show ha hippo --output json stdout, stderr, err := getHA(config, args, outputEnum.String()) if err == nil { - cmd.Printf(stdout) + cmd.Printf("%s", stdout) if stderr != "" { cmd.Printf("\nError returned: %s\n", stderr) } diff --git a/internal/cmd/stop.go b/internal/cmd/stop.go index 0e3226af..9b38f592 100644 --- a/internal/cmd/stop.go +++ b/internal/cmd/stop.go @@ -91,7 +91,7 @@ postgresclusters/hippo stop initiated`) msg, err := patchClusterShutdown(cluster, client, requestArgs) if msg != "" { - cmd.Printf(msg) + cmd.Printf("%s", msg) } if err != nil { return err diff --git a/internal/util/naming.go b/internal/util/naming.go index 82c3ff99..3d5f538e 100644 --- a/internal/util/naming.go +++ b/internal/util/naming.go @@ -34,6 +34,9 @@ const ( // LabelOperator is used to identify operator Pods LabelOperator = "postgres-operator.crunchydata.com/control-plane" + + // LabelPGBackRestDedicated is used to identify the Repo Host pod + LabelPGBackRestDedicated = labelPrefix + "pgbackrest-dedicated" ) const ( @@ -41,6 +44,9 @@ const ( // DataPostgres is a LabelData value that indicates the object has PostgreSQL data. DataPostgres = "postgres" + + // DataBackrest is a LabelData value that indicate the object is a Repo Host. + DataBackrest = "pgbackrest" ) const ( @@ -50,6 +56,10 @@ const ( // currently the leader. RolePatroniLeader = "master" + // RolePatroniReplica is the LabelRole that Patroni sets on the Pod that is + // currently a replica. + RolePatroniReplica = "replica" + // RolePostgresUser is the LabelRole applied to PostgreSQL user secrets. RolePostgresUser = "pguser" ) @@ -60,8 +70,16 @@ const ( // ContainerDatabase is the name of the container running PostgreSQL and // supporting tools: Patroni, pgBackRest, etc. ContainerDatabase = "database" + + ContainerPGBackrest = "pgbackrest" ) +// DBInstanceLabels provides labels for a PostgreSQL cluster primary or replica instance +func DBInstanceLabels(clusterName string) string { + return LabelCluster + "=" + clusterName + "," + + LabelData + "=" + DataPostgres +} + // PrimaryInstanceLabels provides labels for a PostgreSQL cluster primary instance func PrimaryInstanceLabels(clusterName string) string { return LabelCluster + "=" + clusterName + "," + @@ -69,6 +87,12 @@ func PrimaryInstanceLabels(clusterName string) string { LabelRole + "=" + RolePatroniLeader } +// RepoHostInstanceLabels provides labels for a Backrest Repo Host instances +func RepoHostInstanceLabels(clusterName string) string { + return LabelCluster + "=" + clusterName + "," + + LabelPGBackRestDedicated + "=" +} + // PostgresUserSecretLabels provides labels for the Postgres user Secret func PostgresUserSecretLabels(clusterName string) string { return LabelCluster + "=" + clusterName + "," + diff --git a/testing/kuttl/e2e/support-export/01--support_export.yaml b/testing/kuttl/e2e/support-export/01--support_export.yaml index 3d1b0b46..d17dc89d 100644 --- a/testing/kuttl/e2e/support-export/01--support_export.yaml +++ b/testing/kuttl/e2e/support-export/01--support_export.yaml @@ -128,7 +128,7 @@ commands: fi # check that the PGO CLI log file contains expected messages - CLI_LOG="./kuttl-support-cluster/logs/cli" + CLI_LOG="./kuttl-support-cluster/cli.log" # info output includes expected heading if ! grep -Fq -- "- INFO - | PGO CLI Support Export Tool" $CLI_LOG diff --git a/testing/kuttl/e2e/support-export/31--support_export.yaml b/testing/kuttl/e2e/support-export/31--support_export.yaml index 89deff03..1b715877 100644 --- a/testing/kuttl/e2e/support-export/31--support_export.yaml +++ b/testing/kuttl/e2e/support-export/31--support_export.yaml @@ -6,8 +6,8 @@ commands: - script: tar -xzf ./crunchy_k8s_support_export_*.tar.gz - script: | CLEANUP="rm -r ./kuttl-support-monitoring-cluster ./monitoring ./crunchy_k8s_support_export_*.tar.gz" - CLUSTER_DIR="./kuttl-support-monitoring-cluster/logs/" - MONITORING_DIR="./monitoring/logs/" + CLUSTER_DIR="./kuttl-support-monitoring-cluster/pods/" + MONITORING_DIR="./monitoring/pods/" # check for exporter, prometheus, grafana and alertmanager logs found=$(find ${CLUSTER_DIR} -name "*exporter.log" | wc -l) From 3a83853550e29ec484f3e00b86c3b4de223ad6e6 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Fri, 13 Sep 2024 16:27:29 -0400 Subject: [PATCH 09/45] Pgbackrest check and pgcontroldata (#104) * add pgbackrest check to gatherPgBackRestInfo function * bashCommand function is a simple function to execute a one-line bash command in a container. * capture pg_controldata for each DB host --------- Co-authored-by: Philip Hurst Co-authored-by: Benjamin Blattberg --- internal/cmd/exec.go | 17 +++++++++++++ internal/cmd/export.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/internal/cmd/exec.go b/internal/cmd/exec.go index 1d7d6efd..9688b47a 100644 --- a/internal/cmd/exec.go +++ b/internal/cmd/exec.go @@ -39,6 +39,23 @@ func (exec Executor) pgBackRestInfo(output, repoNum string) (string, string, err return stdout.String(), stderr.String(), err } +// bashCommand defines a one-line bash command to exec in a container +func (exec Executor) bashCommand(command string) (string, string, error) { + var stdout, stderr bytes.Buffer + err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) + return stdout.String(), stderr.String(), err +} + +// pgBackRestCheck defines a pgBackRest check command +// Force log-level-console=detail to override if set elsewhere +func (exec Executor) pgBackRestCheck() (string, string, error) { + var stdout, stderr bytes.Buffer + command := "pgbackrest check --log-level-console=detail" + err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) + + return stdout.String(), stderr.String(), err +} + // postgresqlListLogFiles returns the full path of numLogs log files. func (exec Executor) listPGLogFiles(numLogs int) (string, string, error) { var stdout, stderr bytes.Buffer diff --git a/internal/cmd/export.go b/internal/cmd/export.go index b27b6f48..f9ca1a20 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -1107,6 +1107,45 @@ func gatherPostgresLogsAndConfigs(ctx context.Context, } } + // We will execute several bash commands in the DB container + // text is command to execute and desc is a short description + type Command struct { + path string + description string + } + + commands := []Command{ + {path: "pg_controldata", description: "pg_controldata"}, + } + + var buf bytes.Buffer + + for _, command := range commands { + stdout, stderr, err := Executor(exec).bashCommand(command.path) + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + writeDebug(cmd, fmt.Sprintf("Error executing %s\n", command.path)) + writeDebug(cmd, fmt.Sprintf("%s\n", err.Error())) + writeDebug(cmd, "This is acceptable in some configurations.\n") + continue + } + buf.Write([]byte(fmt.Sprintf("%s\n", command.description))) + buf.Write([]byte(stdout)) + if stderr != "" { + buf.Write([]byte(stderr)) + } + buf.Write([]byte("\n\n")) + } + + // Write the buffer to a file + path := clusterName + fmt.Sprintf("/pods/%s/%s", pod.Name, "postgres-info") + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } + } return nil } @@ -1518,6 +1557,21 @@ func gatherPgBackRestInfo(ctx context.Context, buf.Write([]byte(stderr)) } + buf.Write([]byte("pgbackrest check\n")) + stdout, stderr, err = Executor(exec).pgBackRestCheck() + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + return err + } + + buf.Write([]byte(stdout)) + if stderr != "" { + buf.Write([]byte(stderr)) + } + path := clusterName + "/pgbackrest-info" return writeTar(tw, buf.Bytes(), path, cmd) } From edbf76eea7589417fff8f06131aec0b81051df25 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Thu, 3 Oct 2024 12:42:53 -0400 Subject: [PATCH 10/45] Large postgres logs (#105) Support for large Postgres files Large Postgres files may pose a challenge for some systems. This change stores the file on disk, compares the filesize with the remote, and adds the local file to the tar. It has error handling to allow a support export to be generated when there are issues with gathering PG logs. * fixes for lint errors * Remove the cat logic for gathering PG logs. Only do streaming. * Create a unique sudirectory in outputDir (matching the unique timestamp) so that local files are logically separate from other files. Adding messaging and error handling so a user understands. * remove extra call to getRemoteFileSize * refactored getRemoteFileSize to use bashCommand to get the filesize * added copyFile function to cat a file contents to a local file * use the copyFile function rather calling manually * added example comment for fileSpecSrc * added comment to copyFile * rename ConvertBytes to convertBytes --------- Co-authored-by: Philip Hurst --- internal/cmd/exec.go | 10 ++ internal/cmd/export.go | 218 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 209 insertions(+), 19 deletions(-) diff --git a/internal/cmd/exec.go b/internal/cmd/exec.go index 9688b47a..2359e696 100644 --- a/internal/cmd/exec.go +++ b/internal/cmd/exec.go @@ -18,6 +18,7 @@ import ( "bytes" "fmt" "io" + "os" ) // Executor calls commands @@ -110,6 +111,15 @@ func (exec Executor) catFile(filePath string) (string, string, error) { return stdout.String(), stderr.String(), err } +// copyFile takes the full path of a file and a local destination to save the +// file on disk +func (exec Executor) copyFile(source string, destination *os.File) (string, error) { + var stderr bytes.Buffer + command := fmt.Sprintf("cat %s", source) + err := exec(nil, destination, &stderr, "bash", "-ceu", "--", command) + return stderr.String(), err +} + // patronictl takes a patronictl subcommand and returns the output of that command func (exec Executor) patronictl(cmd, output string) (string, string, error) { var stdout, stderr bytes.Buffer diff --git a/internal/cmd/export.go b/internal/cmd/export.go index f9ca1a20..0105352d 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -23,6 +23,8 @@ import ( "io" "os" "os/exec" + "path/filepath" + "strconv" "strings" "text/tabwriter" "time" @@ -449,7 +451,8 @@ Collecting PGO CLI logs... // All Postgres Logs on the Postgres Instances (primary and replicas) if numLogs > 0 { if err == nil { - err = gatherPostgresLogsAndConfigs(ctx, clientset, restConfig, namespace, clusterName, numLogs, tw, cmd) + err = gatherPostgresLogsAndConfigs(ctx, clientset, restConfig, + namespace, clusterName, outputDir, outputFile, numLogs, tw, cmd) } } @@ -955,6 +958,8 @@ func gatherPostgresLogsAndConfigs(ctx context.Context, config *rest.Config, namespace string, clusterName string, + outputDir string, + outputFile string, numLogs int, tw *tar.Writer, cmd *cobra.Command, @@ -1024,30 +1029,57 @@ func gatherPostgresLogsAndConfigs(ctx context.Context, } logFiles := strings.Split(strings.TrimSpace(stdout), "\n") - for _, logFile := range logFiles { - writeDebug(cmd, fmt.Sprintf("LOG FILE: %s\n", logFile)) - var buf bytes.Buffer - stdout, stderr, err := Executor(exec).catFile(logFile) + // localDirectory is created to save data on disk + // e.g. outputDir/crunchy_k8s_support_export_2022-08-08-115726-0400/remotePath + localDirectory := filepath.Join(outputDir, strings.ReplaceAll(outputFile, ".tar.gz", "")) + + // flag to determine whether or not to remove localDirectory after the loop + // When an error happens, this flag will switch to false + // It's nice to have the extra data around when errors have happened + doCleanup := true + + for _, logFile := range logFiles { + // get the file size to stream + fileSize, err := getRemoteFileSize(config, namespace, pod.Name, util.ContainerDatabase, logFile) if err != nil { - if apierrors.IsForbidden(err) { - writeInfo(cmd, err.Error()) - // Continue and output errors for each log file - // Allow the user to see and address all issues at once - continue - } - return err + writeDebug(cmd, fmt.Sprintf("could not get file size for %s: %v\n", logFile, err)) + continue } - buf.Write([]byte(stdout)) - if stderr != "" { - str := fmt.Sprintf("\nError returned: %s\n", stderr) - buf.Write([]byte(str)) + // fileSpecSrc is namespace/podname:path/to/file + // fileSpecDest is the local destination of the file + // These are used to help the user grab the file manually when necessary + // e.g. postgres-operator/hippo-instance1-vp9k-0:pgdata/pg16/log/postgresql-Tue.log + fileSpecSrc := fmt.Sprintf("%s/%s:%s", namespace, pod.Name, logFile) + fileSpecDest := filepath.Join(localDirectory, logFile) + writeInfo(cmd, fmt.Sprintf("\tSize of %-85s %v", fileSpecSrc, convertBytes(fileSize))) + + // Stream the file to disk and write the local file to the tar + err = streamFileFromPod(config, tw, + localDirectory, clusterName, namespace, pod.Name, util.ContainerDatabase, logFile, fileSize) + + if err != nil { + doCleanup = false // prevent the deletion of localDirectory so a user can examine contents + writeInfo(cmd, fmt.Sprintf("\tError streaming file %s: %v", logFile, err)) + writeInfo(cmd, fmt.Sprintf("\tCollect manually with kubectl cp -c %s %s %s", + util.ContainerDatabase, fileSpecSrc, fileSpecDest)) + writeInfo(cmd, fmt.Sprintf("\tRemove %s manually after gathering necessary information", localDirectory)) + continue } - path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile - if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { - return err + } + + // doCleanup is true when there are no errors above. + if doCleanup { + // Remove the local directory created to hold the data + // Errors in removing localDirectory should instruct the user to remove manually. + // This happens often on Windows + err = os.RemoveAll(localDirectory) + if err != nil { + writeInfo(cmd, fmt.Sprintf("\tError removing %s: %v", localDirectory, err)) + writeInfo(cmd, fmt.Sprintf("\tYou may need to remove %s manually", localDirectory)) + continue } } @@ -1800,3 +1832,151 @@ func writeDebug(cmd *cobra.Command, s string) { t := time.Now() cmd.Printf("%s - DEBUG - %s", t.Format(logTimeFormat), s) } + +// streamFileFromPod streams the file from the Kubernetes pod to a local file. +func streamFileFromPod(config *rest.Config, tw *tar.Writer, + localDirectory, clusterName, namespace, podName, containerName, remotePath string, + remoteFileSize int64) error { + + // create localPath to write the streamed data from remotePath + // use the uniqueness of outputFile to avoid overwriting other files + localPath := filepath.Join(localDirectory, remotePath) + if err := os.MkdirAll(filepath.Dir(localPath), 0750); err != nil { + return fmt.Errorf("failed to create path for file: %w", err) + } + outFile, err := os.Create(filepath.Clean(localPath)) + if err != nil { + return fmt.Errorf("failed to create local file: %w", err) + } + + defer func() { + // ignore any errors from Close functions, the writers will be + // closed when the program exits + if outFile != nil { + _ = outFile.Close() + } + }() + + // Get Postgres Log Files + podExec, err := util.NewPodExecutor(config) + if err != nil { + return err + } + exec := func(stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + return podExec(namespace, podName, containerName, + stdin, stdout, stderr, command...) + } + + _, err = Executor(exec).copyFile(remotePath, outFile) + if err != nil { + return fmt.Errorf("error during file streaming: %w", err) + } + + // compare file sizes + localFileInfo, err := os.Stat(localPath) + if err != nil { + return fmt.Errorf("failed to get file info: %w", err) + } + if remoteFileSize != localFileInfo.Size() { + return fmt.Errorf("filesize mismatch: remote size is %v and local size is %v", + remoteFileSize, localFileInfo.Size()) + } + + // add localPath to the support export tar + tarPath := fmt.Sprintf("%s/pods/%s/%s", clusterName, podName, remotePath) + err = addFileToTar(tw, localPath, tarPath) + if err != nil { + return fmt.Errorf("error writing to tar: %w", err) + } + + return nil +} + +// addFileToTar copies a local file into a tar archive +func addFileToTar(tw *tar.Writer, localPath, tarPath string) error { + // Open the file to be added to the tar + file, err := os.Open(filepath.Clean(localPath)) + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer func() { + // ignore any errors from Close functions, the writers will be + // closed when the program exits + if file != nil { + _ = file.Close() + } + }() + + // Get file info to create tar header + fileInfo, err := file.Stat() + if err != nil { + return fmt.Errorf("failed to get file info: %w", err) + } + + // Create tar header + header := &tar.Header{ + Name: tarPath, // Name in the tar archive + Size: fileInfo.Size(), // File size + Mode: int64(fileInfo.Mode()), // File mode + ModTime: fileInfo.ModTime(), // Modification time + } + + // Write header to the tar + err = tw.WriteHeader(header) + if err != nil { + return fmt.Errorf("failed to write tar header: %w", err) + } + + // Stream the file content to the tar + _, err = io.Copy(tw, file) + if err != nil { + return fmt.Errorf("failed to copy file data to tar: %w", err) + } + + return nil +} + +// getRemoteFileSize returns the size of a file within a container so that we can stream its contents +func getRemoteFileSize(config *rest.Config, + namespace string, podName string, containerName string, filePath string) (int64, error) { + + podExec, err := util.NewPodExecutor(config) + if err != nil { + return 0, fmt.Errorf("could not create executor: %w", err) + } + exec := func(stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + return podExec(namespace, podName, containerName, + stdin, stdout, stderr, command...) + } + + // Prepare the command to get the file size using "stat -c %s " + command := fmt.Sprintf("stat -c %s %s", "%s", filePath) + stdout, stderr, err := Executor(exec).bashCommand(command) + if err != nil { + return 0, fmt.Errorf("could not get file size: %w, stderr: %s", err, stderr) + } + + // Parse the file size from stdout + size, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to parse file size: %w", err) + } + + return size, nil +} + +// convertBytes converts a byte size (int64) into a human-readable format. +func convertBytes(bytes int64) string { + const unit = 1024 + if bytes < unit { + return fmt.Sprintf("%d B", bytes) + } + div, exp := int64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} From 7a8260886d452d918e8fc1718362dee002bc4937 Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Thu, 3 Oct 2024 11:57:10 -0500 Subject: [PATCH 11/45] Update test.yaml (#106) --- .github/workflows/test.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b32858c0..1b8e2c9c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -43,7 +43,10 @@ jobs: run: kubectl create namespace postgres-operator - name: Install Operator - run: helm install pgo oci://registry.developers.crunchydata.com/crunchydata/pgo -n postgres-operator + run: | + helm install pgo oci://registry.developers.crunchydata.com/crunchydata/pgo \ + -n postgres-operator \ + --set disable_check_for_upgrades=True - name: Use bash, so we can use double square-bracket [[ syntax in our scripts run: | From d41109faf024196fb48efec37c8c53bd0eb0c761 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Thu, 3 Oct 2024 13:37:46 -0400 Subject: [PATCH 12/45] adust LabelMonitoring to account for new label in PGO Monitoring stack (#107) Co-authored-by: Philip Hurst --- internal/util/naming.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/util/naming.go b/internal/util/naming.go index 3d5f538e..d280af1a 100644 --- a/internal/util/naming.go +++ b/internal/util/naming.go @@ -29,8 +29,9 @@ const ( // LabelRole is used to identify object roles. LabelRole = labelPrefix + "role" - // LabelMonitoring is used to identify monitoring Pods - LabelMonitoring = "app.kubernetes.io/name=postgres-operator-monitoring" + // LabelMonitoring is used to identify monitoring Pods. + // Older versions of PGO monitoring use the label 'postgres-operator-monitoring'. + LabelMonitoring = "app.kubernetes.io/name in (postgres-operator-monitoring,crunchy-monitoring)" // LabelOperator is used to identify operator Pods LabelOperator = "postgres-operator.crunchydata.com/control-plane" From 7c1c509750b7a68e0a30106db49eb94c8d091525 Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Thu, 3 Oct 2024 14:50:53 -0500 Subject: [PATCH 13/45] Create optional backups flag (#103) Make backups optional for create As of PGO v5.7, backups can now be disabled or enabled at any point in the postgrescluster's lifecycle. We don't want to bring that functionality entire over into the CLI, but we do want to start by allowing users to create a cluster without backups. Since this is a not recommended for production, we require confirmation. This should not block later changes that may allow users to disable/enable backups after creation. Issue: [PGO-1530] --- Makefile | 3 ++- .../reference/pgo_create_postgrescluster.md | 5 ++++ internal/cmd/create.go | 27 +++++++++++++++++++ .../00--create_cluster.yaml | 5 ++++ .../e2e/create-without-backups/00-errors.yaml | 20 ++++++++++++++ .../01--create_cluster.yaml | 5 ++++ .../e2e/create-without-backups/01-assert.yaml | 20 ++++++++++++++ .../e2e/create-without-backups/02-errors.yaml | 4 +++ 8 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 testing/kuttl/e2e/create-without-backups/00--create_cluster.yaml create mode 100644 testing/kuttl/e2e/create-without-backups/00-errors.yaml create mode 100644 testing/kuttl/e2e/create-without-backups/01--create_cluster.yaml create mode 100644 testing/kuttl/e2e/create-without-backups/01-assert.yaml create mode 100644 testing/kuttl/e2e/create-without-backups/02-errors.yaml diff --git a/Makefile b/Makefile index 42c42d69..0604eb8b 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ help: ## Display this help .PHONY: all all: check build +bin/kubectl-pgo-%: ## Build the binary bin/kubectl-pgo-%: go.* $(shell ls -1 cmd/**/*.go internal/**/*.go) GOOS=$(word 1,$(subst -, ,$*)) GOARCH=$(word 2,$(subst -, ,$*)) $(GO_BUILD) -o $@ ./cmd/kubectl-pgo @@ -43,7 +44,7 @@ build: bin/kubectl-pgo-$(subst $(eval) ,-,$(shell $(GO) env GOOS GOARCH)) ln -fs $(notdir $<) ./bin/kubectl-pgo .PHONY: check -check: +check: ## Run tests $(GO_TEST) -cover ./... # Expects operator to be running diff --git a/docs/content/reference/pgo_create_postgrescluster.md b/docs/content/reference/pgo_create_postgrescluster.md index 4c0a55ec..f877c78d 100644 --- a/docs/content/reference/pgo_create_postgrescluster.md +++ b/docs/content/reference/pgo_create_postgrescluster.md @@ -26,6 +26,10 @@ pgo create postgrescluster CLUSTER_NAME [flags] # Create a postgrescluster with Postgres 15 pgo create postgrescluster hippo --pg-major-version 15 +# Create a postgrescluster with backups disabled (only available in CPK v5.7+) +# Requires confirmation +pgo create postgrescluster hippo --disable-backups + ``` ### Example output ``` @@ -35,6 +39,7 @@ postgresclusters/hippo created ### Options ``` + --disable-backups Disable backups -h, --help help for postgrescluster --pg-major-version int Set the Postgres major version ``` diff --git a/internal/cmd/create.go b/internal/cmd/create.go index bc29a4ad..612585bb 100644 --- a/internal/cmd/create.go +++ b/internal/cmd/create.go @@ -17,6 +17,7 @@ package cmd import ( "context" "fmt" + "os" "strconv" "github.com/spf13/cobra" @@ -26,6 +27,7 @@ import ( "github.com/crunchydata/postgres-operator-client/internal" "github.com/crunchydata/postgres-operator-client/internal/apis/postgres-operator.crunchydata.com/v1beta1" + "github.com/crunchydata/postgres-operator-client/internal/util" ) // newCreateCommand returns the create subcommand of the PGO plugin. @@ -66,9 +68,16 @@ func newCreateClusterCommand(config *internal.Config) *cobra.Command { cmd.Flags().IntVar(&pgMajorVersion, "pg-major-version", 0, "Set the Postgres major version") cobra.CheckErr(cmd.MarkFlagRequired("pg-major-version")) + var backupsDisabled bool + cmd.Flags().BoolVar(&backupsDisabled, "disable-backups", false, "Disable backups") + cmd.Example = internal.FormatExample(`# Create a postgrescluster with Postgres 15 pgo create postgrescluster hippo --pg-major-version 15 +# Create a postgrescluster with backups disabled (only available in CPK v5.7+) +# Requires confirmation +pgo create postgrescluster hippo --disable-backups + ### Example output postgresclusters/hippo created`) @@ -92,6 +101,24 @@ postgresclusters/hippo created`) return err } + if backupsDisabled { + fmt.Print("WARNING: Running a production postgrescluster without backups " + + "is not recommended. \nAre you sure you want " + + "to continue without backups? (yes/no): ") + var confirmed *bool + for i := 0; confirmed == nil && i < 10; i++ { + // retry 10 times or until a confirmation is given or denied, + // whichever comes first + confirmed = util.Confirm(os.Stdin, os.Stdout) + } + + if confirmed == nil || !*confirmed { + return nil + } + + unstructured.RemoveNestedField(cluster.Object, "spec", "backups") + } + u, err := client. Namespace(namespace). Create(ctx, cluster, config.Patch.CreateOptions(metav1.CreateOptions{})) diff --git a/testing/kuttl/e2e/create-without-backups/00--create_cluster.yaml b/testing/kuttl/e2e/create-without-backups/00--create_cluster.yaml new file mode 100644 index 00000000..e73f82e9 --- /dev/null +++ b/testing/kuttl/e2e/create-without-backups/00--create_cluster.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- script: echo no | kubectl-pgo --namespace $NAMESPACE create postgrescluster --pg-major-version 16 --disable-backups created-without-backups diff --git a/testing/kuttl/e2e/create-without-backups/00-errors.yaml b/testing/kuttl/e2e/create-without-backups/00-errors.yaml new file mode 100644 index 00000000..9c1bd6b4 --- /dev/null +++ b/testing/kuttl/e2e/create-without-backups/00-errors.yaml @@ -0,0 +1,20 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +spec: + instances: + - dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + replicas: 1 + postgresVersion: 16 +status: + instances: + - name: "00" + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 diff --git a/testing/kuttl/e2e/create-without-backups/01--create_cluster.yaml b/testing/kuttl/e2e/create-without-backups/01--create_cluster.yaml new file mode 100644 index 00000000..de0af849 --- /dev/null +++ b/testing/kuttl/e2e/create-without-backups/01--create_cluster.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- script: echo yes | kubectl-pgo --namespace $NAMESPACE create postgrescluster --pg-major-version 16 --disable-backups created-without-backups diff --git a/testing/kuttl/e2e/create-without-backups/01-assert.yaml b/testing/kuttl/e2e/create-without-backups/01-assert.yaml new file mode 100644 index 00000000..9c1bd6b4 --- /dev/null +++ b/testing/kuttl/e2e/create-without-backups/01-assert.yaml @@ -0,0 +1,20 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: created-without-backups +spec: + instances: + - dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + replicas: 1 + postgresVersion: 16 +status: + instances: + - name: "00" + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 diff --git a/testing/kuttl/e2e/create-without-backups/02-errors.yaml b/testing/kuttl/e2e/create-without-backups/02-errors.yaml new file mode 100644 index 00000000..6ae43673 --- /dev/null +++ b/testing/kuttl/e2e/create-without-backups/02-errors.yaml @@ -0,0 +1,4 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: created-without-backups-repo-host From 3c044acbfa56e5302d22628a7cd98ec6f7096f4f Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Thu, 10 Oct 2024 14:53:37 +0000 Subject: [PATCH 14/45] Have Dependabot monitor GitHub Actions --- .github/dependabot.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..639a059e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/customizing-dependency-updates +# +# See: https://www.github.com/dependabot/dependabot-core/issues/4605 +--- +# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: tuesday + groups: + all-github-actions: + patterns: ['*'] From 44a855daea595b87425981938de607f65611c37b Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Fri, 20 Sep 2024 13:39:38 -0500 Subject: [PATCH 15/45] Replace license text with its SPDX identifier The SPDX identifier is easier to manage than boilerplate text and is recognized by tools that scan for license compliance. Issue: PGO-1564 See: https://reuse.software/ See: https://spdx.dev/learn/handling-license-info/ --- .golangci.yaml | 17 +++++------------ cmd/kubectl-pgo/main.go | 12 +----------- hack/boilerplate.go.txt | 12 +----------- hack/generate-docs.go | 12 +----------- .../v1beta1/groupversion_info.go | 12 +----------- .../v1beta1/postgrescluster_types.go | 12 +----------- internal/cmd/backup.go | 12 +----------- internal/cmd/backup_test.go | 12 +----------- internal/cmd/client_version.go | 12 +----------- internal/cmd/create.go | 12 +----------- internal/cmd/create_test.go | 12 +----------- internal/cmd/delete.go | 12 +----------- internal/cmd/exec.go | 12 +----------- internal/cmd/exec_test.go | 12 +----------- internal/cmd/export.go | 12 +----------- internal/cmd/export_test.go | 12 +----------- internal/cmd/pgo.go | 12 +----------- internal/cmd/pgo_test.go | 12 +----------- internal/cmd/restore.go | 12 +----------- internal/cmd/restore_test.go | 12 +----------- internal/cmd/show.go | 12 +----------- internal/cmd/start.go | 12 +----------- internal/cmd/stop.go | 12 +----------- internal/cmd/support.go | 12 +----------- internal/cmd/version.go | 12 +----------- internal/config.go | 12 +----------- internal/format.go | 12 +----------- internal/format_test.go | 12 +----------- internal/testing/cmp/cmp.go | 12 +----------- internal/unstructured.go | 12 +----------- internal/unstructured_test.go | 12 +----------- internal/util/enum.go | 12 +----------- internal/util/executor.go | 12 +----------- internal/util/interactions.go | 12 +----------- internal/util/interactions_test.go | 12 +----------- internal/util/naming.go | 12 +----------- internal/util/naming_test.go | 12 +----------- 37 files changed, 41 insertions(+), 408 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 2a277b28..b96d397c 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -53,19 +53,12 @@ linters-settings: goheader: template: |- - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. + Copyright {{ DATES }} Crunchy Data Solutions, Inc. - 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 + values: + regexp: + DATES: '(202[1-3] - 2024|2024)' goimports: local-prefixes: github.com/crunchydata/postgres-operator-client diff --git a/cmd/kubectl-pgo/main.go b/cmd/kubectl-pgo/main.go index c9dbe4d5..c4bbdff2 100644 --- a/cmd/kubectl-pgo/main.go +++ b/cmd/kubectl-pgo/main.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 main diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 7552d354..7fc3d63c 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,13 +1,3 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 diff --git a/hack/generate-docs.go b/hack/generate-docs.go index 58e310a2..de343a76 100644 --- a/hack/generate-docs.go +++ b/hack/generate-docs.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 //go:build docs diff --git a/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go b/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go index 45c761a6..78de9777 100644 --- a/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go +++ b/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 v1beta1 contains API Schema definitions. package v1beta1 diff --git a/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go b/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go index ee88d085..c5ed10ea 100644 --- a/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go +++ b/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 v1beta1 diff --git a/internal/cmd/backup.go b/internal/cmd/backup.go index b15e3bcc..9c634911 100644 --- a/internal/cmd/backup.go +++ b/internal/cmd/backup.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/backup_test.go b/internal/cmd/backup_test.go index 6b620fa9..ed930229 100644 --- a/internal/cmd/backup_test.go +++ b/internal/cmd/backup_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/client_version.go b/internal/cmd/client_version.go index a37fd5cd..a776d585 100644 --- a/internal/cmd/client_version.go +++ b/internal/cmd/client_version.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/create.go b/internal/cmd/create.go index 612585bb..0f7aa53c 100644 --- a/internal/cmd/create.go +++ b/internal/cmd/create.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/create_test.go b/internal/cmd/create_test.go index aa26511b..cc831055 100644 --- a/internal/cmd/create_test.go +++ b/internal/cmd/create_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/delete.go b/internal/cmd/delete.go index 41e8e885..1ea41a83 100644 --- a/internal/cmd/delete.go +++ b/internal/cmd/delete.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/exec.go b/internal/cmd/exec.go index 2359e696..3e675b90 100644 --- a/internal/cmd/exec.go +++ b/internal/cmd/exec.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/exec_test.go b/internal/cmd/exec_test.go index a6034c4f..de26c8b4 100644 --- a/internal/cmd/exec_test.go +++ b/internal/cmd/exec_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 0105352d..8c4a6ff3 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/export_test.go b/internal/cmd/export_test.go index 9ad782b1..19402d72 100644 --- a/internal/cmd/export_test.go +++ b/internal/cmd/export_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/pgo.go b/internal/cmd/pgo.go index cfd4df5c..743d69e0 100644 --- a/internal/cmd/pgo.go +++ b/internal/cmd/pgo.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/pgo_test.go b/internal/cmd/pgo_test.go index cbaf06b8..36ae1402 100644 --- a/internal/cmd/pgo_test.go +++ b/internal/cmd/pgo_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/restore.go b/internal/cmd/restore.go index 4465430e..8f45c239 100644 --- a/internal/cmd/restore.go +++ b/internal/cmd/restore.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/restore_test.go b/internal/cmd/restore_test.go index d6a923cb..3aacc9a1 100644 --- a/internal/cmd/restore_test.go +++ b/internal/cmd/restore_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/show.go b/internal/cmd/show.go index f8d015e9..00f05193 100644 --- a/internal/cmd/show.go +++ b/internal/cmd/show.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/start.go b/internal/cmd/start.go index 8f34d818..25490150 100644 --- a/internal/cmd/start.go +++ b/internal/cmd/start.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/stop.go b/internal/cmd/stop.go index 9b38f592..fafce494 100644 --- a/internal/cmd/stop.go +++ b/internal/cmd/stop.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/support.go b/internal/cmd/support.go index 07736f27..fe85c108 100644 --- a/internal/cmd/support.go +++ b/internal/cmd/support.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/cmd/version.go b/internal/cmd/version.go index 89ebcbaf..eedf0706 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmd diff --git a/internal/config.go b/internal/config.go index 548e4510..25e6fc49 100644 --- a/internal/config.go +++ b/internal/config.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 internal diff --git a/internal/format.go b/internal/format.go index ef3b06fe..371e5464 100644 --- a/internal/format.go +++ b/internal/format.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 internal diff --git a/internal/format_test.go b/internal/format_test.go index 147991c1..76d016a4 100644 --- a/internal/format_test.go +++ b/internal/format_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 internal diff --git a/internal/testing/cmp/cmp.go b/internal/testing/cmp/cmp.go index 7d459c99..bdaf2301 100644 --- a/internal/testing/cmp/cmp.go +++ b/internal/testing/cmp/cmp.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 cmp diff --git a/internal/unstructured.go b/internal/unstructured.go index da887b03..72184711 100644 --- a/internal/unstructured.go +++ b/internal/unstructured.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 internal diff --git a/internal/unstructured_test.go b/internal/unstructured_test.go index f5b56075..1962f3d4 100644 --- a/internal/unstructured_test.go +++ b/internal/unstructured_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 internal diff --git a/internal/util/enum.go b/internal/util/enum.go index 4b6d8103..c7841954 100644 --- a/internal/util/enum.go +++ b/internal/util/enum.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 util diff --git a/internal/util/executor.go b/internal/util/executor.go index bb93190b..ec83dde1 100644 --- a/internal/util/executor.go +++ b/internal/util/executor.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 util diff --git a/internal/util/interactions.go b/internal/util/interactions.go index be29127d..c9e83cdc 100644 --- a/internal/util/interactions.go +++ b/internal/util/interactions.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 util diff --git a/internal/util/interactions_test.go b/internal/util/interactions_test.go index 17ab2b24..1086aa62 100644 --- a/internal/util/interactions_test.go +++ b/internal/util/interactions_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 util diff --git a/internal/util/naming.go b/internal/util/naming.go index d280af1a..d1753d6b 100644 --- a/internal/util/naming.go +++ b/internal/util/naming.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 util diff --git a/internal/util/naming_test.go b/internal/util/naming_test.go index fb8f98f2..6ecd1db4 100644 --- a/internal/util/naming_test.go +++ b/internal/util/naming_test.go @@ -1,16 +1,6 @@ // Copyright 2021 - 2024 Crunchy Data Solutions, Inc. // -// 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 util From 3ce9639803dbee8b7e0302a8caf01fa8189930f1 Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Thu, 10 Oct 2024 10:06:35 -0500 Subject: [PATCH 16/45] Reject pull requests that change imported licenses We import dependencies that use a handful of open-source licenses. We want to be intentional about any change to these licenses, so this automation flags pull requests that do so. Issue: PGO-1563 --- .../workflows/{trivy-scan.yaml => trivy.yaml} | 41 +++++++++++++------ trivy.yaml | 14 +++++++ 2 files changed, 43 insertions(+), 12 deletions(-) rename .github/workflows/{trivy-scan.yaml => trivy.yaml} (62%) create mode 100644 trivy.yaml diff --git a/.github/workflows/trivy-scan.yaml b/.github/workflows/trivy.yaml similarity index 62% rename from .github/workflows/trivy-scan.yaml rename to .github/workflows/trivy.yaml index 9ea21fff..a3ec3c90 100644 --- a/.github/workflows/trivy-scan.yaml +++ b/.github/workflows/trivy.yaml @@ -1,5 +1,3 @@ -# Uses Trivy to scan every pull request, rejecting those with severe, fixable vulnerabilities. -# Scans on PR to main and weekly with same behavior. name: Trivy on: @@ -9,18 +7,35 @@ on: push: branches: - main - # Scan schedule is same as codeql-analysis job. schedule: - cron: '10 18 * * 2' jobs: - scan: + licenses: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Trivy needs a populated Go module cache to detect Go module licenses. + - uses: actions/setup-go@v5 + with: { go-version: stable } + - run: go mod download + + # Report success only when detected licenses are listed in [/trivy.yaml]. + - name: Scan licenses + uses: aquasecurity/trivy-action@0.26.0 + env: + TRIVY_DEBUG: true + with: + scan-type: filesystem + scanners: license + exit-code: 1 + + vulnerabilities: permissions: - # for github/codeql-action/upload-sarif to upload SARIF results - security-events: write + security-events: write runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 @@ -29,23 +44,25 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.26.0 with: - scan-type: fs + scan-type: filesystem hide-progress: true ignore-unfixed: true - + scanners: secret,vuln + # Upload actionable results to the GitHub Security tab. # Pull request checks fail according to repository settings. # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.26.0 with: - scan-type: fs + scan-type: filesystem ignore-unfixed: true format: 'sarif' output: 'trivy-results.sarif' + scanners: secret,vuln - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 diff --git a/trivy.yaml b/trivy.yaml new file mode 100644 index 00000000..b2ef32d7 --- /dev/null +++ b/trivy.yaml @@ -0,0 +1,14 @@ +# https://aquasecurity.github.io/trivy/latest/docs/references/configuration/config-file/ +--- +# Specify an exact list of recognized and acceptable licenses. +# [A GitHub workflow](/.github/workflows/trivy.yaml) rejects pull requests that +# import licenses not in this list. +# +# https://aquasecurity.github.io/trivy/latest/docs/scanner/license/ +license: + ignored: + - Apache-2.0 + - BSD-2-Clause + - BSD-3-Clause + - ISC + - MIT From 8aaee5fe10e0c07706fef2e5484636b125dc793b Mon Sep 17 00:00:00 2001 From: Chris Bandy Date: Thu, 10 Oct 2024 10:13:13 -0500 Subject: [PATCH 17/45] Run CodeQL security analysis on schedule and push --- .github/workflows/codeql.yaml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/codeql.yaml diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 00000000..1590cafc --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,29 @@ +name: CodeQL + +on: + pull_request: + branches: + - main + push: + branches: + - main + schedule: + - cron: '10 18 * * 2' + +jobs: + analyze: + runs-on: ubuntu-latest + permissions: + security-events: write + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: { go-version: stable } + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: { languages: go, build-mode: autobuild } + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 From 1cf0bce7ebd863f7eb882395ac73bdb9f2ac9134 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:09:59 -0500 Subject: [PATCH 18/45] Bump aquasecurity/trivy-action in the all-github-actions group (#110) Bumps the all-github-actions group with 1 update: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.26.0 to 0.27.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.26.0...0.27.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trivy.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index a3ec3c90..deaa8385 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -23,7 +23,7 @@ jobs: # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.26.0 + uses: aquasecurity/trivy-action@0.27.0 env: TRIVY_DEBUG: true with: @@ -44,7 +44,7 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@0.26.0 + uses: aquasecurity/trivy-action@0.27.0 with: scan-type: filesystem hide-progress: true @@ -56,7 +56,7 @@ jobs: # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.26.0 + uses: aquasecurity/trivy-action@0.27.0 with: scan-type: filesystem ignore-unfixed: true From 138a3237e3c777be6784108ed2387858f1b1716f Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Tue, 15 Oct 2024 11:40:42 -0500 Subject: [PATCH 19/45] Add 0.5.0 release notes and bump version (#108) * Add 0.5.0 release notes and bump version --- docs/config.toml | 2 +- docs/content/reference/pgo_version.md | 2 +- docs/content/releases/0.5.0.md | 25 +++++++++++++++++++++++++ internal/cmd/client_version.go | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 docs/content/releases/0.5.0.md diff --git a/docs/config.toml b/docs/config.toml index 524899e5..1f6753db 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -15,7 +15,7 @@ defaultContentLanguageInSubdir= false enableMissingTranslationPlaceholders = false [params] -clientVersion = "0.4.2" +clientVersion = "0.5.0" # crunchy-hugo-theme params editURL = "https://github.com/CrunchyData/postgres-operator/edit/master/docs/content/" diff --git a/docs/content/reference/pgo_version.md b/docs/content/reference/pgo_version.md index eb8c6a03..9ec1cd67 100644 --- a/docs/content/reference/pgo_version.md +++ b/docs/content/reference/pgo_version.md @@ -31,7 +31,7 @@ pgo version ``` ### Example output ``` -Client Version: v0.4.2 +Client Version: v0.5.0 Operator Version: v5.5.0 ``` diff --git a/docs/content/releases/0.5.0.md b/docs/content/releases/0.5.0.md new file mode 100644 index 00000000..b4bc36e0 --- /dev/null +++ b/docs/content/releases/0.5.0.md @@ -0,0 +1,25 @@ +--- +title: "0.5.0" +draft: false +weight: 993 +--- + +[Crunchy Postgres for Kubernetes]: https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes +[`pgo` CLI documentation]: https://access.crunchydata.com/documentation/postgres-operator-client/latest + +Crunchy Data announces the release of `pgo`, Postgres Operator Client from Crunchy Data 0.5.0. + +Built as a `kubectl` plugin, the `pgo` CLI facilitates the creation and management of PostgreSQL clusters created using [Crunchy Postgres for Kubernetes][]. + +For more information about using the CLI and the various commands available, please see the [`pgo` CLI documentation][]. + +Additionally, please see the [CPK documentation](https://access.crunchydata.com/documentation/postgres-operator/latest) for information about [getting started](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/) with Crunchy Postgres for Kubernetes. + +## Features + +- The `pgo create` command now includes a `--disable-backups` flag. By providing this flag, you can fully disable backups in your PostgresCluster. NOTE: Backups are only able to be disabled when using PGO v5.7+. +- The `support export` command now includes: + - Logs from all Postgres replicas + - Better support for large logs + - The output of the `pgbackrest check` command + - `pg_controldata` from each Postgres instance diff --git a/internal/cmd/client_version.go b/internal/cmd/client_version.go index a776d585..0519766c 100644 --- a/internal/cmd/client_version.go +++ b/internal/cmd/client_version.go @@ -5,4 +5,4 @@ package cmd // store the current PGO CLI version -const clientVersion = "v0.4.2" +const clientVersion = "v0.5.0" From c2f9802f4045a45c068857f7cdc875e3f8c9b076 Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Wed, 23 Oct 2024 14:30:11 -0500 Subject: [PATCH 20/45] Update master to main (#112) * Remove reference to non-existent theme --- docs/config.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/config.toml b/docs/config.toml index 1f6753db..8572534d 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -18,7 +18,6 @@ enableMissingTranslationPlaceholders = false clientVersion = "0.5.0" # crunchy-hugo-theme params -editURL = "https://github.com/CrunchyData/postgres-operator/edit/master/docs/content/" showVisitedLinks = false # default is false # in theme themeStyle = "flex" # "original" or "flex" # default "flex" themeVariant = "" # choose theme variant "green", "gold" , "gray", "blue" (default) From dc1c5bec582dfeecf86ea08b922be3e61672c9e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 02:14:48 +0000 Subject: [PATCH 21/45] Bump aquasecurity/trivy-action in the all-github-actions group Bumps the all-github-actions group with 1 update: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.27.0 to 0.28.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.27.0...0.28.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/trivy.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index deaa8385..10580ffa 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -23,7 +23,7 @@ jobs: # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.27.0 + uses: aquasecurity/trivy-action@0.28.0 env: TRIVY_DEBUG: true with: @@ -44,7 +44,7 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@0.27.0 + uses: aquasecurity/trivy-action@0.28.0 with: scan-type: filesystem hide-progress: true @@ -56,7 +56,7 @@ jobs: # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.27.0 + uses: aquasecurity/trivy-action@0.28.0 with: scan-type: filesystem ignore-unfixed: true From b4a2ca9d840c8c9e36ca32903618f3374611a8d5 Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Fri, 6 Dec 2024 15:36:27 -0500 Subject: [PATCH 22/45] Update the methods of "PatchConfig" so that all use pointer receivers Corrects for recvcheck linter finding. --- internal/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config.go b/internal/config.go index 25e6fc49..c476ddbe 100644 --- a/internal/config.go +++ b/internal/config.go @@ -33,13 +33,13 @@ func (cfg *PatchConfig) AddFlags(flags *pflag.FlagSet) { } // CreateOptions returns a copy of opts with fields set according to cfg. -func (cfg PatchConfig) CreateOptions(opts metav1.CreateOptions) metav1.CreateOptions { +func (cfg *PatchConfig) CreateOptions(opts metav1.CreateOptions) metav1.CreateOptions { opts.FieldManager = cfg.FieldManager return opts } // PatchOptions returns a copy of opts with fields set according to cfg. -func (cfg PatchConfig) PatchOptions(opts metav1.PatchOptions) metav1.PatchOptions { +func (cfg *PatchConfig) PatchOptions(opts metav1.PatchOptions) metav1.PatchOptions { opts.FieldManager = cfg.FieldManager return opts } From 7732fd5032d38965ff14610dd03b4c7353408df2 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Mon, 9 Dec 2024 13:26:59 -0500 Subject: [PATCH 23/45] Improve Logging and Error Handling (#115) * refactor logic to continue if encountering errors with support export. * When there are errors with patronictl and pgbackrest commands, write stderr to the support export log for troubleshooting. * clarifying that info for Kubernetes nodes is gathered at this step, not Postgres nodes. * updated WriteInfo to include data from `err` and `stderr` * `stderr` contains the error message from the subsystem (pgbackrest or patroni) which is critical for the user to have for troubleshooting. Giving both provides consistency with error reporting in other parts of the support export. * remove redundant call to `strings.TrimSpace` --------- Co-authored-by: Philip Hurst --- internal/cmd/export.go | 186 ++++++++++++++++++++++++----------------- 1 file changed, 108 insertions(+), 78 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 8c4a6ff3..bb85efd1 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -387,88 +387,111 @@ Collecting PGO CLI logs... // PGO CLI version err = gatherPGOCLIVersion(ctx, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering PGO CLI Version: %s", err)) + } - if err == nil { - err = gatherPostgresClusterNames(clusterName, ctx, cmd, tw, postgresClient) + // Postgres Cluster Names + err = gatherPostgresClusterNames(clusterName, ctx, cmd, tw, postgresClient) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Postgres Cluster Names: %s", err)) } // Current Kubernetes context - if err == nil { - err = gatherKubeContext(ctx, config, clusterName, tw, cmd) + err = gatherKubeContext(ctx, config, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering current Kubernetes context: %s", err)) } - // Gather cluster wide resources - if err == nil { - err = gatherKubeServerVersion(ctx, discoveryClient, clusterName, tw, cmd) + // Gather Kubernetes Server Version + err = gatherKubeServerVersion(ctx, discoveryClient, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Kubernetes server version: %s", err)) } - if err == nil { - err = gatherNodes(ctx, clientset, clusterName, tw, cmd) + // Gather list of Kubernetes nodes + err = gatherNodes(ctx, clientset, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering list of Kubernetes nodes: %s", err)) } - if err == nil { - err = gatherCurrentNamespace(ctx, clientset, namespace, clusterName, tw, cmd) + // Gather namespace information + err = gatherCurrentNamespace(ctx, clientset, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering namespace information: %s", err)) } - // Namespaced resources - if err == nil { - err = gatherClusterSpec(get, clusterName, tw, cmd) + // Gather PostgresCluster manifest + err = gatherClusterSpec(get, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering PostgresCluster manifest: %s", err)) } // TODO (jmckulk): pod describe output - if err == nil { - // get Namespaced resources that have cluster label - nsListOpts := metav1.ListOptions{ - LabelSelector: "postgres-operator.crunchydata.com/cluster=" + clusterName, - } - err = gatherNamespacedAPIResources(ctx, dynamicClient, namespace, - clusterName, clusterNamespacedResources, nsListOpts, tw, cmd) + // Gather Namespaced API Resources + // get Namespaced resources that have cluster label + nsListOpts := metav1.ListOptions{ + LabelSelector: "postgres-operator.crunchydata.com/cluster=" + clusterName, + } + err = gatherNamespacedAPIResources(ctx, dynamicClient, namespace, + clusterName, clusterNamespacedResources, nsListOpts, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Namespaced API Resources: %s", err)) } - if err == nil { - // get other Namespaced resources that do not have the cluster label - // but may otherwise impact the PostgresCluster's operation - otherListOpts := metav1.ListOptions{} - err = gatherNamespacedAPIResources(ctx, dynamicClient, namespace, - clusterName, otherNamespacedResources, otherListOpts, tw, cmd) + // Gather Namespaced API Resources + // get other Namespaced resources that do not have the cluster label + // but may otherwise impact the PostgresCluster's operation + otherListOpts := metav1.ListOptions{} + err = gatherNamespacedAPIResources(ctx, dynamicClient, namespace, + clusterName, otherNamespacedResources, otherListOpts, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Namespaced API Resources: %s", err)) } - if err == nil { - err = gatherEvents(ctx, clientset, namespace, clusterName, tw, cmd) + // Gather Events + err = gatherEvents(ctx, clientset, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Events: %s", err)) } // Logs // All Postgres Logs on the Postgres Instances (primary and replicas) if numLogs > 0 { - if err == nil { - err = gatherPostgresLogsAndConfigs(ctx, clientset, restConfig, - namespace, clusterName, outputDir, outputFile, numLogs, tw, cmd) + err = gatherPostgresLogsAndConfigs(ctx, clientset, restConfig, + namespace, clusterName, outputDir, outputFile, numLogs, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Postgres Logs and Config: %s", err)) } } // All pgBackRest Logs on the Postgres Instances - if err == nil { - err = gatherDbBackrestLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + err = gatherDbBackrestLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering pgBackRest DB Hosts Logs: %s", err)) } // All pgBackRest Logs on the Repo Host - if err == nil { - err = gatherRepoHostLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + err = gatherRepoHostLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering pgBackRest Repo Host Logs: %s", err)) } // get PostgresCluster Pod logs - if err == nil { - writeInfo(cmd, "Collecting PostgresCluster pod logs...") - err = gatherPodLogs(ctx, clientset, namespace, fmt.Sprintf("%s=%s", util.LabelCluster, clusterName), clusterName, tw, cmd) + writeInfo(cmd, "Collecting PostgresCluster pod logs...") + err = gatherPodLogs(ctx, clientset, namespace, fmt.Sprintf("%s=%s", util.LabelCluster, clusterName), clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering PostgresCluster pod logs: %s", err)) } // get monitoring Pod logs if monitoringNamespace == "" { monitoringNamespace = namespace } - if err == nil { - writeInfo(cmd, "Collecting monitoring pod logs...") - err = gatherPodLogs(ctx, clientset, monitoringNamespace, util.LabelMonitoring, "monitoring", tw, cmd) + writeInfo(cmd, "Collecting monitoring pod logs...") + err = gatherPodLogs(ctx, clientset, monitoringNamespace, util.LabelMonitoring, "monitoring", tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering monitoring pod logs: %s", err)) } // get operator Pod logs and descriptions @@ -478,44 +501,55 @@ Collecting PGO CLI logs... // Operator and Operator upgrade pods should have // "postgres-operator.crunchydata.com/control-plane" label // but with different values - if err == nil { - req, _ := labels.NewRequirement(util.LabelOperator, - selection.Exists, []string{}, - ) - nsListOpts := metav1.ListOptions{ - LabelSelector: req.String(), - } - err = gatherNamespacedAPIResources(ctx, dynamicClient, - operatorNamespace, "operator", operatorNamespacedResources, - nsListOpts, tw, cmd) + req, _ := labels.NewRequirement(util.LabelOperator, + selection.Exists, []string{}, + ) + nsListOpts = metav1.ListOptions{ + LabelSelector: req.String(), + } + err = gatherNamespacedAPIResources(ctx, dynamicClient, + operatorNamespace, "operator", operatorNamespacedResources, + nsListOpts, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Operator Namespace API Resources: %s", err)) } - if err == nil { - writeInfo(cmd, "Collecting operator pod logs...") - err = gatherPodLogs(ctx, clientset, operatorNamespace, util.LabelOperator, "operator", tw, cmd) + + // Gather Operator Pod Logs + writeInfo(cmd, "Collecting operator pod logs...") + err = gatherPodLogs(ctx, clientset, operatorNamespace, util.LabelOperator, "operator", tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Operator Pod logs: %s", err)) } - // Exec resources - if err == nil { - err = gatherPatroniInfo(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + // Exec to get Patroni Information + err = gatherPatroniInfo(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Patroni Info: %s", err)) } - if err == nil { - err = gatherPgBackRestInfo(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + // Exec to get pgBackRest Information + err = gatherPgBackRestInfo(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering pgBackRest Info: %s", err)) } // Exec to get Container processes - if err == nil { - err = gatherProcessInfo(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + err = gatherProcessInfo(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering container processes: %s", err)) } // Exec to get Container system time - if err == nil { - err = gatherSystemTime(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + err = gatherSystemTime(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering container system time: %s", err)) } - if err == nil { - writeInfo(cmd, "Collecting list of kubectl plugins...") - err = gatherPluginList(clusterName, tw, cmd) + // Get kubectl plugins + writeInfo(cmd, "Collecting list of kubectl plugins...") + err = gatherPluginList(clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering kubectl plugins: %s", err)) } // Print cli output @@ -526,13 +560,8 @@ Collecting PGO CLI logs... } // Print final message - if err == nil { - info, err := os.Stat(outputDir + "/" + outputFile) - - if err == nil { - fmt.Print(exportSizeReport(float64(info.Size()))) - } - } + info, err := os.Stat(outputDir + "/" + outputFile) + fmt.Print(exportSizeReport(float64(info.Size()))) return err } @@ -579,6 +608,7 @@ func gatherPGOCLIVersion(_ context.Context, cmd *cobra.Command, ) error { writeInfo(cmd, "Collecting PGO CLI version...") + writeInfo(cmd, fmt.Sprintf("PGO CLI version is %s", clientVersion)) path := clusterName + "/pgo-cli-version" if err := writeTar(tw, []byte(clientVersion), path, cmd); err != nil { return err @@ -1493,7 +1523,7 @@ func gatherPatroniInfo(ctx context.Context, writeInfo(cmd, err.Error()) return nil } - return err + writeInfo(cmd, fmt.Sprintf("Error with patronictl list: %s: %s", err, strings.TrimSpace(stderr))) } buf.Write([]byte(stdout)) @@ -1508,7 +1538,7 @@ func gatherPatroniInfo(ctx context.Context, writeInfo(cmd, err.Error()) return nil } - return err + writeInfo(cmd, fmt.Sprintf("Error with patronictl history: %s: %s", err, strings.TrimSpace(stderr))) } buf.Write([]byte(stdout)) @@ -1571,7 +1601,7 @@ func gatherPgBackRestInfo(ctx context.Context, writeInfo(cmd, err.Error()) return nil } - return err + writeInfo(cmd, fmt.Sprintf("Error with pgbackrest info: %s: %s", err, strings.TrimSpace(stderr))) } buf.Write([]byte(stdout)) @@ -1586,7 +1616,7 @@ func gatherPgBackRestInfo(ctx context.Context, writeInfo(cmd, err.Error()) return nil } - return err + writeInfo(cmd, fmt.Sprintf("Error with pgbackrest check: %s: %s", err, strings.TrimSpace(stderr))) } buf.Write([]byte(stdout)) From 6a753363186311682995632dcb0b35208d44ae04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:10:38 -0600 Subject: [PATCH 24/45] Bump aquasecurity/trivy-action in the all-github-actions group (#113) Bumps the all-github-actions group with 1 update: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.28.0 to 0.29.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.28.0...0.29.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trivy.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index 10580ffa..b25be90e 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -23,7 +23,7 @@ jobs: # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@0.29.0 env: TRIVY_DEBUG: true with: @@ -44,7 +44,7 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@0.29.0 with: scan-type: filesystem hide-progress: true @@ -56,7 +56,7 @@ jobs: # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.28.0 + uses: aquasecurity/trivy-action@0.29.0 with: scan-type: filesystem ignore-unfixed: true From a006d572f47c1c4789ee2e364a7bead93ae2b1c6 Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Wed, 18 Dec 2024 16:39:11 -0600 Subject: [PATCH 25/45] Add release notes and update version to 0.5.1 (#118) * Add release notes and update version --- docs/config.toml | 2 +- docs/content/reference/pgo_version.md | 4 ++-- docs/content/releases/0.5.1.md | 20 ++++++++++++++++++++ internal/cmd/client_version.go | 2 +- internal/cmd/version.go | 2 +- 5 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 docs/content/releases/0.5.1.md diff --git a/docs/config.toml b/docs/config.toml index 8572534d..9a751c9d 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -15,7 +15,7 @@ defaultContentLanguageInSubdir= false enableMissingTranslationPlaceholders = false [params] -clientVersion = "0.5.0" +clientVersion = "0.5.1" # crunchy-hugo-theme params showVisitedLinks = false # default is false # in theme diff --git a/docs/content/reference/pgo_version.md b/docs/content/reference/pgo_version.md index 9ec1cd67..eed771db 100644 --- a/docs/content/reference/pgo_version.md +++ b/docs/content/reference/pgo_version.md @@ -31,8 +31,8 @@ pgo version ``` ### Example output ``` -Client Version: v0.5.0 -Operator Version: v5.5.0 +Client Version: v0.5.1 +Operator Version: v5.7.0 ``` ### Options diff --git a/docs/content/releases/0.5.1.md b/docs/content/releases/0.5.1.md new file mode 100644 index 00000000..ef9889fa --- /dev/null +++ b/docs/content/releases/0.5.1.md @@ -0,0 +1,20 @@ +--- +title: "0.5.1" +draft: false +weight: 992 +--- + +[Crunchy Postgres for Kubernetes]: https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes +[`pgo` CLI documentation]: https://access.crunchydata.com/documentation/postgres-operator-client/latest + +Crunchy Data announces the release of `pgo`, Postgres Operator Client from Crunchy Data 0.5.1. + +Built as a `kubectl` plugin, the `pgo` CLI facilitates the creation and management of PostgreSQL clusters created using [Crunchy Postgres for Kubernetes][]. + +For more information about using the CLI and the various commands available, please see the [`pgo` CLI documentation][]. + +Additionally, please see the [CPK documentation](https://access.crunchydata.com/documentation/postgres-operator/latest) for information about [getting started](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/) with Crunchy Postgres for Kubernetes. + +## Features + +- The `support export` command now continues gathering data if it encounters errors; and surfaces error message reporting from Patroni and pgBackRest by writing errors to the console and the support export log for troubleshooting. diff --git a/internal/cmd/client_version.go b/internal/cmd/client_version.go index 0519766c..d23df7f1 100644 --- a/internal/cmd/client_version.go +++ b/internal/cmd/client_version.go @@ -5,4 +5,4 @@ package cmd // store the current PGO CLI version -const clientVersion = "v0.5.0" +const clientVersion = "v0.5.1" diff --git a/internal/cmd/version.go b/internal/cmd/version.go index eedf0706..f8c80f12 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -45,7 +45,7 @@ pgo version ### Example output Client Version: %s -Operator Version: v5.5.0`, clientVersion)) +Operator Version: v5.7.0`, clientVersion)) cmd.RunE = func(cmd *cobra.Command, args []string) error { From 2e7b0901b0fb6d6d88a5ba8565fbe7451d529ef2 Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Wed, 18 Dec 2024 19:28:46 -0600 Subject: [PATCH 26/45] Add release notes and update version to 0.5.1 (#118) * Add release notes and update tag From 846cd58df99f3fc7987f562fb963cd69e96211fb Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Mon, 16 Dec 2024 14:37:57 -0500 Subject: [PATCH 27/45] Update the support export tool to gather Patroni logs Ensures that the on volume Patroni log file is exported, if that file exists. If the PostgresCluster is not configured to create this file, note in the debug logs that this is acceptable for some configurations. Issue: PGO-1701 --- docs/content/reference/pgo_support_export.md | 3 + internal/cmd/exec.go | 11 ++ internal/cmd/exec_test.go | 19 +++ internal/cmd/export.go | 116 +++++++++++++++++++ 4 files changed, 149 insertions(+) diff --git a/docs/content/reference/pgo_support_export.md b/docs/content/reference/pgo_support_export.md index 38c02d68..c1e03e77 100644 --- a/docs/content/reference/pgo_support_export.md +++ b/docs/content/reference/pgo_support_export.md @@ -101,6 +101,9 @@ Collecting networkpolicies... Collecting limitranges... Collecting events... Collecting Postgres logs... +Collecting pgBackRest logs... +Collecting Patroni logs... +Collecting pgBackRest Repo Host logs... Collecting PostgresCluster pod logs... Collecting monitoring pod logs... Collecting operator pod logs... diff --git a/internal/cmd/exec.go b/internal/cmd/exec.go index 3e675b90..5eab7ef3 100644 --- a/internal/cmd/exec.go +++ b/internal/cmd/exec.go @@ -79,6 +79,17 @@ func (exec Executor) listBackrestLogFiles() (string, string, error) { return stdout.String(), stderr.String(), err } +// listPatroniLogFiles returns the full path of Patroni log file. +// These are the Patroni logs stored on the Postgres instance. +func (exec Executor) listPatroniLogFiles() (string, string, error) { + var stdout, stderr bytes.Buffer + + command := "ls -1dt pgdata/patroni/log/*" + err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) + + return stdout.String(), stderr.String(), err +} + // listBackrestRepoHostLogFiles returns the full path of pgBackRest log files. // These are the pgBackRest logs stored on the repo host func (exec Executor) listBackrestRepoHostLogFiles() (string, string, error) { diff --git a/internal/cmd/exec_test.go b/internal/cmd/exec_test.go index de26c8b4..e66e51ba 100644 --- a/internal/cmd/exec_test.go +++ b/internal/cmd/exec_test.go @@ -79,6 +79,25 @@ func TestListPGLogFiles(t *testing.T) { } +func TestListPatroniLogFiles(t *testing.T) { + + t.Run("default", func(t *testing.T) { + expected := errors.New("pass-through") + exec := func( + stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + assert.DeepEqual(t, command, []string{"bash", "-ceu", "--", "ls -1dt pgdata/patroni/log/*"}) + assert.Assert(t, stdout != nil, "should capture stdout") + assert.Assert(t, stderr != nil, "should capture stderr") + return expected + } + _, _, err := Executor(exec).listPatroniLogFiles() + assert.ErrorContains(t, err, "pass-through") + + }) + +} + func TestCatFile(t *testing.T) { t.Run("default", func(t *testing.T) { diff --git a/internal/cmd/export.go b/internal/cmd/export.go index bb85efd1..2c89b13f 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -282,6 +282,9 @@ Collecting networkpolicies... Collecting limitranges... Collecting events... Collecting Postgres logs... +Collecting pgBackRest logs... +Collecting Patroni logs... +Collecting pgBackRest Repo Host logs... Collecting PostgresCluster pod logs... Collecting monitoring pod logs... Collecting operator pod logs... @@ -471,6 +474,12 @@ Collecting PGO CLI logs... writeInfo(cmd, fmt.Sprintf("Error gathering pgBackRest DB Hosts Logs: %s", err)) } + // Patroni Logs that are stored on the Postgres Instances + err = gatherPatroniLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering Patroni Logs from Instance Pods: %s", err)) + } + // All pgBackRest Logs on the Repo Host err = gatherRepoHostLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) if err != nil { @@ -1308,6 +1317,113 @@ func gatherDbBackrestLogs(ctx context.Context, return nil } +// gatherPatroniLogs gathers all the file-based Patroni logs on the DB instance, +// if configured. By default, these logs will be sent to stdout and captured as +// Pod logs instead. +func gatherPatroniLogs(ctx context.Context, + clientset *kubernetes.Clientset, + config *rest.Config, + namespace string, + clusterName string, + tw *tar.Writer, + cmd *cobra.Command, +) error { + writeInfo(cmd, "Collecting Patroni logs...") + + dbPods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ + LabelSelector: util.DBInstanceLabels(clusterName), + }) + + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + return err + } + + if len(dbPods.Items) == 0 { + writeInfo(cmd, "No database instance pod found for gathering logs") + return nil + } + + writeDebug(cmd, fmt.Sprintf("Found %d Pods\n", len(dbPods.Items))) + + podExec, err := util.NewPodExecutor(config) + if err != nil { + return err + } + + for _, pod := range dbPods.Items { + writeDebug(cmd, fmt.Sprintf("Pod Name is %s\n", pod.Name)) + + exec := func(stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + return podExec(namespace, pod.Name, util.ContainerDatabase, + stdin, stdout, stderr, command...) + } + + // Get Patroni Log Files + stdout, stderr, err := Executor(exec).listPatroniLogFiles() + + // Depending upon the list* function above: + // An error may happen when err is non-nil or stderr is non-empty. + // In both cases, we want to print helpful information and continue to the + // next iteration. + if err != nil || stderr != "" { + + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + + writeDebug(cmd, "Error getting Patroni logs\n") + + if err != nil { + writeDebug(cmd, fmt.Sprintf("%s\n", err.Error())) + } + if stderr != "" { + writeDebug(cmd, stderr) + } + + if strings.Contains(stderr, "No such file or directory") { + writeDebug(cmd, "Cannot find any Patroni log files. This is acceptable in some configurations.\n") + } + continue + } + + logFiles := strings.Split(strings.TrimSpace(stdout), "\n") + for _, logFile := range logFiles { + writeDebug(cmd, fmt.Sprintf("LOG FILE: %s\n", logFile)) + var buf bytes.Buffer + + stdout, stderr, err := Executor(exec).catFile(logFile) + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + // Continue and output errors for each log file + // Allow the user to see and address all issues at once + continue + } + return err + } + + buf.Write([]byte(stdout)) + if stderr != "" { + str := fmt.Sprintf("\nError returned: %s\n", stderr) + buf.Write([]byte(str)) + } + + path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } + } + + } + return nil +} + // gatherRepoHostLogs gathers all the file-based pgBackRest logs on the repo host. // There may not be any logs depending upon pgBackRest's log-level-file. func gatherRepoHostLogs(ctx context.Context, From b019433bc41a17ea5edd03c3274fb02f5600369b Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Mon, 6 Jan 2025 09:58:07 -0500 Subject: [PATCH 28/45] Update copyright to 2025 (#119) --- .golangci.yaml | 2 +- LICENSE.md | 2 +- Makefile | 2 +- cmd/kubectl-pgo/main.go | 2 +- hack/boilerplate.go.txt | 2 +- hack/generate-docs.go | 2 +- .../v1beta1/groupversion_info.go | 2 +- .../v1beta1/postgrescluster_types.go | 2 +- internal/cmd/backup.go | 2 +- internal/cmd/backup_test.go | 2 +- internal/cmd/client_version.go | 2 +- internal/cmd/create.go | 2 +- internal/cmd/create_test.go | 2 +- internal/cmd/delete.go | 2 +- internal/cmd/exec.go | 2 +- internal/cmd/exec_test.go | 2 +- internal/cmd/export.go | 2 +- internal/cmd/export_test.go | 2 +- internal/cmd/pgo.go | 2 +- internal/cmd/pgo_test.go | 2 +- internal/cmd/restore.go | 2 +- internal/cmd/restore_test.go | 2 +- internal/cmd/show.go | 2 +- internal/cmd/start.go | 2 +- internal/cmd/stop.go | 2 +- internal/cmd/support.go | 2 +- internal/cmd/version.go | 2 +- internal/config.go | 2 +- internal/format.go | 2 +- internal/format_test.go | 2 +- internal/testing/cmp/cmp.go | 2 +- internal/unstructured.go | 2 +- internal/unstructured_test.go | 2 +- internal/util/enum.go | 2 +- internal/util/executor.go | 2 +- internal/util/interactions.go | 2 +- internal/util/interactions_test.go | 2 +- internal/util/naming.go | 2 +- internal/util/naming_test.go | 2 +- 39 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index b96d397c..1bb49ae7 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -58,7 +58,7 @@ linters-settings: SPDX-License-Identifier: Apache-2.0 values: regexp: - DATES: '(202[1-3] - 2024|2024)' + DATES: '(202[1-4] - 2025|2025)' goimports: local-prefixes: github.com/crunchydata/postgres-operator-client diff --git a/LICENSE.md b/LICENSE.md index 621e287e..67be9ba6 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright 2021 - 2024 Crunchy Data Solutions, Inc. + Copyright 2021 - 2025 Crunchy Data Solutions, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile index 0604eb8b..051d0f02 100644 --- a/Makefile +++ b/Makefile @@ -98,4 +98,4 @@ tag: git push origin "v$(NEW_VERSION)" git tag "d$(NEW_VERSION)" git push origin "d$(NEW_VERSION)" - @echo "Make release from tag "v$(NEW_VERSION)" + @echo "Make release from tag v$(NEW_VERSION)" diff --git a/cmd/kubectl-pgo/main.go b/cmd/kubectl-pgo/main.go index c4bbdff2..bffda2cc 100644 --- a/cmd/kubectl-pgo/main.go +++ b/cmd/kubectl-pgo/main.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 7fc3d63c..7c662ee2 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,3 +1,3 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/hack/generate-docs.go b/hack/generate-docs.go index de343a76..86511e15 100644 --- a/hack/generate-docs.go +++ b/hack/generate-docs.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go b/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go index 78de9777..35c90b12 100644 --- a/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go +++ b/internal/apis/postgres-operator.crunchydata.com/v1beta1/groupversion_info.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go b/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go index c5ed10ea..8419a7fd 100644 --- a/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go +++ b/internal/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/backup.go b/internal/cmd/backup.go index 9c634911..bfdf582b 100644 --- a/internal/cmd/backup.go +++ b/internal/cmd/backup.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/backup_test.go b/internal/cmd/backup_test.go index ed930229..ce2187f7 100644 --- a/internal/cmd/backup_test.go +++ b/internal/cmd/backup_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/client_version.go b/internal/cmd/client_version.go index d23df7f1..8d588fc1 100644 --- a/internal/cmd/client_version.go +++ b/internal/cmd/client_version.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/create.go b/internal/cmd/create.go index 0f7aa53c..90dd037b 100644 --- a/internal/cmd/create.go +++ b/internal/cmd/create.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/create_test.go b/internal/cmd/create_test.go index cc831055..ee22d214 100644 --- a/internal/cmd/create_test.go +++ b/internal/cmd/create_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/delete.go b/internal/cmd/delete.go index 1ea41a83..a97ae1f7 100644 --- a/internal/cmd/delete.go +++ b/internal/cmd/delete.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/exec.go b/internal/cmd/exec.go index 5eab7ef3..0c81294c 100644 --- a/internal/cmd/exec.go +++ b/internal/cmd/exec.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/exec_test.go b/internal/cmd/exec_test.go index e66e51ba..70e1de6b 100644 --- a/internal/cmd/exec_test.go +++ b/internal/cmd/exec_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 2c89b13f..fa3fd2aa 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/export_test.go b/internal/cmd/export_test.go index 19402d72..07a554eb 100644 --- a/internal/cmd/export_test.go +++ b/internal/cmd/export_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/pgo.go b/internal/cmd/pgo.go index 743d69e0..56d462ac 100644 --- a/internal/cmd/pgo.go +++ b/internal/cmd/pgo.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/pgo_test.go b/internal/cmd/pgo_test.go index 36ae1402..9f08c1b1 100644 --- a/internal/cmd/pgo_test.go +++ b/internal/cmd/pgo_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/restore.go b/internal/cmd/restore.go index 8f45c239..904ee3de 100644 --- a/internal/cmd/restore.go +++ b/internal/cmd/restore.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/restore_test.go b/internal/cmd/restore_test.go index 3aacc9a1..5248f239 100644 --- a/internal/cmd/restore_test.go +++ b/internal/cmd/restore_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/show.go b/internal/cmd/show.go index 00f05193..6e39245c 100644 --- a/internal/cmd/show.go +++ b/internal/cmd/show.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/start.go b/internal/cmd/start.go index 25490150..9dc18adb 100644 --- a/internal/cmd/start.go +++ b/internal/cmd/start.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/stop.go b/internal/cmd/stop.go index fafce494..76517b6f 100644 --- a/internal/cmd/stop.go +++ b/internal/cmd/stop.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/support.go b/internal/cmd/support.go index fe85c108..70270288 100644 --- a/internal/cmd/support.go +++ b/internal/cmd/support.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/cmd/version.go b/internal/cmd/version.go index f8c80f12..8dfff0e1 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/config.go b/internal/config.go index c476ddbe..abe326f8 100644 --- a/internal/config.go +++ b/internal/config.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/format.go b/internal/format.go index 371e5464..2d7e7a0a 100644 --- a/internal/format.go +++ b/internal/format.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/format_test.go b/internal/format_test.go index 76d016a4..2e676c5e 100644 --- a/internal/format_test.go +++ b/internal/format_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/testing/cmp/cmp.go b/internal/testing/cmp/cmp.go index bdaf2301..4cca025d 100644 --- a/internal/testing/cmp/cmp.go +++ b/internal/testing/cmp/cmp.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/unstructured.go b/internal/unstructured.go index 72184711..568c96db 100644 --- a/internal/unstructured.go +++ b/internal/unstructured.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/unstructured_test.go b/internal/unstructured_test.go index 1962f3d4..1a91c09a 100644 --- a/internal/unstructured_test.go +++ b/internal/unstructured_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/util/enum.go b/internal/util/enum.go index c7841954..795a0966 100644 --- a/internal/util/enum.go +++ b/internal/util/enum.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/util/executor.go b/internal/util/executor.go index ec83dde1..88bea765 100644 --- a/internal/util/executor.go +++ b/internal/util/executor.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/util/interactions.go b/internal/util/interactions.go index c9e83cdc..8a30bc05 100644 --- a/internal/util/interactions.go +++ b/internal/util/interactions.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/util/interactions_test.go b/internal/util/interactions_test.go index 1086aa62..6e5236af 100644 --- a/internal/util/interactions_test.go +++ b/internal/util/interactions_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/util/naming.go b/internal/util/naming.go index d1753d6b..446fccf5 100644 --- a/internal/util/naming.go +++ b/internal/util/naming.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 diff --git a/internal/util/naming_test.go b/internal/util/naming_test.go index 6ecd1db4..ff558d2f 100644 --- a/internal/util/naming_test.go +++ b/internal/util/naming_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. // // SPDX-License-Identifier: Apache-2.0 From 6bf41cb2d0df840229471ecef39ce40738a9eec9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 10:35:23 -0500 Subject: [PATCH 29/45] Bump the all-github-actions group across 1 directory with 2 updates (#123) Bumps the all-github-actions group with 2 updates in the / directory: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) and [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `golangci/golangci-lint-action` from 6 to 7 - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v6...v7) Updates `aquasecurity/trivy-action` from 0.29.0 to 0.30.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.29.0...0.30.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yaml | 2 +- .github/workflows/trivy.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index b6d3a537..a3a99e26 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-go@v5 with: { go-version: stable } - - uses: golangci/golangci-lint-action@v6 + - uses: golangci/golangci-lint-action@v7 with: version: latest args: --timeout=5m diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index b25be90e..ee3f295d 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -23,7 +23,7 @@ jobs: # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.30.0 env: TRIVY_DEBUG: true with: @@ -44,7 +44,7 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.30.0 with: scan-type: filesystem hide-progress: true @@ -56,7 +56,7 @@ jobs: # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.29.0 + uses: aquasecurity/trivy-action@0.30.0 with: scan-type: filesystem ignore-unfixed: true From 96de18f200e05b02813aed174dcd3510ea346935 Mon Sep 17 00:00:00 2001 From: andrewlecuyer Date: Thu, 8 May 2025 23:04:23 +0000 Subject: [PATCH 30/45] Update AP Download Link for pgo Client --- README.md | 2 +- docs/content/_index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 343d64e8..518547bb 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ will use the role-based access controls (RBAC) that are configured for your ### Download the binary The `kubectl-pgo` binary is available either through the Crunchy Data -[Access Portal](https://access.crunchydata.com/downloads/) or via +[Access Portal](https://access.crunchydata.com/downloads/browse/containers/postgres-operator/cli/) or via [GitHub](https://github.com/CrunchyData/postgres-operator-client/releases). ### Installing the Client diff --git a/docs/content/_index.md b/docs/content/_index.md index bd452895..1a281925 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -57,5 +57,5 @@ oc pgo version ``` [kubectl plugin]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/ -[Access Portal]: https://access.crunchydata.com/downloads/ +[Access Portal]: https://access.crunchydata.com/downloads/browse/containers/postgres-operator/cli/ [GitHub]: https://github.com/CrunchyData/postgres-operator-client/releases From f71abf0462cab4a0abc9d1f0542e78d785874e8a Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Fri, 23 May 2025 09:58:48 -0500 Subject: [PATCH 31/45] Adjust support for OTel and non-OTel (#127) * Adjust support for OTel and non-OTel With OTel, we've introduced some changes, most notably to the location of the Postgres logs; but also with the introduction of a `receiver` dir in several locations which only the collector container has access to. This PR addresses these issues by using the cluster to determine the location of the logs, and by excluding checking the `receiver` dir in several execs through the use of a '*.*' wildcard. Issues: [PGO-2358] --- internal/cmd/exec.go | 28 ++- internal/cmd/exec_test.go | 19 +- internal/cmd/export.go | 9 +- .../support-export/01--support_export.yaml | 18 +- ...--create_cluster_with_instrumentation.yaml | 19 ++ .../kuttl/e2e/support-export/33-assert.yaml | 32 ++++ .../support-export/34--support_export.yaml | 163 ++++++++++++++++++ 7 files changed, 277 insertions(+), 11 deletions(-) create mode 100644 testing/kuttl/e2e/support-export/33--create_cluster_with_instrumentation.yaml create mode 100644 testing/kuttl/e2e/support-export/33-assert.yaml create mode 100644 testing/kuttl/e2e/support-export/34--support_export.yaml diff --git a/internal/cmd/exec.go b/internal/cmd/exec.go index 0c81294c..885b01ba 100644 --- a/internal/cmd/exec.go +++ b/internal/cmd/exec.go @@ -48,10 +48,22 @@ func (exec Executor) pgBackRestCheck() (string, string, error) { } // postgresqlListLogFiles returns the full path of numLogs log files. -func (exec Executor) listPGLogFiles(numLogs int) (string, string, error) { +func (exec Executor) listPGLogFiles(numLogs int, hasInstrumentation bool) (string, string, error) { var stdout, stderr bytes.Buffer - command := fmt.Sprintf("ls -1dt pgdata/pg[0-9][0-9]/log/* | head -%d", numLogs) + location := "pgdata/pg[0-9][0-9]/log/*" + if hasInstrumentation { + location = "pgdata/logs/postgres/*.*" + } + // Check both the older and the newer log locations. + // If a cluster has used both locations, this will return logs from both. + // If a cluster does not have one location or the other, continue without error. + // This ensures we get as many logs as possible for whatever combination of + // log files exists on this cluster. + // Note the "*.*" pattern to exclude the `receiver` directory, + // which only the collector container has permission to access. + command := fmt.Sprintf("ls -1dt %s | head -%d", + location, numLogs) err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) return stdout.String(), stderr.String(), err @@ -73,7 +85,9 @@ func (exec Executor) listPGConfFiles() (string, string, error) { func (exec Executor) listBackrestLogFiles() (string, string, error) { var stdout, stderr bytes.Buffer - command := "ls -1dt pgdata/pgbackrest/log/*" + // Note the "*.*" pattern to exclude the `receiver` directory, + // which only the collector container has permission to access. + command := "ls -1dt pgdata/pgbackrest/log/*.*" err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) return stdout.String(), stderr.String(), err @@ -84,7 +98,9 @@ func (exec Executor) listBackrestLogFiles() (string, string, error) { func (exec Executor) listPatroniLogFiles() (string, string, error) { var stdout, stderr bytes.Buffer - command := "ls -1dt pgdata/patroni/log/*" + // Note the "*.*" pattern to exclude the `receiver` directory, + // which only the collector container has permission to access. + command := "ls -1dt pgdata/patroni/log/*.*" err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) return stdout.String(), stderr.String(), err @@ -95,7 +111,9 @@ func (exec Executor) listPatroniLogFiles() (string, string, error) { func (exec Executor) listBackrestRepoHostLogFiles() (string, string, error) { var stdout, stderr bytes.Buffer - command := "ls -1dt pgbackrest/*/log/*" + // Note the "*.*" pattern to exclude the `receiver` directory, + // which only the collector container has permission to access. + command := "ls -1dt pgbackrest/*/log/*.*" err := exec(nil, &stdout, &stderr, "bash", "-ceu", "--", command) return stdout.String(), stderr.String(), err diff --git a/internal/cmd/exec_test.go b/internal/cmd/exec_test.go index 70e1de6b..54cd2ed1 100644 --- a/internal/cmd/exec_test.go +++ b/internal/cmd/exec_test.go @@ -72,7 +72,22 @@ func TestListPGLogFiles(t *testing.T) { assert.Assert(t, stderr != nil, "should capture stderr") return expected } - _, _, err := Executor(exec).listPGLogFiles(1) + _, _, err := Executor(exec).listPGLogFiles(1, false) + assert.ErrorContains(t, err, "pass-through") + + }) + + t.Run("has instrumentation", func(t *testing.T) { + expected := errors.New("pass-through") + exec := func( + stdin io.Reader, stdout, stderr io.Writer, command ...string, + ) error { + assert.DeepEqual(t, command, []string{"bash", "-ceu", "--", "ls -1dt pgdata/logs/postgres/*.* | head -1"}) + assert.Assert(t, stdout != nil, "should capture stdout") + assert.Assert(t, stderr != nil, "should capture stderr") + return expected + } + _, _, err := Executor(exec).listPGLogFiles(1, true) assert.ErrorContains(t, err, "pass-through") }) @@ -86,7 +101,7 @@ func TestListPatroniLogFiles(t *testing.T) { exec := func( stdin io.Reader, stdout, stderr io.Writer, command ...string, ) error { - assert.DeepEqual(t, command, []string{"bash", "-ceu", "--", "ls -1dt pgdata/patroni/log/*"}) + assert.DeepEqual(t, command, []string{"bash", "-ceu", "--", "ls -1dt pgdata/patroni/log/*.*"}) assert.Assert(t, stdout != nil, "should capture stdout") assert.Assert(t, stderr != nil, "should capture stderr") return expected diff --git a/internal/cmd/export.go b/internal/cmd/export.go index fa3fd2aa..605b6d3e 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -462,7 +462,7 @@ Collecting PGO CLI logs... // All Postgres Logs on the Postgres Instances (primary and replicas) if numLogs > 0 { err = gatherPostgresLogsAndConfigs(ctx, clientset, restConfig, - namespace, clusterName, outputDir, outputFile, numLogs, tw, cmd) + namespace, clusterName, outputDir, outputFile, numLogs, tw, cmd, get) if err != nil { writeInfo(cmd, fmt.Sprintf("Error gathering Postgres Logs and Config: %s", err)) } @@ -992,6 +992,7 @@ func gatherPostgresLogsAndConfigs(ctx context.Context, numLogs int, tw *tar.Writer, cmd *cobra.Command, + cluster *unstructured.Unstructured, ) error { writeInfo(cmd, "Collecting Postgres logs...") @@ -1029,7 +1030,11 @@ func gatherPostgresLogsAndConfigs(ctx context.Context, } // Get Postgres Log Files - stdout, stderr, err := Executor(exec).listPGLogFiles(numLogs) + // Pass a boolean based on whether the cluster has instrumentation + // since that will determine where the log files are stored + _, hasInstrumentation, _ := unstructured.NestedMap(cluster.Object, + "spec", "instrumentation") + stdout, stderr, err := Executor(exec).listPGLogFiles(numLogs, hasInstrumentation) // Depending upon the list* function above: // An error may happen when err is non-nil or stderr is non-empty. diff --git a/testing/kuttl/e2e/support-export/01--support_export.yaml b/testing/kuttl/e2e/support-export/01--support_export.yaml index d17dc89d..7d346f18 100644 --- a/testing/kuttl/e2e/support-export/01--support_export.yaml +++ b/testing/kuttl/e2e/support-export/01--support_export.yaml @@ -5,7 +5,7 @@ commands: - script: kubectl-pgo --namespace $NAMESPACE --operator-namespace postgres-operator support export kuttl-support-cluster -o . - script: tar -xzf ./crunchy_k8s_support_export_*.tar.gz - script: | - CLEANUP="rm -r ./kuttl-support-cluster ./crunchy_k8s_support_export_*.tar.gz" + CLEANUP="rm -r ./kuttl-support-cluster ./operator ./crunchy_k8s_support_export_*.tar.gz" check_file() { if [ ! -s ./"${1}" ] then @@ -16,6 +16,16 @@ commands: echo "Found ${1}" fi } + check_exists() { + if [ -f ./"${1}" ] + then + echo "Expected ${1} file to exist" + eval "$CLEANUP" + exit 1 + else + echo "Found ${1}" + fi + } # check that the PGO CLI version is recorded VER=$(cat ./kuttl-support-cluster/pgo-cli-version) @@ -93,9 +103,13 @@ commands: # check that the events file exist and is not empty check_file "kuttl-support-cluster/events" + # check that logs exist for the PG + # use `check_exists` so we can use a wildcard + check_exists "kuttl-support-cluster/pods/kuttl-support-cluster-00-*-0/pgdata/pg16/log/postgresql-*.log" + EVENTS="./kuttl-support-cluster/events" # check that the events file contains the expected string - if ! grep -Fq "Created container postgres-startup" $EVENTS + if ! grep -Fq "Started container postgres-startup" $EVENTS then echo "Events file does not contain expected string" eval "$CLEANUP" diff --git a/testing/kuttl/e2e/support-export/33--create_cluster_with_instrumentation.yaml b/testing/kuttl/e2e/support-export/33--create_cluster_with_instrumentation.yaml new file mode 100644 index 00000000..0fabdd8f --- /dev/null +++ b/testing/kuttl/e2e/support-export/33--create_cluster_with_instrumentation.yaml @@ -0,0 +1,19 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: kuttl-support-instrumentation +spec: + postgresVersion: 16 + instances: + - dataVolumeClaimSpec: + accessModes: [ReadWriteOnce] + resources: { requests: { storage: 1Gi } } + backups: + pgbackrest: + repos: + - name: repo1 + volume: + volumeClaimSpec: + accessModes: [ReadWriteOnce] + resources: { requests: { storage: 1Gi } } + instrumentation: {} diff --git a/testing/kuttl/e2e/support-export/33-assert.yaml b/testing/kuttl/e2e/support-export/33-assert.yaml new file mode 100644 index 00000000..cd3ba433 --- /dev/null +++ b/testing/kuttl/e2e/support-export/33-assert.yaml @@ -0,0 +1,32 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: kuttl-support-instrumentation +spec: + backups: + pgbackrest: + repos: + - name: repo1 + volume: + volumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + instances: + - dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + replicas: 1 + postgresVersion: 16 +status: + instances: + - name: "00" + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 + monitoring: {} diff --git a/testing/kuttl/e2e/support-export/34--support_export.yaml b/testing/kuttl/e2e/support-export/34--support_export.yaml new file mode 100644 index 00000000..5f357b7c --- /dev/null +++ b/testing/kuttl/e2e/support-export/34--support_export.yaml @@ -0,0 +1,163 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: +- script: kubectl-pgo --namespace $NAMESPACE --operator-namespace postgres-operator support export kuttl-support-instrumentation -o . +- script: tar -xzf ./crunchy_k8s_support_export_*.tar.gz +- script: | + CLEANUP="rm -r ./kuttl-support-instrumentation ./operator ./crunchy_k8s_support_export_*.tar.gz" + check_file() { + if [ ! -s ./"${1}" ] + then + echo "Expected ${1} file to not be empty" + eval "$CLEANUP" + exit 1 + else + echo "Found ${1}" + fi + } + check_exists() { + if [ -f ./"${1}" ] + then + echo "Expected ${1} file to exist" + eval "$CLEANUP" + exit 1 + else + echo "Found ${1}" + fi + } + + # check that the PGO CLI version is recorded + VER=$(cat ./kuttl-support-instrumentation/pgo-cli-version) + echo "$VER" | grep -E "v[0-9]+\.[0-9]+\.[0-9]+$" + STATUS=$? + [ "$STATUS" = 0 ] || { + echo "Expected PGO CLI version, got:" + echo "${VER}" + eval "$CLEANUP" + exit 1 + } + + # check that the cluster-names file exists and is not empty + check_file "kuttl-support-instrumentation/cluster-names" + + # check that the system-time file exists and is not empty + check_file "kuttl-support-instrumentation/system-time" + + # check that the context file exists and is not empty + check_file "kuttl-support-instrumentation/current-context" + + # check that the patroni info file exists and is not empty + check_file "kuttl-support-instrumentation/patroni-info" + + # check that the pgbackrest info file exists and is not empty + check_file "kuttl-support-instrumentation/pgbackrest-info" + + # check that the plugin list file exists and is not empty + # the file will at least include kubectl-pgo + check_file "kuttl-support-instrumentation/plugin-list" + + # check that the operator file exists and is not empty + # the list file will not be empty for the requested Kubernetes types + check_file "operator/deployments/list" + check_file "operator/replicasets/list" + check_file "operator/pods/list" + + # check for expected gzip compression level + FILE_INFO=$(file ./crunchy_k8s_support_export_*.tar.gz) + case "${FILE_INFO}" in + *'gzip compressed data, max compression'*) + ;; + *) + echo "Expected gzip max compression message, got:" + echo "${FILE_INFO}" + eval "$CLEANUP" + exit 1 + ;; + esac + + # Node directory and list file path + DIR="./kuttl-support-instrumentation/nodes/" + LIST="${DIR}list" + + # check for expected table header in the list file + KV=$(awk 'NR==1 {print $9}' $LIST) + [ "${KV}" = '|KERNEL-VERSION' ] || { + echo "Expected KERNEL-VERSION header, got:" + echo "${KV}" + eval "$CLEANUP" + exit 1 + } + + # check for a .yaml file with the name of the first Node in the list file + NODE="$(awk 'NR==2 {print $1}' $LIST).yaml" + + if [ ! -f "${DIR}${NODE}" ] + then + echo "Expected directory with file ${NODE}, got:" + ls ${DIR} + eval "$CLEANUP" + exit 1 + fi + + # check that the events file exist and is not empty + check_file "kuttl-support-instrumentation/events" + + # check that logs exist for the PG + # use `check_exists` so we can use a wildcard + check_exists "kuttl-support-instrumentation/pods/kuttl-support-cluster-00-*-0/pgdata/logs/postgresql-*.json" + + EVENTS="./kuttl-support-instrumentation/events" + # check that the events file contains the expected string + if ! grep -Fq "Started container postgres-startup" $EVENTS + then + echo "Events file does not contain expected string" + eval "$CLEANUP" + exit 1 + fi + + PROCESSES_DIR="./kuttl-support-instrumentation/processes/" + + # Check for the files that contain an expected pgBackRest server process. + # Expected to be found in the Postgres instance Pod's 'collector', 'database', + # 'replication-cert-copy', 'pgbackrest', and 'pgbackrest-config' containers + # and the pgBackRest repo Pod's 'pgbackrest' and 'pgbackrest-config' + # containers, i.e. 6 files total, but test will pass if at least one is found. + found=$(grep -lR "pgbackrest server" ${PROCESSES_DIR} | wc -l) + if [ "${found}" -lt 1 ]; then + echo "Expected to find pgBackRest process, got ${found}" + eval "$CLEANUP" + exit 1 + fi + + # Check for the files that contain an expected Postgres process. Expected + # to be found in the Postgres instance Pod's 'collector', 'database', 'replication-cert-copy', + # 'pgbackrest', and 'pgbackrest-config' containers, i.e. 5 files total, but + # test will pass if at least one is found. + found=$(grep -lR "postgres -D /pgdata/pg" ${PROCESSES_DIR} | wc -l) + if [ "${found}" -lt 1 ]; then + echo "Expected to find Postgres process, got ${found}" + eval "$CLEANUP" + exit 1 + fi + + # check that the PGO CLI log file contains expected messages + CLI_LOG="./kuttl-support-instrumentation/cli.log" + + # info output includes expected heading + if ! grep -Fq -- "- INFO - | PGO CLI Support Export Tool" $CLI_LOG + then + echo "PGO CLI log does not contain expected info message" + eval "$CLEANUP" + exit 1 + fi + + # debug output includes cluster name argument + if ! grep -Fq -- "- DEBUG - Arg - PostgresCluster Name: kuttl-support-instrumentation" $CLI_LOG + then + echo "PGO CLI log does not contain cluster name debug message" + eval "$CLEANUP" + exit 1 + fi + +- script: rm -r ./kuttl-support-instrumentation ./crunchy_k8s_support_export_*.tar.gz From 5719b276d67922361aa1761995c193812207ecac Mon Sep 17 00:00:00 2001 From: Benjamin Blattberg Date: Wed, 28 May 2025 11:26:40 -0500 Subject: [PATCH 32/45] Update lint (#128) Updates linter and linter rules Also bumps depdendencies. --- .github/workflows/lint.yaml | 2 +- .golangci.yaml | 196 ++++++++++++++++++++---------------- go.mod | 12 ++- go.sum | 16 +-- internal/cmd/export.go | 3 +- internal/cmd/pgo.go | 2 +- internal/cmd/version.go | 6 +- 7 files changed, 133 insertions(+), 104 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index a3a99e26..8aa5f7b5 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-go@v5 with: { go-version: stable } - - uses: golangci/golangci-lint-action@v7 + - uses: golangci/golangci-lint-action@v8 with: version: latest args: --timeout=5m diff --git a/.golangci.yaml b/.golangci.yaml index 1bb49ae7..46298e77 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,95 +1,121 @@ -# https://golangci-lint.run/usage/configuration/ - +version: "2" linters: - disable: - - gofumpt enable: + - asasalint + - asciicheck + - bidichk + - bodyclose - depguard + - durationcheck + - errchkjson + - errorlint + - exhaustive + - gocheckcompilerdirectives + - gochecksumtype - goheader - gomodguard - - gosimple + - gosec + - gosmopolitan - importas + - loggercheck + - makezero - misspell - - predeclared - - tenv - - tparallel + - musttag + - nilerr + - nilnesserr + - noctx + - protogetter + - reassign + - recvcheck + - rowserrcheck + - spancheck + - sqlclosecheck + - testifylint - unconvert - presets: - - bugs - - format - - unused + - unparam + - zerologlint + disable: + - contextcheck + settings: + depguard: + rules: + everything: + deny: + - pkg: io/ioutil + desc: | + Use the "io" and "os" packages instead. See https://go.dev/doc/go1.16#ioutil + not-tests: + files: + - '!$test' + deny: + - pkg: net/http/httptest + desc: Should be used only in tests. + - pkg: gotest.tools + desc: Should be used only in tests. + - pkg: testing/* + desc: The "testing" packages should be used only in tests. + - pkg: github.com/crunchydata/postgres-operator-client/internal/testing/* + desc: The "internal/testing" packages should be used only in tests. + errchkjson: + check-error-free-encoding: true + exhaustive: + default-signifies-exhaustive: true + goheader: + values: + regexp: + DATES: (202[1-4] - 2025|2025) + template: |- + Copyright {{ DATES }} Crunchy Data Solutions, Inc. -linters-settings: - depguard: + SPDX-License-Identifier: Apache-2.0 + gomodguard: + blocked: + modules: + - gopkg.in/yaml.v2: + recommendations: + - sigs.k8s.io/yaml + - gopkg.in/yaml.v3: + recommendations: + - sigs.k8s.io/yaml + - gotest.tools: + recommendations: + - gotest.tools/v3 + - k8s.io/kubernetes: + reason: | + k8s.io/kubernetes is for managing dependencies of the Kubernetes project, i.e. building kubelet and kubeadm. + importas: + alias: + - pkg: k8s.io/api/(\w+)/(v[\w\w]+) + alias: $1$2 + - pkg: k8s.io/apimachinery/pkg/apis/(\w+)/(v[\w\d]+) + alias: $1$2 + - pkg: k8s.io/apimachinery/pkg/api/errors + alias: apierrors + no-unaliased: true + exclusions: + generated: lax rules: - everything: - deny: - - pkg: io/ioutil - desc: > - Use the "io" and "os" packages instead. - See https://go.dev/doc/go1.16#ioutil - - not-tests: - files: - - '!$test' - deny: - - pkg: net/http/httptest - desc: Should be used only in tests. - - - pkg: gotest.tools - desc: Should be used only in tests. - - - pkg: testing/* - desc: The "testing" packages should be used only in tests. - - - pkg: github.com/crunchydata/postgres-operator-client/internal/testing/* - desc: The "internal/testing" packages should be used only in tests. - - gci: - sections: - - standard - - default - - prefix(github.com/crunchydata/postgres-operator-client) - - goheader: - template: |- - Copyright {{ DATES }} Crunchy Data Solutions, Inc. - - SPDX-License-Identifier: Apache-2.0 - values: - regexp: - DATES: '(202[1-4] - 2025|2025)' - - goimports: - local-prefixes: github.com/crunchydata/postgres-operator-client - - gomodguard: - blocked: - modules: - - gopkg.in/yaml.v2: { recommendations: [sigs.k8s.io/yaml] } - - gopkg.in/yaml.v3: { recommendations: [sigs.k8s.io/yaml] } - - gotest.tools: { recommendations: [gotest.tools/v3] } - - k8s.io/kubernetes: - reason: > - k8s.io/kubernetes is for managing dependencies of the Kubernetes - project, i.e. building kubelet and kubeadm. - - importas: - no-unaliased: true - alias: - - pkg: k8s.io/api/(\w+)/(v[\w\w]+) - alias: $1$2 - - pkg: k8s.io/apimachinery/pkg/apis/(\w+)/(v[\w\d]+) - alias: $1$2 - - pkg: k8s.io/apimachinery/pkg/api/errors - alias: apierrors - -issues: - # https://github.com/golangci/golangci-lint/issues/2239 - exclude-use-default: false - - exclude-rules: - # These testing packages are allowed in test files. - - linters: [depguard] - path: ^internal/testing - text: "gotest.tools" + - linters: + - depguard + path: ^internal/testing + text: gotest.tools + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + settings: + gci: + sections: + - standard + - default + - prefix(github.com/crunchydata/postgres-operator-client) + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/go.mod b/go.mod index b6e40e57..a06bb3d1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/crunchydata/postgres-operator-client -go 1.19 +go 1.23.0 + +toolchain go1.24.2 require ( github.com/spf13/cobra v1.5.0 @@ -56,11 +58,11 @@ require ( github.com/stretchr/testify v1.7.0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/go.sum b/go.sum index 84f651e0..c8ffd62c 100644 --- a/go.sum +++ b/go.sum @@ -611,8 +611,8 @@ golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -704,12 +704,12 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -719,8 +719,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 605b6d3e..e0f8b73c 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -1571,7 +1571,8 @@ func gatherPodLogs(ctx context.Context, Container: container.Name, }).Do(ctx) - if result.Error() != nil { + err = result.Error() + if err != nil { if apierrors.IsForbidden(result.Error()) { writeInfo(cmd, result.Error().Error()) // Continue and output errors for each pod log diff --git a/internal/cmd/pgo.go b/internal/cmd/pgo.go index 56d462ac..2a843258 100644 --- a/internal/cmd/pgo.go +++ b/internal/cmd/pgo.go @@ -107,7 +107,7 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e // Add flags for kubeconfig, authentication, namespace, and timeout to // every subcommand. // - https://docs.k8s.io/concepts/configuration/organize-cluster-access-kubeconfig/ - config.ConfigFlags.AddFlags(root.PersistentFlags()) + config.AddFlags(root.PersistentFlags()) // Defined command output. If not set, it falls back to [os.Stderr]. // - https://pkg.go.dev/github.com/spf13/cobra#Command.Print diff --git a/internal/cmd/version.go b/internal/cmd/version.go index 8dfff0e1..70622ccd 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -71,10 +71,10 @@ Operator Version: v5.7.0`, clientVersion)) } if crd != nil && - crd.ObjectMeta.Labels != nil && - crd.ObjectMeta.Labels["app.kubernetes.io/version"] != "" { + crd.Labels != nil && + crd.Labels["app.kubernetes.io/version"] != "" { - cmd.Printf("Operator Version: v%s\n", crd.ObjectMeta.Labels["app.kubernetes.io/version"]) + cmd.Printf("Operator Version: v%s\n", crd.Labels["app.kubernetes.io/version"]) } else { cmd.Println("Operator version not found.") } From e7abefe0455c28e3a253fcaafb5c886ff445e691 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:56:57 -0500 Subject: [PATCH 33/45] Bump aquasecurity/trivy-action in the all-github-actions group (#130) Bumps the all-github-actions group with 1 update: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.30.0 to 0.31.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.30.0...0.31.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-version: 0.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/trivy.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index ee3f295d..be1ac0e8 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -23,7 +23,7 @@ jobs: # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.30.0 + uses: aquasecurity/trivy-action@0.31.0 env: TRIVY_DEBUG: true with: @@ -44,7 +44,7 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@0.30.0 + uses: aquasecurity/trivy-action@0.31.0 with: scan-type: filesystem hide-progress: true @@ -56,7 +56,7 @@ jobs: # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.30.0 + uses: aquasecurity/trivy-action@0.31.0 with: scan-type: filesystem ignore-unfixed: true From 83d4554e236e408fd7153e08c32a34627364b183 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 04:09:38 +0000 Subject: [PATCH 34/45] Bump aquasecurity/trivy-action in the all-github-actions group Bumps the all-github-actions group with 1 update: [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `aquasecurity/trivy-action` from 0.31.0 to 0.32.0 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.31.0...0.32.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-version: 0.32.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/trivy.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index be1ac0e8..658d89cb 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -23,7 +23,7 @@ jobs: # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.31.0 + uses: aquasecurity/trivy-action@0.32.0 env: TRIVY_DEBUG: true with: @@ -44,7 +44,7 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@0.31.0 + uses: aquasecurity/trivy-action@0.32.0 with: scan-type: filesystem hide-progress: true @@ -56,7 +56,7 @@ jobs: # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.31.0 + uses: aquasecurity/trivy-action@0.32.0 with: scan-type: filesystem ignore-unfixed: true From 8fb0eea60cc750417cf6e0d444fa3bd973c6c97d Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Wed, 23 Jul 2025 10:09:24 -0400 Subject: [PATCH 35/45] copy pgBackRest logs to local machine rather than stream (#129) This features copies the pgBackRest logs to a user's local machine. It mimics the behavior we use for the Postgres logs. We have found situation where pgBackRest are large and the previous behavior would time out. --- internal/cmd/export.go | 131 ++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 36 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index e0f8b73c..6726d8f9 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -469,7 +469,7 @@ Collecting PGO CLI logs... } // All pgBackRest Logs on the Postgres Instances - err = gatherDbBackrestLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + err = gatherDbBackrestLogs(ctx, clientset, restConfig, namespace, clusterName, outputDir, outputFile, tw, cmd) if err != nil { writeInfo(cmd, fmt.Sprintf("Error gathering pgBackRest DB Hosts Logs: %s", err)) } @@ -481,7 +481,7 @@ Collecting PGO CLI logs... } // All pgBackRest Logs on the Repo Host - err = gatherRepoHostLogs(ctx, clientset, restConfig, namespace, clusterName, tw, cmd) + err = gatherRepoHostLogs(ctx, clientset, restConfig, namespace, clusterName, outputDir, outputFile, tw, cmd) if err != nil { writeInfo(cmd, fmt.Sprintf("Error gathering pgBackRest Repo Host Logs: %s", err)) } @@ -1223,6 +1223,8 @@ func gatherDbBackrestLogs(ctx context.Context, config *rest.Config, namespace string, clusterName string, + outputDir string, + outputFile string, tw *tar.Writer, cmd *cobra.Command, ) error { @@ -1291,30 +1293,58 @@ func gatherDbBackrestLogs(ctx context.Context, } logFiles := strings.Split(strings.TrimSpace(stdout), "\n") + + // localDirectory is created to save data on disk + // e.g. outputDir/crunchy_k8s_support_export_2022-08-08-115726-0400/remotePath + localDirectory := filepath.Join(outputDir, strings.ReplaceAll(outputFile, ".tar.gz", "")) + + // flag to determine whether or not to remove localDirectory after the loop + // When an error happens, this flag will switch to false + // It's nice to have the extra data around when errors have happened + doCleanup := true + for _, logFile := range logFiles { writeDebug(cmd, fmt.Sprintf("LOG FILE: %s\n", logFile)) - var buf bytes.Buffer - - stdout, stderr, err := Executor(exec).catFile(logFile) + // get the file size to stream + fileSize, err := getRemoteFileSize(config, namespace, pod.Name, util.ContainerDatabase, logFile) if err != nil { - if apierrors.IsForbidden(err) { - writeInfo(cmd, err.Error()) - // Continue and output errors for each log file - // Allow the user to see and address all issues at once - continue - } - return err + writeDebug(cmd, fmt.Sprintf("could not get file size for %s: %v\n", logFile, err)) + continue } - buf.Write([]byte(stdout)) - if stderr != "" { - str := fmt.Sprintf("\nError returned: %s\n", stderr) - buf.Write([]byte(str)) + // fileSpecSrc is namespace/podname:path/to/file + // fileSpecDest is the local destination of the file + // These are used to help the user grab the file manually when necessary + // e.g. postgres-operator/hippo-instance1-vp9k-0:pgdata/pgbackrest/log/db-stanza-create.log + fileSpecSrc := fmt.Sprintf("%s/%s:%s", namespace, pod.Name, logFile) + fileSpecDest := filepath.Join(localDirectory, logFile) + writeInfo(cmd, fmt.Sprintf("\tSize of %-85s %v", fileSpecSrc, convertBytes(fileSize))) + + // Stream the file to disk and write the local file to the tar + err = streamFileFromPod(config, tw, + localDirectory, clusterName, namespace, pod.Name, util.ContainerDatabase, logFile, fileSize) + + if err != nil { + doCleanup = false // prevent the deletion of localDirectory so a user can examine contents + writeInfo(cmd, fmt.Sprintf("\tError streaming file %s: %v", logFile, err)) + writeInfo(cmd, fmt.Sprintf("\tCollect manually with kubectl cp -c %s %s %s", + util.ContainerDatabase, fileSpecSrc, fileSpecDest)) + writeInfo(cmd, fmt.Sprintf("\tRemove %s manually after gathering necessary information", localDirectory)) + continue } - path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile - if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { - return err + } + + // doCleanup is true when there are no errors above. + if doCleanup { + // Remove the local directory created to hold the data + // Errors in removing localDirectory should instruct the user to remove manually. + // This happens often on Windows + err = os.RemoveAll(localDirectory) + if err != nil { + writeInfo(cmd, fmt.Sprintf("\tError removing %s: %v", localDirectory, err)) + writeInfo(cmd, fmt.Sprintf("\tYou may need to remove %s manually", localDirectory)) + continue } } @@ -1436,6 +1466,8 @@ func gatherRepoHostLogs(ctx context.Context, config *rest.Config, namespace string, clusterName string, + outputDir string, + outputFile string, tw *tar.Writer, cmd *cobra.Command, ) error { @@ -1503,30 +1535,57 @@ func gatherRepoHostLogs(ctx context.Context, } logFiles := strings.Split(strings.TrimSpace(stdout), "\n") + + // localDirectory is created to save data on disk + // e.g. outputDir/crunchy_k8s_support_export_2022-08-08-115726-0400/remotePath + localDirectory := filepath.Join(outputDir, strings.ReplaceAll(outputFile, ".tar.gz", "")) + + // flag to determine whether or not to remove localDirectory after the loop + // When an error happens, this flag will switch to false + // It's nice to have the extra data around when errors have happened + doCleanup := true + for _, logFile := range logFiles { writeDebug(cmd, fmt.Sprintf("LOG FILE: %s\n", logFile)) - var buf bytes.Buffer - - stdout, stderr, err := Executor(exec).catFile(logFile) + // get the file size to stream + fileSize, err := getRemoteFileSize(config, namespace, pod.Name, util.ContainerPGBackrest, logFile) if err != nil { - if apierrors.IsForbidden(err) { - writeInfo(cmd, err.Error()) - // Continue and output errors for each log file - // Allow the user to see and address all issues at once - continue - } - return err + writeDebug(cmd, fmt.Sprintf("could not get file size for %s: %v\n", logFile, err)) + continue } - buf.Write([]byte(stdout)) - if stderr != "" { - str := fmt.Sprintf("\nError returned: %s\n", stderr) - buf.Write([]byte(str)) + // fileSpecSrc is namespace/podname:path/to/file + // fileSpecDest is the local destination of the file + // These are used to help the user grab the file manually when necessary + // e.g. postgres-operator/hippo-repo-host-0:pgbackrest/repo1/log/db-backup.log + fileSpecSrc := fmt.Sprintf("%s/%s:%s", namespace, pod.Name, logFile) + fileSpecDest := filepath.Join(localDirectory, logFile) + writeInfo(cmd, fmt.Sprintf("\tSize of %-85s %v", fileSpecSrc, convertBytes(fileSize))) + + // Stream the file to disk and write the local file to the tar + err = streamFileFromPod(config, tw, + localDirectory, clusterName, namespace, pod.Name, util.ContainerPGBackrest, logFile, fileSize) + + if err != nil { + doCleanup = false // prevent the deletion of localDirectory so a user can examine contents + writeInfo(cmd, fmt.Sprintf("\tError streaming file %s: %v", logFile, err)) + writeInfo(cmd, fmt.Sprintf("\tCollect manually with kubectl cp -c %s %s %s", + util.ContainerPGBackrest, fileSpecSrc, fileSpecDest)) + writeInfo(cmd, fmt.Sprintf("\tRemove %s manually after gathering necessary information", localDirectory)) + continue } + } - path := clusterName + fmt.Sprintf("/pods/%s/", pod.Name) + logFile - if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { - return err + // doCleanup is true when there are no errors above. + if doCleanup { + // Remove the local directory created to hold the data + // Errors in removing localDirectory should instruct the user to remove manually. + // This happens often on Windows + err = os.RemoveAll(localDirectory) + if err != nil { + writeInfo(cmd, fmt.Sprintf("\tError removing %s: %v", localDirectory, err)) + writeInfo(cmd, fmt.Sprintf("\tYou may need to remove %s manually", localDirectory)) + continue } } From 3f28188aa65f4828cb8650f235cc42b8ca806dff Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Thu, 24 Jul 2025 12:26:26 -0400 Subject: [PATCH 36/45] gather PGUpgrade resource (#132) * gather PGUpgrade resource * use CommandContext to get around linters * rename 'get' to 'getCluster' for clarity --- internal/cmd/export.go | 52 +++++++++++++++++++++++++++++++++++++---- internal/util/naming.go | 6 +++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 6726d8f9..56d7aa80 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -351,9 +351,9 @@ Collecting PGO CLI logs... return err } - get, err := postgresClient.Namespace(namespace).Get(ctx, + getCluster, err := postgresClient.Namespace(namespace).Get(ctx, clusterName, metav1.GetOptions{}) - if err != nil || get == nil { + if err != nil || getCluster == nil { if apierrors.IsForbidden(err) || apierrors.IsNotFound(err) { return err } @@ -425,7 +425,7 @@ Collecting PGO CLI logs... } // Gather PostgresCluster manifest - err = gatherClusterSpec(get, clusterName, tw, cmd) + err = gatherClusterSpec(getCluster, clusterName, tw, cmd) if err != nil { writeInfo(cmd, fmt.Sprintf("Error gathering PostgresCluster manifest: %s", err)) } @@ -462,7 +462,7 @@ Collecting PGO CLI logs... // All Postgres Logs on the Postgres Instances (primary and replicas) if numLogs > 0 { err = gatherPostgresLogsAndConfigs(ctx, clientset, restConfig, - namespace, clusterName, outputDir, outputFile, numLogs, tw, cmd, get) + namespace, clusterName, outputDir, outputFile, numLogs, tw, cmd, getCluster) if err != nil { writeInfo(cmd, fmt.Sprintf("Error gathering Postgres Logs and Config: %s", err)) } @@ -561,6 +561,22 @@ Collecting PGO CLI logs... writeInfo(cmd, fmt.Sprintf("Error gathering kubectl plugins: %s", err)) } + // Get PGUpgrade spec (if available) + writeInfo(cmd, "Collecting PGUpgrade spec (if available)...") + + key := util.AllowUpgradeAnnotation() + value, exists := getCluster.GetAnnotations()[key] + + if exists { + writeInfo(cmd, fmt.Sprintf("The PGUpgrade object is: %s", value)) + err = gatherPGUpgradeSpec(clusterName, namespace, value, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering PGUpgrade spec: %s", err)) + } + } else { + writeInfo(cmd, fmt.Sprintf("There is no PGUpgrade object associated with cluster '%s'", clusterName)) + } + // Print cli output writeInfo(cmd, "Collecting PGO CLI logs...") path := clusterName + "/cli.log" @@ -579,7 +595,10 @@ Collecting PGO CLI logs... } func gatherPluginList(clusterName string, tw *tar.Writer, cmd *cobra.Command) error { - ex := exec.Command("kubectl", "plugin", "list") + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() // Ensure the context is canceled to avoid leaks + + ex := exec.CommandContext(ctx, "kubectl", "plugin", "list") msg, err := ex.Output() if err != nil { @@ -594,6 +613,29 @@ func gatherPluginList(clusterName string, tw *tar.Writer, cmd *cobra.Command) er return nil } +func gatherPGUpgradeSpec(clusterName, namespace, pgUpgrade string, tw *tar.Writer, cmd *cobra.Command) error { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() // Ensure the context is canceled to avoid leaks + + ex := exec.CommandContext(ctx, "kubectl", "get", "pgupgrade", pgUpgrade, "-n", namespace, "-o", "yaml") + msg, err := ex.Output() + + if err != nil { + msg = append(msg, err.Error()...) + msg = append(msg, []byte(` +There was an error running 'kubectl get pgupgrade'. Verify permissions and that the resource exists.`)...) + + writeInfo(cmd, fmt.Sprintf("Error: '%s'", msg)) + } + + path := clusterName + "/pgupgrade.yaml" + if err := writeTar(tw, msg, path, cmd); err != nil { + return err + } + + return nil +} + // exportSizeReport defines the message displayed when a support export archive // is created. If the size of the archive file is greater than 25MiB, an alternate // message is displayed. diff --git a/internal/util/naming.go b/internal/util/naming.go index 446fccf5..7d11e780 100644 --- a/internal/util/naming.go +++ b/internal/util/naming.go @@ -89,3 +89,9 @@ func PostgresUserSecretLabels(clusterName string) string { return LabelCluster + "=" + clusterName + "," + LabelRole + "=" + RolePostgresUser } + +// AllowUpgradeAnnotation is the annotation key to allow of PostgresCluster +// to upgrade. Its value is the name of the PGUpgrade object. +func AllowUpgradeAnnotation() string { + return labelPrefix + "allow-upgrade" +} From 717edc0d5ded8ae9ecc92c94ccc1f8c0eddc6193 Mon Sep 17 00:00:00 2001 From: TJ Moore Date: Tue, 29 Jul 2025 14:33:41 -0400 Subject: [PATCH 37/45] Update versions and add release notes for v0.5.2 --- docs/config.toml | 2 +- docs/content/reference/pgo_version.md | 2 +- docs/content/releases/0.5.2.md | 24 ++++++++++++++++++++++++ internal/cmd/client_version.go | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 docs/content/releases/0.5.2.md diff --git a/docs/config.toml b/docs/config.toml index 9a751c9d..3162c4be 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -15,7 +15,7 @@ defaultContentLanguageInSubdir= false enableMissingTranslationPlaceholders = false [params] -clientVersion = "0.5.1" +clientVersion = "0.5.2" # crunchy-hugo-theme params showVisitedLinks = false # default is false # in theme diff --git a/docs/content/reference/pgo_version.md b/docs/content/reference/pgo_version.md index eed771db..2845b235 100644 --- a/docs/content/reference/pgo_version.md +++ b/docs/content/reference/pgo_version.md @@ -31,7 +31,7 @@ pgo version ``` ### Example output ``` -Client Version: v0.5.1 +Client Version: v0.5.2 Operator Version: v5.7.0 ``` diff --git a/docs/content/releases/0.5.2.md b/docs/content/releases/0.5.2.md new file mode 100644 index 00000000..918eea65 --- /dev/null +++ b/docs/content/releases/0.5.2.md @@ -0,0 +1,24 @@ +--- +title: "0.5.2" +draft: false +weight: 991 +--- + +[Crunchy Postgres for Kubernetes]: https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes +[`pgo` CLI documentation]: https://access.crunchydata.com/documentation/postgres-operator-client/latest + +Crunchy Data announces the release of `pgo`, Postgres Operator Client from Crunchy Data 0.5.2. + +Built as a `kubectl` plugin, the `pgo` CLI facilitates the creation and management of PostgreSQL clusters created using [Crunchy Postgres for Kubernetes][]. + +For more information about using the CLI and the various commands available, please see the [`pgo` CLI documentation][]. + +Additionally, please see the [CPK documentation](https://access.crunchydata.com/documentation/postgres-operator/latest) for information about [getting started](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/) with Crunchy Postgres for Kubernetes. + +## Features + +- The `support export` command now includes the following improvements + - Adds support for gathering on volume Patroni logs. + - Adds support for gathering logs based on OTel configuration settings. + - Adds the ability to copy pgBackRest logs to a user's local machine. + - Gathers PGUpgrade resources. \ No newline at end of file diff --git a/internal/cmd/client_version.go b/internal/cmd/client_version.go index 8d588fc1..3e99094e 100644 --- a/internal/cmd/client_version.go +++ b/internal/cmd/client_version.go @@ -5,4 +5,4 @@ package cmd // store the current PGO CLI version -const clientVersion = "v0.5.1" +const clientVersion = "v0.5.2" From 8e246577585d16c7d5629742e345a31239dd77ac Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Tue, 5 Aug 2025 12:42:24 -0400 Subject: [PATCH 38/45] updates to support export (#135) * add ResourceQuotas We want to gather ResourceQuotas as part of the information gathered by the Support Export. * gather additional Postgres data points We want to gather additional Postgres data points like disk free, disk usage, and the count of *.ready files. This aids in troubleshooting clusters. --- internal/cmd/export.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 56d7aa80..abfad52c 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -160,6 +160,10 @@ var otherNamespacedResources = []schema.GroupVersionResource{{ Group: corev1.SchemeGroupVersion.Group, Version: corev1.SchemeGroupVersion.Version, Resource: "limitranges", +}, { + Group: corev1.SchemeGroupVersion.Group, + Version: corev1.SchemeGroupVersion.Version, + Resource: "resourcequotas", }} // newSupportCommand returns the support subcommand of the PGO plugin. @@ -1224,6 +1228,9 @@ func gatherPostgresLogsAndConfigs(ctx context.Context, commands := []Command{ {path: "pg_controldata", description: "pg_controldata"}, + {path: "df -h /pgdata", description: "disk free"}, + {path: "du -h /pgdata", description: "disk usage"}, + {path: "ls /pgdata/*/archive_status/*.ready | wc -l", description: "Archive Ready File Count"}, } var buf bytes.Buffer From 97e24eada7a5ed1503bde25c7a46ba5ed1317899 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:17:46 +0000 Subject: [PATCH 39/45] Bump actions/checkout from 4 to 5 in the all-github-actions group Bumps the all-github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4 to 5 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yaml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yaml | 4 ++-- .github/workflows/trivy.yaml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 1590cafc..95229cc5 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -17,7 +17,7 @@ jobs: security-events: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: { go-version: stable } diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 8aa5f7b5..b97cc791 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,7 +12,7 @@ jobs: contents: read checks: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: { go-version: stable } diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1b8e2c9c..3c6ebdce 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -12,7 +12,7 @@ jobs: go-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: { go-version: stable } @@ -27,7 +27,7 @@ jobs: matrix: kubernetes: [v1.27, v1.24, v1.21] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: { go-version: stable } diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index 658d89cb..d5a02e72 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -14,7 +14,7 @@ jobs: licenses: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Trivy needs a populated Go module cache to detect Go module licenses. - uses: actions/setup-go@v5 @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Run trivy and log detected and fixed vulnerabilities # This report should match the uploaded code scan report below From 683c1e1385aa3c0b0fd72a37077b437482fb3b5f Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Mon, 25 Aug 2025 16:07:32 -0400 Subject: [PATCH 40/45] include kubectl describe output (#137) * gather pg_settings Query pg_settings to capture current Postgres settings directly from the DB. * run kubectl describe commands on various resources * removing cluster-info cluster-info returns a lot of sensitive information and it out-of-scope for the support export. * getting CRDs Remove the `kubectl describe crds` and replace with getting the CRDs via the correct APIs. This brings CRDs into the support export output on the same level as pods, sts, etc. * fix unnecessary use of fmt.Sprintf * better gathering of PGAdmin resources Treats PGAdmin resources separately since they are independent of a PostgresCluster. Gathers all PGAdmin resources in a namespace. This is usually one, but it could be several. * fix describe for clusterrole and clusterrolebinding on openshift * refactor describe clusterrole and clusterrolebinding This makes it more clear that 'postgresoperator' is a special case for certain installers. * refactor to account for labels on some installers Some installers don't label the CRD with app.kubernetes.io/name=pgo. This refactor filters all the CRDs for a name containing "postgres-operator.crunchydata.com" --- .../v1beta1/pgadmin_types.go | 39 +++ internal/cmd/export.go | 232 +++++++++++++++++- internal/util/naming.go | 3 + 3 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 internal/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go diff --git a/internal/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go b/internal/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go new file mode 100644 index 00000000..3003e641 --- /dev/null +++ b/internal/apis/postgres-operator.crunchydata.com/v1beta1/pgadmin_types.go @@ -0,0 +1,39 @@ +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/dynamic" +) + +func NewPgadminClient(rcg resource.RESTClientGetter) ( + *meta.RESTMapping, dynamic.NamespaceableResourceInterface, error, +) { + gvk := GroupVersion.WithKind("PGAdmin") + + mapper, err := rcg.ToRESTMapper() + if err != nil { + return nil, nil, err + } + + mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + return nil, nil, err + } + + config, err := rcg.ToRESTConfig() + if err != nil { + return nil, nil, err + } + + client, err := dynamic.NewForConfig(config) + if err != nil { + return nil, nil, err + } + + return mapping, client.Resource(mapping.Resource), nil +} diff --git a/internal/cmd/export.go b/internal/cmd/export.go index abfad52c..0ac814fe 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -27,6 +27,8 @@ import ( networkingv1 "k8s.io/api/networking/v1" policyv1 "k8s.io/api/policy/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -341,6 +343,11 @@ Collecting PGO CLI logs... return err } + apiExtensionClientSet, err := apiextensionsclientset.NewForConfig(restConfig) + if err != nil { + return err + } + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) if err != nil { return err @@ -456,6 +463,12 @@ Collecting PGO CLI logs... writeInfo(cmd, fmt.Sprintf("Error gathering Namespaced API Resources: %s", err)) } + // Gather CRDs + err = gatherCrds(ctx, apiExtensionClientSet, clusterName, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering CRDs: %s", err)) + } + // Gather Events err = gatherEvents(ctx, clientset, namespace, clusterName, tw, cmd) if err != nil { @@ -581,6 +594,60 @@ Collecting PGO CLI logs... writeInfo(cmd, fmt.Sprintf("There is no PGUpgrade object associated with cluster '%s'", clusterName)) } + // Run kubectl describe and similar commands + writeInfo(cmd, "Running kubectl describe nodes...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/nodes", "describe", "nodes") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe nodes: %s", err)) + } + + writeInfo(cmd, "Running kubectl describe postgrescluster...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/postgrescluster", "describe", "postgrescluster", clusterName, "-n", namespace) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe postgrescluster: %s", err)) + } + + // Resource name is generally 'postgres-operator' but in some environments + // like Openshift it could be 'postgresoperator' + writeInfo(cmd, "Running kubectl describe clusterrole...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrole", "describe", "clusterrole", "postgres-operator") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrole: %s", err)) + writeInfo(cmd, "Could not find clusterrole 'postgres-operator'. Looking for 'postgresoperator'...") + + // Check for the alternative spelling with 'postgresoperator' + err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrole", "describe", "clusterrole", "postgresoperator") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrole: %s", err)) + } + } + + // Resource name is generally 'postgres-operator' but in some environments + // like Openshift it could be 'postgresoperator' + writeInfo(cmd, "Running kubectl describe clusterrolebinding...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrolebinding", "describe", "clusterrolebinding", "postgres-operator") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrolebinding: %s", err)) + + // Check for the alternative spelling with 'postgresoperator' + writeInfo(cmd, "Could not find clusterrolebinding 'postgres-operator'. Looking for 'postgresoperator'...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrolebinding", "describe", "clusterrolebinding", "postgresoperator") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrolebinding: %s", err)) + } + } + + writeInfo(cmd, "Running kubectl describe lease...") + err = runKubectlCommand(tw, cmd, "operator/describe/lease", "describe", "lease", "-n", operatorNamespace) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe lease: %s", err)) + } + + err = gatherPgadminResources(config, clientset, ctx, namespace, tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering PGAdmin Resources: %s", err)) + } + // Print cli output writeInfo(cmd, "Collecting PGO CLI logs...") path := clusterName + "/cli.log" @@ -598,6 +665,74 @@ Collecting PGO CLI logs... return cmd } +func gatherPgadminResources(config *internal.Config, + clientset *kubernetes.Clientset, + ctx context.Context, + namespace string, + tw *tar.Writer, cmd *cobra.Command) error { + + _, pgadminClient, err := v1beta1.NewPgadminClient(config) + + if err != nil { + return err + } + + pgadmins, err := pgadminClient.Namespace(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + return err + } + + if len(pgadmins.Items) == 0 { + // If we didn't find any resources, skip + writeInfo(cmd, "Resource PGAdmin not found, skipping") + return nil + } + + // Create a buffer to generate string with the table formatted list + var buf bytes.Buffer + if err := printers.NewTablePrinter(printers.PrintOptions{}). + PrintObj(pgadmins, &buf); err != nil { + return err + } + + // Define the file name/path where the list file will be created and + // write to the tar + path := "pgadmin" + "/list" + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } + + for _, obj := range pgadmins.Items { + b, err := yaml.Marshal(obj) + if err != nil { + return err + } + + path := "pgadmin" + "/" + obj.GetName() + ".yaml" + if err := writeTar(tw, b, path, cmd); err != nil { + return err + } + + writeInfo(cmd, "Collecting PGAdmin pod logs...") + err = gatherPodLogs(ctx, clientset, namespace, fmt.Sprintf("%s=%s", util.LabelPgadmin, obj.GetName()), "pgadmin", tw, cmd) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error gathering PGAdmin pod logs: %s", err)) + } + + writeInfo(cmd, "Running kubectl describe pgadmin") + err = runKubectlCommand(tw, cmd, "pgadmin/describe/"+obj.GetName(), "describe", "pgadmin", obj.GetName(), "-n", namespace) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe pgadmin: %s", err)) + } + } + + return nil +} + func gatherPluginList(clusterName string, tw *tar.Writer, cmd *cobra.Command) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Ensure the context is canceled to avoid leaks @@ -640,6 +775,29 @@ There was an error running 'kubectl get pgupgrade'. Verify permissions and that return nil } +func runKubectlCommand(tw *tar.Writer, cmd *cobra.Command, path string, cmdArgs ...string) error { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() // Ensure the context is canceled to avoid leaks + + ex := exec.CommandContext(ctx, "kubectl", cmdArgs...) + msg, err := ex.Output() + + if err != nil { + msg = append(msg, err.Error()...) + msg = append(msg, []byte(` +There was an error running the command. Verify permissions and that the resource exists.`)...) + + writeInfo(cmd, fmt.Sprintf("Error: '%s'", msg)) + return err + } + + if err := writeTar(tw, msg, path, cmd); err != nil { + return err + } + + return nil +} + // exportSizeReport defines the message displayed when a support export archive // is created. If the size of the archive file is greater than 25MiB, an alternate // message is displayed. @@ -956,6 +1114,73 @@ func gatherNamespacedAPIResources(ctx context.Context, return nil } +// gatherCrds gathers all the CRDs with a name=pgo label +func gatherCrds(ctx context.Context, + clientset *apiextensionsclientset.Clientset, + clusterName string, + tw *tar.Writer, + cmd *cobra.Command, +) error { + writeInfo(cmd, "Collecting CRDs...") + + crdList, err := clientset.ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{}) + + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + return err + } + + // Get only the CRDs matching our filter + nameFilter := "postgres-operator.crunchydata.com" + + filteredCRDs := &apiextensionsv1.CustomResourceDefinitionList{ + Items: []apiextensionsv1.CustomResourceDefinition{}, + } + for _, crd := range crdList.Items { + if strings.Contains(crd.Name, nameFilter) { + filteredCRDs.Items = append(filteredCRDs.Items, crd) + } + } + + if len(filteredCRDs.Items) == 0 { + // If we didn't find any resources, skip + writeInfo(cmd, "Resource CRDs not found, skipping") + return nil + } + + // Create a buffer to generate string with the table formatted list + var buf bytes.Buffer + if err := printers.NewTablePrinter(printers.PrintOptions{}). + PrintObj(filteredCRDs, &buf); err != nil { + return err + } + + // Define the file name/path where the list file will be created and + // write to the tar + path := clusterName + "/" + "crds" + "/list" + if err := writeTar(tw, buf.Bytes(), path, cmd); err != nil { + return err + } + + for _, obj := range filteredCRDs.Items { + b, err := yaml.Marshal(obj) + if err != nil { + return err + } + + path := clusterName + "/" + "crds" + "/" + obj.GetName() + ".yaml" + if err := writeTar(tw, b, path, cmd); err != nil { + return err + } + } + + return nil + +} + // gatherEvents gathers all events from a namespace, selects information (based on // what kubectl outputs), formats the data then prints to the tar file func gatherEvents(ctx context.Context, @@ -1229,8 +1454,9 @@ func gatherPostgresLogsAndConfigs(ctx context.Context, commands := []Command{ {path: "pg_controldata", description: "pg_controldata"}, {path: "df -h /pgdata", description: "disk free"}, - {path: "du -h /pgdata", description: "disk usage"}, + {path: "du -h /pgdata | column -t -o \" \"", description: "disk usage"}, {path: "ls /pgdata/*/archive_status/*.ready | wc -l", description: "Archive Ready File Count"}, + {path: "psql -P format=wrapped -P columns=180 -c \"select name,setting,source,sourcefile,sourceline FROM pg_settings order by 1\"", description: "PG Settings"}, } var buf bytes.Buffer @@ -1670,6 +1896,10 @@ func gatherPodLogs(ctx context.Context, } for _, pod := range pods.Items { + err = runKubectlCommand(tw, cmd, rootDir+"/describe/"+"pods/"+pod.GetName(), "describe", "pods", pod.GetName(), "-n", namespace) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe pods: %s", err)) + } containers := pod.Spec.Containers containers = append(containers, pod.Spec.InitContainers...) for _, container := range containers { diff --git a/internal/util/naming.go b/internal/util/naming.go index 7d11e780..06e8646c 100644 --- a/internal/util/naming.go +++ b/internal/util/naming.go @@ -13,6 +13,9 @@ const ( // LabelCluster is used to label PostgresCluster objects. LabelCluster = labelPrefix + "cluster" + // LabelPgadmin is used to label PGAdmin objects. + LabelPgadmin = labelPrefix + "pgadmin" + // LabelData is used to identify Pods and Volumes store Postgres data. LabelData = labelPrefix + "data" From db3197cf191f71111010cdcc08c19b2a3d0db07f Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Fri, 19 Sep 2025 15:29:55 -0400 Subject: [PATCH 41/45] Migrating PGO CLI kuttl tests to chainsaw (#138) * Kyverno policies We can use Kyverno policies to facilitate testing with different image repos. * initial set of templates These are helpful templates to use when testing the PGO CLI * default config and values YAML files * Chainsaw test for PGO CLI backup * Chainsaw test for PGO CLI backup * Chainsaw tests for PGO CLI create * Chainsaw tests for PGO CLI delete * Chainsaw test for PGO CLI show * Chainsaw tests for PGO CLI version * Add Chainsaw README Instructions for how to run the tests * remove skipDelete The chainsaw-show Test should remove resources after it completes. * Chainsaw test for PGO CLI start-stop * Chainsaw test for PGO CLI restore * Update chainsaw tests with helpful sleeps * Update template with better check for backup complete * added newlines at the end * renamed steps in version test * refactored backupSelector The way we defined the selector for backups was quite terse. This refactor simplifies the logic. * Chainsaw test for PGO CLI support export --- testing/chainsaw/README | 22 + .../chainsaw/e2e/backup/chainsaw-test.yaml | 658 +++++++++++++ .../chainsaw/e2e/backup/change-ownership.yaml | 9 + testing/chainsaw/e2e/config.yaml | 12 + .../chainsaw/e2e/create/chainsaw-test.yaml | 146 +++ .../chainsaw/e2e/delete/chainsaw-test.yaml | 88 ++ .../chainsaw/e2e/restore/chainsaw-test.yaml | 713 ++++++++++++++ testing/chainsaw/e2e/show/chainsaw-test.yaml | 402 ++++++++ .../e2e/start-stop/chainsaw-test.yaml | 255 +++++ .../chainsaw/e2e/support/chainsaw-test.yaml | 932 ++++++++++++++++++ .../e2e/templates/confirm-created.yaml | 24 + .../create-cluster-from-manifest.yaml | 36 + .../create-cluster-without-backups.yaml | 27 + .../e2e/templates/create-cluster.yaml | 31 + testing/chainsaw/e2e/templates/psql-data.yaml | 74 ++ .../templates/replica-backup-complete.yaml | 25 + testing/chainsaw/e2e/values.yaml | 5 + .../chainsaw/e2e/version/chainsaw-test.yaml | 50 + testing/chainsaw/policies/README | 49 + .../chainsaw/policies/v1.15.1/00-rbac.yaml | 32 + ...1-inject-imagepullsecret-to-namespace.yaml | 58 ++ .../v1.15.1/02-add-imagepullsecrets.yaml | 53 + 22 files changed, 3701 insertions(+) create mode 100644 testing/chainsaw/README create mode 100644 testing/chainsaw/e2e/backup/chainsaw-test.yaml create mode 100644 testing/chainsaw/e2e/backup/change-ownership.yaml create mode 100644 testing/chainsaw/e2e/config.yaml create mode 100644 testing/chainsaw/e2e/create/chainsaw-test.yaml create mode 100644 testing/chainsaw/e2e/delete/chainsaw-test.yaml create mode 100644 testing/chainsaw/e2e/restore/chainsaw-test.yaml create mode 100644 testing/chainsaw/e2e/show/chainsaw-test.yaml create mode 100644 testing/chainsaw/e2e/start-stop/chainsaw-test.yaml create mode 100644 testing/chainsaw/e2e/support/chainsaw-test.yaml create mode 100644 testing/chainsaw/e2e/templates/confirm-created.yaml create mode 100644 testing/chainsaw/e2e/templates/create-cluster-from-manifest.yaml create mode 100644 testing/chainsaw/e2e/templates/create-cluster-without-backups.yaml create mode 100644 testing/chainsaw/e2e/templates/create-cluster.yaml create mode 100644 testing/chainsaw/e2e/templates/psql-data.yaml create mode 100644 testing/chainsaw/e2e/templates/replica-backup-complete.yaml create mode 100644 testing/chainsaw/e2e/values.yaml create mode 100644 testing/chainsaw/e2e/version/chainsaw-test.yaml create mode 100644 testing/chainsaw/policies/README create mode 100644 testing/chainsaw/policies/v1.15.1/00-rbac.yaml create mode 100644 testing/chainsaw/policies/v1.15.1/01-inject-imagepullsecret-to-namespace.yaml create mode 100644 testing/chainsaw/policies/v1.15.1/02-add-imagepullsecrets.yaml diff --git a/testing/chainsaw/README b/testing/chainsaw/README new file mode 100644 index 00000000..f5843a20 --- /dev/null +++ b/testing/chainsaw/README @@ -0,0 +1,22 @@ + +# README + +## Install PGO + +```shell +kubectl apply -k kustomize/install/namespace +kubectl apply --server-side -k kustomize/install/default +``` + +## Configure Image Pull Secrets + +If your images are not publicly accessible, you will need to configure ImagePullSecrets. +Kyverno Policies can be used to inject them into the tests for you. +See [README](./policies/README) for the details. + + +## Run Chainsaw + +```shell +chainsaw test --config ./e2e/config.yaml --values ./e2e/values.yaml --test-dir e2e +``` diff --git a/testing/chainsaw/e2e/backup/chainsaw-test.yaml b/testing/chainsaw/e2e/backup/chainsaw-test.yaml new file mode 100644 index 00000000..23719ef9 --- /dev/null +++ b/testing/chainsaw/e2e/backup/chainsaw-test.yaml @@ -0,0 +1,658 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-replica-create-backup +spec: + bindings: + - name: cluster + value: check-replica-create-backup + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=check-replica-create-backup', + 'postgres-operator.crunchydata.com/pgbackrest-backup=replica-create', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: Verify COMMAND_OPTS matches "--stanza=db --repo=1" + try: + - sleep: + duration: 10s + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "SELECTOR" + value: ($backupSelector) + entrypoint: kubectl + args: + - get + - pod + - -n + - $NAMESPACE + - -l + - $SELECTOR + - -o + - jsonpath="{.items[*].spec.containers[*].env[?(@.name=='COMMAND_OPTS')].value}" + check: + (contains($stdout, '--stanza=db --repo=1')): true +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-backup-command +spec: + bindings: + - name: cluster + value: check-backup-command + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=check-backup-command', + 'postgres-operator.crunchydata.com/pgbackrest-backup=manual', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: "Sleep 10s" + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Trigger a CLI backup with --options="--type=full" + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: kubectl-pgo + args: + - -n + - $NAMESPACE + - backup + - $CLUSTER + - --repoName + - repo1 + - --options + - --type=full + - sleep: + duration: 10s + + - name: "Sleep 10s" + try: + - sleep: + duration: 10s + + - name: Verify COMMAND_OPTS matches "--stanza=db --repo=1 --type=full" + try: + - sleep: + duration: 10s + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "SELECTOR" + value: ($backupSelector) + entrypoint: kubectl + args: + - get + - pod + - -n + - $NAMESPACE + - -l + - $SELECTOR + - -o + - jsonpath="{.items[*].spec.containers[*].env[?(@.name=='COMMAND_OPTS')].value}" + check: + ($stdout): '"--stanza=db --repo=1 --type=full"' + + - name: Wait for CLI backup to succeed" + try: + - wait: + apiVersion: v1 + kind: Pod + selector: ($backupSelector) + timeout: 300s + for: + jsonPath: + path: '{.status.phase}' + value: 'Succeeded' +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-backup-command-longer-options +spec: + bindings: + - name: cluster + value: check-backup-command-longer-options + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=check-backup-command-longer-options', + 'postgres-operator.crunchydata.com/pgbackrest-backup=manual', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Trigger a backup through CLI with longer options + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: kubectl-pgo + args: + - -n + - $NAMESPACE + - backup + - $CLUSTER + - --repoName + - repo1 + - --options + - --type=full --start-fast=n + - sleep: + duration: 10s + + - name: Verify COMMAND_OPTS matches "--stanza=db --repo=1 --type=full --start-fast=n" + try: + - sleep: + duration: 10s + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "SELECTOR" + value: ($backupSelector) + entrypoint: kubectl + args: + - get + - pod + - -n + - $NAMESPACE + - -l + - $SELECTOR + - -o + - jsonpath="{.items[*].spec.containers[*].env[?(@.name=='COMMAND_OPTS')].value}" + check: + ($stdout): '"--stanza=db --repo=1 --type=full --start-fast=n"' + + - name: Wait for CLI backup to succeed" + try: + - wait: + apiVersion: v1 + kind: Pod + selector: ($backupSelector) + timeout: 300s + for: + jsonPath: + path: '{.status.phase}' + value: 'Succeeded' +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-backup-command-multiple-flags +spec: + bindings: + - name: cluster + value: check-backup-command-multiple-flags + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=check-backup-command-multiple-flags', + 'postgres-operator.crunchydata.com/pgbackrest-backup=manual', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: "Sleep 10s" + try: + - sleep: + duration: 10s + + - name: Trigger a backup through CLI with multiple flags + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: kubectl-pgo + args: + - -n + - $NAMESPACE + - backup + - $CLUSTER + - --repoName + - repo1 + - --options + - --type=full + - --options + - --start-fast=n + - sleep: + duration: 10s + + - name: Verify COMMAND_OPTS matches "--stanza=db --repo=1 --type=full --start-fast=n" + try: + - sleep: + duration: 10s + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "SELECTOR" + value: ($backupSelector) + entrypoint: kubectl + args: + - get + - pod + - -n + - $NAMESPACE + - -l + - $SELECTOR + - -o + - jsonpath="{.items[*].spec.containers[*].env[?(@.name=='COMMAND_OPTS')].value}" + check: + ($stdout): '"--stanza=db --repo=1 --type=full --start-fast=n"' + + - name: Wait for CLI backup to succeed" + try: + - wait: + apiVersion: v1 + kind: Pod + selector: ($backupSelector) + timeout: 300s + for: + jsonPath: + path: '{.status.phase}' + value: 'Succeeded' +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-backup-command-no-flags +spec: + bindings: + - name: cluster + value: check-backup-command-no-flags + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=check-backup-command-no-flags', + 'postgres-operator.crunchydata.com/pgbackrest-backup=manual', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: Trigger a backup through CLI with no flags + try: + - description: Get prior annotation + command: + outputs: + - name: prior_annotation + value: ($stdout) + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "GOTEMPLATE" + value: go-template={{ if .metadata.annotations }}{{ index .metadata.annotations "postgres-operator.crunchydata.com/pgbackrest-backup" }}{{ else }}not-found{{ end }} + entrypoint: kubectl + args: + - get + - postgrescluster + - $CLUSTER + - -n + - $NAMESPACE + - --output + - '$GOTEMPLATE' + + + - description: Print Prior Annotation + script: + env: + - name: INPUT + value: ($prior_annotation) + content: | + echo $(date) + echo $INPUT + + - description: Start backup with PGO CLI + command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: kubectl-pgo + args: + - -n + - $NAMESPACE + - backup + - $CLUSTER + - --repoName + - repo1 + - sleep: + duration: 10s + + - description: Get current annotation + command: + outputs: + - name: current_annotation + value: ($stdout) + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "GOTEMPLATE" + value: go-template={{ if .metadata.annotations }}{{ index .metadata.annotations "postgres-operator.crunchydata.com/pgbackrest-backup" }}{{ else }}not-found{{ end }} + entrypoint: kubectl + args: + - get + - postgrescluster + - $CLUSTER + - -n + - $NAMESPACE + - --output + - '$GOTEMPLATE' + + - description: Create ConfigMap to compare annotations + apply: + resource: + apiVersion: v1 + kind: ConfigMap + metadata: + name: compare-annotations + data: + key1: ($prior_annotation) + key2: ($current_annotation) + + - description: Compare annotations + assert: + timeout: 30s + resource: + apiVersion: v1 + kind: ConfigMap + metadata: + name: compare-annotations + data: + (key1 != key2): true + (key2 != 'not-found'): true + + - name: Wait for CLI backup to succeed" + try: + - wait: + apiVersion: v1 + kind: Pod + selector: ($backupSelector) + timeout: 300s + for: + jsonPath: + path: '{.status.phase}' + value: 'Succeeded' +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-backup-command-force-conflicts +spec: + bindings: + - name: cluster + value: check-backup-command-force-conflicts + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: Change ownership of spec.backups.pgbackrest.manual.repoName + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + envsubst < change-ownership.yaml | kubectl apply --server-side -n ${NAMESPACE} --field-manager=test-manager -f - + + - assert: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: + pgbackrest: + manual: + repoName: repo3 + + + - name: Attempt a backup on Repo1 without force conflicts and get an error + try: + - description: Start backup with PGO CLI + command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: kubectl-pgo + args: + - -n + - $NAMESPACE + - backup + - $CLUSTER + - --repoName + - repo1 + - --options + - --type=full + check: + (contains($stderr, 'conflict with "test-manager"')): true + - sleep: + duration: 10s + + - name: Attempt a backup on Repo1 with force conflicts and succeed + try: + - description: Start backup with PGO CLI + command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: kubectl-pgo + args: + - -n + - $NAMESPACE + - backup + - $CLUSTER + - --repoName + - repo1 + - --options + - --type=full + - --force-conflicts + check: + (contains($stdout, 'backup initiated')): true + - sleep: + duration: 10s \ No newline at end of file diff --git a/testing/chainsaw/e2e/backup/change-ownership.yaml b/testing/chainsaw/e2e/backup/change-ownership.yaml new file mode 100644 index 00000000..b126e6a3 --- /dev/null +++ b/testing/chainsaw/e2e/backup/change-ownership.yaml @@ -0,0 +1,9 @@ +apiVersion: postgres-operator.crunchydata.com/v1beta1 +kind: PostgresCluster +metadata: + name: ${CLUSTER} +spec: + backups: + pgbackrest: + manual: + repoName: repo3 \ No newline at end of file diff --git a/testing/chainsaw/e2e/config.yaml b/testing/chainsaw/e2e/config.yaml new file mode 100644 index 00000000..caa43a9a --- /dev/null +++ b/testing/chainsaw/e2e/config.yaml @@ -0,0 +1,12 @@ +apiVersion: chainsaw.kyverno.io/v1alpha2 +kind: Configuration +metadata: + name: end-to-end +spec: + namespace: + template: + metadata: + labels: { postgres-operator-test: chainsaw } + timeouts: + assert: 3m + cleanup: 3m diff --git a/testing/chainsaw/e2e/create/chainsaw-test.yaml b/testing/chainsaw/e2e/create/chainsaw-test.yaml new file mode 100644 index 00000000..710ab0c4 --- /dev/null +++ b/testing/chainsaw/e2e/create/chainsaw-test.yaml @@ -0,0 +1,146 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: create-cluster +spec: + bindings: + - name: cluster + value: create-cluster + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: psql + value: + image: ($values.images.psql) + connect: { name: PGCONNECT_TIMEOUT, value: '5' } + + - name: confirmQuery + value: | + DO $script$ + DECLARE + pg_is_in_recovery boolean; + pgVer TEXT; + BEGIN + SELECT pg_is_in_recovery() INTO pg_is_in_recovery; + SELECT postgresVersion into pgVer; + RAISE NOTICE 'pgVer is %', pgVer; + RAISE NOTICE 'version() is %', version(); + ASSERT pg_is_in_recovery = FALSE AND + position('PostgreSQL ' || pgVer in version()) > 0; + END $script$; + + steps: + + - name: 'Create Cluster with PGO CLI' + use: + template: '../templates/create-cluster.yaml' + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: 'Verify Postgres is running correct version' + description: > + Confirm that Postgres is running and using the expected version + use: + template: '../templates/psql-data.yaml' + with: + bindings: + - name: target + value: ($cluster) + - name: job + value: (join('-', [$cluster, 'verify-version'])) + - name: command + value: (replace_all($confirmQuery, 'postgresVersion', $postgresVersion)) +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: create-cluster-without-backups +spec: + bindings: + - name: cluster + value: create-cluster-without-backups + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: psql + value: + image: ($values.images.psql) + connect: { name: PGCONNECT_TIMEOUT, value: '5' } + + - name: confirmQuery + value: | + DO $script$ + DECLARE + pg_is_in_recovery boolean; + pgVer TEXT; + BEGIN + SELECT pg_is_in_recovery() INTO pg_is_in_recovery; + SELECT postgresVersion into pgVer; + RAISE NOTICE 'pgVer is %', pgVer; + RAISE NOTICE 'version() is %', version(); + ASSERT pg_is_in_recovery = FALSE AND + position('PostgreSQL ' || pgVer in version()) > 0; + END $script$; + + steps: + + - name: 'Create Cluster with PGO CLI' + use: + template: '../templates/create-cluster-without-backups.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Verify Postgres is running correct version' + description: > + Confirm that Postgres is running and using the expected version + use: + template: '../templates/psql-data.yaml' + with: + bindings: + - name: target + value: ($cluster) + - name: job + value: (join('-', [$cluster, 'verify-version'])) + - name: command + value: (replace_all($confirmQuery, 'postgresVersion', $postgresVersion)) + + - name: Confirm spec.backups is not in the manifest + try: + - error: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: {} \ No newline at end of file diff --git a/testing/chainsaw/e2e/delete/chainsaw-test.yaml b/testing/chainsaw/e2e/delete/chainsaw-test.yaml new file mode 100644 index 00000000..731afd1f --- /dev/null +++ b/testing/chainsaw/e2e/delete/chainsaw-test.yaml @@ -0,0 +1,88 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: chainsaw-delete-cluster-no +spec: + bindings: + - name: cluster + value: chainsaw-delete-cluster-no + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: run 'delete cluster' with confirm 'n' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo 'n' | kubectl pgo delete postgrescluster $CLUSTER --namespace=$NAMESPACE" + timeout: 10s + + - name: confirm cluster did not delete + try: + - assert: + timeout: 30s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + namespace: ($namespace) +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: chainsaw-delete-cluster-yes +spec: + bindings: + - name: cluster + value: chainsaw-delete-cluster-yes + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: run 'delete cluster' with confirm 'y' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo 'y' | kubectl pgo delete postgrescluster $CLUSTER --namespace=$NAMESPACE" + timeout: 10s + + - name: "Sleep 60s" + try: + - sleep: + duration: 60s + + - name: confirm cluster deleted + try: + - error: + timeout: 500s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + namespace: ($namespace) diff --git a/testing/chainsaw/e2e/restore/chainsaw-test.yaml b/testing/chainsaw/e2e/restore/chainsaw-test.yaml new file mode 100644 index 00000000..e55022ae --- /dev/null +++ b/testing/chainsaw/e2e/restore/chainsaw-test.yaml @@ -0,0 +1,713 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-restore-no-repo-name +spec: + bindings: + - name: cluster + value: check-restore-no-repo-name + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=CLUSTER', + 'postgres-operator.crunchydata.com/pgbackrest-backup=BACKUP_TYPE', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: run 'restore' with no options + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "kubectl-pgo restore $CLUSTER -n $NAMESPACE" + timeout: 10s + check: + (contains($stderr, 'Required value')): true + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: Confirm spec.backups.pgbackrest.restore is not in the manifest + try: + - error: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: + pgbackrest: + restore: {} +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-restore-confirm-no +spec: + bindings: + - name: cluster + value: check-restore-confirm-no + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=CLUSTER', + 'postgres-operator.crunchydata.com/pgbackrest-backup=BACKUP_TYPE', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: run 'restore' with confirm 'no' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo no | kubectl-pgo restore $CLUSTER -n $NAMESPACE --repoName repo1" + timeout: 10s + check: + (contains($stderr, '')): true + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: Confirm spec.backups.pgbackrest.restore is not in the manifest + try: + - error: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: + pgbackrest: + restore: {} +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-restore-confirm-yes +spec: + bindings: + - name: cluster + value: check-restore-confirm-yes + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=CLUSTER', + 'postgres-operator.crunchydata.com/pgbackrest-backup=BACKUP_TYPE', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: run 'restore' with confirm 'yes' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo yes | kubectl-pgo restore $CLUSTER -n $NAMESPACE --repoName repo1" + timeout: 10s + check: + (contains($stdout, 'patched')): true + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: Confirm restore annotation is present + try: + - assert: + timeout: 60s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + annotations: + postgres-operator.crunchydata.com/pgbackrest-restore: {} + + - name: Confirm manifest is correct after the restore + try: + - assert: + timeout: 60s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: + pgbackrest: + restore: + enabled: true + repoName: repo1 + status: + pgbackrest: + restore: + succeeded: 1 + instances: + - replicas: 2 +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-restore-confirm-yes-with-options +spec: + bindings: + - name: cluster + value: check-restore-confirm-yes-with-options + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=CLUSTER', + 'postgres-operator.crunchydata.com/pgbackrest-backup=BACKUP_TYPE', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: run 'restore' with confirm 'yes' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo yes | kubectl-pgo restore $CLUSTER -n $NAMESPACE --repoName repo1 --options=--buffer-size=8MiB --options=--io-timeout=120 --options=--process-max=2" + timeout: 10s + check: + (contains($stdout, 'patched')): true + (contains($stdout, 'options:[--buffer-size=8MiB --io-timeout=120 --process-max=2] repoName:repo1')): true + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: Confirm restore annotation is present + try: + - assert: + timeout: 60s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + annotations: + postgres-operator.crunchydata.com/pgbackrest-restore: {} + + - name: Confirm manifest is correct after the restore + try: + - assert: + timeout: 60s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: + pgbackrest: + restore: + enabled: true + repoName: repo1 + options: + - --buffer-size=8MiB + - --io-timeout=120 + - --process-max=2 + status: + pgbackrest: + restore: + succeeded: 1 + instances: + - replicas: 2 +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-restore-disable-restores +spec: + bindings: + - name: cluster + value: check-restore-disable-restores + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=CLUSTER', + 'postgres-operator.crunchydata.com/pgbackrest-backup=BACKUP_TYPE', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: run 'restore disable' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "kubectl-pgo restore disable $CLUSTER -n $NAMESPACE" + timeout: 10s + check: + (contains($stdout, 'patched')): true + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: Confirm spec.backups.pgbackrest.restore is not in the manifest + try: + - error: + timeout: 60s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: + pgbackrest: + restore: {} +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-restore-predefined-settings +spec: + bindings: + - name: cluster + value: check-restore-predefined-settings + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=CLUSTER', + 'postgres-operator.crunchydata.com/pgbackrest-backup=BACKUP_TYPE', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: Change ownership of spec.backups.pgbackrest.restore to kubectl-patch + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + kubectl patch postgrescluster $CLUSTER -n $NAMESPACE --type=merge -p '{ + "spec": { + "backups": { + "pgbackrest": { + "restore": { "enabled": true, "repoName": "repo1" } + } + } + } + }' + check: + (contains($stdout, 'patched')): true + + - name: run 'restore' with confirm 'yes' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo yes | kubectl-pgo restore $CLUSTER -n $NAMESPACE --repoName repo1 --options=--buffer-size=8MiB" + timeout: 10s + check: + (contains($stdout, 'patched')): true + (contains($stdout, 'options:[--buffer-size=8MiB] repoName:repo1')): true + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: Confirm restore annotation is present + try: + - assert: + timeout: 60s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + annotations: + postgres-operator.crunchydata.com/pgbackrest-restore: {} + + - name: Confirm manifest is correct after the restore + try: + - assert: + timeout: 60s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + backups: + pgbackrest: + restore: + enabled: true + repoName: repo1 + options: + - --buffer-size=8MiB + status: + pgbackrest: + restore: + succeeded: 1 + instances: + - replicas: 2 +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: check-restore-force-conflicts +spec: + bindings: + - name: cluster + value: check-restore-force-conflicts + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + - name: backupSelector + value: (join(',', [ + 'postgres-operator.crunchydata.com/cluster=CLUSTER', + 'postgres-operator.crunchydata.com/pgbackrest-backup=BACKUP_TYPE', + 'postgres-operator.crunchydata.com/pgbackrest-repo=repo1' + ])) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 10s + try: + - sleep: + duration: 10s + + - name: Change ownership of spec.backups.pgbackrest.restore to kubectl-patch + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + kubectl patch postgrescluster $CLUSTER -n $NAMESPACE --type=merge -p '{ + "spec": { + "backups": { + "pgbackrest": { + "restore": { "enabled": false, "repoName": "repo1" } + } + } + } + }' + check: + (contains($stdout, 'patched')): true + + - name: Change ownership of annotation to kubectl-annotate + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + kubectl --namespace "${NAMESPACE}" annotate postgrescluster/$CLUSTER \ + --overwrite 'postgres-operator.crunchydata.com/pgbackrest-restore=anything' + check: + (contains($stdout, 'annotated')): true + + - name: run 'restore' with confirm 'yes' and expect error + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo yes | kubectl-pgo restore $CLUSTER -n $NAMESPACE --repoName repo1" + timeout: 10s + check: + (contains($stderr, 'Apply failed')): true + (contains($stderr, '2 conflicts')): true + + - name: run 'restore' with confirm 'yes' and --force-conflicts + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + pgbackrest_restore_annotation() { + kubectl --namespace "${NAMESPACE}" get postgrescluster/$CLUSTER \ + --output "jsonpath-as-json={.metadata.annotations['postgres-operator\.crunchydata\.com/pgbackrest-restore']}" + } + kubectl --namespace "${NAMESPACE}" annotate postgrescluster/$CLUSTER \ + postgres-operator.crunchydata.com/pgbackrest-restore="$(date)" --overwrite || exit + PRIOR=$(pgbackrest_restore_annotation) + # Running restore will update the annotation. + echo yes | kubectl-pgo --namespace="${NAMESPACE}" restore $CLUSTER --repoName="repo1" --force-conflicts + CURRENT=$(pgbackrest_restore_annotation) + if [ "${CURRENT}" != "${PRIOR}" ]; then + echo "restore was successful" + exit 0 + fi + printf 'Expected annotation to change, got PRIOR %s CURRENT %s' "${PRIOR}" "${CURRENT}" + echo "RESULT from taking restore: ${RESULT}" + exit 1 + check: + (contains($stdout, 'restore was successful')): true diff --git a/testing/chainsaw/e2e/show/chainsaw-test.yaml b/testing/chainsaw/e2e/show/chainsaw-test.yaml new file mode 100644 index 00000000..2645f315 --- /dev/null +++ b/testing/chainsaw/e2e/show/chainsaw-test.yaml @@ -0,0 +1,402 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: chainsaw-show +spec: + bindings: + - name: cluster + value: chainsaw-show + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + + - name: Show pgbackrest info + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + EXEC_INFO=$( + kubectl exec --namespace "${NAMESPACE}" "${PRIMARY}" -- \ + pgbackrest info + ) + CLI_INFO=$( + kubectl-pgo --namespace "${NAMESPACE}" show backup $CLUSTER + ) + status=$? + if [ "$status" -ne 0 ]; then + echo "pgo command unsuccessful" + exit 1 + fi + + # check command output is not empty and equals 'exec' output + if [ -n "$CLI_INFO" ] && [ "$EXEC_INFO" = "$CLI_INFO" ]; then + exit 0 + fi + + exit 1 + + - name: Show pgbackrest info repo1 + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + EXEC_INFO=$( + kubectl exec --namespace "${NAMESPACE}" "${PRIMARY}" -- \ + pgbackrest info --repo=1 + ) + CLI_INFO=$( + kubectl-pgo --namespace "${NAMESPACE}" show backup $CLUSTER --repoName=repo1 + ) + status=$? + if [ "$status" -ne 0 ]; then + echo "pgo command unsuccessful" + exit 1 + fi + + # check command output is not empty and equals 'exec' output + if [ -n "$CLI_INFO" ] && [ "$EXEC_INFO" = "$CLI_INFO" ]; then + exit 0 + fi + + exit 1 + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: Show pgbackrest info output json + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + EXEC_INFO=$( + kubectl exec --namespace "${NAMESPACE}" "${PRIMARY}" -- \ + pgbackrest info --output=json + ) + CLI_INFO=$( + kubectl-pgo --namespace "${NAMESPACE}" show backup $CLUSTER --output=json + ) + status=$? + if [ "$status" -ne 0 ]; then + echo "pgo command unsuccessful" + exit 1 + fi + + # check command output is not empty and equals 'exec' output + if [ -n "$CLI_INFO" ] && [ "$EXEC_INFO" = "$CLI_INFO" ]; then + exit 0 + fi + + exit 1 + + - name: Show pgbackrest info output bad format + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + CLI_INFO=$( + kubectl-pgo --namespace "${NAMESPACE}" show backup $CLUSTER -o bad 2>&1 + ) + status=$? + if [ "$status" -ne 1 ]; then + echo "expected bad format to fail" + exit 1 + fi + + # check command output is not empty and contains the expected error + # Note: case is used as it allows for the use of a wildcard (*) + # and is POSIX compliant + case "$CLI_INFO" in + "") + exit 1 + ;; + *"must be one of \"text\", \"json\""*) + exit 0 + ;; + esac + + exit 1 + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + + - name: Show patronictl list + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + EXEC_INFO=$( + kubectl exec --namespace "${NAMESPACE}" "${PRIMARY}" -- \ + patronictl list + ) + CLI_HA=$( + kubectl-pgo --namespace "${NAMESPACE}" show ha $CLUSTER + ) + status=$? + if [ "$status" -ne 0 ]; then + echo "pgo command unsuccessful" + exit 1 + fi + + # check command output is not empty and equals 'exec' output + if [ -n "$CLI_HA" ] && [ "$EXEC_INFO" = "$CLI_HA" ]; then + exit 0 + fi + + exit 1 + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + + - name: Show patronictl list output json + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + EXEC_INFO=$( + kubectl exec --namespace "${NAMESPACE}" "${PRIMARY}" -- \ + patronictl list -f json + ) + CLI_HA=$( + kubectl-pgo --namespace "${NAMESPACE}" show ha $CLUSTER -o json + ) + status=$? + if [ "$status" -ne 0 ]; then + echo "pgo command unsuccessful" + exit 1 + fi + + # check command output is not empty and equals 'exec' output + if [ -n "$CLI_HA" ] && [ "$EXEC_INFO" = "$CLI_HA" ]; then + exit 0 + fi + + exit 1 + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: Show patronictl list output bad + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + + CLI_HA=$( + kubectl-pgo --namespace "${NAMESPACE}" show ha $CLUSTER -o bad 2>&1 + ) + status=$? + if [ "$status" -ne 1 ]; then + echo "expected bad format to fail" + exit 1 + fi + + # check command output contains the expected error + # Note: case is used as it allows for the use of a wildcard (*) + # and is POSIX compliant + case "$CLI_HA" in + "") + exit 1 + ;; + *"must be one of \"pretty\", \"tsv\", \"json\", \"yaml\""*) + exit 0 + ;; + esac + + exit 1 + + + - name: Show user + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + + CLI_USER=$( + kubectl-pgo --namespace "${NAMESPACE}" show user --cluster $CLUSTER + ) + + status=$? + if [ "$status" -ne 0 ]; then + echo "pgo command unsuccessful" + exit 1 + fi + + # expected output + SHOW_USER_OUTPUT=" + CLUSTER USERNAME + chainsaw-show chainsaw-show" + + + # check command output is not empty and equals the expected output + if [ -z ${CLI_USER} ] || [ "${CLI_USER}" != "${SHOW_USER_OUTPUT}" ]; then + echo "pgo command output unexpected: expected ${SHOW_USER_OUTPUT} got ${CLI_USER}" + exit 1 + fi + + CLI_USER_SENSITIVE=$( + echo yes | kubectl-pgo --namespace "${NAMESPACE}" show user --cluster ${CLUSTER} --show-connection-info + ) + + SECRET_DATA=$(kubectl get secret -n "${NAMESPACE}" ${CLUSTER}-pguser-${CLUSTER} -o jsonpath={.data}) + + PASSWORD=$(echo "${SECRET_DATA}" | jq -r .password | base64 -d) + USER=$(echo "${SECRET_DATA}" | jq -r .user | base64 -d) + HOST=$(echo "${SECRET_DATA}" | jq -r .host | base64 -d) + PORT=$(echo "${SECRET_DATA}" | jq -r .port | base64 -d) + + # check command output is not empty and contains the connection URL field + # Note: case is used as it allows for the use of a wildcard (*) + # and is POSIX compliant + case "$CLI_USER_SENSITIVE" in + "") + exit 1 + ;; + *"postgres://${USER}:${PASSWORD}@${HOST}:${PORT}/${CLUSTER}"*) + exit 0 + ;; + esac + + echo "pgo command output for connection info unexpected: got ${CLI_USER_SENSITIVE}" + exit 1 + + + - name: Show entire command + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + content: | + SELECTOR=postgres-operator.crunchydata.com/cluster=$CLUSTER,postgres-operator.crunchydata.com/role=master + PRIMARY=$(kubectl get pod --namespace "${NAMESPACE}" --output name --selector ${SELECTOR}) + + PGBACKREST_INFO_EXEC=$( + kubectl exec --namespace "${NAMESPACE}" "${PRIMARY}" -- \ + pgbackrest info + ) + + PATRONI_LIST_EXEC=$( + kubectl exec --namespace "${NAMESPACE}" "${PRIMARY}" -- \ + patronictl list + ) + + EXPECTED_SHOW_COMMAND="BACKUP + + $PGBACKREST_INFO_EXEC + + HA + + $PATRONI_LIST_EXEC" + + echo $EXPECTED_SHOW_COMMAND + + CLI_SHOW=$( + kubectl-pgo --namespace "${NAMESPACE}" show $CLUSTER + ) + + status=$? + if [ "$status" -ne 0 ]; then + echo "pgo command unsuccessful" + exit 1 + fi + + # check command output is not empty and equals expected output + if [ -n "$CLI_SHOW" ] && [ "$EXPECTED_SHOW_COMMAND" = "$CLI_SHOW" ]; then + exit 0 + fi + + exit 1 + + diff --git a/testing/chainsaw/e2e/start-stop/chainsaw-test.yaml b/testing/chainsaw/e2e/start-stop/chainsaw-test.yaml new file mode 100644 index 00000000..2017f6df --- /dev/null +++ b/testing/chainsaw/e2e/start-stop/chainsaw-test.yaml @@ -0,0 +1,255 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: stop-cluster-no-with-force-conflicts +spec: + + bindings: + - name: cluster + value: stop-cluster-no-with-force-conflicts + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: run 'stop cluster' with confirm 'n' and force-conflicts + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo 'n' | kubectl-pgo stop $CLUSTER --force-conflicts --namespace=$NAMESPACE" + timeout: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: stop-cluster-yes-with-force-conflicts +spec: + + bindings: + - name: cluster + value: stop-cluster-yes-with-force-conflicts + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: run 'stop cluster' with confirm 'y' and force-conflicts + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo 'y' | kubectl-pgo stop $CLUSTER --force-conflicts --namespace=$NAMESPACE" + timeout: 10s + - sleep: + duration: 10s + + - name: confirm cluster did stop + try: + - assert: + timeout: 30s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + namespace: ($namespace) + spec: + shutdown: true + - sleep: + duration: 10s +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: start-cluster +spec: + + bindings: + - name: cluster + value: start-cluster + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: "Sleep 30s" + try: + - sleep: + duration: 30s + + - name: run 'stop cluster' with confirm 'y' and force-conflicts + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "echo 'y' | kubectl-pgo stop $CLUSTER --force-conflicts --namespace=$NAMESPACE" + timeout: 10s + - sleep: + duration: 10s + + - name: confirm cluster did stop + try: + - assert: + timeout: 30s + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + namespace: ($namespace) + spec: + shutdown: true + - sleep: + duration: 10s + + - name: run 'start cluster' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "kubectl-pgo start $CLUSTER --namespace=$NAMESPACE" + timeout: 10s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: start-cluster-already-running +spec: + + bindings: + - name: cluster + value: start-cluster-already-running + + - name: postgresVersion + value: (to_string($values.versions.postgres)) + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: run 'start cluster' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "sleep 5 && kubectl-pgo start $CLUSTER -n $NAMESPACE" + timeout: 10s + check: + (contains($stdout, 'start initiated')): true + + - name: run 'start cluster' + try: + - command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + entrypoint: "sh" + args: + - "-c" + - "sleep 5 && kubectl-pgo start $CLUSTER -n $NAMESPACE" + timeout: 10s + check: + (contains($stdout, 'Cluster already Started')): true + diff --git a/testing/chainsaw/e2e/support/chainsaw-test.yaml b/testing/chainsaw/e2e/support/chainsaw-test.yaml new file mode 100644 index 00000000..f85a5224 --- /dev/null +++ b/testing/chainsaw/e2e/support/chainsaw-test.yaml @@ -0,0 +1,932 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: kuttl-support-cluster +spec: + bindings: + - name: cluster + value: kuttl-support-cluster + + - name: postgresVersion + value: 16 + + - name: psql + value: + image: ($values.images.psql) + connect: { name: PGCONNECT_TIMEOUT, value: '5' } + + - name: confirmQuery + value: | + DO $script$ + DECLARE + pg_is_in_recovery boolean; + pgVer TEXT; + BEGIN + SELECT pg_is_in_recovery() INTO pg_is_in_recovery; + SELECT postgresVersion into pgVer; + RAISE NOTICE 'pgVer is %', pgVer; + RAISE NOTICE 'version() is %', version(); + ASSERT pg_is_in_recovery = FALSE AND + position('PostgreSQL ' || pgVer in version()) > 0; + END $script$; + + steps: + + - name: 'Create Cluster from manifest' + use: + template: '../templates/create-cluster-from-manifest.yaml' + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: 'Confirm cluster is created' + use: + template: '../templates/confirm-created.yaml' + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: 'Confirm Replica backup completed' + use: + template: '../templates/replica-backup-complete.yaml' + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: Run support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-cluster + content: | + kubectl-pgo --namespace $NAMESPACE --operator-namespace postgres-operator support export $CLUSTER -o . + check: + (contains($stdout, 'PGO CLI Support Export Tool')): true + + - name: Expand support export + try: + - script: + content: | + tar -xzf ./crunchy_k8s_support_export_*.tar.gz + + + - name: Check support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-cluster + content: | + CLEANUP="rm -r ./kuttl-support-cluster ./operator ./crunchy_k8s_support_export_*.tar.gz" + check_file() { + if [ ! -s ./"${1}" ] + then + echo "Expected ${1} file to not be empty" + eval "$CLEANUP" + exit 1 + else + echo "Found ${1}" + fi + } + check_exists() { + if [ -f ./"${1}" ] + then + echo "Expected ${1} file to exist" + eval "$CLEANUP" + exit 1 + else + echo "Found ${1}" + fi + } + + # check that the PGO CLI version is recorded + VER=$(cat ./kuttl-support-cluster/pgo-cli-version) + echo "$VER" | grep -E "v[0-9]+\.[0-9]+\.[0-9]+$" + STATUS=$? + [ "$STATUS" = 0 ] || { + echo "Expected PGO CLI version, got:" + echo "${VER}" + eval "$CLEANUP" + exit 1 + } + + # check that the cluster-names file exists and is not empty + check_file "kuttl-support-cluster/cluster-names" + + # check that the system-time file exists and is not empty + check_file "kuttl-support-cluster/system-time" + + # check that the context file exists and is not empty + check_file "kuttl-support-cluster/current-context" + + # check that the patroni info file exists and is not empty + check_file "kuttl-support-cluster/patroni-info" + + # check that the pgbackrest info file exists and is not empty + check_file "kuttl-support-cluster/pgbackrest-info" + + # check that the plugin list file exists and is not empty + # the file will at least include kubectl-pgo + check_file "kuttl-support-cluster/plugin-list" + + # check that the operator file exists and is not empty + # the list file will not be empty for the requested Kubernetes types + check_file "operator/deployments/list" + check_file "operator/replicasets/list" + check_file "operator/pods/list" + + # check for expected gzip compression level + FILE_INFO=$(file ./crunchy_k8s_support_export_*.tar.gz) + case "${FILE_INFO}" in + *'gzip compressed data, max compression'*) + ;; + *) + echo "Expected gzip max compression message, got:" + echo "${FILE_INFO}" + eval "$CLEANUP" + exit 1 + ;; + esac + + # Node directory and list file path + DIR="./kuttl-support-cluster/nodes/" + LIST="${DIR}list" + + # check for expected table header in the list file + KV=$(awk 'NR==1 {print $9}' $LIST) + [ "${KV}" = '|KERNEL-VERSION' ] || { + echo "Expected KERNEL-VERSION header, got:" + echo "${KV}" + eval "$CLEANUP" + exit 1 + } + + # check for a .yaml file with the name of the first Node in the list file + NODE="$(awk 'NR==2 {print $1}' $LIST).yaml" + + if [ ! -f "${DIR}${NODE}" ] + then + echo "Expected directory with file ${NODE}, got:" + ls ${DIR} + eval "$CLEANUP" + exit 1 + fi + + # check that the events file exist and is not empty + check_file "kuttl-support-cluster/events" + + # check that logs exist for the PG + # use `check_exists` so we can use a wildcard + check_exists "kuttl-support-cluster/pods/kuttl-support-cluster-00-*-0/pgdata/pg16/log/postgresql-*.log" + + EVENTS="./kuttl-support-cluster/events" + # check that the events file contains the expected string + if ! grep -Fq "Started container postgres-startup" $EVENTS + then + echo "Events file does not contain expected string" + eval "$CLEANUP" + exit 1 + fi + + PROCESSES_DIR="./kuttl-support-cluster/processes/" + + # Check for the files that contain an expected pgBackRest server process. + # Expected to be found in the Postgres instance Pod's 'database', + # 'replication-cert-copy', 'pgbackrest', and 'pgbackrest-config' containers + # and the pgBackRest repo Pod's 'pgbackrest' and 'pgbackrest-config' + # containers, i.e. 6 files total, but test will pass if at least one is found. + found=$(grep -lR "pgbackrest server" ${PROCESSES_DIR} | wc -l) + if [ "${found}" -lt 1 ]; then + echo "Expected to find pgBackRest process, got ${found}" + eval "$CLEANUP" + exit 1 + fi + + # Check for the files that contain an expected Postgres process. Expected + # to be found in the Postgres instance Pod's 'database', 'replication-cert-copy', + # 'pgbackrest', and 'pgbackrest-config' containers, i.e. 4 files total, but + # test will pass if at least one is found. + found=$(grep -lR "postgres -D /pgdata/pg" ${PROCESSES_DIR} | wc -l) + if [ "${found}" -lt 1 ]; then + echo "Expected to find Postgres process, got ${found}" + eval "$CLEANUP" + exit 1 + fi + + # check that the PGO CLI log file contains expected messages + CLI_LOG="./kuttl-support-cluster/cli.log" + + # info output includes expected heading + if ! grep -Fq -- "- INFO - | PGO CLI Support Export Tool" $CLI_LOG + then + echo "PGO CLI log does not contain expected info message" + eval "$CLEANUP" + exit 1 + fi + + # debug output includes cluster name argument + if ! grep -Fq -- "- DEBUG - Arg - PostgresCluster Name: kuttl-support-cluster" $CLI_LOG + then + echo "PGO CLI log does not contain cluster name debug message" + eval "$CLEANUP" + exit 1 + fi + + rm -r ./kuttl-support-cluster ./operator ./crunchy_k8s_support_export_*.tar.gz + + + - name: Run support export for invalid cluster + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-cluster + content: | + kubectl-pgo --namespace $NAMESPACE --operator-namespace postgres-operator support export invalid -o . + check: + (contains($stderr, '"invalid" not found')): true + + + + - name: 'Create Limit Range' + try: + - apply: + resource: + apiVersion: v1 + kind: LimitRange + metadata: + name: kuttl-test-limitrange + spec: + limits: + - type: PersistentVolumeClaim + max: + storage: 2Gi + min: + storage: 500Mi + + - name: 'Create Ingress' + try: + - apply: + resource: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: kuttl-test-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + spec: + ingressClassName: simple-example + rules: + - http: + paths: + - path: /testpath + pathType: Prefix + backend: + service: + name: test + port: + number: 80 + + + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: Run support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-cluster + content: | + kubectl-pgo --namespace $NAMESPACE --operator-namespace postgres-operator support export $CLUSTER -o . + check: + (contains($stdout, 'PGO CLI Support Export Tool')): true + + - name: Expand support export + try: + - script: + content: | + tar -xzf ./crunchy_k8s_support_export_*.tar.gz + + + - name: Check support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-cluster + content: | + CLEANUP="rm -r ./kuttl-support-cluster ./crunchy_k8s_support_export_*.tar.gz" + + # LimitRange directory and list file path + LR_DIR="./kuttl-support-cluster/limitranges/" + LR_LIST="${LR_DIR}list" + + # check for limitrange object name + LRO=$(awk 'NR==2 {print $1}' "${LR_LIST}") + [ "${LRO}" = 'kuttl-test-limitrange' ] || { + echo "Expected 'kuttl-test-limitrange' limitrange, got:" + echo "${LRO}" + eval "$CLEANUP" + exit 1 + } + + # check for a .yaml file for the limitrange object + LR_FILE="${LR_DIR}kuttl-test-limitrange.yaml" + if [ ! -f ${LR_FILE} ] + then + echo "Expected directory with file, ${LR_FILE}, got:" + ls ${LR_DIR} + eval "$CLEANUP" + exit 1 + fi + + # Ingress directory and list file path + I_DIR="./kuttl-support-cluster/ingresses/" + I_LIST="${I_DIR}list" + + # check for ingress object name + IO=$(awk 'NR==2 {print $1}' ${I_LIST}) + [ "${IO}" = 'kuttl-test-ingress' ] || { + echo "Expected 'kuttl-test-ingress' ingress, got:" + echo "${IO}" + eval "$CLEANUP" + exit 1 + } + + # check for a .yaml file for the ingress object + I_FILE="${I_DIR}kuttl-test-ingress.yaml" + if [ ! -f ${I_FILE} ] + then + echo "Expected directory with file, ${I_FILE}, got:" + ls ${I_DIR} + eval "$CLEANUP" + exit 1 + fi + + rm -r ./kuttl-support-cluster ./operator ./crunchy_k8s_support_export_*.tar.gz + + + + - name: Delete resources + try: + - delete: + ref: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + name: kuttl-support-cluster + - delete: + ref: + apiVersion: v1 + kind: LimitRange + name: kuttl-test-limitrange + - delete: + ref: + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: kuttl-test-ingress + + + - name: 'Create Monitoring Cluster' + try: + - apply: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: kuttl-support-monitoring-cluster + spec: + postgresVersion: 16 + instances: + - dataVolumeClaimSpec: + accessModes: [ReadWriteOnce] + resources: { requests: { storage: 1Gi } } + backups: + pgbackrest: + repos: + - name: repo1 + volume: + volumeClaimSpec: + accessModes: [ReadWriteOnce] + resources: { requests: { storage: 1Gi } } + monitoring: + pgmonitor: + exporter: {} + - apply: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: crunchy-prometheus + spec: + selector: + matchLabels: + app.kubernetes.io/name: postgres-operator-monitoring + name: crunchy-prometheus + template: + metadata: + labels: + app.kubernetes.io/name: postgres-operator-monitoring + name: crunchy-prometheus + spec: + containers: + - image: prom/prometheus:v2.33.5 + name: prometheus + # Override default command to avoid 'permission denied' error in some + # environments (e.g. Openshift). Echo 'hello' so the Pod log is not empty. + command: ["sh", "-c", "while true; do echo hello; sleep 10;done"] + - apply: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: crunchy-grafana + spec: + selector: + matchLabels: + app.kubernetes.io/name: postgres-operator-monitoring + name: crunchy-grafana + template: + metadata: + labels: + app.kubernetes.io/name: postgres-operator-monitoring + name: crunchy-grafana + spec: + containers: + - image: grafana/grafana:8.5.10 + name: grafana + - apply: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: crunchy-alertmanager + spec: + selector: + matchLabels: + app.kubernetes.io/name: postgres-operator-monitoring + name: crunchy-alertmanager + template: + metadata: + labels: + app.kubernetes.io/name: postgres-operator-monitoring + name: crunchy-alertmanager + spec: + containers: + - image: prom/alertmanager:v0.22.2 + name: alertmanager + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: Assert resources exist + try: + - assert: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: kuttl-support-monitoring-cluster + spec: + backups: + pgbackrest: + repos: + - name: repo1 + volume: + volumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + instances: + - dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + replicas: 1 + postgresVersion: 16 + status: + instances: + - name: "00" + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 + monitoring: {} + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: crunchy-prometheus + status: + availableReplicas: 1 + observedGeneration: 1 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: crunchy-grafana + status: + availableReplicas: 1 + observedGeneration: 1 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: crunchy-alertmanager + status: + availableReplicas: 1 + observedGeneration: 1 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 + + + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: Run support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-monitoring-cluster + content: | + kubectl-pgo --namespace $NAMESPACE support export kuttl-support-monitoring-cluster -o . + check: + (contains($stdout, 'PGO CLI Support Export Tool')): true + + - name: Expand support export + try: + - script: + content: | + tar -xzf ./crunchy_k8s_support_export_*.tar.gz + + + - name: Check support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-monitoring-cluster + content: | + CLEANUP="rm -r ./kuttl-support-monitoring-cluster ./monitoring ./crunchy_k8s_support_export_*.tar.gz" + CLUSTER_DIR="./kuttl-support-monitoring-cluster/pods/" + MONITORING_DIR="./monitoring/pods/" + + # check for exporter, prometheus, grafana and alertmanager logs + found=$(find ${CLUSTER_DIR} -name "*exporter.log" | wc -l) + if [ "${found}" -eq 0 ]; then + echo "exporter not found" + eval "$CLEANUP" + exit 1 + fi + + found=$(find ${MONITORING_DIR} -name "*prometheus.log" | wc -l) + if [ "${found}" -eq 0 ]; then + echo "prometheus not found" + eval "$CLEANUP" + exit 1 + fi + + found=$(find ${MONITORING_DIR} -name "*grafana.log" | wc -l) + if [ "${found}" -eq 0 ]; then + echo "grafana not found" + eval "$CLEANUP" + exit 1 + fi + + found=$(find ${MONITORING_DIR} -name "*alertmanager.log" | wc -l) + if [ "${found}" -eq 0 ]; then + echo "alertmanager not found" + eval "$CLEANUP" + exit 1 + fi + + rm -r ./kuttl-support-monitoring-cluster ./monitoring ./operator ./crunchy_k8s_support_export_*.tar.gz + + + - name: Delete resources + try: + - delete: + ref: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + name: kuttl-support-monitoring-cluster + - delete: + ref: + apiVersion: apps/v1 + kind: Deployment + name: crunchy-prometheus + - delete: + ref: + apiVersion: apps/v1 + kind: Deployment + name: crunchy-alertmanager + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + + - name: 'Create Instrumentation Cluster' + try: + - apply: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: kuttl-support-instrumentation + spec: + postgresVersion: 16 + instances: + - dataVolumeClaimSpec: + accessModes: [ReadWriteOnce] + resources: { requests: { storage: 1Gi } } + backups: + pgbackrest: + repos: + - name: repo1 + volume: + volumeClaimSpec: + accessModes: [ReadWriteOnce] + resources: { requests: { storage: 1Gi } } + instrumentation: {} + + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + - name: Assert resources exist + try: + - assert: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: kuttl-support-instrumentation + spec: + backups: + pgbackrest: + repos: + - name: repo1 + volume: + volumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + instances: + - dataVolumeClaimSpec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: 1Gi + replicas: 1 + postgresVersion: 16 + status: + instances: + - name: "00" + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 + monitoring: {} + + + - name: Sleep for 30s + try: + - sleep: + duration: 30s + + + - name: Run support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-monitoring-cluster + content: | + kubectl-pgo --namespace $NAMESPACE --operator-namespace postgres-operator support export kuttl-support-instrumentation -o . + check: + (contains($stdout, 'PGO CLI Support Export Tool')): true + + - name: Expand support export + try: + - script: + content: | + tar -xzf ./crunchy_k8s_support_export_*.tar.gz + + + - name: Check support export + try: + - script: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: kuttl-support-monitoring-cluster + content: | + CLEANUP="rm -r ./kuttl-support-instrumentation ./operator ./crunchy_k8s_support_export_*.tar.gz" + check_file() { + if [ ! -s ./"${1}" ] + then + echo "Expected ${1} file to not be empty" + eval "$CLEANUP" + exit 1 + else + echo "Found ${1}" + fi + } + check_exists() { + if [ -f ./"${1}" ] + then + echo "Expected ${1} file to exist" + eval "$CLEANUP" + exit 1 + else + echo "Found ${1}" + fi + } + + # check that the PGO CLI version is recorded + VER=$(cat ./kuttl-support-instrumentation/pgo-cli-version) + echo "$VER" | grep -E "v[0-9]+\.[0-9]+\.[0-9]+$" + STATUS=$? + [ "$STATUS" = 0 ] || { + echo "Expected PGO CLI version, got:" + echo "${VER}" + eval "$CLEANUP" + exit 1 + } + + # check that the cluster-names file exists and is not empty + check_file "kuttl-support-instrumentation/cluster-names" + + # check that the system-time file exists and is not empty + check_file "kuttl-support-instrumentation/system-time" + + # check that the context file exists and is not empty + check_file "kuttl-support-instrumentation/current-context" + + # check that the patroni info file exists and is not empty + check_file "kuttl-support-instrumentation/patroni-info" + + # check that the pgbackrest info file exists and is not empty + check_file "kuttl-support-instrumentation/pgbackrest-info" + + # check that the plugin list file exists and is not empty + # the file will at least include kubectl-pgo + check_file "kuttl-support-instrumentation/plugin-list" + + # check that the operator file exists and is not empty + # the list file will not be empty for the requested Kubernetes types + check_file "operator/deployments/list" + check_file "operator/replicasets/list" + check_file "operator/pods/list" + + # check for expected gzip compression level + FILE_INFO=$(file ./crunchy_k8s_support_export_*.tar.gz) + case "${FILE_INFO}" in + *'gzip compressed data, max compression'*) + ;; + *) + echo "Expected gzip max compression message, got:" + echo "${FILE_INFO}" + eval "$CLEANUP" + exit 1 + ;; + esac + + # Node directory and list file path + DIR="./kuttl-support-instrumentation/nodes/" + LIST="${DIR}list" + + # check for expected table header in the list file + KV=$(awk 'NR==1 {print $9}' $LIST) + [ "${KV}" = '|KERNEL-VERSION' ] || { + echo "Expected KERNEL-VERSION header, got:" + echo "${KV}" + eval "$CLEANUP" + exit 1 + } + + # check for a .yaml file with the name of the first Node in the list file + NODE="$(awk 'NR==2 {print $1}' $LIST).yaml" + + if [ ! -f "${DIR}${NODE}" ] + then + echo "Expected directory with file ${NODE}, got:" + ls ${DIR} + eval "$CLEANUP" + exit 1 + fi + + # check that the events file exist and is not empty + check_file "kuttl-support-instrumentation/events" + + # check that logs exist for the PG + # use `check_exists` so we can use a wildcard + check_exists "kuttl-support-instrumentation/pods/kuttl-support-cluster-00-*-0/pgdata/logs/postgresql-*.json" + + EVENTS="./kuttl-support-instrumentation/events" + # check that the events file contains the expected string + if ! grep -Fq "Started container postgres-startup" $EVENTS + then + echo "Events file does not contain expected string" + eval "$CLEANUP" + exit 1 + fi + + PROCESSES_DIR="./kuttl-support-instrumentation/processes/" + + # Check for the files that contain an expected pgBackRest server process. + # Expected to be found in the Postgres instance Pod's 'collector', 'database', + # 'replication-cert-copy', 'pgbackrest', and 'pgbackrest-config' containers + # and the pgBackRest repo Pod's 'pgbackrest' and 'pgbackrest-config' + # containers, i.e. 6 files total, but test will pass if at least one is found. + found=$(grep -lR "pgbackrest server" ${PROCESSES_DIR} | wc -l) + if [ "${found}" -lt 1 ]; then + echo "Expected to find pgBackRest process, got ${found}" + eval "$CLEANUP" + exit 1 + fi + + # Check for the files that contain an expected Postgres process. Expected + # to be found in the Postgres instance Pod's 'collector', 'database', 'replication-cert-copy', + # 'pgbackrest', and 'pgbackrest-config' containers, i.e. 5 files total, but + # test will pass if at least one is found. + found=$(grep -lR "postgres -D /pgdata/pg" ${PROCESSES_DIR} | wc -l) + if [ "${found}" -lt 1 ]; then + echo "Expected to find Postgres process, got ${found}" + eval "$CLEANUP" + exit 1 + fi + + # check that the PGO CLI log file contains expected messages + CLI_LOG="./kuttl-support-instrumentation/cli.log" + + # info output includes expected heading + if ! grep -Fq -- "- INFO - | PGO CLI Support Export Tool" $CLI_LOG + then + echo "PGO CLI log does not contain expected info message" + eval "$CLEANUP" + exit 1 + fi + + # debug output includes cluster name argument + if ! grep -Fq -- "- DEBUG - Arg - PostgresCluster Name: kuttl-support-instrumentation" $CLI_LOG + then + echo "PGO CLI log does not contain cluster name debug message" + eval "$CLEANUP" + exit 1 + fi + + rm -r ./kuttl-support-instrumentation ./monitoring ./operator ./crunchy_k8s_support_export_*.tar.gz + diff --git a/testing/chainsaw/e2e/templates/confirm-created.yaml b/testing/chainsaw/e2e/templates/confirm-created.yaml new file mode 100644 index 00000000..7a2cafe1 --- /dev/null +++ b/testing/chainsaw/e2e/templates/confirm-created.yaml @@ -0,0 +1,24 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: confirm-created +spec: + try: + - description: > + Confirm the cluster is created + assert: + resource: + apiVersion: v1 + kind: Pod + metadata: + labels: + postgres-operator.crunchydata.com/cluster: ($cluster) + status: + (containerStatuses[?name == 'database']): + - name: database + ready: true + catch: + - describe: + apiVersion: v1 + kind: Pod + selector: (concat('postgres-operator.crunchydata.com/cluster=', $cluster)) diff --git a/testing/chainsaw/e2e/templates/create-cluster-from-manifest.yaml b/testing/chainsaw/e2e/templates/create-cluster-from-manifest.yaml new file mode 100644 index 00000000..c1c773fb --- /dev/null +++ b/testing/chainsaw/e2e/templates/create-cluster-from-manifest.yaml @@ -0,0 +1,36 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: create-cluster-from-manifest +spec: + try: + - + description: > + Create a standard cluster with two replicas and a single pgBackRest repository + apply: + resource: + apiVersion: postgres-operator.crunchydata.com/v1beta1 + kind: PostgresCluster + metadata: + name: ($cluster) + spec: + postgresVersion: (to_number($postgresVersion)) + instances: + - replicas: 2 + dataVolumeClaimSpec: { accessModes: [ "ReadWriteOnce" ], resources: { requests: { storage: 1Gi } } } + backups: + pgbackrest: + global: + log-level-console: detail + log-level-file: detail + start-fast: 'y' + repos: + - name: repo1 + volume: + volumeClaimSpec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: 1Gi } } } + + catch: + - describe: + apiVersion: v1 + kind: Pod + selector: (concat('postgres-operator.crunchydata.com/cluster=', $cluster)) diff --git a/testing/chainsaw/e2e/templates/create-cluster-without-backups.yaml b/testing/chainsaw/e2e/templates/create-cluster-without-backups.yaml new file mode 100644 index 00000000..23e6695a --- /dev/null +++ b/testing/chainsaw/e2e/templates/create-cluster-without-backups.yaml @@ -0,0 +1,27 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: create-cluster-without-backups +spec: + try: + - + description: > + Create a PostgresCluster with the PGO CLI without backups + command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "PGVERSION" + value: ($postgresVersion) + entrypoint: "sh" + args: + - "-c" + - "echo yes | kubectl-pgo --namespace $NAMESPACE create postgrescluster --pg-major-version $PGVERSION --disable-backups $CLUSTER" + timeout: 10s + catch: + - describe: + apiVersion: v1 + kind: Pod + selector: (concat('postgres-operator.crunchydata.com/cluster=', $cluster)) diff --git a/testing/chainsaw/e2e/templates/create-cluster.yaml b/testing/chainsaw/e2e/templates/create-cluster.yaml new file mode 100644 index 00000000..c3d4fafa --- /dev/null +++ b/testing/chainsaw/e2e/templates/create-cluster.yaml @@ -0,0 +1,31 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: create-cluster +spec: + try: + - + description: > + Create a PostgresCluster with the PGO CLI + command: + env: + - name: "NAMESPACE" + value: ($namespace) + - name: "CLUSTER" + value: ($cluster) + - name: "PGVERSION" + value: ($postgresVersion) + entrypoint: "kubectl" + args: + - "pgo" + - "create" + - "postgrescluster" + - "$CLUSTER" + - "--pg-major-version=$PGVERSION" + - "--namespace=$NAMESPACE" + timeout: 10s + catch: + - describe: + apiVersion: v1 + kind: Pod + selector: (concat('postgres-operator.crunchydata.com/cluster=', $cluster)) diff --git a/testing/chainsaw/e2e/templates/psql-data.yaml b/testing/chainsaw/e2e/templates/psql-data.yaml new file mode 100644 index 00000000..d950f824 --- /dev/null +++ b/testing/chainsaw/e2e/templates/psql-data.yaml @@ -0,0 +1,74 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: psql-data +spec: + bindings: + - name: target + value: 'The name of the PostgresCluster' + - name: job + value: 'The name of the job' + - name: command + value: 'The command to run on the psql pod' + + try: + + - command: + entrypoint: 'true' + skipCommandOutput: true + outputs: + - name: secret + value: (join('-', [$target, 'pguser', $target])) + + - + description: > + Run a command through psql + apply: + resource: + apiVersion: batch/v1 + kind: Job + metadata: + name: ($job) + spec: + backoffLimit: 3 + template: + spec: + restartPolicy: Never + containers: + - name: psql + image: ($psql.image) + env: + - ($psql.connect) + - name: PGHOST + valueFrom: { secretKeyRef: { name: ($secret), key: host } } + - name: PGPORT + valueFrom: { secretKeyRef: { name: ($secret), key: port } } + - name: PGDATABASE + valueFrom: { secretKeyRef: { name: ($secret), key: dbname } } + - name: PGUSER + valueFrom: { secretKeyRef: { name: ($secret), key: user } } + - name: PGPASSWORD + valueFrom: { secretKeyRef: { name: ($secret), key: password } } + command: + - psql + - -qa + - --set=ON_ERROR_STOP=1 + - --command + - ($command) + + - assert: + resource: + apiVersion: batch/v1 + kind: Job + metadata: + name: ($job) + status: + succeeded: 1 + + catch: + - + description: > + Read all log lines from the job pods + podLogs: + selector: (join('', ['batch.kubernetes.io/job-name in (', $job, ')'])) + tail: -1 diff --git a/testing/chainsaw/e2e/templates/replica-backup-complete.yaml b/testing/chainsaw/e2e/templates/replica-backup-complete.yaml new file mode 100644 index 00000000..520d435f --- /dev/null +++ b/testing/chainsaw/e2e/templates/replica-backup-complete.yaml @@ -0,0 +1,25 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: replica-backup-complete +spec: + try: + - sleep: + duration: 30s + + - description: Wait for initial backup to complete' + wait: + for: + jsonPath: + path: '{.status.succeeded}' + value: "1" + timeout: 5m + apiVersion: batch/v1 + kind: Job + namespace: ($namespace) + selector: postgres-operator.crunchydata.com/pgbackrest-backup=replica-create + catch: + - describe: + apiVersion: v1 + kind: Pod + selector: (concat('postgres-operator.crunchydata.com/cluster=', $cluster)) diff --git a/testing/chainsaw/e2e/values.yaml b/testing/chainsaw/e2e/values.yaml new file mode 100644 index 00000000..0c8a3ce5 --- /dev/null +++ b/testing/chainsaw/e2e/values.yaml @@ -0,0 +1,5 @@ +versions: + postgres: '17' + +images: + psql: 'registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi9-17.5-2520' diff --git a/testing/chainsaw/e2e/version/chainsaw-test.yaml b/testing/chainsaw/e2e/version/chainsaw-test.yaml new file mode 100644 index 00000000..1fc5fa9f --- /dev/null +++ b/testing/chainsaw/e2e/version/chainsaw-test.yaml @@ -0,0 +1,50 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + creationTimestamp: null + name: version +spec: + steps: + - name: Run kubectl pgo version + try: + - script: + content: | + contains() { bash -ceu '[[ "$1" == *"$2"* ]]' - "$@"; } + + VERSION_OUTPUT=$(kubectl pgo version) + { contains "${VERSION_OUTPUT}" "Client Version:"; } || { + echo "Expected: Client Version:*" + echo "Actual: ${VERSION_OUTPUT}" + exit 1 + } + - script: + content: | + contains() { bash -ceu '[[ "$1" == *"$2"* ]]' - "$@"; } + + OPERATOR_VERSION=$( + kubectl get crd postgresclusters.postgres-operator.crunchydata.com \ + -o go-template='{{ index .metadata.labels "app.kubernetes.io/version" }}' + ) + + VERSION_OUTPUT=$(kubectl pgo version) + + { contains "${VERSION_OUTPUT}" "Operator Version: v${OPERATOR_VERSION}"; } || { + echo "Expected: ${OPERATOR_VERSION}" + echo "Actual: ${VERSION_OUTPUT}" + exit 1 + } + - name: Run kubectl pgo version --client + try: + - script: + content: | + contains() { bash -ceu '[[ "$1" == *"$2"* ]]' - "$@"; } + + VERSION_OUTPUT=$(KUBECONFIG=blah kubectl pgo version --client) + + { contains "${VERSION_OUTPUT}" "Client Version:"; } || { + echo "Expected: Client Version:*" + echo "Actual: ${VERSION_OUTPUT}" + exit 1 + } + diff --git a/testing/chainsaw/policies/README b/testing/chainsaw/policies/README new file mode 100644 index 00000000..7e671619 --- /dev/null +++ b/testing/chainsaw/policies/README @@ -0,0 +1,49 @@ + +# Kyverno Policies + +## Install Kyverno + + +Each 1.x version of Kyverno supports a range of Kubernetes version. Consult the +chart for appropriate versions for your test. + +The latest is v1.15 and is not included in the website documentation. + + +| Kyverno Version | Kubernetes Min | Kubernetes Max | +|-----------------|----------------|----------------| +| 1.12.x | 1.26 | 1.29 | +| 1.13.x | 1.28 | 1.31 | +| 1.14.x | 1.29 | 1.32 | + +- https://kyverno.io/docs/installation/#compatibility-matrix + + +```shell +kubectl create -f https://github.com/kyverno/kyverno/releases/download/v1.15.1/install.yaml +``` + +## Image Pull Secret Policies + +Execute these commands after Kyverno successfully deploys. + +```shell +kubectl apply -f v1.15.1/00-rbac.yaml +kubectl apply -f v1.15.1/01-inject-imagepullsecret-to-namespace.yaml +kubectl apply -f v1.15.1/02-add-imagepullsecrets.yaml +``` + +## Create Registry Secrets + +```shell +kubectl create secret docker-registry crunchy-access-portal -n default \ + --docker-server=registry.crunchydata.com \ + --docker-username=$CRUNCHY_AP_USERNAME \ + --docker-email=$CRUNCHY_AP_USERNAME \ + --docker-password=$CRUNCHY_AP_PWD + +kubectl create secret docker-registry crunchy-harbor -n default \ + --docker-server registry.internal.crunchydata.com \ + --docker-username=$HARBOR_USER \ + --docker-password=$HARBOR_PWD +``` diff --git a/testing/chainsaw/policies/v1.15.1/00-rbac.yaml b/testing/chainsaw/policies/v1.15.1/00-rbac.yaml new file mode 100644 index 00000000..03b4311d --- /dev/null +++ b/testing/chainsaw/policies/v1.15.1/00-rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kyverno:list-get-secrets + labels: + rbac.kyverno.io/aggregate-to-admission-controller: "true" +rules: +- apiGroups: + - '' + resources: + - secrets + verbs: + - list + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kyverno:create-update-secrets + labels: + rbac.kyverno.io/aggregate-to-background-controller: "true" +rules: +- apiGroups: + - '' + resources: + - secrets + verbs: + - create + - update + - delete + - get + - list \ No newline at end of file diff --git a/testing/chainsaw/policies/v1.15.1/01-inject-imagepullsecret-to-namespace.yaml b/testing/chainsaw/policies/v1.15.1/01-inject-imagepullsecret-to-namespace.yaml new file mode 100644 index 00000000..90e55fe8 --- /dev/null +++ b/testing/chainsaw/policies/v1.15.1/01-inject-imagepullsecret-to-namespace.yaml @@ -0,0 +1,58 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: inject-imagepullsecret-to-namespace + annotations: + policies.kyverno.io/title: Clone ImagePullSecret to New Namespaces + policies.kyverno.io/subject: Namespace + policies.kyverno.io/description: >- + Clones imagePullSecrets to new namespaces (except system namespaces). +spec: + generateExisting: true + rules: + - name: clone-crunchy-access-portal-secret + match: + any: + - resources: + kinds: + - Namespace + exclude: + any: + - resources: + namespaces: + - kube-system + - kube-node-lease + - kube-public + - kyverno + generate: + apiVersion: v1 + kind: Secret + name: crunchy-access-portal + namespace: "{{ request.object.metadata.name }}" + synchronize: true + clone: + namespace: default + name: crunchy-access-portal + - name: clone-crunchy-harbor-secret + match: + any: + - resources: + kinds: + - Namespace + exclude: + any: + - resources: + namespaces: + - kube-system + - kube-node-lease + - kube-public + - kyverno + generate: + apiVersion: v1 + kind: Secret + name: crunchy-harbor + namespace: "{{ request.object.metadata.name }}" + synchronize: true + clone: + namespace: default + name: crunchy-harbor \ No newline at end of file diff --git a/testing/chainsaw/policies/v1.15.1/02-add-imagepullsecrets.yaml b/testing/chainsaw/policies/v1.15.1/02-add-imagepullsecrets.yaml new file mode 100644 index 00000000..e82e7872 --- /dev/null +++ b/testing/chainsaw/policies/v1.15.1/02-add-imagepullsecrets.yaml @@ -0,0 +1,53 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: add-imagepullsecrets + annotations: + policies.kyverno.io/title: Add ImagePullSecrets for Multiple Registries + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Conditionally adds imagePullSecrets to pods based on the registry of their container images. +spec: + rules: + - name: add-crunchy-access-portal-imagepullsecret + match: + any: + - resources: + kinds: + - Pod + context: + - name: images_in_crunchy_access_portal + variable: + jmesPath: "[request.object.spec.containers[*].image.contains(@, 'registry.crunchydata.com')][]" + default: [] + preconditions: + all: + - key: true + operator: In + value: "{{ images_in_crunchy_access_portal }}" + mutate: + patchStrategicMerge: + spec: + imagePullSecrets: + - name: crunchy-access-portal + - name: add-crunchy-harbor-imagepullsecret + match: + any: + - resources: + kinds: + - Pod + context: + - name: images_in_crunchy_harbor + variable: + jmesPath: "[request.object.spec.containers[*].image.contains(@, 'registry.internal.crunchydata.com')][]" + default: [] + preconditions: + all: + - key: true + operator: In + value: "{{ images_in_crunchy_harbor }}" + mutate: + patchStrategicMerge: + spec: + imagePullSecrets: + - name: crunchy-harbor \ No newline at end of file From bebc6a08ddbe45639c1138a06f329cbdd74f406c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:49:39 -0500 Subject: [PATCH 42/45] Bump the all-github-actions group across 1 directory with 2 updates (#140) Bumps the all-github-actions group with 2 updates in the / directory: [actions/setup-go](https://github.com/actions/setup-go) and [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action). Updates `actions/setup-go` from 5 to 6 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) Updates `aquasecurity/trivy-action` from 0.32.0 to 0.33.1 - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.32.0...0.33.1) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-github-actions - dependency-name: aquasecurity/trivy-action dependency-version: 0.33.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yaml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yaml | 4 ++-- .github/workflows/trivy.yaml | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 95229cc5..08489bfe 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: { go-version: stable } - name: Initialize CodeQL diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index b97cc791..8e9cf68c 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -13,7 +13,7 @@ jobs: checks: write steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: { go-version: stable } - uses: golangci/golangci-lint-action@v8 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3c6ebdce..1a928f8c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: { go-version: stable } - run: make check @@ -28,7 +28,7 @@ jobs: kubernetes: [v1.27, v1.24, v1.21] steps: - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: { go-version: stable } - name: Start k3s diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index d5a02e72..6e8c637d 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -17,13 +17,13 @@ jobs: - uses: actions/checkout@v5 # Trivy needs a populated Go module cache to detect Go module licenses. - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: { go-version: stable } - run: go mod download # Report success only when detected licenses are listed in [/trivy.yaml]. - name: Scan licenses - uses: aquasecurity/trivy-action@0.32.0 + uses: aquasecurity/trivy-action@0.33.1 env: TRIVY_DEBUG: true with: @@ -44,7 +44,7 @@ jobs: # and is a convenience/redundant effort for those who prefer to # read logs and/or if anything goes wrong with the upload. - name: Log all detected vulnerabilities - uses: aquasecurity/trivy-action@0.32.0 + uses: aquasecurity/trivy-action@0.33.1 with: scan-type: filesystem hide-progress: true @@ -56,7 +56,7 @@ jobs: # - https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github # - https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning - name: Report actionable vulnerabilities - uses: aquasecurity/trivy-action@0.32.0 + uses: aquasecurity/trivy-action@0.33.1 with: scan-type: filesystem ignore-unfixed: true From 8a33167589d2f347668fae83c9dd8b019e1db7ef Mon Sep 17 00:00:00 2001 From: ValClarkson Date: Wed, 1 Oct 2025 15:31:15 -0400 Subject: [PATCH 43/45] updated for release 0.5.3 PGO-2699 --- docs/config.toml | 2 +- docs/content/reference/pgo_version.md | 2 +- docs/content/releases/0.5.3..md | 32 +++++++++++++++++++++++++++ internal/cmd/client_version.go | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 docs/content/releases/0.5.3..md diff --git a/docs/config.toml b/docs/config.toml index 3162c4be..efd1c9c4 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -15,7 +15,7 @@ defaultContentLanguageInSubdir= false enableMissingTranslationPlaceholders = false [params] -clientVersion = "0.5.2" +clientVersion = "0.5.3" # crunchy-hugo-theme params showVisitedLinks = false # default is false # in theme diff --git a/docs/content/reference/pgo_version.md b/docs/content/reference/pgo_version.md index 2845b235..74ff555e 100644 --- a/docs/content/reference/pgo_version.md +++ b/docs/content/reference/pgo_version.md @@ -31,7 +31,7 @@ pgo version ``` ### Example output ``` -Client Version: v0.5.2 +Client Version: v0.5.3 Operator Version: v5.7.0 ``` diff --git a/docs/content/releases/0.5.3..md b/docs/content/releases/0.5.3..md new file mode 100644 index 00000000..2910c8bd --- /dev/null +++ b/docs/content/releases/0.5.3..md @@ -0,0 +1,32 @@ +--- +title: "0.5.3" +draft: false +weight: 990 +--- + +[Crunchy Postgres for Kubernetes]: https://www.crunchydata.com/products/crunchy-postgresql-for-kubernetes +[`pgo` CLI documentation]: https://access.crunchydata.com/documentation/postgres-operator-client/latest + +Crunchy Data announces the release of `pgo`, Postgres Operator Client from Crunchy Data 0.5.3. + +Built as a `kubectl` plugin, the `pgo` CLI facilitates the creation and management of PostgreSQL clusters created using [Crunchy Postgres for Kubernetes][]. + +For more information about using the CLI and the various commands available, please see the [`pgo` CLI documentation][]. + +Additionally, please see the [CPK documentation](https://access.crunchydata.com/documentation/postgres-operator/latest) for information about [getting started](https://access.crunchydata.com/documentation/postgres-operator/latest/quickstart/) with Crunchy Postgres for Kubernetes. + +## Features + +- The `support export` command now includes the following improvements + - Adds support for ResourceQuotas + - Gather additional Postgres data points + - Better gathering of PGAdmin resources + +## Changes + +- The `support export` command now includes the following changes + - Removed cluster-info. Cluster-info returns a lot of sensitive information and it out-of-scope for the support export + - Fix unnecessary use of fmt.Sprintf + - Fix describe for clusterrole and clusterrolebinding on openshift + - refactor describe clusterrole and clusterrolebinding + - refactor to account for labels on some installers diff --git a/internal/cmd/client_version.go b/internal/cmd/client_version.go index 3e99094e..cf91b61b 100644 --- a/internal/cmd/client_version.go +++ b/internal/cmd/client_version.go @@ -5,4 +5,4 @@ package cmd // store the current PGO CLI version -const clientVersion = "v0.5.2" +const clientVersion = "v0.5.3" From 06f6a516daaac7d5032ce17a62e75fe0ebfd5ddd Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 1 Oct 2025 16:08:09 -0400 Subject: [PATCH 44/45] Update docs/content/releases/0.5.3..md Co-authored-by: Benjamin Blattberg --- docs/content/releases/0.5.3..md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/releases/0.5.3..md b/docs/content/releases/0.5.3..md index 2910c8bd..ed2118e4 100644 --- a/docs/content/releases/0.5.3..md +++ b/docs/content/releases/0.5.3..md @@ -25,7 +25,7 @@ Additionally, please see the [CPK documentation](https://access.crunchydata.com/ ## Changes - The `support export` command now includes the following changes - - Removed cluster-info. Cluster-info returns a lot of sensitive information and it out-of-scope for the support export + - Removed cluster-info. Cluster-info returns a lot of sensitive information and is out-of-scope for the support export - Fix unnecessary use of fmt.Sprintf - Fix describe for clusterrole and clusterrolebinding on openshift - refactor describe clusterrole and clusterrolebinding From 0c389fb7690f79b319920d8b56e29db5a2372e5d Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 1 Oct 2025 16:08:15 -0400 Subject: [PATCH 45/45] Update docs/content/releases/0.5.3..md Co-authored-by: Benjamin Blattberg --- docs/content/releases/0.5.3..md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/releases/0.5.3..md b/docs/content/releases/0.5.3..md index ed2118e4..42e9d6e5 100644 --- a/docs/content/releases/0.5.3..md +++ b/docs/content/releases/0.5.3..md @@ -28,5 +28,5 @@ Additionally, please see the [CPK documentation](https://access.crunchydata.com/ - Removed cluster-info. Cluster-info returns a lot of sensitive information and is out-of-scope for the support export - Fix unnecessary use of fmt.Sprintf - Fix describe for clusterrole and clusterrolebinding on openshift - - refactor describe clusterrole and clusterrolebinding - - refactor to account for labels on some installers + - Refactor describe clusterrole and clusterrolebinding + - Refactor to account for labels on some installers