diff --git a/.gitignore b/.gitignore index 2bd0422bcee..d1e0be2b342 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,8 @@ cache/ debug.test* # Others -.envrc \ No newline at end of file +.envrc + +# must-gather +must-gather/must-gather/ +must-gather/must-gather.local.*/ diff --git a/must-gather/Dockerfile b/must-gather/Dockerfile index d994aaacbe2..de552c4cfd8 100644 --- a/must-gather/Dockerfile +++ b/must-gather/Dockerfile @@ -1,39 +1,39 @@ -FROM --platform=$BUILDPLATFORM quay.io/konveyor/builder:ubi9-latest AS konveyor-builder +FROM --platform=$BUILDPLATFORM quay.io/konveyor/builder:ubi9-latest AS builder ARG BUILDPLATFORM ARG TARGETOS ARG TARGETARCH -ARG RESTIC_BRANCH=konveyor-0.15.0 -ARG VELERO_BRANCH=konveyor-dev -WORKDIR /build -RUN curl --location --output velero.tgz https://github.com/openshift/velero/archive/refs/heads/${VELERO_BRANCH}.tar.gz && \ - tar -xzvf velero.tgz && cd velero-${VELERO_BRANCH} && \ - VELERO_COMMIT=$(git ls-remote https://github.com/openshift/velero HEAD | awk '{printf $1}') && \ - CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -mod=mod -ldflags '-extldflags "-static" -X github.com/vmware-tanzu/velero/pkg/buildinfo.Version='"${VELERO_BRANCH}"' -X github.com/vmware-tanzu/velero/pkg/buildinfo.GitSHA='"${VELERO_COMMIT}" -o /velero github.com/vmware-tanzu/velero/cmd/velero && \ - cd .. && rm -rf velero.tgz velero-${VELERO_BRANCH} && \ - curl --location --output restic.tgz https://github.com/openshift/restic/archive/refs/heads/${RESTIC_BRANCH}.tar.gz && \ - tar -xzvf restic.tgz && cd restic-${RESTIC_BRANCH} && \ - CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -mod=mod -ldflags '-extldflags "-static"' -o /restic github.com/restic/restic/cmd/restic && \ - cd .. && rm -rf restic.tgz restic-${RESTIC_BRANCH} +ARG KOPIA_BRANCH=konveyor-dev +ARG RESTIC_BRANCH=konveyor-dev -FROM registry.access.redhat.com/ubi9/go-toolset:latest AS gobuilder +WORKDIR /workspace -RUN go install -v github.com/google/pprof@latest +COPY go.mod go.mod +COPY go.sum go.sum -FROM quay.io/openshift/origin-must-gather:latest AS builder +RUN go mod download -FROM registry.access.redhat.com/ubi9-minimal:latest +COPY cmd/main.go cmd/main.go +COPY pkg/ pkg/ + +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -mod=mod -a -o gather cmd/main.go + +RUN curl --location --output kopia.tgz https://github.com/migtools/kopia/archive/refs/heads/${KOPIA_BRANCH}.tar.gz && \ + tar -xzvf kopia.tgz && cd kopia-${KOPIA_BRANCH} && \ + CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -mod=mod -a -ldflags '-extldflags "-static"' -o /kopia github.com/kopia/kopia && \ + cd .. && rm -rf kopia.tgz kopia-${KOPIA_BRANCH} -RUN echo -ne "[centos-9-appstream]\nname = CentOS 9 (RPMs) - AppStream\nbaseurl = https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/\nenabled = 1\ngpgcheck = 0" > /etc/yum.repos.d/centos-9-appstream.repo -RUN microdnf -y install rsync tar gzip graphviz findutils +RUN curl --location --output restic.tgz https://github.com/openshift/restic/archive/refs/heads/${RESTIC_BRANCH}.tar.gz && \ + tar -xzvf restic.tgz && cd restic-${RESTIC_BRANCH} && \ + CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -mod=mod -a -ldflags '-extldflags "-static"' -o /restic github.com/restic/restic/cmd/restic && \ + cd .. && rm -rf restic.tgz restic-${RESTIC_BRANCH} + +FROM registry.access.redhat.com/ubi9-minimal:latest -COPY --from=gobuilder /opt/app-root/src/go/bin/pprof /usr/bin/pprof -COPY --from=builder /usr/bin/oc /usr/bin/oc -COPY --from=konveyor-builder /velero /usr/bin/velero -COPY --from=konveyor-builder /restic /usr/bin/restic +# oc adm must-gather uses these packages to download the output +RUN microdnf -y install rsync tar -COPY collection-scripts/* /usr/bin/ -COPY collection-scripts/debug/* /usr/bin/ -COPY collection-scripts/logs/* /usr/bin/ -COPY collection-scripts/time_window_gather /usr/bin/ +COPY --from=builder /workspace/gather /usr/bin/gather +COPY --from=builder /kopia /usr/bin/kopia +COPY --from=builder /restic /usr/bin/restic -ENTRYPOINT /usr/bin/gather +ENTRYPOINT ["/usr/bin/gather"] diff --git a/must-gather/Makefile b/must-gather/Makefile deleted file mode 100644 index c0dbd0b81ff..00000000000 --- a/must-gather/Makefile +++ /dev/null @@ -1,51 +0,0 @@ -IMAGE_REGISTRY ?= quay.io -IMAGE_TAG ?= latest -IMAGE_NAME ?= oadp/must-gather - -PROMETHEUS_LOCAL_DATA_DIR ?= /tmp/oadp-data-dump -# Search for prom_data.tar.gz archive in must-gather output in currect directory by default -PROMETHEUS_DUMP_PATH ?= $(shell find ./must-gather.local* -name prom_data.tar.gz -printf "%T@ %p\n" | sort -n | tail -1 | cut -d" " -f2) - -build: docker-build docker-push - -run: IMAGE_REGISTRY:=ttl.sh -run: IMAGE_NAME:=oadp/must-gather-$(shell git rev-parse --short HEAD)-$(shell echo $$RANDOM) -run: IMAGE_TAG:=1h -run: - IMAGE_REGISTRY=$(IMAGE_REGISTRY) IMAGE_NAME=$(IMAGE_NAME) IMAGE_TAG=$(IMAGE_TAG) make build && \ - oc adm must-gather --image ${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} - -PLATFORM ?= linux/amd64 -docker-build: - docker build --platform=${PLATFORM} -t ${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} . - -docker-push: - docker push ${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} - -.PHONY: build docker-build docker-push - -prometheus-run: prometheus-cleanup-container prometheus-load-dump - docker run -d \ - --mount type=bind,source=${PROMETHEUS_LOCAL_DATA_DIR},target=/prometheus \ - --name oadp-prometheus \ - --publish 127.0.0.1:9090:9090 \ - prom/prometheus:v2.21.0 \ - && echo "Started Prometheus on http://localhost:9090" - -prometheus-load-dump: prometheus-check-archive-file prometheus-cleanup-data - mkdir -p ${PROMETHEUS_LOCAL_DATA_DIR} - tar xvf ${PROMETHEUS_DUMP_PATH} -C ${PROMETHEUS_LOCAL_DATA_DIR} --strip-components=1 --no-same-owner - chmod -R 777 ${PROMETHEUS_LOCAL_DATA_DIR} - -prometheus-cleanup-container: - # delete data files directly from the container to allow delete data directory from outside of the container - docker exec oadp-prometheus rm -rf /prometheus || true - docker rm -f oadp-prometheus || true - -prometheus-cleanup-data: - rm -rf ${PROMETHEUS_LOCAL_DATA_DIR} - -prometheus-cleanup: prometheus-cleanup-container prometheus-cleanup-data - -prometheus-check-archive-file: - test -f "${PROMETHEUS_DUMP_PATH}" || (echo "Error: Prometheus archive file does not exist. Specify valid file in PROMETHEUS_DUMP_PATH environment variable."; exit 1) diff --git a/must-gather/README.md b/must-gather/README.md index 854652a043d..509bc4368c5 100644 --- a/must-gather/README.md +++ b/must-gather/README.md @@ -1,83 +1,73 @@ -# OADP must-gather +# OADP Must-gather -`must-gather` is a tool built on top of [OpenShift must-gather](https://github.com/openshift/must-gather) -that expands its capabilities to gather OADP operator specific resources - -### Usage - -**Full gather** +To test OADP Must-gather, run ```sh -oc adm must-gather --image=quay.io/konveyor/oadp-must-gather:latest +go run cmd/main.go -h +go run cmd/main.go ``` -The command above will create a local directory with a dump of the OADP Operator state. - -You will get a dump of: -- All namespaces where OADP operator is installed, including pod logs -- All velero.io resources located in those namespaces -- Prometheus metrics - -**Essential-only gather** - -Differences from full gather: - - Logs are only gathered from specified time window - - Skips collection of prometheus metrics. Removes duplicate logs from payload. -``` -# Essential gather (available time windows: [1h, 6h, 24h, 72h, all]) -oc adm must-gather --image=quay.io/konveyor/oadp-must-gather:latest -- /usr/bin/gather_24h_essential +To test OADP Must-gather with `oc adm must-gather`, run +```sh +podman build -t ttl.sh/oadp/must-gather-$(git rev-parse --short HEAD)-$(echo $RANDOM) -f Dockerfile . --platform= +podman push +oc adm must-gather --image= -- /usr/bin/gather -h +oc adm must-gather --image= ``` +TODO mention e2e tests -#### Preview metrics on local Prometheus server +To test omg tool, create `omg.Dockerfile` file +```Dockerfile +FROM python:3.10.12-slim-bullseye -Get Prometheus metrics data directory dump (last day, might take a while): -```sh -oc adm must-gather --image quay.io/konveyor/oadp-must-gather:latest -- /usr/bin/gather_metrics_dump +WORKDIR /test-omg +COPY ./ ./ +RUN pip install o-must-gather ``` - -Run local Prometheus instance with dumped data: +and run ```sh -make prometheus-run # and prometheus-cleanup when you're done +podman build -t omg-container -f omg.Dockerfile . +podman run -ti --rm omg-container bash +# inside container +omg use must-gather/clusters/ +omg get backup -n # and other OADP resources ``` -The latest Prometheus data file (prom_data.tar.gz) in current directory/subdirectories is searched by default. Could be specified in ```PROMETHEUS_DUMP_PATH``` environment variable. -### Known Limitations - -`velero backups` with phase `FailedValidation` could cause `must-gather` to be slow. In order to speed up the process, pass a timeout value to the command as follows, +To update OADP Must-gather `go.mod` dependencies, run ```sh -oc adm must-gather --image=quay.io/konveyor/oadp-must-gather:latest -- /usr/bin/gather_with_timeout +go get github.com/openshift/oadp-operator@master +go get github.com/migtools/oadp-non-admin@master +# manually update github.com/openshift/velero version in replace section of go.mod to match OADP operator +go mod tidy +go mod verify ``` -### Support for insecure TLS connections - -If a custom CA cert is used, then must-gather pod fails to grab the output for velero logs/describe. In this case, the user can pass a flag to the must-gather command to allow insecure TLS connections. +Update it often. It must be updated prior to releases. +Possible necessary updates over the time ```sh -oc adm must-gather --image=quay.io/konveyor/oadp-must-gather:latest -- /usr/bin/gather_without_tls +go get github.com/openshift/oc@ +go mod tidy +go mod verify ``` -Note: By default the flag value is set to `false` -### Combining Options +## OADP release -It is not currently possible to combine multiple gather scripts, for example specifying a timeout value at the same time as allowing insecure TLS. However, it is possible in some cases to work around this limitation by setting internal variables on the must-gather command line, like this: +Prior to each release, OADP Must-gather must be updated. +To update OADP Must-gather `go.mod` dependencies, run ```sh -oc adm must-gather --image=quay.io/konveyor/oadp-must-gather:latest -- skip_tls=true /usr/bin/gather_with_timeout +go get github.com/openshift/oadp-operator@ +go get github.com/migtools/oadp-non-admin@ +# manually update github.com/openshift/velero version in replace section of go.mod to match OADP operator +go mod tidy +go mod verify ``` -In this case, the `skip_tls` variable is set before running the gather_with_timeout script, and the net effect is a combination of gather_with_timeout and gather_without_tls. The only other variables that can be specified this way are `logs_since`, with a default value of `72h`, and `request_timeout`, with a default value of `0s`. - -### Development -You can build the image locally using the Dockerfile included. - -A `makefile` is also provided. To use it, you must pass a repository via the command-line using the variable `IMAGE_NAME`. -You can also specify the registry using the variable `IMAGE_REGISTRY` (default is [quay.io](https://quay.io)) and the tag via `IMAGE_TAG` (default is `latest`). - -The targets for `make` are as follows: -- `build`: builds the image with the supplied name and pushes it -- `docker-build`: builds the image but does not push it -- `docker-push`: pushes an already-built image - -For example: -```sh -make build IMAGE_NAME=my-repo/must-gather +`must-gather/pkg/cli.go` file must be updated +```diff + const ( +- mustGatherVersion = "1.5.0" ++ mustGatherVersion = "1.5.1" + mustGatherImage = "registry.redhat.io/oadp/oadp-mustgather-rhel9:v1.5" ``` -would build the local repository as `quay.io/my-repo/must-gather:latest` and then push it. + +> **Note:** If it is a minor release, `mustGatherImage` must also be updated. diff --git a/must-gather/cmd/main.go b/must-gather/cmd/main.go new file mode 100644 index 00000000000..257393d0710 --- /dev/null +++ b/must-gather/cmd/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/openshift/oadp-operator/must-gather/pkg" +) + +func init() { + pkg.CLI.Flags().DurationVarP(&pkg.RequestTimeout, "request-timeout", "r", pkg.DefaultRequestTimeout, "Timeout per OADP server request (like collecting logs from a backup)") + pkg.CLI.Flags().BoolVarP(&pkg.SkipTLS, "skip-tls", "s", false, "Run OADP server requests with insecure TLS connections (recommended if a custom CA certificate is used) (default false)") + // TODO caCertFile? + pkg.CLI.Flags().BoolP("help", "h", false, "Show OADP Must-gather help message.") + + pkg.CLI.SetHelpCommand(&cobra.Command{Hidden: true, Use: ""}) +} + +func main() { + // TODO JSON output in the future? + err := pkg.CLI.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/must-gather/collection-scripts/_metrics_chart_template_part_1.html b/must-gather/collection-scripts/_metrics_chart_template_part_1.html deleted file mode 100644 index 817bc3eea1d..00000000000 --- a/must-gather/collection-scripts/_metrics_chart_template_part_1.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Metrics Chart - - - - - - - - - - - -

Metrics Chart

