From 5fb75ab37b92f5d31eb58bf1ec4c8f2158d96d8a Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Tue, 19 Aug 2025 14:49:22 +0000 Subject: [PATCH 1/9] gather pg_settings Query pg_settings to capture current Postgres settings directly from the DB. --- internal/cmd/export.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index abfad52..545b975 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -1229,8 +1229,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 From 4d502ad0b70e26a5048fa9e0ada8d43e2a4294ee Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Tue, 19 Aug 2025 14:50:12 +0000 Subject: [PATCH 2/9] run kubectl describe commands on various resources --- internal/cmd/export.go | 93 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 545b975..fcf3cd5 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -581,6 +581,73 @@ 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 cluster-info dump...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/cluster-info.json", "cluster-info", "dump") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl cluster-info dump: %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)) + } + + writeInfo(cmd, "Running kubectl describe crd crunchybridgeclusters...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/crunchybridgeclusters", "describe", "crds", "crunchybridgeclusters") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd crunchybridgeclusters: %s", err)) + } + + writeInfo(cmd, "Running kubectl describe crd pgadmins...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/pgadmins", "describe", "crds", "pgadmins") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd pgadmins: %s", err)) + } + + writeInfo(cmd, "Running kubectl describe crd pgupgrades...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/pgupgrades", "describe", "crds", "pgupgrades") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd pgupgrades: %s", err)) + } + + writeInfo(cmd, "Running kubectl describe crd postgresclusters...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/postgresclusters", "describe", "crds", "postgresclusters") + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd postgresclusters: %s", err)) + } + + 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, "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)) + } + + 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)) + } + + writeInfo(cmd, "Running kubectl describe pgadmin...") + err = runKubectlCommand(tw, cmd, "pgadmin/describe/pgadmin", "describe", "pgadmin", "-n", namespace) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe pgadmin: %s", err)) + } + // Print cli output writeInfo(cmd, "Collecting PGO CLI logs...") path := clusterName + "/cli.log" @@ -640,6 +707,28 @@ 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)) + } + + 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. @@ -1671,6 +1760,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 { From 27ce4b6a5305f88a724a2c947e04e15030904b5b Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Fri, 22 Aug 2025 19:24:00 +0000 Subject: [PATCH 3/9] removing cluster-info cluster-info returns a lot of sensitive information and it out-of-scope for the support export. --- internal/cmd/export.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index fcf3cd5..ce88cc8 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -588,12 +588,6 @@ Collecting PGO CLI logs... writeInfo(cmd, fmt.Sprintf("Error running kubectl describe nodes: %s", err)) } - writeInfo(cmd, "Running kubectl cluster-info dump...") - err = runKubectlCommand(tw, cmd, clusterName+"/describe/cluster-info.json", "cluster-info", "dump") - if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl cluster-info dump: %s", err)) - } - writeInfo(cmd, "Running kubectl describe postgrescluster...") err = runKubectlCommand(tw, cmd, clusterName+"/describe/postgrescluster", "describe", "postgrescluster", clusterName, "-n", namespace) if err != nil { From 8675cfa35d58d81343e5875ea4805846e19115d8 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Fri, 22 Aug 2025 19:28:23 +0000 Subject: [PATCH 4/9] 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. --- internal/cmd/export.go | 93 +++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index ce88cc8..53d887d 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -27,6 +27,7 @@ import ( networkingv1 "k8s.io/api/networking/v1" policyv1 "k8s.io/api/policy/v1" policyv1beta1 "k8s.io/api/policy/v1beta1" + 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 +342,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 +462,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 { @@ -594,30 +606,6 @@ Collecting PGO CLI logs... writeInfo(cmd, fmt.Sprintf("Error running kubectl describe postgrescluster: %s", err)) } - writeInfo(cmd, "Running kubectl describe crd crunchybridgeclusters...") - err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/crunchybridgeclusters", "describe", "crds", "crunchybridgeclusters") - if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd crunchybridgeclusters: %s", err)) - } - - writeInfo(cmd, "Running kubectl describe crd pgadmins...") - err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/pgadmins", "describe", "crds", "pgadmins") - if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd pgadmins: %s", err)) - } - - writeInfo(cmd, "Running kubectl describe crd pgupgrades...") - err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/pgupgrades", "describe", "crds", "pgupgrades") - if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd pgupgrades: %s", err)) - } - - writeInfo(cmd, "Running kubectl describe crd postgresclusters...") - err = runKubectlCommand(tw, cmd, clusterName+"/describe/crds/postgresclusters", "describe", "crds", "postgresclusters") - if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl describe crd postgresclusters: %s", err)) - } - writeInfo(cmd, "Running kubectl describe clusterrole...") err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrole", "describe", "clusterrole", "postgres-operator") if err != nil { @@ -1039,6 +1027,63 @@ 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 events...") + // list, err := clientset.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{}) + + labelSelector := "app.kubernetes.io/name=pgo" + crds, err := clientset.ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{LabelSelector: labelSelector}) + + if err != nil { + if apierrors.IsForbidden(err) { + writeInfo(cmd, err.Error()) + return nil + } + return err + } + + if len(crds.Items) == 0 { + // If we didn't find any resources, skip + writeInfo(cmd, fmt.Sprintf("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(crds, &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 crds.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, From 066f42f3a6bb41108172eca1d9e94dcbf4f633de Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Fri, 22 Aug 2025 19:35:53 +0000 Subject: [PATCH 5/9] fix unnecessary use of fmt.Sprintf --- internal/cmd/export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 53d887d..17e5181 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -1050,7 +1050,7 @@ func gatherCrds(ctx context.Context, if len(crds.Items) == 0 { // If we didn't find any resources, skip - writeInfo(cmd, fmt.Sprintf("Resource CRDs not found, skipping")) + writeInfo(cmd, "Resource CRDs not found, skipping") return nil } From 308444adb7ecd8382b688c874cfe901bc5677573 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Fri, 22 Aug 2025 20:45:08 +0000 Subject: [PATCH 6/9] 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. --- .../v1beta1/pgadmin_types.go | 39 ++++++++++ internal/cmd/export.go | 73 ++++++++++++++++++- internal/util/naming.go | 3 + 3 files changed, 112 insertions(+), 3 deletions(-) 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 0000000..3003e64 --- /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 17e5181..ee7338e 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -624,10 +624,9 @@ Collecting PGO CLI logs... writeInfo(cmd, fmt.Sprintf("Error running kubectl describe lease: %s", err)) } - writeInfo(cmd, "Running kubectl describe pgadmin...") - err = runKubectlCommand(tw, cmd, "pgadmin/describe/pgadmin", "describe", "pgadmin", "-n", namespace) + err = gatherPgadminResources(config, clientset, ctx, namespace, tw, cmd) if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl describe pgadmin: %s", err)) + writeInfo(cmd, fmt.Sprintf("Error gathering PGAdmin Resources: %s", err)) } // Print cli output @@ -647,6 +646,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 diff --git a/internal/util/naming.go b/internal/util/naming.go index 7d11e78..06e8646 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 e42945e23ac4f1bf30ed4d7b57d4aa76aa8961c2 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Mon, 25 Aug 2025 17:49:51 +0000 Subject: [PATCH 7/9] fix describe for clusterrole and clusterrolebinding on openshift --- internal/cmd/export.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index ee7338e..bbb51b2 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -608,12 +608,23 @@ Collecting PGO CLI logs... writeInfo(cmd, "Running kubectl describe clusterrole...") err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrole", "describe", "clusterrole", "postgres-operator") + if err != nil { + writeInfo(cmd, "Could not find clusterrole 'postgres-operator'. Looking for 'postgresoperator'...") + writeInfo(cmd, "Running kubectl describe clusterrole...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrole", "describe", "clusterrole", "postgresoperator") + } + if err != nil { writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrole: %s", err)) } writeInfo(cmd, "Running kubectl describe clusterrolebinding...") err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrolebinding", "describe", "clusterrolebinding", "postgres-operator") + if err != nil { + writeInfo(cmd, "Could not find clusterrolebinding 'postgres-operator'. Looking for 'postgresoperator'...") + writeInfo(cmd, "Running kubectl describe clusterrole...") + err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrolebinding", "describe", "clusterrolebinding", "postgresoperator") + } if err != nil { writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrolebinding: %s", err)) } @@ -769,6 +780,7 @@ func runKubectlCommand(tw *tar.Writer, cmd *cobra.Command, path string, cmdArgs 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 { @@ -1101,7 +1113,7 @@ func gatherCrds(ctx context.Context, tw *tar.Writer, cmd *cobra.Command, ) error { - writeInfo(cmd, "Collecting events...") + writeInfo(cmd, "Collecting CRDs...") // list, err := clientset.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{}) labelSelector := "app.kubernetes.io/name=pgo" From c1e0103f78c5afe05848d2057363dddde91eae66 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Mon, 25 Aug 2025 19:52:56 +0000 Subject: [PATCH 8/9] refactor describe clusterrole and clusterrolebinding This makes it more clear that 'postgresoperator' is a special case for certain installers. --- internal/cmd/export.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index bbb51b2..26b8570 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -606,27 +606,34 @@ Collecting PGO CLI logs... 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'...") - writeInfo(cmd, "Running kubectl describe clusterrole...") - err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrole", "describe", "clusterrole", "postgresoperator") - } - if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrole: %s", err)) + // 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'...") - writeInfo(cmd, "Running kubectl describe clusterrole...") err = runKubectlCommand(tw, cmd, clusterName+"/describe/clusterrolebinding", "describe", "clusterrolebinding", "postgresoperator") - } - if err != nil { - writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrolebinding: %s", err)) + if err != nil { + writeInfo(cmd, fmt.Sprintf("Error running kubectl describe clusterrolebinding: %s", err)) + } } writeInfo(cmd, "Running kubectl describe lease...") From a22c0618608a5b473f14bf3e12b9ab8944b678e3 Mon Sep 17 00:00:00 2001 From: Philip Hurst Date: Mon, 25 Aug 2025 19:54:20 +0000 Subject: [PATCH 9/9] 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" --- internal/cmd/export.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/internal/cmd/export.go b/internal/cmd/export.go index 26b8570..0ac814f 100644 --- a/internal/cmd/export.go +++ b/internal/cmd/export.go @@ -27,6 +27,7 @@ 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" @@ -1121,10 +1122,8 @@ func gatherCrds(ctx context.Context, cmd *cobra.Command, ) error { writeInfo(cmd, "Collecting CRDs...") - // list, err := clientset.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{}) - labelSelector := "app.kubernetes.io/name=pgo" - crds, err := clientset.ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{LabelSelector: labelSelector}) + crdList, err := clientset.ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{}) if err != nil { if apierrors.IsForbidden(err) { @@ -1134,7 +1133,19 @@ func gatherCrds(ctx context.Context, return err } - if len(crds.Items) == 0 { + // 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 @@ -1143,7 +1154,7 @@ func gatherCrds(ctx context.Context, // Create a buffer to generate string with the table formatted list var buf bytes.Buffer if err := printers.NewTablePrinter(printers.PrintOptions{}). - PrintObj(crds, &buf); err != nil { + PrintObj(filteredCRDs, &buf); err != nil { return err } @@ -1154,7 +1165,7 @@ func gatherCrds(ctx context.Context, return err } - for _, obj := range crds.Items { + for _, obj := range filteredCRDs.Items { b, err := yaml.Marshal(obj) if err != nil { return err