-
-
-
- -
- - - - diff --git a/must-gather/collection-scripts/debug/tail_failed_pods b/must-gather/collection-scripts/debug/tail_failed_pods deleted file mode 100755 index fe12b26c8e1..00000000000 --- a/must-gather/collection-scripts/debug/tail_failed_pods +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -namespace="${1:-openshift-adp}" -skip_tls="${2:-false}" - -for pod in $(oc get pods --insecure-skip-tls-verify=${skip_tls} -n ${namespace} -o jsonpath='{.items[?(.status.containerStatuses[0].lastState.terminated.reason=="Error")].metadata.name}'); do - echo "***" - echo "* Last logs from failed pod ${namespace}/${pod}:" - oc logs -n ${namespace} --insecure-skip-tls-verify=${skip_tls} $pod --tail=10 - echo -e "\n\n\n" -done diff --git a/must-gather/collection-scripts/gather b/must-gather/collection-scripts/gather deleted file mode 100755 index debbc0e55e1..00000000000 --- a/must-gather/collection-scripts/gather +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# Store PIDs of all the subprocesses -pids=() - -# Default to gathering 72h of logs unless specified as var -logs_since="${logs_since:-72h}" -request_timeout="${request_timeout:-0s}" -skip_tls="${skip_tls:-false}" -unset KUBECONFIG - -clusterID=$(oc get clusterversion -o jsonpath='{.items[].spec.clusterID}' | cut -c -8) -namespaces=() -for ns in $(oc get dataprotectionapplications.oadp.openshift.io --all-namespaces --no-headers | awk '{print $1}') - do - echo "[namespace=${ns}] Detected OADP operator installation" - namespaces+=(${ns}) - done -# Collect all resources in OADP operator namespaces with must-gather -for ns in ${namespaces[@]}; do - echo "[namespace=${ns}] Running oc adm inspect" - /usr/bin/oc adm inspect --dest-dir must-gather/clusters/${clusterID} --all-namespaces ns/${ns} & - pids+=($!) - done - -# Collect Velero and OADP CRs -echo "Gathering Velero and OADP CRs" -/usr/bin/gather_crs ${clusterID} & -pids+=($!) - -# Collect Velero and OADP CRDs -echo "Gathering Velero and OADP CRDs" -/usr/bin/gather_crds ${clusterID} & -pids+=($!) - -# Collect the logs" -echo "[cluster=${clusterID}] Gathering logs for namespaces [${namespaces[@]}]" -/usr/bin/gather_logs ${clusterID} ${logs_since} ${request_timeout} ${skip_tls} ${namespaces[@]} & -pids+=($!) - - -# Collect metrics from Prometheus -if [ -z "$essential_only" ]; then - echo "[cluster=${clusterID}] Gathering prometheus metrics" - /usr/bin/gather_metrics & - pids+=($!) -else - echo "Essential-only must-gather was requested. Skipping prometheus metrics collection" -fi - -# Gather version information -echo "Gathering version information for top-level debug file" -/usr/bin/gather_versions "/must-gather/clusters/${clusterID}/oadp-debug-info" ${skip_tls} & -pids+=($!) - -# Check if PID array has any values, if so, wait for them to finish -if [ ${#pids[@]} -ne 0 ]; then - echo "Waiting on subprocesses to finish execution." - wait "${pids[@]}" -fi - - -# If running essential-only must-gather, delete duplicated logs collected by oc adm inspect -if [ -z "$essential_only" ]; then - echo "Full must-gather was requested. Keeping full log payload from oc adm inspect" -else - echo "Essential-only must-gather was requested. Removing duplicate pod logs from oc adm inspect to reduce must-gather size" - find /must-gather/clusters/*/namespaces/*/pods/ -name '*.log' -delete -fi - -# Tar all must-gather artifacts for faster transmission -echo "Tarring must-gather artifacts..." -archive_path="/must-gather-archive" -mkdir -p ${archive_path} -tar -zcf ${archive_path}/must-gather.tar.gz /must-gather/ -rm -rf /must-gather/* -mv ${archive_path}/must-gather.tar.gz /must-gather/ -rmdir ${archive_path} -echo "Created /must-gather/must-gather.tar.gz" - - -echo "Waiting for copy phase..." -exit 0 diff --git a/must-gather/collection-scripts/gather_crds b/must-gather/collection-scripts/gather_crds deleted file mode 100755 index ef716eb64c1..00000000000 --- a/must-gather/collection-scripts/gather_crds +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Cluster passed in from main gather -clusterID=$1 - -# Resource list -resources=() - -# OADP -resources+=($(/usr/bin/oc get crd | awk '/oadp.openshift.io/{print $1}')) - -# Velero -resources+=($(/usr/bin/oc get crd | awk '/velero.io/{print $1}')) - -echo "Starting collection of CRDs:" "${resources[@]}" -object_collection_path=/must-gather/clusters/${clusterID}/cluster-scoped-resources/apiextensions.k8s.io/customresourcedefinitions - -for resource in "${resources[@]}"; do - echo "Collecting ${resource} CRD" - mkdir -p "${object_collection_path}" - /usr/bin/oc get crd "${resource}" -o yaml &> "${object_collection_path}/${resource}.yaml" & -done -wait diff --git a/must-gather/collection-scripts/gather_crs b/must-gather/collection-scripts/gather_crs deleted file mode 100755 index ea36f0129fa..00000000000 --- a/must-gather/collection-scripts/gather_crs +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -source pwait -max_parallelism=10 - -# Cluster passed in from main gather -clusterID=$1 - -# Resource list -resources=() - -# OADP -resources+=($(/usr/bin/oc get crd | awk '/oadp.openshift.io/{print $1}')) - -# Velero -resources+=($(/usr/bin/oc get crd | awk '/velero.io/{print $1}')) - -echo "Starting collection of:" "${resources[@]}" - -# we use nested loops to nicely output objects partitioned per namespace, kind -for resource in "${resources[@]}"; do - /usr/bin/oc get "${resource}" --all-namespaces -o custom-columns=NAMESPACE:.metadata.namespace --no-headers 2> /dev/null | uniq | \ - while read namespace; do - resource_name=$(echo "$resource" | cut -d "." -f 1) - echo "Collecting ${resource_name} in ${namespace} namespace" - if echo "${resource}" | grep -q velero.io; then - object_collection_path=/must-gather/clusters/${clusterID}/namespaces/${namespace}/velero.io/${resource_name} - else - object_collection_path=/must-gather/clusters/${clusterID}/namespaces/${namespace}/oadp.openshift.io/${resource_name} - fi - mkdir -p "${object_collection_path}" - /usr/bin/oc get "${resource}" -n "${namespace}" -o yaml &> "${object_collection_path}/${resource_name}.yaml" & - pwait $max_parallelism - done -done -wait diff --git a/must-gather/collection-scripts/gather_logs b/must-gather/collection-scripts/gather_logs deleted file mode 100755 index 08912e841d4..00000000000 --- a/must-gather/collection-scripts/gather_logs +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash -source pwait -max_parallelism=10 - -# Cluster passed in from main gather -cluster=$1 -logs_since=$2 -timeout=$3 -skip_tls=$4 -shift -shift -shift -shift -namespaces=("$@") - - -# Collect all Pod logs from namespaces where OADP operator is installed -for ns in ${namespaces[@]}; do - # Gather Pod associated logs - /usr/bin/gather_logs_pods ${cluster} ${ns} ${logs_since} ${max_parallelism} & - - # Gather logs for each oadp operator instance - for dpa in $(oc get dpa --namespace ${ns} --no-headers | awk '{print $1}'); do - plan_path="/must-gather/clusters/${cluster}/namespaces/${ns}/oadp.openshift.io/dpa-${dpa}" - mkdir -p ${plan_path} - - for backup in $(oc get backup --namespace ${ns} --no-headers | awk '{print $1}'); do - # Gather backup - backup_path="${plan_path}/backup-${backup}" - mkdir -p ${backup_path} - /usr/bin/gather_logs_backup ${cluster} ${ns} ${logs_since} ${max_parallelism} ${backup} ${backup_path} ${timeout} ${skip_tls} & - pwait $max_parallelism - for pvb in $(oc get podvolumebackup --namespace ${ns} -l "velero.io/backup-name"="${backup}" --no-headers | awk '{print $1}'); do - # Gather PVB - pvb_path="${backup_path}/podvolumebackup-${pvb}" - mkdir -p ${pvb_path} - /usr/bin/gather_logs_pvb ${cluster} ${ns} ${logs_since} ${max_parallelism} ${pvb} ${pvb_path} & - pwait $max_parallelism - done - done - - for restore in $(oc get restore --namespace ${ns} --no-headers | awk '{print $1}'); do - # Gather restore - restore_path="${plan_path}/restore-${restore}" - mkdir -p ${restore_path} - /usr/bin/gather_logs_restore ${cluster} ${ns} ${logs_since} ${max_parallelism} ${restore} ${restore_path} ${timeout} ${skip_tls} & - pwait $max_parallelism - for pvr in $(oc get podvolumerestore --namespace ${ns} -l "velero.io/restore-name"="${restore}" --no-headers | awk '{print $1}'); do - # Gather PVR - pvr_path="${restore_path}/podvolumerestore-${pvr}" - mkdir -p ${pvr_path} - /usr/bin/gather_logs_pvr ${cluster} ${ns} ${logs_since} ${max_parallelism} ${pvr} ${pvr_path} & - pwait $max_parallelism - done - done - - done -done - -# Wait for all background jobs to complete -wait \ No newline at end of file diff --git a/must-gather/collection-scripts/gather_logs_without_zip b/must-gather/collection-scripts/gather_logs_without_zip deleted file mode 100755 index 8defefe0467..00000000000 --- a/must-gather/collection-scripts/gather_logs_without_zip +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# Default to gathering 72h of logs unless specified as var -logs_since="${logs_since:-72h}" -request_timeout="${request_timeout:-0s}" -max_parallelism=10 -unset KUBECONFIG - -clusterID=$(oc get clusterversion -o jsonpath='{.items[].spec.clusterID}' | cut -c -8) -namespaces=() -for ns in $(oc get dataprotectionapplications.oadp.openshift.io --all-namespaces --no-headers | awk '{print $1}') - do - echo "[namespace=${ns}] Detected OADP operator installation" - namespaces+=(${ns}) - done -# Collect all resources in OADP operator namespaces with must-gather -for ns in ${namespaces[@]}; do - echo "[namespace=${ns}] Running oc adm inspect" - /usr/bin/oc adm inspect --dest-dir must-gather/clusters/${clusterID} --all-namespaces ns/${ns} & -done - -# Collect the logs" -for ns in ${namespaces[@]}; do - echo "[cluster=${clusterID}] Gathering logs for namespace ${ns}]" - /usr/bin/gather_logs_pods ${clusterID} ${ns} ${logs_since} ${max_parallelism} & -done - -# Waits for gather_crs, gather_logs, gather_metrics running in background -echo "Waiting for background gather tasks to finish" -wait - -# If running essential-only must-gather, delete duplicated logs collected by oc adm inspect -if [ -z "$essential_only" ]; then - echo "Full must-gather was requested. Keeping full log payload from oc adm inspect" -else - echo "Essential-only must-gather was requested. Removing duplicate pod logs from oc adm inspect to reduce must-gather size" - find /must-gather/clusters/*/namespaces/*/pods/ -name '*.log' -delete -fi - -echo "Waiting for copy phase..." -exit 0 diff --git a/must-gather/collection-scripts/gather_metrics b/must-gather/collection-scripts/gather_metrics deleted file mode 100755 index e3f6bfabde8..00000000000 --- a/must-gather/collection-scripts/gather_metrics +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -# Prepare gather setup -unset KUBECONFIG -object_collection_path="/must-gather/metrics" -mkdir ${object_collection_path} -mkdir "${object_collection_path}/prometheus_queries" - -# Setup vars -time_now=$(date +%s) -time_ago=$(($time_now - 6*60*60)) - -# OpenShift monitoring namespace status -oc get all -n openshift-monitoring > "${object_collection_path}/openshift_monitoring_status" - -# Prometheus - metadata json dump -echo "[cluster=host] Dumping Prometheus metadata ..." -oc exec -n openshift-monitoring prometheus-k8s-0 -- \ - curl -G http://localhost:9090/api/v1/targets/metadata -s --data match_target%3D%7Binstance!%3D%22%22%7D \ - > "${object_collection_path}/prometheus_target_metadata.json" - -# Prometheus - filtered metrics json using query_range and HTML files with charts -for metric_name in velero_backup_total velero_restore_total -do - echo "[cluster=host] Capturing prometheus metric [${metric_name}]" - oc exec -n openshift-monitoring prometheus-k8s-0 -- curl -s "http://localhost:9090/api/v1/query_range?query=${metric_name}&start=${time_ago}&end=${time_now}&step=14" \ - > dump.json - - # JSON data - echo "[cluster=host] Building chart for prometheus metric [${metric_name}]" - cat dump.json > "${object_collection_path}/prometheus_queries/${metric_name}.json" - - # HTML file with the chart (using concatenated write to file, an elegant string replacement/variable evaluation didn't work with 100s kBs data) - cat /usr/bin/_metrics_chart_template_part_1.html > "${object_collection_path}/prometheus_queries/${metric_name}.html" - cat dump.json >> "${object_collection_path}/prometheus_queries/${metric_name}.html" - cat /usr/bin/_metrics_chart_template_part_2.html >> "${object_collection_path}/prometheus_queries/${metric_name}.html" - - # Cleanup the temp file - rm -f dump.json -done diff --git a/must-gather/collection-scripts/gather_metrics_dump b/must-gather/collection-scripts/gather_metrics_dump deleted file mode 100755 index 9081728650e..00000000000 --- a/must-gather/collection-scripts/gather_metrics_dump +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# -# NOTICE: THIS FILE IS NOT INCLUDED IN THE DEFAULT GATHER SCRIPT -# -# Can be executed by: oc adm must-gather --image quay.io/oadp/must-gather:latest -- /usr/bin/gather_metrics_dump -# - -# Prepare gather setup -unset KUBECONFIG -object_collection_path="/must-gather/metrics" -mkdir ${object_collection_path} - -# Setup vars -time_now=$(date +%s) -time_day_ago=$(($time_now - 24*60*60)) - -# Prometheus - last day data files dump -echo "Running Prometheus data files last day dump ... (might take a while)" -oc exec -n openshift-monitoring prometheus-k8s-0 -- \ - bash -c "find /prometheus -newermt \"$(date '+%Y-%m-%d %H:%M' -d @${time_day_ago})\" | tar cvzf - --no-recursion --exclude \"queries.active\" --files-from -" \ - > "${object_collection_path}/prom_data.tar.gz" - -# oc exec and tar could be unhappy with file changes during its compression, however if the dump archive was created, it is success -if [ -f "${object_collection_path}/prom_data.tar.gz" ]; then - exit 0 -else - exit $? -fi diff --git a/must-gather/collection-scripts/gather_versions b/must-gather/collection-scripts/gather_versions deleted file mode 100755 index 10fc2c46c24..00000000000 --- a/must-gather/collection-scripts/gather_versions +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash - -debug_info="${1:-oadp-debug-info}" -skip_tls="${2:-false}" - -mkdir -p $(dirname ${debug_info}) - -# Write information section to debug file, and fill with output of given command -function log_command() { - heading=$1 - command=$2 - echo "***" >> ${debug_info} - echo "* $heading" >> ${debug_info} - $command 2>&1 >> ${debug_info} - echo -e "\n\n" >> ${debug_info} -} - -# Search for given operator in all available namespaces, and save information -function log_operator() { - display_name=$1 - operator_name=$2 - - # Find all operators with matching name in all namespaces - namespaces=$(oc get subscriptions.operators.coreos.com --all-namespaces --insecure-skip-tls-verify=${skip_tls} \ - -o jsonpath='{.items[?(.metadata.name=="'"${operator_name}"'")].metadata.namespace}') - if [ -z "${namespaces}" ]; then # No matches, mark as not found - log_command "${display_name} information" "echo ${operator_name} operator not found" - else # Loop through matching operators and save information to debug file - for namespace in ${namespaces}; do - log_command "${display_name} version information in namespace ${namespace}" \ - "oc get subscriptions.operators.coreos.com ${operator_name} -n ${namespace} --insecure-skip-tls-verify=${skip_tls} -o yaml" - done - fi -} - -# Search for named deployments in all namespaces -function log_deployment() { - display_name=$1 - deployment_name=$2 - - namespaces=$(oc get deployment --all-namespaces --insecure-skip-tls-verify=${skip_tls} \ - -o jsonpath='{.items[?(.metadata.name=="'"${deployment_name}"'")].metadata.namespace}') - if [ -z "${namespaces}" ]; then - log_command "${display_name} information" "echo ${deployment_name} deployment not found" - else - for namespace in ${namespaces}; do - log_command "${display_name} deployment information in namespace ${namespace}" \ - "oc get deployment ${deployment_name} -n ${namespace} --insecure-skip-tls-verify=${skip_tls} -o yaml" - done - fi -} - -# OpenShift version -log_command "OpenShift version information"\ - "oc version --insecure-skip-tls-verify=${skip_tls} -o yaml" - -# Red Hat OADP operator information -log_operator "OADP" "redhat-oadp-operator" - -# Community OADP operator -log_operator "Community OADP operator" "oadp-operator" - -# Restic version -if [ -z "${skip_tls}" ]; then - log_command "Restic version" "restic version" -else - log_command "Restic version" "restic version --insecure-tls" -fi - -# Velero versions -log_command "Velero client version" "velero version --client-only" -log_deployment "Velero" "velero" - -# All storage classes -storageclasses=$(oc get storageclass --insecure-skip-tls-verify=${skip_tls} -o jsonpath='{range .items[*]}{.metadata.name}{" "}') -if [ -z "${storageclasses}" -o "${storageclasses}" == " " ]; then - log_command "StorageClass" "echo No StorageClasses found in cluster" -else - log_command "StorageClasses" "oc get storageclasses --insecure-skip-tls-verify=${skip_tls} -o yaml" -fi - -# CSI volume snapshot classes -volumesnapshotclasses=$(oc get volumesnapshotclass --insecure-skip-tls-verify=${skip_tls} -o jsonpath='{range .items[?(.metadata.annotations.velero\.io/csi-volumesnapshot-class == "true")]}{.metadata.name}{" "}') -if [ -z "${volumesnapshotclasses}" -o "${volumesnapshotclasses}" == " " ]; then - log_command "CSI VolumeSnapshotClasses" "echo No CSI VolumeSnapshotClasses found in cluster" -else - while read volumesnapshotclass; do - log_command "VolumeSnapshotClass ${volumesnapshotclass}" "oc get volumesnapshotclass --insecure-skip-tls-verify=${skip_tls} ${volumesnapshotclass} -o yaml" - done <<< "${volumesnapshotclasses}" -fi - - -# DataProtectionApplications -dpas=$(oc get dpa --all-namespaces --insecure-skip-tls-verify=${skip_tls} -o custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --no-headers) -if [ -z "${dpas}" ]; then - log_command "DataProtectionApplication CRs" "echo No DPAs found in cluster" -else - while read dpa namespace; do - log_command "DataProtectionApplication ${namespace}/${dpa}" "oc get dpa --insecure-skip-tls-verify=${skip_tls} -n ${namespace} ${dpa} -o yaml" - done <<< "${dpas}" -fi diff --git a/must-gather/collection-scripts/gather_without_tls b/must-gather/collection-scripts/gather_without_tls deleted file mode 100755 index 13566005d17..00000000000 --- a/must-gather/collection-scripts/gather_without_tls +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -echo "insecure-skip-tls-verify: $1" -skip_tls=$1 /usr/bin/gather diff --git a/must-gather/collection-scripts/logs/gather_logs_backup b/must-gather/collection-scripts/logs/gather_logs_backup deleted file mode 100755 index 4cbd01b59d7..00000000000 --- a/must-gather/collection-scripts/logs/gather_logs_backup +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -source pwait - -# Cluster passed in from main gather -cluster=$1 -ns=$2 -logs_since=$3 -max_parallelism=$4 -backup=$5 -object_collection_path=$6 -timeout=$7 -skip_tls=$8 - -# Gather backup logs and description -mkdir -p "${object_collection_path}" -echo "[cluster=${cluster}][ns=${ns}] Gathering 'velero backup describe ${backup}'" -if [ "$timeout" = "0s" ]; then - timeout 30s velero describe backup ${backup} --namespace ${ns} --insecure-skip-tls-verify=${skip_tls} --details &> "${object_collection_path}/backup-describe-${backup}.txt" & -else - timeout ${timeout} velero describe backup ${backup} --namespace ${ns} --insecure-skip-tls-verify=${skip_tls} --details &> "${object_collection_path}/backup-describe-${backup}.txt" & -fi -echo "[cluster=${cluster}][ns=${ns}] Gathering 'velero backup logs ${backup}'" -timeout 30s velero backup logs ${backup} --namespace ${ns} --insecure-skip-tls-verify=${skip_tls} --timeout=30s &> "${object_collection_path}/backup-${backup}.log" & - -wait \ No newline at end of file diff --git a/must-gather/collection-scripts/logs/gather_logs_pods b/must-gather/collection-scripts/logs/gather_logs_pods deleted file mode 100755 index 0b7e6b60325..00000000000 --- a/must-gather/collection-scripts/logs/gather_logs_pods +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -source pwait - -# Cluster passed in from gather_logs -cluster=$1 -ns=$2 -logs_since=$3 -max_parallelism=$4 - -for pod in $(/usr/bin/oc get pods --no-headers --namespace $ns | awk '{print $1}'); do - object_collection_path="/must-gather/clusters/${cluster}/namespaces/${ns}/logs/${pod}" - mkdir -p ${object_collection_path} - - containers="--all-containers" - - echo "[cluster=${cluster}][ns=${ns}][pod=${pod}] Collecting Pod logs..." - /usr/bin/oc logs ${containers} --namespace ${ns} ${pod} --since ${logs_since} &> "${object_collection_path}/current.log" & - echo "[cluster=${cluster}][ns=${ns}][pod=${pod}] Collecting previous Pod logs..." - /usr/bin/oc logs --previous ${containers} --namespace ${ns} ${pod} --since ${logs_since} &> "${object_collection_path}/previous.log" & - pwait $max_parallelism -done - -wait \ No newline at end of file diff --git a/must-gather/collection-scripts/logs/gather_logs_pvb b/must-gather/collection-scripts/logs/gather_logs_pvb deleted file mode 100755 index c805045c14b..00000000000 --- a/must-gather/collection-scripts/logs/gather_logs_pvb +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -source pwait - -# Cluster passed in from main gather -cluster=$1 -ns=$2 -logs_since=$3 -max_parallelism=$4 -pvb=$5 -object_collection_path=$6 - -# Get logs -node=$(oc get podvolumebackup $pvb --namespace $ns -o jsonpath='{.spec.node}') -mkdir -p ${object_collection_path} -oc describe podvolumebackup ${pvb} --namespace ${ns} &> "${object_collection_path}/pvb-describe-${pvb}.txt" & -# logs covered by restic pod logs in gather_logs_pods -wait \ No newline at end of file diff --git a/must-gather/collection-scripts/logs/gather_logs_pvr b/must-gather/collection-scripts/logs/gather_logs_pvr deleted file mode 100755 index bfdb24b8080..00000000000 --- a/must-gather/collection-scripts/logs/gather_logs_pvr +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -source pwait - -# Cluster passed in from main gather -cluster=$1 -ns=$2 -logs_since=$3 -max_parallelism=$4 -pvr=$5 -object_collection_path=$6 - -# Gather PVR describe -mkdir -p ${object_collection_path} -oc describe podvolumerestores.velero.io ${pvr} --namespace ${ns} &> "${object_collection_path}/pvr-describe-${pvr}.txt" -# logs covered by restic pod logs in gather_logs_pods -wait \ No newline at end of file diff --git a/must-gather/collection-scripts/logs/gather_logs_restore b/must-gather/collection-scripts/logs/gather_logs_restore deleted file mode 100755 index 68a5a29d2dd..00000000000 --- a/must-gather/collection-scripts/logs/gather_logs_restore +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -source pwait - -# Cluster passed in from main gather -cluster=$1 -ns=$2 -logs_since=$3 -max_parallelism=$4 -restore=$5 -object_collection_path=$6 -timeout=$7 -skip_tls=$8 - -# Gather restore describe and logs -mkdir -p "${object_collection_path}" -echo "[cluster=${cluster}][ns=${ns}] Gathering 'velero restore describe ${restore}'" -if [ "$timeout" = "0s" ]; then - timeout 30s velero describe restore ${restore} --namespace ${ns} --insecure-skip-tls-verify=${skip_tls} --details &> "${object_collection_path}/restore-describe-${restore}.txt" & -else - timeout ${timeout} velero describe restore ${restore} --namespace ${ns} --insecure-skip-tls-verify=${skip_tls} --details &> "${object_collection_path}/restore-describe-${restore}.txt" & -fi -echo "[cluster=${cluster}][ns=${ns}] Gathering 'velero restore logs ${restore}'" -timeout 30s velero restore logs ${restore} --namespace ${ns} --insecure-skip-tls-verify=${skip_tls} --timeout=30s &> "${object_collection_path}/restore-${restore}.log" & - -wait \ No newline at end of file diff --git a/must-gather/collection-scripts/pwait b/must-gather/collection-scripts/pwait deleted file mode 100644 index 8624ccca74f..00000000000 --- a/must-gather/collection-scripts/pwait +++ /dev/null @@ -1,7 +0,0 @@ -# Limits background job parallelism. -# Usage: `pwait 10` -> wait for background jobs to be < 10 before proceeding. -function pwait() { - while [ $(jobs -p | wc -l) -ge $1 ]; do - sleep 1 - done -} \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_1h b/must-gather/collection-scripts/time_window_gather/gather_1h deleted file mode 100755 index 746961d1cd1..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_1h +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 1 hour of logs -logs_since="1h" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_1h_essential b/must-gather/collection-scripts/time_window_gather/gather_1h_essential deleted file mode 100755 index 446fdbd43f9..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_1h_essential +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 1 hour of essential data (skip oc adm inspect, prometheus metrics, memory profile) -logs_since="1h" essential_only="true" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_24h b/must-gather/collection-scripts/time_window_gather/gather_24h deleted file mode 100755 index 378ffa3a7ba..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_24h +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 24 hours of logs -logs_since="24h" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_24h_essential b/must-gather/collection-scripts/time_window_gather/gather_24h_essential deleted file mode 100755 index c0566900ff7..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_24h_essential +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 24 hours of essential data (skip oc adm inspect, prometheus metrics, memory profile) -logs_since="24h" essential_only="true" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_6h b/must-gather/collection-scripts/time_window_gather/gather_6h deleted file mode 100755 index ceea9bd55e2..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_6h +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 6 hours of logs -logs_since="6h" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_6h_essential b/must-gather/collection-scripts/time_window_gather/gather_6h_essential deleted file mode 100755 index fe59c5657de..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_6h_essential +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 6 hours of essential data (skip oc adm inspect, prometheus metrics, memory profile) -logs_since="6h" essential_only="true" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_72h b/must-gather/collection-scripts/time_window_gather/gather_72h deleted file mode 100755 index bf99f5e274c..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_72h +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 72 hours of logs -logs_since="72h" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_72h_essential b/must-gather/collection-scripts/time_window_gather/gather_72h_essential deleted file mode 100755 index 97abe5e1a5b..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_72h_essential +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather 72 hours of essential data (skip oc adm inspect, prometheus metrics, memory profile) -logs_since="72h" essential_only="true" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_all b/must-gather/collection-scripts/time_window_gather/gather_all deleted file mode 100755 index f3d774781a2..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_all +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather infinity hours of logs -logs_since="0" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_all_essential b/must-gather/collection-scripts/time_window_gather/gather_all_essential deleted file mode 100755 index aca9e0713fe..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_all_essential +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -# Gather infinity hours of essential data (skip oc adm inspect, prometheus metrics, memory profile) -logs_since="0" essential_only="true" /usr/bin/gather \ No newline at end of file diff --git a/must-gather/collection-scripts/time_window_gather/gather_with_timeout b/must-gather/collection-scripts/time_window_gather/gather_with_timeout deleted file mode 100755 index 660917fce21..00000000000 --- a/must-gather/collection-scripts/time_window_gather/gather_with_timeout +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -# Gather with request timeout set -echo "timeout: $1" -request_timeout=$1 /usr/bin/gather \ No newline at end of file diff --git a/must-gather/go.mod b/must-gather/go.mod new file mode 100644 index 00000000000..080966a35e0 --- /dev/null +++ b/must-gather/go.mod @@ -0,0 +1,132 @@ +module github.com/openshift/oadp-operator/must-gather + +go 1.23.1 + +toolchain go1.23.4 + +require ( + github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0 + github.com/migtools/oadp-non-admin v0.0.0-20250324123917-741ae3b7a67d + github.com/openshift/api v0.0.0-20240912201240-0a8800162826 + github.com/openshift/oadp-operator v1.0.2-0.20250327225822-5cc21cec9653 + github.com/openshift/oc v0.0.0-alpha.0.0.20250108103617-ae1bd9e4a75b + github.com/operator-framework/api v0.26.0 + github.com/spf13/cobra v1.8.1 + github.com/vmware-tanzu/velero v1.14.0 + k8s.io/api v0.31.3 + k8s.io/apiextensions-apiserver v0.31.3 + k8s.io/apimachinery v0.31.3 + k8s.io/cli-runtime v0.31.3 + k8s.io/client-go v0.31.3 + sigs.k8s.io/controller-runtime v0.19.3 +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/aws/aws-sdk-go v1.53.12 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.26.3 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.26 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect + github.com/aws/smithy-go v1.20.3 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/moby/spdystream v0.4.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/net v0.36.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.9.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // 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 + k8s.io/component-base v0.31.3 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kubectl v0.30.5 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/kustomize/api v0.17.2 // indirect + sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.3 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +replace github.com/vmware-tanzu/velero => github.com/openshift/velero v0.10.2-0.20250313160323-584cf1148a74 diff --git a/must-gather/go.sum b/must-gather/go.sum new file mode 100644 index 00000000000..8d760790ce9 --- /dev/null +++ b/must-gather/go.sum @@ -0,0 +1,391 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aws/aws-sdk-go v1.53.12 h1:8f8K+YaTy2qwtGwVIo2Ftq22UCH96xQAX7Q0lyZKDiA= +github.com/aws/aws-sdk-go v1.53.12/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= +github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA= +github.com/aws/aws-sdk-go-v2/config v1.26.3/go.mod h1:Bxgi+DeeswYofcYO0XyGClwlrq3DZEXli0kLf4hkGA0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk= +github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 h1:I6lAa3wBWfCz/cKkOpAcumsETRkFAl70sWi8ItcMEsM= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11/go.mod h1:be1NIO30kJA23ORBLqPo1LttEM6tPNSEcjkd1eKzNW0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10/go.mod h1:FHbKWQtRBYUz4vO5WBWjzMD2by126ny5y/1EoaWoLfI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 h1:L0ai8WICYHozIKK+OtPzVJBugL7culcuM4E4JOpIEm8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10/go.mod h1:byqfyxJBshFk0fF9YmK0M0ugIO8OWjzH2T3bPG4eGuA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 h1:j3YK74myEQRxR/srciTpOrm221SAvz6J5OVWbyfeXFo= +github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0/go.mod h1:FlyYFe32mPxKEPaRXKNxfX576d1AoCzstYDoOOnyMA4= +github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0 h1:mjQG0Vakr2h246kEDR85U8y8ZhPgT3bguTCajRa/jaw= +github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0/go.mod h1:E3vdYxHj2C2q6qo8/Da4g7P+IcwqRZyy3gJBzYybV9Y= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/migtools/oadp-non-admin v0.0.0-20250324123917-741ae3b7a67d h1:Amw7cIyzbyRujsUkp7JrnSqNQkRpEXiTUWznUefdkiI= +github.com/migtools/oadp-non-admin v0.0.0-20250324123917-741ae3b7a67d/go.mod h1:4wloQQc4eCqzSjDCrhNWHuz5wpRwpK6rYriBlfDixt8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/openshift/api v0.0.0-20240912201240-0a8800162826 h1:A8D9SN/hJUwAbdO0rPCVTqmuBOctdgurr53gK701SYo= +github.com/openshift/api v0.0.0-20240912201240-0a8800162826/go.mod h1:OOh6Qopf21pSzqNVCB5gomomBXb8o5sGKZxG2KNpaXM= +github.com/openshift/oadp-operator v1.0.2-0.20250327225822-5cc21cec9653 h1:p6t+nVTJsrT1FQSaL/n/sf9begEoLXfX08OVxfjdLYM= +github.com/openshift/oadp-operator v1.0.2-0.20250327225822-5cc21cec9653/go.mod h1:tzlEfI5g4gomtv5dZ4qgEQSNSiwiOrsBj2P5nTqawPY= +github.com/openshift/oc v0.0.0-alpha.0.0.20250108103617-ae1bd9e4a75b h1:dBEu456jnnhr7KH1T+Mrna1DvFadk1qN+NEtLb8Nn8U= +github.com/openshift/oc v0.0.0-alpha.0.0.20250108103617-ae1bd9e4a75b/go.mod h1:22L7FMouzG54z19RZ/o+RcMCLa8vCdvZ4jQ+u3iHWHw= +github.com/openshift/velero v0.10.2-0.20250313160323-584cf1148a74 h1:ZHO0O6g1Enel2O4rAk7VfWLHlQKYkOcdWGAmoiZ3fQw= +github.com/openshift/velero v0.10.2-0.20250313160323-584cf1148a74/go.mod h1:sASoDB9pLWqvIi1nD1ZFOpmj5JB+p10lHVm+f+Hp1oU= +github.com/operator-framework/api v0.26.0 h1:YVntU2NkVl5zSLLwK5kFcH6P3oSvN9QDgTsY9mb4yUM= +github.com/operator-framework/api v0.26.0/go.mod h1:3IxOwzVUeGxYlzfwKCcfCyS+q3EEhWA/4kv7UehbeyM= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= +github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= +go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= +k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= +k8s.io/apiextensions-apiserver v0.31.3 h1:+GFGj2qFiU7rGCsA5o+p/rul1OQIq6oYpQw4+u+nciE= +k8s.io/apiextensions-apiserver v0.31.3/go.mod h1:2DSpFhUZZJmn/cr/RweH1cEVVbzFw9YBu4T+U3mf1e4= +k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= +k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/cli-runtime v0.31.3 h1:fEQD9Xokir78y7pVK/fCJN090/iYNrLHpFbGU4ul9TI= +k8s.io/cli-runtime v0.31.3/go.mod h1:Q2jkyTpl+f6AtodQvgDI8io3jrfr+Z0LyQBPJJ2Btq8= +k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= +k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= +k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ= +k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.30.5 h1:UHIdbLWHjL/+CaFc9ZESwpwdvpp9EDPLdgGolhMv8tQ= +k8s.io/kubectl v0.30.5/go.mod h1:Pr+NYcX5Lx8eG04B/0t1rzjWcXsvvbda8swiZV9plQw= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= +sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= +sigs.k8s.io/structured-merge-diff/v4 v4.4.3 h1:sCP7Vv3xx/CWIuTPVN38lUPx0uw0lcLfzaiDa8Ja01A= +sigs.k8s.io/structured-merge-diff/v4 v4.4.3/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/must-gather/pkg/cli.go b/must-gather/pkg/cli.go new file mode 100644 index 00000000000..d413ba69c30 --- /dev/null +++ b/must-gather/pkg/cli.go @@ -0,0 +1,357 @@ +package pkg + +import ( + "fmt" + "slices" + "strconv" + "strings" + "time" + + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + nac1alpha1 "github.com/migtools/oadp-non-admin/api/v1alpha1" + openshiftconfigv1 "github.com/openshift/api/config/v1" + oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1" + ocadminspect "github.com/openshift/oc/pkg/cli/admin/inspect" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/spf13/cobra" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + "k8s.io/cli-runtime/pkg/genericiooptions" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + + "github.com/openshift/oadp-operator/must-gather/pkg/gather" + "github.com/openshift/oadp-operator/must-gather/pkg/templates" +) + +const ( + mustGatherVersion = "master-branch" + mustGatherImage = "registry.redhat.io/oadp/oadp-mustgather-rhel9:v1.5" + + addToSchemeError = "Exiting OADP must-gather, an error happened while adding %s to scheme: %v\n" + + DefaultRequestTimeout = 5 * time.Second +) + +var ( + Timeout time.Duration + RequestTimeout time.Duration + SkipTLS bool + + CLI = &cobra.Command{ + Use: fmt.Sprintf("oc adm must-gather --image=%[1]s -- /usr/bin/gather", mustGatherImage), + Long: `OADP Must-gather: a tool to collect information about OADP installation in a cluster, along with information about its custom resources and cluster storage. + +For more information, check OADP must-gather documentation: https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/backup_and_restore/oadp-application-backup-and-restore#migration-using-must-gather_oadp-troubleshooting`, + Args: cobra.NoArgs, + Example: fmt.Sprintf(` # running OADP Must-gather with default configuration + oc adm must-gather --image=%[1]s + + # running OADP Must-gather with timeout of 1 minute per OADP server request + oc adm must-gather --image=%[1]s -- /usr/bin/gather --request-timeout 1m + + # running OADP Must-gather with timeout of 15 seconds per OADP server request and with insecure TLS connections + oc adm must-gather --image=%[1]s -- /usr/bin/gather --request-timeout 15s --skip-tls`, mustGatherImage), + SilenceErrors: true, + SilenceUsage: true, + RunE: func(_ *cobra.Command, _ []string) error { + if RequestTimeout <= 0 { + err := fmt.Errorf("--request-timeout value must be greater than zero") + fmt.Printf("Exiting OADP must-gather: %v\n", err) + return err + } + + clusterConfig := config.GetConfigOrDie() + // https://github.com/openshift/oc/blob/46db7c2bce5a57e3c3d9347e7e1e107e61dbd306/pkg/cli/admin/inspect/inspect.go#L142 + clusterConfig.QPS = 999999 + clusterConfig.Burst = 999999 + + clusterClient, err := client.New(clusterConfig, client.Options{}) + if err != nil { + fmt.Printf("Exiting OADP must-gather, an error happened while creating Go client: %v\n", err) + return err + } + + err = openshiftconfigv1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "github.com/openshift/api/config/v1", err) + return err + } + err = operatorsv1alpha1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "github.com/operator-framework/api/pkg/operators/v1alpha1", err) + return err + } + err = storagev1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "k8s.io/api/storage/v1", err) + return err + } + err = volumesnapshotv1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1", err) + return err + } + err = corev1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "k8s.io/api/core/v1", err) + return err + } + // OADP CRDs + err = oadpv1alpha1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "github.com/openshift/oadp-operator/api/v1alpha1", err) + return err + } + err = nac1alpha1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "github.com/migtools/oadp-non-admin/api/v1alpha1", err) + return err + } + err = velerov1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "github.com/vmware-tanzu/velero/pkg/apis/velero/v1", err) + return err + } + err = velerov2alpha1.AddToScheme(clusterClient.Scheme()) + if err != nil { + fmt.Printf(addToSchemeError, "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1", err) + return err + } + + clusterVersionList := &openshiftconfigv1.ClusterVersionList{} + err = gather.AllResources(clusterClient, clusterVersionList) + if err != nil { + fmt.Printf("Exiting OADP must-gather, an error happened while gathering ClusterVersion: %v\n", err) + return err + } + if len(clusterVersionList.Items) == 0 { + err = fmt.Errorf("no ClusterVersion found in cluster") + fmt.Printf("Exiting OADP must-gather, an error happened while gathering ClusterVersion: %v\n", err) + return err + } + clusterVersion := &clusterVersionList.Items[0] + clusterID := string(clusterVersion.Spec.ClusterID[:8]) + versionParts := strings.Split(clusterVersion.Status.Desired.Version, ".") + major, err := strconv.Atoi(versionParts[0]) + if err != nil { + fmt.Printf("Exiting OADP must-gather, an error happened while parsing OpenShift major version: %v\n", err) + return err + } + minor, err := strconv.Atoi(versionParts[1]) + if err != nil { + fmt.Printf("Exiting OADP must-gather, an error happened while parsing OpenShift minor version: %v\n", err) + return err + } + + // be careful about folder structure, otherwise may break `omg` usage + outputPath := fmt.Sprintf("must-gather/clusters/%s/", clusterID) + + var resourcesToGather []client.ObjectList + infrastructureList := &openshiftconfigv1.InfrastructureList{} + nodeList := &corev1.NodeList{} + clusterServiceVersionList := &operatorsv1alpha1.ClusterServiceVersionList{} + subscriptionList := &operatorsv1alpha1.SubscriptionList{} + + dataProtectionApplicationList := &oadpv1alpha1.DataProtectionApplicationList{} + cloudStorageList := &oadpv1alpha1.CloudStorageList{} + backupStorageLocationList := &velerov1.BackupStorageLocationList{} + volumeSnapshotLocationList := &velerov1.VolumeSnapshotLocationList{} + backupList := &velerov1.BackupList{} + restoreList := &velerov1.RestoreList{} + scheduleList := &velerov1.ScheduleList{} + backupRepositoryList := &velerov1.BackupRepositoryList{} + dataUploadList := &velerov2alpha1.DataUploadList{} + dataDownloadList := &velerov2alpha1.DataDownloadList{} + podVolumeBackupList := &velerov1.PodVolumeBackupList{} + podVolumeRestoreList := &velerov1.PodVolumeRestoreList{} + + deleteBackupRequestList := &velerov1.DeleteBackupRequestList{} + serverStatusRequestList := &velerov1.ServerStatusRequestList{} + nonAdminBackupStorageLocationRequestList := &nac1alpha1.NonAdminBackupStorageLocationRequestList{} + nonAdminBackupStorageLocationList := &nac1alpha1.NonAdminBackupStorageLocationList{} + nonAdminBackupList := &nac1alpha1.NonAdminBackupList{} + nonAdminRestoreList := &nac1alpha1.NonAdminRestoreList{} + nonAdminDownloadRequestList := &nac1alpha1.NonAdminDownloadRequestList{} + + storageClassList := &storagev1.StorageClassList{} + volumeSnapshotClassList := &volumesnapshotv1.VolumeSnapshotClassList{} + csiDriverList := &storagev1.CSIDriverList{} + resourcesToGather = append(resourcesToGather, + infrastructureList, + nodeList, + clusterServiceVersionList, + subscriptionList, + + dataProtectionApplicationList, + cloudStorageList, + backupStorageLocationList, + volumeSnapshotLocationList, + backupList, + restoreList, + scheduleList, + backupRepositoryList, + dataUploadList, + dataDownloadList, + podVolumeBackupList, + podVolumeRestoreList, + + deleteBackupRequestList, + serverStatusRequestList, + nonAdminBackupStorageLocationRequestList, + nonAdminBackupStorageLocationList, + nonAdminBackupList, + nonAdminRestoreList, + nonAdminDownloadRequestList, + + storageClassList, + volumeSnapshotClassList, + csiDriverList, + ) + for _, resource := range resourcesToGather { + // TODO do this part in parallel? + err = gather.AllResources(clusterClient, resource) + if err != nil { + fmt.Println(err) + } + } + + // get namespaces with OADP installs + if len(clusterServiceVersionList.Items) == 0 { + fmt.Println(fmt.Errorf("no ClusterServiceVersion found in cluster")) + } + oadpOperatorsText := "" + foundOADP := false + foundRelatedProducts := false + importantCSVsByNamespace := map[string][]operatorsv1alpha1.ClusterServiceVersion{} + importantSubscriptionsByNamespace := map[string][]operatorsv1alpha1.Subscription{} + oldOADPError := "" + + // ?Managed Velero operator? only available in ROSA? https://github.com/openshift/managed-velero-operator + // + // ?Dell Power Protect? + // labels: + // app: ppdm-controller + // app.kubernetes.io/name: powerprotect + // name: powerprotect-controller-c8dcf8648-nlg85 + // + // upstream velero? + relatedProducts := []string{ + "OpenShift Virtualization", + "Advanced Cluster Management for Kubernetes", + "Submariner", + "IBM Storage Fusion", + } + communityProducts := []string{"KubeVirt HyperConverged Cluster Operator"} + + for _, csv := range clusterServiceVersionList.Items { + // OADP dev, community and prod operators have same spec.displayName + if csv.Spec.DisplayName == "OADP Operator" { + oadpOperatorsText += fmt.Sprintf("Found **%v** version **%v** installed in **%v** namespace\n\n", csv.Spec.DisplayName, csv.Spec.Version, csv.Namespace) + if (csv.Spec.Version.Major < 1 || csv.Spec.Version.Minor < 5) && major >= 4 && minor >= 19 { + oldOADPError += "❌ OADP 1.4 and lower is not supported in OpenShift 4.19 and higher\n\n" + } + foundOADP = true + importantCSVsByNamespace[csv.Namespace] = append(importantCSVsByNamespace[csv.Namespace], csv) + for _, subscription := range subscriptionList.Items { + if subscription.Status.InstalledCSV == csv.Name { + importantSubscriptionsByNamespace[subscription.Namespace] = append(importantSubscriptionsByNamespace[subscription.Namespace], subscription) + } + } + } + if slices.Contains(relatedProducts, csv.Spec.DisplayName) { + oadpOperatorsText += fmt.Sprintf("Found related product **%v** version **%v** installed in **%v** namespace\n\n", csv.Spec.DisplayName, csv.Spec.Version, csv.Namespace) + foundRelatedProducts = true + importantCSVsByNamespace[csv.Namespace] = append(importantCSVsByNamespace[csv.Namespace], csv) + for _, subscription := range subscriptionList.Items { + if subscription.Status.InstalledCSV == csv.Name { + importantSubscriptionsByNamespace[subscription.Namespace] = append(importantSubscriptionsByNamespace[subscription.Namespace], subscription) + } + } + } + if slices.Contains(communityProducts, csv.Spec.DisplayName) { + oadpOperatorsText += fmt.Sprintf("⚠️ Found related product **%v (Community)** version **%v** installed in **%v** namespace\n\n", csv.Spec.DisplayName, csv.Spec.Version, csv.Namespace) + foundRelatedProducts = true + importantCSVsByNamespace[csv.Namespace] = append(importantCSVsByNamespace[csv.Namespace], csv) + for _, subscription := range subscriptionList.Items { + if subscription.Status.InstalledCSV == csv.Name { + importantSubscriptionsByNamespace[subscription.Namespace] = append(importantSubscriptionsByNamespace[subscription.Namespace], subscription) + } + } + } + } + + // oc adm inspect --dest-dir must-gather/clusters/${clusterID} ns/${ns} + if len(importantCSVsByNamespace) != 0 { + ocAdmInspect := ocadminspect.NewInspectOptions(genericiooptions.NewTestIOStreamsDiscard()) + ocAdmInspect.DestDir = outputPath + ocAdmInspectNamespaces := []string{} + for namespace := range importantCSVsByNamespace { + ocAdmInspectNamespaces = append(ocAdmInspectNamespaces, "ns/"+namespace) + } + + // https://github.com/openshift/oc/blob/ae1bd9e4a75b8ab617a569e5c8e1a0d7285a16f6/pkg/cli/admin/inspect/inspect.go#L108 + err = ocAdmInspect.Complete(ocAdmInspectNamespaces) + if err != nil { + fmt.Println(err) + } + err = ocAdmInspect.Validate() + if err != nil { + fmt.Println(err) + } + err = ocAdmInspect.Run() + if err != nil { + fmt.Println(err) + } + } + + // TODO do processes in parallel!? + // https://gobyexample.com/waitgroups + // https://github.com/konveyor/analyzer-lsp/blob/main/engine/engine.go + templates.ReplaceMustGatherVersion(mustGatherVersion) + templates.ReplaceClusterInformationSection(outputPath, clusterID, clusterVersion, infrastructureList, nodeList) + templates.ReplaceOADPOperatorInstallationSection(outputPath, importantCSVsByNamespace, importantSubscriptionsByNamespace, foundOADP, foundRelatedProducts, oldOADPError, oadpOperatorsText) + templates.ReplaceDataProtectionApplicationsSection(outputPath, dataProtectionApplicationList) + templates.ReplaceCloudStoragesSection(outputPath, cloudStorageList) + templates.ReplaceBackupStorageLocationsSection(outputPath, backupStorageLocationList) + templates.ReplaceVolumeSnapshotLocationsSection(outputPath, volumeSnapshotLocationList) + // this creates DownloadRequests CRs + templates.ReplaceBackupsSection(outputPath, backupList, clusterClient, deleteBackupRequestList, podVolumeBackupList, RequestTimeout, SkipTLS) + templates.ReplaceRestoresSection(outputPath, restoreList, clusterClient, podVolumeRestoreList, RequestTimeout, SkipTLS) + + downloadRequestList := &velerov1.DownloadRequestList{} + err = gather.AllResources(clusterClient, downloadRequestList) + if err != nil { + fmt.Println(err) + } + + templates.ReplaceSchedulesSection(outputPath, scheduleList) + templates.ReplaceBackupRepositoriesSection(outputPath, backupRepositoryList) + templates.ReplaceDataUploadsSection(outputPath, dataUploadList) + templates.ReplaceDataDownloadsSection(outputPath, dataDownloadList) + templates.ReplacePodVolumeBackupsSection(outputPath, podVolumeBackupList) + templates.ReplacePodVolumeRestoresSection(outputPath, podVolumeRestoreList) + templates.ReplaceDownloadRequestsSection(outputPath, downloadRequestList) + templates.ReplaceDeleteBackupRequestsSection(outputPath, deleteBackupRequestList) + templates.ReplaceServerStatusRequestsSection(outputPath, serverStatusRequestList) + templates.ReplaceNonAdminBackupStorageLocationRequestsSection(outputPath, nonAdminBackupStorageLocationRequestList) + templates.ReplaceNonAdminBackupStorageLocationsSection(outputPath, nonAdminBackupStorageLocationList) + templates.ReplaceNonAdminBackupsSection(outputPath, nonAdminBackupList) + templates.ReplaceNonAdminRestoresSection(outputPath, nonAdminRestoreList) + templates.ReplaceNonAdminDownloadRequestsSection(outputPath, nonAdminDownloadRequestList) + templates.ReplaceAvailableStorageClassesSection(outputPath, storageClassList) + templates.ReplaceAvailableVolumeSnapshotClassesSection(outputPath, volumeSnapshotClassList) + templates.ReplaceAvailableCSIDriversSection(outputPath, csiDriverList) + templates.ReplaceCustomResourceDefinitionsSection(outputPath, clusterConfig) + // do not tar! + err = templates.Write(outputPath) + if err != nil { + fmt.Printf("Error occurred: %v\n", err) + return err + } + return nil + }, + } +) diff --git a/must-gather/pkg/gather/gather.go b/must-gather/pkg/gather/gather.go new file mode 100644 index 00000000000..4b406cca9dc --- /dev/null +++ b/must-gather/pkg/gather/gather.go @@ -0,0 +1,11 @@ +package gather + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func AllResources(clusterClient client.Client, clusterResource client.ObjectList) error { + return clusterClient.List(context.Background(), clusterResource) +} diff --git a/must-gather/pkg/gvk/gvk.go b/must-gather/pkg/gvk/gvk.go new file mode 100644 index 00000000000..fc9dd54b770 --- /dev/null +++ b/must-gather/pkg/gvk/gvk.go @@ -0,0 +1,143 @@ +package gvk + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + CustomResourceDefinitionGVK = schema.GroupVersionKind{ + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", + } + ListGVK = schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "List", + } + ClusterServiceVersionGVK = schema.GroupVersionKind{ + Group: "operators.coreos.com", + Version: "v1alpha1", + Kind: "ClusterServiceVersion", + } + SubscriptionsGVK = schema.GroupVersionKind{ + Group: "operators.coreos.com", + Version: "v1alpha1", + Kind: "Subscription", + } + DataProtectionApplicationGVK = schema.GroupVersionKind{ + Group: "oadp.openshift.io", + Version: "v1alpha1", + Kind: "DataProtectionApplication", + } + CloudStorageGVK = schema.GroupVersionKind{ + Group: "oadp.openshift.io", + Version: "v1alpha1", + Kind: "CloudStorage", + } + BackupStorageLocationGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "BackupStorageLocation", + } + VolumeSnapshotLocationGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "VolumeSnapshotLocation", + } + BackupGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "Backup", + } + RestoreGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "Restore", + } + ScheduleGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "Schedule", + } + BackupRepositoryGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "BackupRepository", + } + DataUploadGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v2alpha1", + Kind: "DataUpload", + } + DataDownloadGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v2alpha1", + Kind: "DataDownload", + } + PodVolumeBackupGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "PodVolumeBackup", + } + PodVolumeRestoreGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "PodVolumeRestore", + } + DownloadRequestGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "DownloadRequest", + } + DeleteBackupRequestGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "DeleteBackupRequest", + } + ServerStatusRequestGVK = schema.GroupVersionKind{ + Group: "velero.io", + Version: "v1", + Kind: "ServerStatusRequest", + } + NonAdminBackupStorageLocationRequestGVK = schema.GroupVersionKind{ + Group: "oadp.openshift.io", + Version: "v1alpha1", + Kind: "NonAdminBackupStorageLocationRequest", + } + NonAdminBackupStorageLocationGVK = schema.GroupVersionKind{ + Group: "oadp.openshift.io", + Version: "v1alpha1", + Kind: "NonAdminBackupStorageLocation", + } + NonAdminBackupGVK = schema.GroupVersionKind{ + Group: "oadp.openshift.io", + Version: "v1alpha1", + Kind: "NonAdminBackup", + } + NonAdminRestoreGVK = schema.GroupVersionKind{ + Group: "oadp.openshift.io", + Version: "v1alpha1", + Kind: "NonAdminRestore", + } + NonAdminDownloadRequestGVK = schema.GroupVersionKind{ + Group: "oadp.openshift.io", + Version: "v1alpha1", + Kind: "NonAdminDownloadRequest", + } + StorageClassGVK = schema.GroupVersionKind{ + Group: "storage.k8s.io", + Version: "v1", + Kind: "StorageClass", + } + VolumeSnapshotClassGVK = schema.GroupVersionKind{ + Group: "snapshot.storage.k8s.io", + Version: "v1", + Kind: "VolumeSnapshotClass", + } + CSIDriverGVK = schema.GroupVersionKind{ + Group: "storage.k8s.io", + Version: "v1", + Kind: "CSIDriver", + } +) diff --git a/must-gather/pkg/templates/summary.go b/must-gather/pkg/templates/summary.go new file mode 100644 index 00000000000..6b1e1fc30df --- /dev/null +++ b/must-gather/pkg/templates/summary.go @@ -0,0 +1,1692 @@ +package templates + +import ( + "bytes" + "context" + "fmt" + "os" + "path" + "slices" + "strings" + "time" + + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" + nac1alpha1 "github.com/migtools/oadp-non-admin/api/v1alpha1" + openshiftconfigv1 "github.com/openshift/api/config/v1" + oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1" + "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" + "github.com/vmware-tanzu/velero/pkg/cmd/util/output" + "github.com/vmware-tanzu/velero/pkg/label" + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/oadp-operator/must-gather/pkg/gvk" +) + +const ( + FilePermission = 0644 + FolderPermission = 0777 +) + +var ( + summaryTemplateReplacesKeys = []string{ + "MUST_GATHER_VERSION", + "ERRORS", + "CLUSTER_ID", "OCP_VERSION", "CLOUD", "ARCH", "CLUSTER_VERSION", + "OADP_VERSIONS", + "DATA_PROTECTION_APPLICATIONS", + "CLOUD_STORAGES", + "BACKUP_STORAGE_LOCATIONS", + "VOLUME_SNAPSHOT_LOCATIONS", + "BACKUPS", + "RESTORES", + "SCHEDULES", + "BACKUPS_REPOSITORIES", + "DATA_UPLOADS", + "DATA_DOWNLOADS", + "POD_VOLUME_BACKUPS", + "POD_VOLUME_RESTORES", + "DOWNLOAD_REQUESTS", + "DELETE_BACKUP_REQUESTS", + "SERVER_STATUS_REQUESTS", + "NON_ADMIN_BACKUP_STORAGE_LOCATION_REQUESTS", + "NON_ADMIN_BACKUP_STORAGE_LOCATIONS", + "NON_ADMIN_BACKUPS", + "NON_ADMIN_RESTORES", + "NON_ADMIN_DOWNLOAD_REQUESTS", + "STORAGE_CLASSES", + "VOLUME_SNAPSHOT_CLASSES", + "CSI_DRIVERS", + "CUSTOM_RESOURCE_DEFINITION", + } + summaryTemplateReplaces = map[string]string{} +) + +const summaryTemplate = `# OADP must-gather summary version <> + +[OADP Frequently Asked Questions](https://access.redhat.com/articles/5456281) + +# Table of Contents + +- [Errors](#errors) +- [Cluster information](#cluster-information) +- [OADP operator installation information](#oadp-operator-installation-information) + - [DataProtectionApplications (DPAs)](#dataprotectionapplications-dpas) + - [CloudStorages](#cloudstorages) + - [BackupStorageLocations (BSLs)](#backupstoragelocations-bsls) + - [VolumeSnapshotLocations (VSLs)](#volumesnapshotlocations-vsls) + - [Backups](#backups) + - [Restores](#restores) + - [Schedules](#schedules) + - [BackupRepositories](#backuprepositories) + - [DataUploads](#datauploads) + - [DataDownloads](#datadownloads) + - [PodVolumeBackups](#podvolumebackups) + - [PodVolumeRestores](#podvolumerestores) + - [DownloadRequests](#downloadrequests) + - [DeleteBackupRequests](#deletebackuprequests) + - [ServerStatusRequests](#serverstatusrequests) + - [NonAdminBackupStorageLocationRequests](#nonadminbackupstoragelocationrequests) + - [NonAdminBackupStorageLocations](#nonadminbackupstoragelocations) + - [NonAdminBackups](#nonadminbackups) + - [NonAdminRestores](#nonadminrestores) + - [NonAdminDownloadRequests](#nonadmindownloadrequests) +- Storage + - [Available StorageClasses in cluster](#available-storageclasses-in-cluster) + - [Available VolumeSnapshotClasses in cluster](#available-volumesnapshotclasses-in-cluster) + - [Available CSIDrivers in cluster](#available-csidrivers-in-cluster) +- [CustomResourceDefinitions](#customresourcedefinitions) + +## Errors + +<> + +## Cluster information + +| Cluster ID | OpenShift version | Cloud provider | Architecture | +| ---------- | ----------------- | -------------- | ------------ | +| <> | <> | <> | <> | + +<> + +## OADP operator installation information + +<> + +### DataProtectionApplications (DPAs) + +<> + +### CloudStorages + +<> + +### BackupStorageLocations (BSLs) + +<> + +### VolumeSnapshotLocations (VSLs) + +<> + +### Backups + +<> + +### Restores + +<> + +### Schedules + +<> + +### BackupRepositories + +<> + +### DataUploads + +<> + +### DataDownloads + +<> + +### PodVolumeBackups + +<> + +### PodVolumeRestores + +<> + +### DownloadRequests + +<> + +### DeleteBackupRequests + +<> + +### ServerStatusRequests + +<> + +### NonAdminBackupStorageLocationRequests + +<> + +### NonAdminBackupStorageLocations + +<> + +### NonAdminBackups + +<> + +### NonAdminRestores + +<> + +### NonAdminDownloadRequests + +<> + +## Available StorageClasses in cluster + +<> + +## Available VolumeSnapshotClasses in cluster + +<> + +## Available CSIDrivers in cluster + +<> + +> **Note:** check [supported Container Storage Interface drivers for OpenShift](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/storage/using-container-storage-interface-csi#csi-drivers-supported_persistent-storage-csi) + +## CustomResourceDefinitions + +<> +` + +func init() { + for _, key := range summaryTemplateReplacesKeys { + summaryTemplateReplaces[key] = "" + } +} + +func ReplaceMustGatherVersion(version string) { + summaryTemplateReplaces["MUST_GATHER_VERSION"] = "`" + version + "`" +} + +func ReplaceClusterInformationSection( + outputPath string, + clusterID string, + clusterVersion *openshiftconfigv1.ClusterVersion, + infrastructureList *openshiftconfigv1.InfrastructureList, + nodeList *corev1.NodeList, +) { + summaryTemplateReplaces["CLUSTER_ID"] = clusterID + + summaryTemplateReplaces["OCP_VERSION"] = clusterVersion.Status.Desired.Version + summaryTemplateReplaces["CLUSTER_VERSION"] = createYAML(outputPath, "cluster-scoped-resources/config.openshift.io/clusterversions.yaml", clusterVersion) + + if infrastructureList != nil && len(infrastructureList.Items) != 0 { + cloudProvider := string(infrastructureList.Items[0].Spec.PlatformSpec.Type) + summaryTemplateReplaces["CLOUD"] = cloudProvider + } else { + summaryTemplateReplaces["CLOUD"] = "❌ no Infrastructure found in cluster" + summaryTemplateReplaces["ERRORS"] += "⚠️ No Infrastructure found in cluster\n\n" + } + + if nodeList != nil && len(nodeList.Items) != 0 { + architectureText := "" + for _, node := range nodeList.Items { + arch := node.Status.NodeInfo.OperatingSystem + "/" + node.Status.NodeInfo.Architecture + if len(architectureText) == 0 { + architectureText += arch + } else { + if !strings.Contains(architectureText, arch) { + architectureText += " | " + arch + } + } + } + summaryTemplateReplaces["ARCH"] = architectureText + } else { + summaryTemplateReplaces["ARCH"] = "❌ no Node found in cluster" + summaryTemplateReplaces["ERRORS"] += "⚠️ No Node found in cluster\n\n" + } +} + +func ReplaceOADPOperatorInstallationSection( + outputPath string, + importantCSVsByNamespace map[string][]operatorsv1alpha1.ClusterServiceVersion, + importantSubscriptionsByNamespace map[string][]operatorsv1alpha1.Subscription, + foundOADP bool, + foundRelatedProducts bool, + oldOADPError string, + oadpOperatorsText string, +) { + if len(importantCSVsByNamespace) == 0 { + summaryTemplateReplaces["OADP_VERSIONS"] = "❌ No OADP Operator was found installed in the cluster\n\nNo related product was found installed in the cluster" + summaryTemplateReplaces["ERRORS"] += "🚫 No OADP Operator was found installed in the cluster\n\n" + } else { + for namespace, csvs := range importantCSVsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + for _, csv := range csvs { + csv.GetObjectKind().SetGroupVersionKind(gvk.ClusterServiceVersionGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &csv}) + } + folder := fmt.Sprintf("namespaces/%s/operators.coreos.com/clusterserviceversions", namespace) + oadpOperatorsText += createYAML(outputPath, folder+"/clusterserviceversions.yaml", list) + } + for namespace, subscriptions := range importantSubscriptionsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + for _, subscription := range subscriptions { + subscription.GetObjectKind().SetGroupVersionKind(gvk.SubscriptionsGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &subscription}) + } + folder := fmt.Sprintf("namespaces/%s/operators.coreos.com/subscriptions", namespace) + oadpOperatorsText += createYAML(outputPath, folder+"/subscriptions.yaml", list) + } + if len(oldOADPError) > 0 { + summaryTemplateReplaces["ERRORS"] += oldOADPError + } + if !foundOADP { + summaryTemplateReplaces["OADP_VERSIONS"] += "❌ No OADP Operator was found installed in the cluster\n\n" + summaryTemplateReplaces["ERRORS"] += "🚫 No OADP Operator was found installed in the cluster\n\n" + } + summaryTemplateReplaces["OADP_VERSIONS"] += oadpOperatorsText + if !foundRelatedProducts { + summaryTemplateReplaces["OADP_VERSIONS"] += "No related product was found installed in the cluster\n\n" + } + summaryTemplateReplaces["OADP_VERSIONS"] += fmt.Sprintf("For information about all objects collected in each namespace, check [`%[1]snamespaces`](%[1]snamespaces) folder", outputPath) + } +} + +func ReplaceDataProtectionApplicationsSection(outputPath string, dataProtectionApplicationList *oadpv1alpha1.DataProtectionApplicationList) { + if dataProtectionApplicationList != nil && len(dataProtectionApplicationList.Items) != 0 { + dataProtectionApplicationsByNamespace := map[string][]oadpv1alpha1.DataProtectionApplication{} + + for _, dataProtectionApplication := range dataProtectionApplicationList.Items { + dataProtectionApplicationsByNamespace[dataProtectionApplication.Namespace] = append(dataProtectionApplicationsByNamespace[dataProtectionApplication.Namespace], dataProtectionApplication) + } + + summaryTemplateReplaces["DATA_PROTECTION_APPLICATIONS"] += "| Namespace | Name | spec.unsupportedOverrides | status.conditions[0] | yaml |\n| --- | --- | --- | --- | --- |\n" + for namespace, dataProtectionApplications := range dataProtectionApplicationsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/oadp.openshift.io/dataprotectionapplications", namespace) + file := folder + "/dataprotectionapplications.yaml" + for _, dataProtectionApplication := range dataProtectionApplications { + dataProtectionApplication.GetObjectKind().SetGroupVersionKind(gvk.DataProtectionApplicationGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &dataProtectionApplication}) + + unsupportedOverridesText := "false" + if dataProtectionApplication.Spec.UnsupportedOverrides != nil { + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ DataProtectionApplication **%v** in **%v** namespace is using **unsupportedOverrides**\n\n", + dataProtectionApplication.Name, namespace, + ) + unsupportedOverridesText = "⚠️ true" + } + + dpaStatus := "" + if len(dataProtectionApplication.Status.Conditions) == 0 { + dpaStatus = "⚠️ no status" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ DataProtectionApplication **%v** with **no status** in **%v** namespace\n\n", + dataProtectionApplication.Name, namespace, + ) + } else { + condition := dataProtectionApplication.Status.Conditions[0] + if condition.Status == v1.ConditionTrue { + dpaStatus = fmt.Sprintf("✅ status %s: %s", condition.Type, condition.Status) + } else { + dpaStatus = fmt.Sprintf("❌ status %s: %s", condition.Type, condition.Status) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ DataProtectionApplication **%v** with **status %s: %s** in **%v** namespace\n\n", + dataProtectionApplication.Name, condition.Type, condition.Status, namespace, + ) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["DATA_PROTECTION_APPLICATIONS"] += fmt.Sprintf( + "| %v | %v | %v | %v | %s |\n", + namespace, dataProtectionApplication.Name, unsupportedOverridesText, dpaStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["DATA_PROTECTION_APPLICATIONS"] = "❌ No DataProtectionApplication was found in the cluster" + summaryTemplateReplaces["ERRORS"] += "⚠️ No DataProtectionApplication was found in the cluster\n\n" + } +} + +func ReplaceCloudStoragesSection(outputPath string, cloudStorageList *oadpv1alpha1.CloudStorageList) { + if cloudStorageList != nil && len(cloudStorageList.Items) != 0 { + cloudStorageByNamespace := map[string][]oadpv1alpha1.CloudStorage{} + + for _, cloudStorage := range cloudStorageList.Items { + cloudStorageByNamespace[cloudStorage.Namespace] = append(cloudStorageByNamespace[cloudStorage.Namespace], cloudStorage) + } + + summaryTemplateReplaces["CLOUD_STORAGES"] += "| Namespace | Name | yaml |\n| --- | --- | --- |\n" + for namespace, cloudStorages := range cloudStorageByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/oadp.openshift.io/cloudstorages", namespace) + file := folder + "/cloudstorages.yaml" + for _, cloudStorage := range cloudStorages { + cloudStorage.GetObjectKind().SetGroupVersionKind(gvk.CloudStorageGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &cloudStorage}) + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["BACKUPS"] += fmt.Sprintf( + "| %v | %v | %s |\n", + namespace, cloudStorage.Name, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["CLOUD_STORAGES"] = "❌ No CloudStorage was found in the cluster" + } +} + +func ReplaceBackupStorageLocationsSection(outputPath string, backupStorageLocationList *velerov1.BackupStorageLocationList) { + if backupStorageLocationList != nil && len(backupStorageLocationList.Items) != 0 { + backupStorageLocationsByNamespace := map[string][]velerov1.BackupStorageLocation{} + + for _, backupStorageLocation := range backupStorageLocationList.Items { + backupStorageLocationsByNamespace[backupStorageLocation.Namespace] = append(backupStorageLocationsByNamespace[backupStorageLocation.Namespace], backupStorageLocation) + } + + summaryTemplateReplaces["BACKUP_STORAGE_LOCATIONS"] += "| Namespace | Name | spec.default | status.phase | yaml |\n| --- | --- | --- | --- | --- |\n" + for namespace, backupStorageLocations := range backupStorageLocationsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/backupstoragelocations", namespace) + file := folder + "/backupstoragelocations.yaml" + for _, backupStorageLocation := range backupStorageLocations { + backupStorageLocation.GetObjectKind().SetGroupVersionKind(gvk.BackupStorageLocationGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &backupStorageLocation}) + + bslStatus := "" + bslStatusPhase := backupStorageLocation.Status.Phase + if len(bslStatusPhase) == 0 { + bslStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ BackupStorageLocation **%v** with **no status phase** in **%v** namespace\n\n", + backupStorageLocation.Name, namespace, + ) + } else { + if bslStatusPhase == velerov1.BackupStorageLocationPhaseAvailable { + bslStatus = fmt.Sprintf("✅ status phase %s", bslStatusPhase) + } else { + bslStatus = fmt.Sprintf("❌ status phase %s", bslStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ BackupStorageLocation **%v** with **status phase %s** in **%v** namespace\n\n", + backupStorageLocation.Name, bslStatusPhase, namespace, + ) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["BACKUP_STORAGE_LOCATIONS"] += fmt.Sprintf( + "| %v | %v | %t | %v | %s |\n", + namespace, backupStorageLocation.Name, backupStorageLocation.Spec.Default, bslStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["BACKUP_STORAGE_LOCATIONS"] = "❌ No BackupStorageLocation was found in the cluster" + summaryTemplateReplaces["ERRORS"] += "⚠️ No BackupStorageLocation was found in the cluster\n\n" + } +} + +func ReplaceVolumeSnapshotLocationsSection(outputPath string, volumeSnapshotLocationList *velerov1.VolumeSnapshotLocationList) { + if volumeSnapshotLocationList != nil && len(volumeSnapshotLocationList.Items) != 0 { + volumeSnapshotLocationsByNamespace := map[string][]velerov1.VolumeSnapshotLocation{} + + for _, volumeSnapshotLocation := range volumeSnapshotLocationList.Items { + volumeSnapshotLocationsByNamespace[volumeSnapshotLocation.Namespace] = append(volumeSnapshotLocationsByNamespace[volumeSnapshotLocation.Namespace], volumeSnapshotLocation) + } + + summaryTemplateReplaces["VOLUME_SNAPSHOT_LOCATIONS"] += "| Namespace | Name | yaml |\n| --- | --- | --- |\n" + for namespace, volumeSnapshotLocations := range volumeSnapshotLocationsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/volumesnapshotlocations", namespace) + file := folder + "/volumesnapshotlocations.yaml" + for _, volumeSnapshotLocation := range volumeSnapshotLocations { + volumeSnapshotLocation.GetObjectKind().SetGroupVersionKind(gvk.VolumeSnapshotLocationGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &volumeSnapshotLocation}) + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["VOLUME_SNAPSHOT_LOCATIONS"] += fmt.Sprintf( + "| %v | %v | %s |\n", + namespace, volumeSnapshotLocation.Name, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["VOLUME_SNAPSHOT_LOCATIONS"] = "❌ No VolumeSnapshotLocation was found in the cluster" + } +} + +func ReplaceBackupsSection( + outputPath string, + backupList *velerov1.BackupList, + clusterClient client.Client, + deleteBackupRequestList *velerov1.DeleteBackupRequestList, + podVolumeBackupList *velerov1.PodVolumeBackupList, + requestTimeot time.Duration, + skipTLS bool, +) { + if backupList != nil && len(backupList.Items) != 0 { + backupsByNamespace := map[string][]velerov1.Backup{} + + for _, backup := range backupList.Items { + backupsByNamespace[backup.Namespace] = append(backupsByNamespace[backup.Namespace], backup) + } + + summaryTemplateReplaces["BACKUPS"] += "| Namespace | Name | status.phase | describe | logs | yaml |\n| --- | --- | --- | --- | --- | ---|\n" + for namespace, backups := range backupsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/backups", namespace) + file := folder + "/backups.yaml" + for _, backup := range backups { + backup.GetObjectKind().SetGroupVersionKind(gvk.BackupGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &backup}) + + backupStatus := "" + backupStatusPhase := backup.Status.Phase + if len(backupStatusPhase) == 0 { + backupStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ Backup **%v** with **no status phase** in **%v** namespace\n\n", + backup.Name, namespace, + ) + } else { + failedStates := []velerov1.BackupPhase{ + velerov1.BackupPhaseFailed, + velerov1.BackupPhasePartiallyFailed, + velerov1.BackupPhaseFinalizingPartiallyFailed, + velerov1.BackupPhaseWaitingForPluginOperationsPartiallyFailed, + velerov1.BackupPhaseFailedValidation, + } + if backupStatusPhase == velerov1.BackupPhaseCompleted { + backupStatus = fmt.Sprintf("✅ status phase %s", backupStatusPhase) + } else if slices.Contains(failedStates, backupStatusPhase) { + backupStatus = fmt.Sprintf("❌ status phase %s", backupStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ Backup **%v** with **status phase %s** in **%v** namespace\n\n", + backup.Name, backupStatusPhase, namespace, + ) + } else { + backupStatus = fmt.Sprintf("⚠️ status phase %s", backupStatusPhase) + } + } + + var relatedDeleteBackupRequests []velerov1.DeleteBackupRequest + for _, deleteBackupRequest := range deleteBackupRequestList.Items { + if deleteBackupRequest.Labels[velerov1.BackupNameLabel] == label.GetValidName(backup.Name) && + deleteBackupRequest.Labels[velerov1.BackupUIDLabel] == string(backup.UID) { + relatedDeleteBackupRequests = append(relatedDeleteBackupRequests, deleteBackupRequest) + } + } + var relatedPodVolumeBackupLists []velerov1.PodVolumeBackup + for _, podVolumeBackup := range podVolumeBackupList.Items { + if podVolumeBackup.Labels[velerov1.BackupNameLabel] == label.GetValidName(backup.Name) { + relatedPodVolumeBackupLists = append(relatedPodVolumeBackupLists, podVolumeBackup) + } + } + + // TODO caCertFile? + describeOutput := func(ctx context.Context) string { + ctx, cancel := context.WithTimeout(ctx, requestTimeot) + defer cancel() + return output.DescribeBackup(ctx, clusterClient, &backup, relatedDeleteBackupRequests, relatedPodVolumeBackupLists, true, skipTLS, "") + }(context.Background()) + + writeTo := &bytes.Buffer{} + // TODO caCertFile? + err := downloadrequest.Stream(context.Background(), clusterClient, backup.Namespace, backup.Name, velerov1.DownloadTargetKindBackupLog, writeTo, requestTimeot, skipTLS, "") + var logs string + if err != nil { + fmt.Println(err) + logs = fmt.Sprintf("❌ %s", err) + } else { + logs = createFile( + outputPath, + folder+"/"+backup.Name+".log", + writeTo.String(), + "logs", + ) + } + yamlLink := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["BACKUPS"] += fmt.Sprintf( + "| %v | %v | %s | %s | %s | %s |\n", + namespace, backup.Name, + backupStatus, + createFile( + outputPath, + folder+"/describe-"+backup.Name+".txt", + describeOutput, + "describe", + ), + logs, + yamlLink, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["BACKUPS"] = "❌ No Backup was found in the cluster" + } +} + +func ReplaceRestoresSection( + outputPath string, + restoreListList *velerov1.RestoreList, + clusterClient client.Client, + podVolumeRestoreList *velerov1.PodVolumeRestoreList, + requestTimeot time.Duration, + skipTLS bool, +) { + if restoreListList != nil && len(restoreListList.Items) != 0 { + restoresByNamespace := map[string][]velerov1.Restore{} + + for _, restore := range restoreListList.Items { + restoresByNamespace[restore.Namespace] = append(restoresByNamespace[restore.Namespace], restore) + } + + summaryTemplateReplaces["RESTORES"] += "| Namespace | Name | status.phase | describe | logs | yaml |\n| --- | --- | --- | --- | --- | --- |\n" + for namespace, restores := range restoresByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/restores", namespace) + file := folder + "/restores.yaml" + for _, restore := range restores { + restore.GetObjectKind().SetGroupVersionKind(gvk.RestoreGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &restore}) + + restoreStatus := "" + restoreStatusPhase := restore.Status.Phase + if len(restoreStatusPhase) == 0 { + restoreStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ Restore **%v** with **no status phase** in **%v** namespace\n\n", + restore.Name, namespace, + ) + } else { + failedStates := []velerov1.RestorePhase{ + velerov1.RestorePhaseFailed, + velerov1.RestorePhasePartiallyFailed, + velerov1.RestorePhaseFinalizingPartiallyFailed, + velerov1.RestorePhaseWaitingForPluginOperationsPartiallyFailed, + velerov1.RestorePhaseFailedValidation, + } + if restoreStatusPhase == velerov1.RestorePhaseCompleted { + restoreStatus = fmt.Sprintf("✅ status phase %s", restoreStatusPhase) + } else if slices.Contains(failedStates, restoreStatusPhase) { + restoreStatus = fmt.Sprintf("❌ status phase %s", restoreStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ Restore **%v** with **status phase %s** in **%v** namespace\n\n", + restore.Name, restoreStatusPhase, namespace, + ) + } else { + restoreStatus = fmt.Sprintf("⚠️ status phase %s", restoreStatusPhase) + } + } + + var relatedPodVolumeRestoreLists []velerov1.PodVolumeRestore + for _, podVolumeRestore := range podVolumeRestoreList.Items { + if podVolumeRestore.Labels[velerov1.RestoreNameLabel] == label.GetValidName(restore.Name) { + relatedPodVolumeRestoreLists = append(relatedPodVolumeRestoreLists, podVolumeRestore) + } + } + + // TODO caCertFile? + describeOutput := func(ctx context.Context) string { + ctx, cancel := context.WithTimeout(ctx, requestTimeot) + defer cancel() + return output.DescribeRestore(ctx, clusterClient, &restore, relatedPodVolumeRestoreLists, true, skipTLS, "") + }(context.Background()) + + writeTo := &bytes.Buffer{} + // TODO caCertFile? + err := downloadrequest.Stream(context.Background(), clusterClient, restore.Namespace, restore.Name, velerov1.DownloadTargetKindRestoreLog, writeTo, requestTimeot, skipTLS, "") + var logs string + if err != nil { + fmt.Println(err) + logs = fmt.Sprintf("❌ %s", err) + } else { + logs = createFile( + outputPath, + folder+"/"+restore.Name+".log", + writeTo.String(), + "logs", + ) + } + + yamllink := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["RESTORES"] += fmt.Sprintf( + "| %v | %v | %s | %s | %s | %s |\n", + namespace, restore.Name, + restoreStatus, + createFile( + outputPath, + folder+"/describe-"+restore.Name+".txt", + describeOutput, + "describe", + ), + logs, + yamllink, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["RESTORES"] = "❌ No Restore was found in the cluster" + } +} + +func ReplaceSchedulesSection(outputPath string, scheduleList *velerov1.ScheduleList) { + if scheduleList != nil && len(scheduleList.Items) != 0 { + schedulesByNamespace := map[string][]velerov1.Schedule{} + + for _, schedule := range scheduleList.Items { + schedulesByNamespace[schedule.Namespace] = append(schedulesByNamespace[schedule.Namespace], schedule) + } + + summaryTemplateReplaces["SCHEDULES"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, schedules := range schedulesByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/schedules", namespace) + file := folder + "/schedules.yaml" + for _, schedule := range schedules { + schedule.GetObjectKind().SetGroupVersionKind(gvk.ScheduleGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &schedule}) + + scheduleStatus := "" + scheduleStatusPhase := schedule.Status.Phase + if len(scheduleStatusPhase) == 0 { + scheduleStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ Schedule **%v** with **no status phase** in **%v** namespace\n\n", + schedule.Name, namespace, + ) + } else { + if scheduleStatusPhase == velerov1.SchedulePhaseEnabled { + scheduleStatus = fmt.Sprintf("✅ status phase %s", scheduleStatusPhase) + } else if scheduleStatusPhase == velerov1.SchedulePhaseFailedValidation { + scheduleStatus = fmt.Sprintf("❌ status phase %s", scheduleStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ Schedule **%v** with **status phase %s** in **%v** namespace\n\n", + schedule.Name, scheduleStatusPhase, namespace, + ) + } else { + scheduleStatus = fmt.Sprintf("⚠️ status phase %s", scheduleStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["SCHEDULES"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, schedule.Name, scheduleStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["SCHEDULES"] = "❌ No Schedule was found in the cluster" + } +} + +func ReplaceBackupRepositoriesSection(outputPath string, backupRepositoryList *velerov1.BackupRepositoryList) { + if backupRepositoryList != nil && len(backupRepositoryList.Items) != 0 { + backupRepositoriesByNamespace := map[string][]velerov1.BackupRepository{} + + for _, backupRepository := range backupRepositoryList.Items { + backupRepositoriesByNamespace[backupRepository.Namespace] = append(backupRepositoriesByNamespace[backupRepository.Namespace], backupRepository) + } + + summaryTemplateReplaces["BACKUPS_REPOSITORIES"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, backupRepositories := range backupRepositoriesByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/backuprepositories", namespace) + file := folder + "/backuprepositories.yaml" + for _, backupRepository := range backupRepositories { + backupRepository.GetObjectKind().SetGroupVersionKind(gvk.BackupRepositoryGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &backupRepository}) + + backupRepositoryStatus := "" + backupRepositoryStatusPhase := backupRepository.Status.Phase + if len(backupRepositoryStatusPhase) == 0 { + backupRepositoryStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ BackupRepository **%v** with **no status phase** in **%v** namespace\n\n", + backupRepository.Name, namespace, + ) + } else { + if backupRepositoryStatusPhase == velerov1.BackupRepositoryPhaseReady { + backupRepositoryStatus = fmt.Sprintf("✅ status phase %s", backupRepositoryStatusPhase) + } else if backupRepositoryStatusPhase == velerov1.BackupRepositoryPhaseNotReady { + backupRepositoryStatus = fmt.Sprintf("❌ status phase %s", backupRepositoryStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ BackupRepository **%v** with **status phase %s** in **%v** namespace\n\n", + backupRepository.Name, backupRepositoryStatusPhase, namespace, + ) + } else { + backupRepositoryStatus = fmt.Sprintf("⚠️ status phase %s", backupRepositoryStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["BACKUPS_REPOSITORIES"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, backupRepository.Name, backupRepositoryStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["BACKUPS_REPOSITORIES"] = "❌ No BackupRepository was found in the cluster" + } +} + +func ReplaceDataUploadsSection(outputPath string, dataUploadList *velerov2alpha1.DataUploadList) { + if dataUploadList != nil && len(dataUploadList.Items) != 0 { + dataUploadByNamespace := map[string][]velerov2alpha1.DataUpload{} + + for _, dataUpload := range dataUploadList.Items { + dataUploadByNamespace[dataUpload.Namespace] = append(dataUploadByNamespace[dataUpload.Namespace], dataUpload) + } + + summaryTemplateReplaces["DATA_UPLOADS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, dataUploads := range dataUploadByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/datauploads", namespace) + file := folder + "/datauploads.yaml" + for _, dataUpload := range dataUploads { + dataUpload.GetObjectKind().SetGroupVersionKind(gvk.DataUploadGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &dataUpload}) + + dataUploadStatus := "" + dataUploadStatusPhase := dataUpload.Status.Phase + if len(dataUploadStatusPhase) == 0 { + dataUploadStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ DataUpload **%v** with **no status phase** in **%v** namespace\n\n", + dataUpload.Name, namespace, + ) + } else { + failedStates := []velerov2alpha1.DataUploadPhase{ + velerov2alpha1.DataUploadPhaseCanceling, + velerov2alpha1.DataUploadPhaseCanceled, + velerov2alpha1.DataUploadPhaseFailed, + } + if dataUploadStatusPhase == velerov2alpha1.DataUploadPhaseCompleted { + dataUploadStatus = fmt.Sprintf("✅ status phase %s", dataUploadStatusPhase) + } else if slices.Contains(failedStates, dataUploadStatusPhase) { + dataUploadStatus = fmt.Sprintf("❌ status phase %s", dataUploadStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ DataUpload **%v** with **status phase %s** in **%v** namespace\n\n", + dataUpload.Name, dataUploadStatusPhase, namespace, + ) + } else { + dataUploadStatus = fmt.Sprintf("⚠️ status phase %s", dataUploadStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["DATA_UPLOADS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, dataUpload.Name, dataUploadStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["DATA_UPLOADS"] = "❌ No DataUpload was found in the cluster" + } +} + +func ReplaceDataDownloadsSection(outputPath string, dataDownloadList *velerov2alpha1.DataDownloadList) { + if dataDownloadList != nil && len(dataDownloadList.Items) != 0 { + dataDownloadByNamespace := map[string][]velerov2alpha1.DataDownload{} + + for _, dataDownload := range dataDownloadList.Items { + dataDownloadByNamespace[dataDownload.Namespace] = append(dataDownloadByNamespace[dataDownload.Namespace], dataDownload) + } + + summaryTemplateReplaces["DATA_DOWNLOADS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, dataDownloads := range dataDownloadByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/datadownloads", namespace) + file := folder + "/datadownloads.yaml" + for _, dataDownload := range dataDownloads { + dataDownload.GetObjectKind().SetGroupVersionKind(gvk.DataDownloadGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &dataDownload}) + + dataDownloadStatus := "" + dataDownloadStatusPhase := dataDownload.Status.Phase + if len(dataDownloadStatusPhase) == 0 { + dataDownloadStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ DataDownload **%v** with **no status phase** in **%v** namespace\n\n", + dataDownload.Name, namespace, + ) + } else { + failedStates := []velerov2alpha1.DataDownloadPhase{ + velerov2alpha1.DataDownloadPhaseCanceling, + velerov2alpha1.DataDownloadPhaseCanceled, + velerov2alpha1.DataDownloadPhaseFailed, + } + if dataDownloadStatusPhase == velerov2alpha1.DataDownloadPhaseCompleted { + dataDownloadStatus = fmt.Sprintf("✅ status phase %s", dataDownloadStatusPhase) + } else if slices.Contains(failedStates, dataDownloadStatusPhase) { + dataDownloadStatus = fmt.Sprintf("❌ status phase %s", dataDownloadStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ DataDownload **%v** with **status phase %s** in **%v** namespace\n\n", + dataDownload.Name, dataDownloadStatusPhase, namespace, + ) + } else { + dataDownloadStatus = fmt.Sprintf("⚠️ status phase %s", dataDownloadStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["DATA_DOWNLOADS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, dataDownload.Name, dataDownloadStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["DATA_DOWNLOADS"] = "❌ No DataDownload was found in the cluster" + } +} + +func ReplacePodVolumeBackupsSection(outputPath string, podVolumeBackupList *velerov1.PodVolumeBackupList) { + if podVolumeBackupList != nil && len(podVolumeBackupList.Items) != 0 { + podVolumeBackupsByNamespace := map[string][]velerov1.PodVolumeBackup{} + + for _, podVolumeBackup := range podVolumeBackupList.Items { + podVolumeBackupsByNamespace[podVolumeBackup.Namespace] = append(podVolumeBackupsByNamespace[podVolumeBackup.Namespace], podVolumeBackup) + } + + summaryTemplateReplaces["POD_VOLUME_BACKUPS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, podVolumeBackups := range podVolumeBackupsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/podvolumebackups", namespace) + file := folder + "/podvolumebackups.yaml" + for _, podVolumeBackup := range podVolumeBackups { + podVolumeBackup.GetObjectKind().SetGroupVersionKind(gvk.PodVolumeBackupGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &podVolumeBackup}) + + podVolumeBackupStatus := "" + podVolumeBackupStatusPhase := podVolumeBackup.Status.Phase + if len(podVolumeBackupStatusPhase) == 0 { + podVolumeBackupStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ PodVolumeBackup **%v** with **no status phase** in **%v** namespace\n\n", + podVolumeBackup.Name, namespace, + ) + } else { + if podVolumeBackupStatusPhase == velerov1.PodVolumeBackupPhaseCompleted { + podVolumeBackupStatus = fmt.Sprintf("✅ status phase %s", podVolumeBackupStatusPhase) + } else if podVolumeBackupStatusPhase == velerov1.PodVolumeBackupPhaseFailed { + podVolumeBackupStatus = fmt.Sprintf("❌ status phase %s", podVolumeBackupStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ PodVolumeBackup **%v** with **status phase %s** in **%v** namespace\n\n", + podVolumeBackup.Name, podVolumeBackupStatusPhase, namespace, + ) + } else { + podVolumeBackupStatus = fmt.Sprintf("⚠️ status phase %s", podVolumeBackupStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["POD_VOLUME_BACKUPS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, podVolumeBackup.Name, podVolumeBackupStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["POD_VOLUME_BACKUPS"] = "❌ No PodVolumeBackup was found in the cluster" + } +} + +func ReplacePodVolumeRestoresSection(outputPath string, podVolumeRestoreList *velerov1.PodVolumeRestoreList) { + if podVolumeRestoreList != nil && len(podVolumeRestoreList.Items) != 0 { + podVolumeRestoresByNamespace := map[string][]velerov1.PodVolumeRestore{} + + for _, podVolumeRestore := range podVolumeRestoreList.Items { + podVolumeRestoresByNamespace[podVolumeRestore.Namespace] = append(podVolumeRestoresByNamespace[podVolumeRestore.Namespace], podVolumeRestore) + } + + summaryTemplateReplaces["POD_VOLUME_RESTORES"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, podVolumeRestores := range podVolumeRestoresByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/podvolumerestores", namespace) + file := folder + "/podvolumerestores.yaml" + for _, podVolumeRestore := range podVolumeRestores { + podVolumeRestore.GetObjectKind().SetGroupVersionKind(gvk.PodVolumeRestoreGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &podVolumeRestore}) + + podVolumeRestoreStatus := "" + podVolumeRestoreStatusPhase := podVolumeRestore.Status.Phase + if len(podVolumeRestoreStatusPhase) == 0 { + podVolumeRestoreStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ PodVolumeRestore **%v** with **no status phase** in **%v** namespace\n\n", + podVolumeRestore.Name, namespace, + ) + } else { + if podVolumeRestoreStatusPhase == velerov1.PodVolumeRestorePhaseCompleted { + podVolumeRestoreStatus = fmt.Sprintf("✅ status phase %s", podVolumeRestoreStatusPhase) + } else if podVolumeRestoreStatusPhase == velerov1.PodVolumeRestorePhaseFailed { + podVolumeRestoreStatus = fmt.Sprintf("❌ status phase %s", podVolumeRestoreStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ PodVolumeRestore **%v** with **status phase %s** in **%v** namespace\n\n", + podVolumeRestore.Name, podVolumeRestoreStatusPhase, namespace, + ) + } else { + podVolumeRestoreStatus = fmt.Sprintf("⚠️ status phase %s", podVolumeRestoreStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["POD_VOLUME_RESTORES"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, podVolumeRestore.Name, podVolumeRestoreStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["POD_VOLUME_RESTORES"] = "❌ No PodVolumeRestore was found in the cluster" + } +} + +func ReplaceDownloadRequestsSection(outputPath string, downloadRequestList *velerov1.DownloadRequestList) { + if downloadRequestList != nil && len(downloadRequestList.Items) != 0 { + downloadRequestsByNamespace := map[string][]velerov1.DownloadRequest{} + + for _, downloadRequest := range downloadRequestList.Items { + downloadRequestsByNamespace[downloadRequest.Namespace] = append(downloadRequestsByNamespace[downloadRequest.Namespace], downloadRequest) + } + + summaryTemplateReplaces["DOWNLOAD_REQUESTS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, downloadRequests := range downloadRequestsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/downloadrequests", namespace) + file := folder + "/downloadrequests.yaml" + for _, downloadRequest := range downloadRequests { + downloadRequest.GetObjectKind().SetGroupVersionKind(gvk.DownloadRequestGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &downloadRequest}) + + downloadRequestStatus := "" + downloadRequestStatusPhase := downloadRequest.Status.Phase + if len(downloadRequestStatusPhase) == 0 { + downloadRequestStatus = "⚠️ no status" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ DownloadRequest **%v** with **no status** in **%v** namespace\n\n", + downloadRequest.Name, namespace, + ) + } else { + if downloadRequestStatusPhase == velerov1.DownloadRequestPhaseProcessed { + downloadRequestStatus = fmt.Sprintf("✅ status phase %s", downloadRequestStatusPhase) + } else { + downloadRequestStatus = fmt.Sprintf("⚠️ status phase %s", downloadRequestStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["DOWNLOAD_REQUESTS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, downloadRequest.Name, downloadRequestStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["DOWNLOAD_REQUESTS"] = "❌ No DownloadRequest was found in the cluster" + } +} + +func ReplaceDeleteBackupRequestsSection(outputPath string, deleteBackupRequestList *velerov1.DeleteBackupRequestList) { + if deleteBackupRequestList != nil && len(deleteBackupRequestList.Items) != 0 { + deleteBackupRequestsByNamespace := map[string][]velerov1.DeleteBackupRequest{} + + for _, deleteBackupRequest := range deleteBackupRequestList.Items { + deleteBackupRequestsByNamespace[deleteBackupRequest.Namespace] = append(deleteBackupRequestsByNamespace[deleteBackupRequest.Namespace], deleteBackupRequest) + } + + summaryTemplateReplaces["DELETE_BACKUP_REQUESTS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, deleteBackupRequests := range deleteBackupRequestsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/deletebackuprequests", namespace) + file := folder + "/deletebackuprequests.yaml" + for _, deleteBackupRequest := range deleteBackupRequests { + deleteBackupRequest.GetObjectKind().SetGroupVersionKind(gvk.DeleteBackupRequestGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &deleteBackupRequest}) + + deleteBackupRequestStatus := "" + deleteBackupRequestStatusPhase := deleteBackupRequest.Status.Phase + if len(deleteBackupRequestStatusPhase) == 0 { + deleteBackupRequestStatus = "⚠️ no status" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ DeleteBackupRequest **%v** with **no status** in **%v** namespace\n\n", + deleteBackupRequest.Name, namespace, + ) + } else { + if deleteBackupRequestStatusPhase == velerov1.DeleteBackupRequestPhaseProcessed { + deleteBackupRequestStatus = fmt.Sprintf("✅ status phase %s", deleteBackupRequestStatusPhase) + } else { + deleteBackupRequestStatus = fmt.Sprintf("⚠️ status phase %s", deleteBackupRequestStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["DELETE_BACKUP_REQUESTS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, deleteBackupRequest.Name, deleteBackupRequestStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["DELETE_BACKUP_REQUESTS"] = "❌ No DeleteBackupRequest was found in the cluster" + } +} + +func ReplaceServerStatusRequestsSection(outputPath string, serverStatusRequestList *velerov1.ServerStatusRequestList) { + if serverStatusRequestList != nil && len(serverStatusRequestList.Items) != 0 { + serverStatusRequestsByNamespace := map[string][]velerov1.ServerStatusRequest{} + + for _, serverStatusRequest := range serverStatusRequestList.Items { + serverStatusRequestsByNamespace[serverStatusRequest.Namespace] = append(serverStatusRequestsByNamespace[serverStatusRequest.Namespace], serverStatusRequest) + } + + summaryTemplateReplaces["SERVER_STATUS_REQUESTS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, serverStatusRequests := range serverStatusRequestsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/velero.io/serverstatusrequests", namespace) + file := folder + "/serverstatusrequests.yaml" + for _, serverStatusRequest := range serverStatusRequests { + serverStatusRequest.GetObjectKind().SetGroupVersionKind(gvk.ServerStatusRequestGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &serverStatusRequest}) + + serverStatusRequestStatus := "" + serverStatusRequestStatusPhase := serverStatusRequest.Status.Phase + if len(serverStatusRequestStatusPhase) == 0 { + serverStatusRequestStatus = "⚠️ no status" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ ServerStatusRequest **%v** with **no status** in **%v** namespace\n\n", + serverStatusRequest.Name, namespace, + ) + } else { + if serverStatusRequestStatusPhase == velerov1.ServerStatusRequestPhaseProcessed { + serverStatusRequestStatus = fmt.Sprintf("✅ status phase %s", serverStatusRequestStatusPhase) + } else { + serverStatusRequestStatus = fmt.Sprintf("⚠️ status phase %s", serverStatusRequestStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["SERVER_STATUS_REQUESTS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, serverStatusRequest.Name, serverStatusRequestStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["SERVER_STATUS_REQUESTS"] = "❌ No ServerStatusRequest was found in the cluster" + } +} + +func ReplaceNonAdminBackupStorageLocationRequestsSection(outputPath string, nonAdminBackupStorageLocationRequestList *nac1alpha1.NonAdminBackupStorageLocationRequestList) { + if nonAdminBackupStorageLocationRequestList != nil && len(nonAdminBackupStorageLocationRequestList.Items) != 0 { + nonAdminBackupStorageLocationRequestsByNamespace := map[string][]nac1alpha1.NonAdminBackupStorageLocationRequest{} + + for _, request := range nonAdminBackupStorageLocationRequestList.Items { + nonAdminBackupStorageLocationRequestsByNamespace[request.Namespace] = append(nonAdminBackupStorageLocationRequestsByNamespace[request.Namespace], request) + } + + summaryTemplateReplaces["NON_ADMIN_BACKUP_STORAGE_LOCATION_REQUESTS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, requests := range nonAdminBackupStorageLocationRequestsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/oadp.openshift.io/nonadminbackupstoragelocationrequests", namespace) + file := folder + "/nonadminbackupstoragelocationrequests.yaml" + for _, request := range requests { + request.GetObjectKind().SetGroupVersionKind(gvk.NonAdminBackupStorageLocationRequestGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &request}) + + requestStatus := "" + requestStatusPhase := request.Status.Phase + if len(requestStatusPhase) == 0 { + requestStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ NonAdminBackupStorageLocationRequest **%v** with **no status phase** in **%v** namespace\n\n", + request.Name, namespace, + ) + } else { + if requestStatusPhase == nac1alpha1.NonAdminBSLRequestPhaseApproved { + requestStatus = fmt.Sprintf("✅ status phase %s", requestStatusPhase) + } else if requestStatusPhase == nac1alpha1.NonAdminBSLRequestPhaseRejected { + requestStatus = fmt.Sprintf("❌ status phase %s", requestStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ NonAdminBackupStorageLocationRequest **%v** with **status phase %s** in **%v** namespace\n\n", + request.Name, requestStatusPhase, namespace, + ) + } else { + requestStatus = fmt.Sprintf("⚠️ status phase %s", requestStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["NON_ADMIN_BACKUP_STORAGE_LOCATION_REQUESTS"] += fmt.Sprintf( + "| %v | %v | %v | %s |\n", + namespace, request.Name, requestStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["NON_ADMIN_BACKUP_STORAGE_LOCATION_REQUESTS"] = "❌ No NonAdminBackupStorageLocationRequest was found in the cluster" + } +} + +func ReplaceNonAdminBackupStorageLocationsSection(outputPath string, nonAdminBackupStorageLocationList *nac1alpha1.NonAdminBackupStorageLocationList) { + if nonAdminBackupStorageLocationList != nil && len(nonAdminBackupStorageLocationList.Items) != 0 { + nonAdminBackupStorageLocationsByNamespace := map[string][]nac1alpha1.NonAdminBackupStorageLocation{} + + for _, backupStorageLocation := range nonAdminBackupStorageLocationList.Items { + nonAdminBackupStorageLocationsByNamespace[backupStorageLocation.Namespace] = append(nonAdminBackupStorageLocationsByNamespace[backupStorageLocation.Namespace], backupStorageLocation) + } + + summaryTemplateReplaces["NON_ADMIN_BACKUP_STORAGE_LOCATIONS"] += "| Namespace | Name | Approved | status.phase | yaml |\n| --- | --- | --- | --- | --- |\n" + for namespace, backupStorageLocations := range nonAdminBackupStorageLocationsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/oadp.openshift.io/nonadminbackupstoragelocations", namespace) + file := folder + "/nonadminbackupstoragelocations.yaml" + for _, backupStorageLocation := range backupStorageLocations { + backupStorageLocation.GetObjectKind().SetGroupVersionKind(gvk.NonAdminBackupStorageLocationGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &backupStorageLocation}) + + bslStatus := "" + bslStatusPhase := backupStorageLocation.Status.Phase + if len(bslStatusPhase) == 0 { + bslStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ NonAdminBackupStorageLocation **%v** with **no status phase** in **%v** namespace\n\n", + backupStorageLocation.Name, namespace, + ) + } else { + if bslStatusPhase == nac1alpha1.NonAdminPhaseCreated { + bslStatus = fmt.Sprintf("✅ status phase %s", bslStatusPhase) + } else if bslStatusPhase == nac1alpha1.NonAdminPhaseBackingOff { + bslStatus = fmt.Sprintf("❌ status phase %s", bslStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ NonAdminBackupStorageLocation **%v** with **status phase %s** in **%v** namespace\n\n", + backupStorageLocation.Name, bslStatusPhase, namespace, + ) + } else { + bslStatus = fmt.Sprintf("⚠️ status phase %s", bslStatusPhase) + } + } + bslStatusApproved := "" + conditionInNABSL := meta.FindStatusCondition(backupStorageLocation.Status.Conditions, string(nac1alpha1.NonAdminBSLConditionApproved)) + if conditionInNABSL == nil { + bslStatusApproved = "⚠️ no status condition approved" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ NonAdminBackupStorageLocation **%v** with **no status condition approved** in **%v** namespace\n\n", + backupStorageLocation.Name, namespace, + ) + } else { + if conditionInNABSL.Status == v1.ConditionTrue { + bslStatusApproved = fmt.Sprintf("✅ status condition approved %s", conditionInNABSL.Status) + } else { + bslStatusApproved = fmt.Sprintf("❌ status condition approved %s", conditionInNABSL.Status) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["NON_ADMIN_BACKUP_STORAGE_LOCATIONS"] += fmt.Sprintf( + "| %v | %v | %s | %v | %s |\n", + namespace, backupStorageLocation.Name, bslStatusApproved, bslStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["NON_ADMIN_BACKUP_STORAGE_LOCATIONS"] = "❌ No NonAdminBackupStorageLocation was found in the cluster" + } +} + +func ReplaceNonAdminBackupsSection(outputPath string, nonAdminBackupList *nac1alpha1.NonAdminBackupList) { + if nonAdminBackupList != nil && len(nonAdminBackupList.Items) != 0 { + backupsByNamespace := map[string][]nac1alpha1.NonAdminBackup{} + + for _, backup := range nonAdminBackupList.Items { + backupsByNamespace[backup.Namespace] = append(backupsByNamespace[backup.Namespace], backup) + } + + summaryTemplateReplaces["NON_ADMIN_BACKUPS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, backups := range backupsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/oadp.openshift.io/nonadminbackups", namespace) + file := folder + "/nonadminbackups.yaml" + for _, backup := range backups { + backup.GetObjectKind().SetGroupVersionKind(gvk.NonAdminBackupGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &backup}) + + backupStatus := "" + backupStatusPhase := backup.Status.Phase + if len(backupStatusPhase) == 0 { + backupStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ NonAdminBackup **%v** with **no status phase** in **%v** namespace\n\n", + backup.Name, namespace, + ) + } else { + if backupStatusPhase == nac1alpha1.NonAdminPhaseCreated { + backupStatus = fmt.Sprintf("✅ status phase %s", backupStatusPhase) + } else if backupStatusPhase == nac1alpha1.NonAdminPhaseBackingOff { + backupStatus = fmt.Sprintf("❌ status phase %s", backupStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ NonAdminBackup **%v** with **status phase %s** in **%v** namespace\n\n", + backup.Name, backupStatusPhase, namespace, + ) + } else { + backupStatus = fmt.Sprintf("⚠️ status phase %s", backupStatusPhase) + } + } + + yamlLink := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["NON_ADMIN_BACKUPS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, backup.Name, + backupStatus, + yamlLink, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["NON_ADMIN_BACKUPS"] = "❌ No NonAdminBackup was found in the cluster" + } +} + +func ReplaceNonAdminRestoresSection(outputPath string, nonAdminRestoreList *nac1alpha1.NonAdminRestoreList) { + if nonAdminRestoreList != nil && len(nonAdminRestoreList.Items) != 0 { + restoresByNamespace := map[string][]nac1alpha1.NonAdminRestore{} + + for _, restore := range nonAdminRestoreList.Items { + restoresByNamespace[restore.Namespace] = append(restoresByNamespace[restore.Namespace], restore) + } + + summaryTemplateReplaces["NON_ADMIN_RESTORES"] += "| Namespace | Name | status.phase | describe | logs | yaml |\n| --- | --- | --- | --- | --- | --- |\n" + for namespace, restores := range restoresByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/oadp.openshift.io/nonadminrestores", namespace) + file := folder + "/nonadminrestores.yaml" + for _, restore := range restores { + restore.GetObjectKind().SetGroupVersionKind(gvk.NonAdminRestoreGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &restore}) + + restoreStatus := "" + restoreStatusPhase := restore.Status.Phase + if len(restoreStatusPhase) == 0 { + restoreStatus = "⚠️ no status phase" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ NonAdminRestore **%v** with **no status phase** in **%v** namespace\n\n", + restore.Name, namespace, + ) + } else { + if restoreStatusPhase == nac1alpha1.NonAdminPhaseCreated { + restoreStatus = fmt.Sprintf("✅ status phase %s", restoreStatusPhase) + } else if restoreStatusPhase == nac1alpha1.NonAdminPhaseBackingOff { + restoreStatus = fmt.Sprintf("❌ status phase %s", restoreStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ NonAdminRestore **%v** with **status phase %s** in **%v** namespace\n\n", + restore.Name, restoreStatusPhase, namespace, + ) + } else { + restoreStatus = fmt.Sprintf("⚠️ status phase %s", restoreStatusPhase) + } + } + + yamllink := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["NON_ADMIN_RESTORES"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, restore.Name, + restoreStatus, + yamllink, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["NON_ADMIN_RESTORES"] = "❌ No NonAdminRestore was found in the cluster" + } +} + +func ReplaceNonAdminDownloadRequestsSection(outputPath string, nonAdminDownloadRequestList *nac1alpha1.NonAdminDownloadRequestList) { + if nonAdminDownloadRequestList != nil && len(nonAdminDownloadRequestList.Items) != 0 { + downloadRequestsByNamespace := map[string][]nac1alpha1.NonAdminDownloadRequest{} + + for _, downloadRequest := range nonAdminDownloadRequestList.Items { + downloadRequestsByNamespace[downloadRequest.Namespace] = append(downloadRequestsByNamespace[downloadRequest.Namespace], downloadRequest) + } + + summaryTemplateReplaces["NON_ADMIN_DOWNLOAD_REQUESTS"] += "| Namespace | Name | status.phase | yaml |\n| --- | --- | --- | --- |\n" + for namespace, downloadRequests := range downloadRequestsByNamespace { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + folder := fmt.Sprintf("namespaces/%s/oadp.openshift.io/nonadmindownloadrequests", namespace) + file := folder + "/nonadmindownloadrequests.yaml" + for _, downloadRequest := range downloadRequests { + downloadRequest.GetObjectKind().SetGroupVersionKind(gvk.NonAdminDownloadRequestGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &downloadRequest}) + + downloadRequestStatus := "" + downloadRequestStatusPhase := downloadRequest.Status.Phase + if len(downloadRequestStatusPhase) == 0 { + downloadRequestStatus = "⚠️ no status" + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "⚠️ NonAdminDownloadRequest **%v** with **no status** in **%v** namespace\n\n", + downloadRequest.Name, namespace, + ) + } else { + if downloadRequestStatusPhase == nac1alpha1.NonAdminPhaseCreated { + downloadRequestStatus = fmt.Sprintf("✅ status phase %s", downloadRequestStatusPhase) + } else if downloadRequestStatusPhase == nac1alpha1.NonAdminPhaseBackingOff { + downloadRequestStatus = fmt.Sprintf("❌ status phase %s", downloadRequestStatusPhase) + summaryTemplateReplaces["ERRORS"] += fmt.Sprintf( + "❌ NonAdminDownloadRequest **%v** with **status phase %s** in **%v** namespace\n\n", + downloadRequest.Name, downloadRequestStatusPhase, namespace, + ) + } else { + downloadRequestStatus = fmt.Sprintf("⚠️ status phase %s", downloadRequestStatusPhase) + } + } + + link := fmt.Sprintf("[`yaml`](%s)", file) + summaryTemplateReplaces["NON_ADMIN_DOWNLOAD_REQUESTS"] += fmt.Sprintf( + "| %v | %v | %s | %s |\n", + namespace, downloadRequest.Name, downloadRequestStatus, link, + ) + } + + createYAML(outputPath, file, list) + } + } else { + summaryTemplateReplaces["NON_ADMIN_DOWNLOAD_REQUESTS"] = "❌ No NonAdminDownloadRequest was found in the cluster" + } +} + +func ReplaceAvailableStorageClassesSection(outputPath string, storageClassList *storagev1.StorageClassList) { + if storageClassList != nil && len(storageClassList.Items) != 0 { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + for _, storageClass := range storageClassList.Items { + storageClass.GetObjectKind().SetGroupVersionKind(gvk.StorageClassGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &storageClass}) + } + summaryTemplateReplaces["STORAGE_CLASSES"] = createYAML(outputPath, "cluster-scoped-resources/storage.k8s.io/storageclasses/storageclasses.yaml", list) + } else { + summaryTemplateReplaces["STORAGE_CLASSES"] = "❌ No StorageClass was found in the cluster" + summaryTemplateReplaces["ERRORS"] += "⚠️ No StorageClass was found in the cluster\n\n" + } +} + +func ReplaceAvailableVolumeSnapshotClassesSection(outputPath string, volumeSnapshotClassList *volumesnapshotv1.VolumeSnapshotClassList) { + if volumeSnapshotClassList != nil && len(volumeSnapshotClassList.Items) != 0 { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + for _, volumeSnapshotClass := range volumeSnapshotClassList.Items { + volumeSnapshotClass.GetObjectKind().SetGroupVersionKind(gvk.VolumeSnapshotClassGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &volumeSnapshotClass}) + } + summaryTemplateReplaces["VOLUME_SNAPSHOT_CLASSES"] = createYAML(outputPath, "cluster-scoped-resources/snapshot.storage.k8s.io/volumesnapshotclasses/volumesnapshotclasses.yaml", list) + } else { + summaryTemplateReplaces["VOLUME_SNAPSHOT_CLASSES"] = "❌ No VolumeSnapshotClass was found in the cluster" + summaryTemplateReplaces["ERRORS"] += "⚠️ No VolumeSnapshotClass was found in the cluster\n\n" + } +} + +func ReplaceAvailableCSIDriversSection(outputPath string, csiDriverList *storagev1.CSIDriverList) { + if csiDriverList != nil && len(csiDriverList.Items) != 0 { + list := &corev1.List{} + list.GetObjectKind().SetGroupVersionKind(gvk.ListGVK) + + for _, csiDriver := range csiDriverList.Items { + csiDriver.GetObjectKind().SetGroupVersionKind(gvk.CSIDriverGVK) + list.Items = append(list.Items, runtime.RawExtension{Object: &csiDriver}) + } + summaryTemplateReplaces["CSI_DRIVERS"] = createYAML(outputPath, "cluster-scoped-resources/storage.k8s.io/csidrivers/csidrivers.yaml", list) + } else { + summaryTemplateReplaces["CSI_DRIVERS"] = "❌ No CSIDriver was found in the cluster" + summaryTemplateReplaces["ERRORS"] += "⚠️ No CSIDriver was found in the cluster\n\n" + } +} + +func ReplaceCustomResourceDefinitionsSection(outputPath string, clusterConfig *rest.Config) { + errorMessage := "❌ Unable to write CustomResourceDefinitions section: " + + client, err := apiextensionsclientset.NewForConfig(clusterConfig) + if err != nil { + summaryTemplateReplaces["ERRORS"] += errorMessage + err.Error() + "\n\n" + summaryTemplateReplaces["CUSTOM_RESOURCE_DEFINITION"] = errorMessage + err.Error() + return + } + + crdsPath := "cluster-scoped-resources/apiextensions.k8s.io/customresourcedefinitions" + + // CRD spec.names.plural : CRD spec.group + crds := map[string]string{ + "dataprotectionapplications": gvk.DataProtectionApplicationGVK.Group, + "cloudstorages": gvk.CloudStorageGVK.Group, + "backupstoragelocations": gvk.BackupStorageLocationGVK.Group, + "volumesnapshotlocations": gvk.VolumeSnapshotLocationGVK.Group, + "backups": gvk.BackupGVK.Group, + "restores": gvk.RestoreGVK.Group, + "schedules": gvk.ScheduleGVK.Group, + "backuprepositories": gvk.BackupRepositoryGVK.Group, + "datauploads": gvk.DataUploadGVK.Group, + "datadownloads": gvk.DataDownloadGVK.Group, + "podvolumebackups": gvk.PodVolumeBackupGVK.Group, + "podvolumerestores": gvk.PodVolumeRestoreGVK.Group, + "downloadrequests": gvk.DownloadRequestGVK.Group, + "deletebackuprequests": gvk.DeleteBackupRequestGVK.Group, + "serverstatusrequests": gvk.ServerStatusRequestGVK.Group, + "nonadminbackupstoragelocationrequests": gvk.NonAdminBackupStorageLocationRequestGVK.Group, + "nonadminbackupstoragelocations": gvk.NonAdminBackupStorageLocationGVK.Group, + "nonadminbackups": gvk.NonAdminBackupGVK.Group, + "nonadminrestores": gvk.NonAdminRestoreGVK.Group, + "nonadmindownloadrequests": gvk.NonAdminDownloadRequestGVK.Group, + "clusterserviceversions": gvk.ClusterServiceVersionGVK.Group, + "subscriptions": gvk.SubscriptionsGVK.Group, + } + + for crdName, crdGroup := range crds { + crd, err := client.ApiextensionsV1().CustomResourceDefinitions().Get(context.Background(), crdName+"."+crdGroup, v1.GetOptions{}) + if err != nil { + summaryTemplateReplaces["ERRORS"] += errorMessage + err.Error() + "\n\n" + summaryTemplateReplaces["CUSTOM_RESOURCE_DEFINITION"] += errorMessage + err.Error() + "\n\n" + continue + } + crd.GetObjectKind().SetGroupVersionKind(gvk.CustomResourceDefinitionGVK) + createYAML(outputPath, crdsPath+fmt.Sprintf("/%s.yaml", crdName), crd) + } + + summaryTemplateReplaces["CUSTOM_RESOURCE_DEFINITION"] += fmt.Sprintf("For more information, check [`%s`](%s)\n\n", crdsPath, crdsPath) +} + +func createYAML(outputPath string, yamlPath string, obj runtime.Object) string { + objFilePath := outputPath + yamlPath + dir := path.Dir(objFilePath) + err := os.MkdirAll(dir, FolderPermission) + if err != nil { + return "❌ Unable to create dir " + dir + } + result := "" + newFile, err := os.Create(objFilePath) + if err != nil { + fmt.Println(err) + result = "❌ Unable to create file " + objFilePath + } else { + printer := printers.YAMLPrinter{} + err = printer.PrintObj(obj, newFile) + if err != nil { + fmt.Println(err) + result = "❌ Unable to write " + objFilePath + } else { + result = fmt.Sprintf("For more information, check [`%s`](%s)\n\n", yamlPath, yamlPath) + } + } + defer newFile.Close() + return result +} + +func createFile(outputPath string, describePath string, describeOutput string, describeTitle string) string { + describeFilePath := outputPath + describePath + dir := path.Dir(describeFilePath) + err := os.MkdirAll(dir, FolderPermission) + if err != nil { + return "❌ Unable to create dir " + dir + } + result := "" + newFile, err := os.Create(describeFilePath) + if err != nil { + fmt.Println(err) + result = "❌ Unable to create file " + describeFilePath + } else { + err := os.WriteFile(describeFilePath, []byte(describeOutput), FilePermission) + if err != nil { + fmt.Println(err) + result = "❌ Unable to write " + describeFilePath + } else { + result = fmt.Sprintf("[`"+describeTitle+"`](%s)", describePath) + } + } + defer newFile.Close() + return result +} + +func Write(outputPath string) error { + if len(summaryTemplateReplaces["ERRORS"]) == 0 { + summaryTemplateReplaces["ERRORS"] += "No errors happened or were found while running OADP must-gather\n\n" + } + + summary := summaryTemplate + for _, key := range summaryTemplateReplacesKeys { + value, ok := summaryTemplateReplaces[key] + if !ok { + return fmt.Errorf("key '%s' not set in SummaryTemplateReplaces", key) + } + if len(value) == 0 { + return fmt.Errorf("value for key '%s' not set in SummaryTemplateReplaces", key) + } + summary = strings.ReplaceAll( + summary, + fmt.Sprintf("<<%s>>", key), + value, + ) + } + + summaryPath := outputPath + "oadp-must-gather-summary.md" + sumary, err := os.Create(summaryPath) + if err != nil { + return err + } + err = os.WriteFile(summaryPath, []byte(summary), FilePermission) + if err != nil { + return err + } + defer sumary.Close() + return nil +} diff --git a/tests/e2e/backup_restore_suite_test.go b/tests/e2e/backup_restore_suite_test.go index 48294feaf74..60cdf6fa366 100644 --- a/tests/e2e/backup_restore_suite_test.go +++ b/tests/e2e/backup_restore_suite_test.go @@ -29,10 +29,8 @@ type BackupRestoreCase struct { type ApplicationBackupRestoreCase struct { BackupRestoreCase - ApplicationTemplate string - PvcSuffixName string - MustGatherFiles []string // list of files expected in must-gather under quay.io.../clusters/clustername/... ie. "namespaces/openshift-adp/oadp.openshift.io/dpa-ts-example-velero/ts-example-velero.yml" - MustGatherValidationFunction *func(string) error // validation function for must-gather where string parameter is the path to "quay.io.../clusters/clustername/" + ApplicationTemplate string + PvcSuffixName string } func todoListReady(preBackupState bool, twoVol bool, database string) VerificationFunction { diff --git a/tests/e2e/e2e_suite_test.go b/tests/e2e/e2e_suite_test.go index 00f0c0fdd60..82c6c043789 100644 --- a/tests/e2e/e2e_suite_test.go +++ b/tests/e2e/e2e_suite_test.go @@ -206,6 +206,12 @@ var _ = ginkgo.BeforeSuite(func() { }) var _ = ginkgo.AfterSuite(func() { + // TODO run OADP must-gather + // log.Printf("Running OADP must-gather") + // err := lib.RunMustGather(oc_cli, artifact_dir) + // gomega.Expect(err).ToNot(gomega.HaveOccurred()) + // TODO validate that everything was collected? + log.Printf("Deleting Secrets") err := lib.DeleteSecret(kubernetesClientForSuiteRun, namespace, vslSecretName) gomega.Expect(err).ToNot(gomega.HaveOccurred()) diff --git a/tests/e2e/lib/apps.go b/tests/e2e/lib/apps.go index d89b30c273e..6c33a52d43a 100755 --- a/tests/e2e/lib/apps.go +++ b/tests/e2e/lib/apps.go @@ -352,6 +352,8 @@ func RunMustGather(oc_cli string, artifact_dir string) error { ocClient := oc_cli ocAdmin := "adm" mustGatherCmd := "must-gather" + // TODO update image + // need to reopen https://github.com/openshift/release/pull/55587 mustGatherImg := "--image=quay.io/konveyor/oadp-must-gather:latest" destDir := "--dest-dir=" + artifact_dir diff --git a/tests/e2e/must-gather_suite_test.go b/tests/e2e/must-gather_suite_test.go deleted file mode 100644 index 8eb74be5a2a..00000000000 --- a/tests/e2e/must-gather_suite_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package e2e_test - -import ( - "log" - "os" - "strings" - "time" - - "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" - - "github.com/openshift/oadp-operator/tests/e2e/lib" -) - -var _ = ginkgo.Describe("Backup and restore tests with must-gather", func() { - var lastBRCase ApplicationBackupRestoreCase - var lastInstallTime time.Time - updateLastBRcase := func(brCase ApplicationBackupRestoreCase) { - lastBRCase = brCase - } - updateLastInstallTime := func() { - lastInstallTime = time.Now() - } - - var _ = ginkgo.AfterEach(func(ctx ginkgo.SpecContext) { - tearDownBackupAndRestore(lastBRCase.BackupRestoreCase, lastInstallTime, ctx.SpecReport()) - }) - - ginkgo.DescribeTable("Backup and restore applications and run must-gather", - func(brCase ApplicationBackupRestoreCase, expectedErr error) { - if ginkgo.CurrentSpecReport().NumAttempts > 1 && !knownFlake { - ginkgo.Fail("No known FLAKE found in a previous run, marking test as failed.") - } - runApplicationBackupAndRestore(brCase, updateLastBRcase, updateLastInstallTime) - - baseReportDir := artifact_dir + "/" + brCase.Name - err := os.MkdirAll(baseReportDir, 0755) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - log.Printf("Running must gather for backup/restore test - " + brCase.Name) - err = lib.RunMustGather(oc_cli, baseReportDir+"/must-gather") - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - // get dirs in must-gather dir - dirEntries, err := os.ReadDir(baseReportDir + "/must-gather") - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - clusterDir := "" - for _, dirEntry := range dirEntries { - if dirEntry.IsDir() && strings.HasPrefix(dirEntry.Name(), "quay-io") { - mustGatherImageDir := baseReportDir + "/must-gather/" + dirEntry.Name() - // extract must-gather.tar.gz - err = lib.ExtractTarGz(mustGatherImageDir, "must-gather.tar.gz") - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - mustGatherDir := mustGatherImageDir + "/must-gather" - clusters, err := os.ReadDir(mustGatherDir + "/clusters") - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - for _, cluster := range clusters { - if cluster.IsDir() { - clusterDir = mustGatherDir + "/clusters/" + cluster.Name() - } - } - } - } - if len(brCase.MustGatherFiles) > 0 && clusterDir != "" { - for _, file := range brCase.MustGatherFiles { - _, err := os.Stat(clusterDir + "/" + file) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - } - } - if brCase.MustGatherValidationFunction != nil && clusterDir != "" { - err = (*brCase.MustGatherValidationFunction)(clusterDir) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - } - }, - ginkgo.Entry("Mongo application DATAMOVER", ginkgo.FlakeAttempts(flakeAttempts), ApplicationBackupRestoreCase{ - ApplicationTemplate: "./sample-applications/mongo-persistent/mongo-persistent-csi.yaml", - BackupRestoreCase: BackupRestoreCase{ - Namespace: "mongo-persistent", - Name: "mongo-datamover-e2e", - BackupRestoreType: lib.CSIDataMover, - PreBackupVerify: todoListReady(true, false, "mongo"), - PostRestoreVerify: todoListReady(false, false, "mongo"), - BackupTimeout: 20 * time.Minute, - }, - MustGatherFiles: []string{ - "namespaces/" + namespace + "/oadp.openshift.io/dpa-ts-" + instanceName + "/ts-" + instanceName + ".yml", - "namespaces/" + namespace + "/velero.io/backupstoragelocations.velero.io/ts-" + instanceName + "-1.yaml", - }, - }, nil), - ) -})