From 338ad7f4e3577aa7c9b5be293a97689d84a28442 Mon Sep 17 00:00:00 2001 From: John Collier <jcollier@redhat.com> Date: Fri, 7 Jun 2024 20:10:06 -0400 Subject: [PATCH] Remove HAS controllers (#481) * Remove HAS controllers Signed-off-by: John Collier <jcollier@redhat.com> * Update dockerfile Signed-off-by: John Collier <jcollier@redhat.com> * Fix tests Signed-off-by: John Collier <jcollier@redhat.com> * Fix tests Signed-off-by: John Collier <jcollier@redhat.com> * Fix entrypoint Signed-off-by: John Collier <jcollier@redhat.com> --------- Signed-off-by: John Collier <jcollier@redhat.com> --- .../workflows/build-cdq-analysis-image.yml | 41 - .github/workflows/pact_postmerge.yml | 67 - .github/workflows/pr.yml | 70 - .tekton/push-cdq-analysis.yaml | 30 - Dockerfile | 15 +- Makefile | 4 - PROJECT | 24 - cdq-analysis/Dockerfile | 27 - cdq-analysis/entrypoint.sh | 6 - cdq-analysis/go.mod | 150 - cdq-analysis/go.sum | 625 --- cdq-analysis/main.go | 128 - cdq-analysis/pkg/componentdetectionquery.go | 340 -- .../pkg/componentdetectionquery_test.go | 422 --- cdq-analysis/pkg/detect.go | 397 -- cdq-analysis/pkg/detect_mock.go | 268 -- cdq-analysis/pkg/detect_test.go | 268 -- cdq-analysis/pkg/devfile.go | 356 -- cdq-analysis/pkg/devfile_test.go | 652 ---- cdq-analysis/pkg/errors.go | 113 - cdq-analysis/pkg/errors_test.go | 87 - cdq-analysis/pkg/ioutils.go | 58 - cdq-analysis/pkg/ioutils_test.go | 155 - cdq-analysis/pkg/mock.go | 68 - cdq-analysis/pkg/util.go | 281 -- cdq-analysis/pkg/util_test.go | 637 ---- contracts/pact_state_handlers_methods.go | 174 - contracts/pact_state_params.go | 60 - contracts/pact_states.go | 29 - contracts/pact_test.go | 137 - contracts/pact_utils.go | 69 - controllers/application_controller.go | 267 -- .../application_controller_conditions.go | 101 - controllers/application_controller_test.go | 197 - controllers/component_controller.go | 532 --- .../component_controller_conditions.go | 113 - controllers/component_controller_test.go | 2265 ----------- .../componentdetectionquery_controller.go | 546 --- ...entdetectionquery_controller_conditions.go | 95 - ...tectionquery_controller_conditions_test.go | 181 - ...componentdetectionquery_controller_test.go | 3357 ----------------- controllers/errors.go | 30 - controllers/mapper.go | 43 - controllers/mapper_test.go | 139 - controllers/start_test_env.go | 146 - controllers/suite_test.go | 46 - controllers/update.go | 689 ---- controllers/update_test.go | 2342 ------------ entrypoint.sh | 3 +- gitops/generate_build.go | 178 - gitops/generate_build_test.go | 458 --- gitops/prepare/prepare.go | 57 - gitops/prepare/prepare_test.go | 135 - go.mod | 122 +- go.sum | 1019 +---- main.go | 109 +- pkg/availability/git.go | 71 - pkg/availability/git_test.go | 41 - pkg/devfile/constants.go | 48 - pkg/devfile/devfile.go | 371 -- pkg/devfile/devfile_test.go | 790 ---- pkg/devfile/errors.go | 61 - pkg/devfile/errors_test.go | 55 - pkg/devfile/utils.go | 36 - pkg/devfile/utils_test.go | 69 - pkg/github/errors.go | 34 - pkg/github/github.go | 173 - pkg/github/github_test.go | 522 --- pkg/github/mock.go | 312 -- pkg/github/token.go | 250 -- pkg/github/token_mock.go | 69 - pkg/github/token_test.go | 325 -- pkg/log/log.go | 47 - pkg/metrics/application.go | 60 - pkg/metrics/component.go | 115 - pkg/metrics/component_test.go | 93 - pkg/metrics/git.go | 84 - pkg/metrics/gitops.go | 40 - pkg/metrics/metrics.go | 42 - pkg/spi/spi.go | 167 - pkg/spi/spi_mock.go | 201 - pkg/spi/spi_test.go | 136 - pkg/util/ioutils/ioutils.go | 70 - pkg/util/ioutils/ioutils_test.go | 201 - pkg/util/util.go | 75 - pkg/util/util_test.go | 169 - .../application_webhook.go | 0 .../application_webhook_test.go | 0 .../application_webhook_unit_test.go | 0 .../component_webhook.go | 0 .../component_webhook_test.go | 0 .../component_webhook_unit_test.go | 0 .../webhook_suite_test.go | 2 +- .../webhooks => webhooks}/webhooks.go | 0 94 files changed, 11 insertions(+), 23646 deletions(-) delete mode 100644 .github/workflows/build-cdq-analysis-image.yml delete mode 100644 .github/workflows/pact_postmerge.yml delete mode 100644 .tekton/push-cdq-analysis.yaml delete mode 100644 cdq-analysis/Dockerfile delete mode 100755 cdq-analysis/entrypoint.sh delete mode 100644 cdq-analysis/go.mod delete mode 100644 cdq-analysis/go.sum delete mode 100644 cdq-analysis/main.go delete mode 100644 cdq-analysis/pkg/componentdetectionquery.go delete mode 100644 cdq-analysis/pkg/componentdetectionquery_test.go delete mode 100644 cdq-analysis/pkg/detect.go delete mode 100644 cdq-analysis/pkg/detect_mock.go delete mode 100644 cdq-analysis/pkg/detect_test.go delete mode 100644 cdq-analysis/pkg/devfile.go delete mode 100644 cdq-analysis/pkg/devfile_test.go delete mode 100644 cdq-analysis/pkg/errors.go delete mode 100644 cdq-analysis/pkg/errors_test.go delete mode 100644 cdq-analysis/pkg/ioutils.go delete mode 100644 cdq-analysis/pkg/ioutils_test.go delete mode 100644 cdq-analysis/pkg/mock.go delete mode 100644 cdq-analysis/pkg/util.go delete mode 100644 cdq-analysis/pkg/util_test.go delete mode 100644 contracts/pact_state_handlers_methods.go delete mode 100644 contracts/pact_state_params.go delete mode 100644 contracts/pact_states.go delete mode 100644 contracts/pact_test.go delete mode 100644 contracts/pact_utils.go delete mode 100644 controllers/application_controller.go delete mode 100644 controllers/application_controller_conditions.go delete mode 100644 controllers/application_controller_test.go delete mode 100644 controllers/component_controller.go delete mode 100644 controllers/component_controller_conditions.go delete mode 100644 controllers/component_controller_test.go delete mode 100644 controllers/componentdetectionquery_controller.go delete mode 100644 controllers/componentdetectionquery_controller_conditions.go delete mode 100644 controllers/componentdetectionquery_controller_conditions_test.go delete mode 100644 controllers/componentdetectionquery_controller_test.go delete mode 100644 controllers/errors.go delete mode 100644 controllers/mapper.go delete mode 100644 controllers/mapper_test.go delete mode 100644 controllers/start_test_env.go delete mode 100644 controllers/suite_test.go delete mode 100644 controllers/update.go delete mode 100644 controllers/update_test.go delete mode 100644 gitops/generate_build.go delete mode 100644 gitops/generate_build_test.go delete mode 100644 gitops/prepare/prepare.go delete mode 100644 gitops/prepare/prepare_test.go delete mode 100644 pkg/availability/git.go delete mode 100644 pkg/availability/git_test.go delete mode 100644 pkg/devfile/constants.go delete mode 100644 pkg/devfile/devfile.go delete mode 100644 pkg/devfile/devfile_test.go delete mode 100644 pkg/devfile/errors.go delete mode 100644 pkg/devfile/errors_test.go delete mode 100644 pkg/devfile/utils.go delete mode 100644 pkg/devfile/utils_test.go delete mode 100644 pkg/github/errors.go delete mode 100644 pkg/github/github.go delete mode 100644 pkg/github/github_test.go delete mode 100644 pkg/github/mock.go delete mode 100644 pkg/github/token.go delete mode 100644 pkg/github/token_mock.go delete mode 100644 pkg/github/token_test.go delete mode 100644 pkg/log/log.go delete mode 100644 pkg/metrics/application.go delete mode 100644 pkg/metrics/component.go delete mode 100644 pkg/metrics/component_test.go delete mode 100644 pkg/metrics/git.go delete mode 100644 pkg/metrics/gitops.go delete mode 100644 pkg/metrics/metrics.go delete mode 100644 pkg/spi/spi.go delete mode 100644 pkg/spi/spi_mock.go delete mode 100644 pkg/spi/spi_test.go delete mode 100644 pkg/util/ioutils/ioutils.go delete mode 100644 pkg/util/ioutils/ioutils_test.go rename {controllers/webhooks => webhooks}/application_webhook.go (100%) rename {controllers/webhooks => webhooks}/application_webhook_test.go (100%) rename {controllers/webhooks => webhooks}/application_webhook_unit_test.go (100%) rename {controllers/webhooks => webhooks}/component_webhook.go (100%) rename {controllers/webhooks => webhooks}/component_webhook_test.go (100%) rename {controllers/webhooks => webhooks}/component_webhook_unit_test.go (100%) rename {controllers/webhooks => webhooks}/webhook_suite_test.go (98%) rename {controllers/webhooks => webhooks}/webhooks.go (100%) diff --git a/.github/workflows/build-cdq-analysis-image.yml b/.github/workflows/build-cdq-analysis-image.yml deleted file mode 100644 index 2842b37a9..000000000 --- a/.github/workflows/build-cdq-analysis-image.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Build CDQ Analysis Container Image - -on: - push: - branches: [ main ] - -jobs: - build-image: - runs-on: ubuntu-20.04 - steps: - - name: Checkout application-service source code - uses: actions/checkout@v2 - - name: Change to the cdq-analysis directory - run: cd cdq-analysis/ - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: | - quay.io/redhat-appstudio/cdq-analysis - tags: | - next - type=sha - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.QUAY_USERNAME }} - password: ${{ secrets.QUAY_PASSWORD }} - registry: quay.io - repository: redhat-appstudio/cdq-analysis - - name: Docker Build & Push - application-service Operator Image - uses: docker/build-push-action@v3 - with: - dockerfile: Dockerfile - platforms: linux/amd64,linux/ppc64le - push: true - tags: ${{ steps.meta.outputs.tags }} \ No newline at end of file diff --git a/.github/workflows/pact_postmerge.yml b/.github/workflows/pact_postmerge.yml deleted file mode 100644 index 32b4ca892..000000000 --- a/.github/workflows/pact_postmerge.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Publish Pact results - -on: - push: - branches: [ main ] -jobs: - pact: - name: Run Pact tests and push verification results - runs-on: ubuntu-latest - env: - OPERATOR_SDK_VERSION: v1.14.0 - steps: - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: 1.19 - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Cache Operator SDK ${{ env.OPERATOR_SDK_VERSION }} - uses: actions/cache@v2 - id: cache-operator-sdk - with: - path: ~/cache - key: operator-sdk-${{ env.OPERATOR_SDK_VERSION }} - - name: Download Operator SDK ${{ env.OPERATOR_SDK_VERSION }} - if: steps.cache-operator-sdk.outputs.cache-hit != 'true' - run: | - mkdir -p ~/cache - wget https://github.com/operator-framework/operator-sdk/releases/download/${OPERATOR_SDK_VERSION}/operator-sdk_linux_amd64 -O ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} > /dev/null -O ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} > /dev/null - chmod +x ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} - - name: Install Operator SDK ${{ env.OPERATOR_SDK_VERSION }} - run: | - mkdir -p ~/bin - cp ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} ~/bin/operator-sdk - echo "$HOME/bin" >> $GITHUB_PATH - - name: Cache go modules - id: cache-mod - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: Download dependencies - run: go mod download - if: steps.cache-mod.outputs.cache-hit != 'true' - - name: Test Pact contracts - shell: bash - env: - PACT_BROKER_PASSWORD: ${{ secrets.PACT_BROKER_PASSWORD }} - PACT_BROKER_USERNAME: ${{ secrets.PACT_BROKER_USERNAME }} - PROVIDER_BRANCH: "main" - run: | - go get github.com/pact-foundation/pact-go/v2@2.x.x - go install github.com/pact-foundation/pact-go/v2@2.x.x - sudo /home/runner/go/bin/pact-go -l DEBUG install - echo "Running Pact tests from the \"$(git branch --show-current)\" brach." - # Run Pact tests and publish results. Required variables to be set: - # COMMIT_SHA sets the version - # PROVIDER_BRANCH sets the branch tag - # PACT_BROKER_PASSWORD login info with push rights - # PACT_BROKER_USERNAME login info with push rights - - export COMMIT_SHA=$(git rev-parse --short HEAD) - make pact diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index df3f84bcb..301deef70 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -127,76 +127,6 @@ jobs: fetch-depth: 0 - name: Check if dockerimage build is working run: docker build -f ./Dockerfile . - build-cdq-analysis-image: - name: Check CDQ Analysis Image Build - runs-on: ubuntu-latest - steps: - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: 1.18 - - name: Change to the cdq-analysis directory - run: cd cdq-analysis/ - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Check if dockerimage build is working - run: docker build -f ./Dockerfile . - pact: - name: Pact tests - runs-on: ubuntu-latest - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - COMMIT_SHA: ${{ github.event.pull_request.head.sha }} - PR_CHECK: true - OPERATOR_SDK_VERSION: v1.14.0 - steps: - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: 1.19 - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Cache Operator SDK ${{ env.OPERATOR_SDK_VERSION }} - uses: actions/cache@v2 - id: cache-operator-sdk - with: - path: ~/cache - key: operator-sdk-${{ env.OPERATOR_SDK_VERSION }} - - name: Download Operator SDK ${{ env.OPERATOR_SDK_VERSION }} - if: steps.cache-operator-sdk.outputs.cache-hit != 'true' - run: | - mkdir -p ~/cache - wget https://github.com/operator-framework/operator-sdk/releases/download/${OPERATOR_SDK_VERSION}/operator-sdk_linux_amd64 -O ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} > /dev/null -O ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} > /dev/null - chmod +x ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} - - name: Install Operator SDK ${{ env.OPERATOR_SDK_VERSION }} - run: | - mkdir -p ~/bin - cp ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} ~/bin/operator-sdk - echo "$HOME/bin" >> $GITHUB_PATH - - name: Cache go modules - id: cache-mod - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: Download dependencies - run: go mod download - if: steps.cache-mod.outputs.cache-hit != 'true' - - name: Test Pact contracts - run: | - go get github.com/pact-foundation/pact-go/v2@2.x.x - go install github.com/pact-foundation/pact-go/v2@2.x.x - sudo /home/runner/go/bin/pact-go -l DEBUG install - COMMIT_SHA=${COMMIT_SHA:0:7} - make pact kube-linter: runs-on: ubuntu-latest steps: diff --git a/.tekton/push-cdq-analysis.yaml b/.tekton/push-cdq-analysis.yaml deleted file mode 100644 index 1b6a5770d..000000000 --- a/.tekton/push-cdq-analysis.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: tekton.dev/v1beta1 -kind: PipelineRun -metadata: - name: pipeline-to-push-cdq-image - annotations: - pipelinesascode.tekton.dev/on-cel-expression: | - event == "push" && target_branch == "main" && "cdq-analysis/***".pathChanged() - pipelinesascode.tekton.dev/max-keep-runs: "2" -spec: - params: - - name: git-url - value: "{{repo_url}}" - - name: revision - value: "{{revision}}" - - name: output-image - value: "quay.io/redhat-appstudio/cdq-analysis:{{revision}}" - - name: path-context - value: "cdq-analysis" - pipelineRef: - name: docker-build - bundle: quay.io/redhat-appstudio-tekton-catalog/pipeline-core-services-docker-build:latest - workspaces: - - name: workspace - volumeClaimTemplate: - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi diff --git a/Dockerfile b/Dockerfile index ec824e857..8a8e3fb6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum -COPY cdq-analysis/ cdq-analysis/ # cache deps before building and copying source so that we don't need to re-download as much # and so that source changes don't invalidate our downloaded layer RUN go mod download @@ -13,22 +12,12 @@ RUN go mod download # Copy the go source COPY main.go main.go # ToDo: Uncomment once API added -COPY controllers/ controllers/ +COPY webhooks/ webhooks/ COPY pkg pkg/ -COPY gitops gitops/ # Build RUN CGO_ENABLED=0 GOOS=linux go build -a -o manager main.go -# Build the tini binary -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 as tini-builder -RUN microdnf update --setopt=install_weak_deps=0 -y && microdnf install git cmake make gcc gcc-c++ -# build tini -RUN git clone --branch v0.19.0 https://github.com/krallin/tini /tini -WORKDIR /tini -ENV CFLAGS="-DPR_SET_CHILD_SUBREAPER=36 -DPR_GET_CHILD_SUBREAPER=37" -RUN cmake . && make tini - FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 RUN microdnf update --setopt=install_weak_deps=0 -y && microdnf install git COPY entrypoint.sh /usr/local/bin/entrypoint.sh @@ -48,8 +37,6 @@ COPY --from=builder /workspace/manager . COPY appdata.gitconfig /.gitconfig RUN chgrp -R 0 /.gitconfig && chmod -R g=u /.gitconfig -# copy tini -COPY --from=tini-builder /tini/tini /usr/bin WORKDIR / USER 1001 diff --git a/Makefile b/Makefile index c069499e9..2ddff0bf4 100644 --- a/Makefile +++ b/Makefile @@ -147,9 +147,6 @@ lint: unit-tests: KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" SKIP_PACT_TESTS=true go test ./... -coverprofile cover.out -v -cdq-analysis-unit-tests: - cd ./cdq-analysis && go test ./... -coverprofile cover.out -v - pact-tests: KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -v --run TestContracts @@ -159,7 +156,6 @@ pact: manifests generate fmt vet envtest ## Run just Pact tests. test: manifests generate fmt vet envtest ## Run tests. make unit-tests make pact-tests - make cdq-analysis-unit-tests ##@ Build diff --git a/PROJECT b/PROJECT index 06edb9db1..bb23fa6e2 100644 --- a/PROJECT +++ b/PROJECT @@ -6,28 +6,4 @@ plugins: scorecard.sdk.operatorframework.io/v2: {} projectName: application-service repo: github.com/redhat-appstudio/application-service -resources: -- controller: true - domain: redhat.com - group: appstudio - kind: Application - version: v1alpha1 - webhooks: - defaulting: true - validation: true - webhookVersion: v1 -- controller: true - domain: redhat.com - group: appstudio - kind: Component - version: v1alpha1 - webhooks: - defaulting: true - validation: true - webhookVersion: v1 -- controller: true - domain: redhat.com - group: appstudio - kind: ComponentDetectionQuery - version: v1alpha1 version: "3" diff --git a/cdq-analysis/Dockerfile b/cdq-analysis/Dockerfile deleted file mode 100644 index eed515ddc..000000000 --- a/cdq-analysis/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Build the gitops generator binary -FROM golang:1.18 as builder - -WORKDIR /workspace -# Copy the source code -COPY . . -# cache deps before building and copying source so that we don't need to re-download as much -# and so that source changes don't invalidate our downloaded layer -RUN go mod download - -# Build -RUN CGO_ENABLED=0 GOOS=linux go build -a -o cdq-analysis main.go - - -FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6-751 -RUN microdnf update --setopt=install_weak_deps=0 -y && microdnf install git - -# Set up the non-root workspace and copy over the gitops generator binary and entrypoint script -WORKDIR /workspace -COPY --from=builder /workspace/cdq-analysis . - -COPY entrypoint.sh . -RUN chgrp -R 0 /workspace && chmod -R g=u /workspace - -USER 1001 - -ENTRYPOINT ["/workspace/entrypoint.sh"] \ No newline at end of file diff --git a/cdq-analysis/entrypoint.sh b/cdq-analysis/entrypoint.sh deleted file mode 100755 index 503216a6c..000000000 --- a/cdq-analysis/entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -eux - -./cdq-analysis --name "$NAME" --namespace "$NAMESPACE" --contextPath "$CONTEXT_PATH" \ - --revision "$REVISION" --URL "$URL" --devfileRegistryURL "$DEVFILE_REGISTRY_URL" \ - --createK8sJob $CREATE_K8S_Job diff --git a/cdq-analysis/go.mod b/cdq-analysis/go.mod deleted file mode 100644 index 4e1e296e2..000000000 --- a/cdq-analysis/go.mod +++ /dev/null @@ -1,150 +0,0 @@ -module github.com/redhat-appstudio/application-service/cdq-analysis - -go 1.19 - -require ( - github.com/devfile/alizer v1.4.0 - github.com/devfile/api/v2 v2.2.2 - github.com/devfile/library/v2 v2.2.2 - github.com/devfile/registry-support/index/generator v0.0.0-20240311135803-6215550f93d4 - github.com/devfile/registry-support/registry-library v0.0.0-20240404132339-d10fc19d3acc - github.com/go-logr/logr v1.4.1 - github.com/hashicorp/go-multierror v1.1.1 - github.com/pkg/errors v0.9.1 - github.com/spf13/afero v1.11.0 - github.com/stretchr/testify v1.8.4 - go.uber.org/zap v1.27.0 - k8s.io/api v0.26.10 - k8s.io/apimachinery v0.27.3 - k8s.io/client-go v0.26.10 - sigs.k8s.io/controller-runtime v0.14.7 - sigs.k8s.io/yaml v1.3.0 - -) - -require ( - dario.cat/mergo v1.0.0 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.12.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/containerd/containerd v1.7.13 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v25.0.3+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v25.0.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.0 // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-metrics v0.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.14.1 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.11.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/swag v0.22.3 // 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/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.13.1 // indirect - github.com/moby/locker v1.0.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/openshift/api v0.0.0-20210503193030-25175d9d392d // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.17.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect - github.com/sergi/go-diff v1.3.1 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.19.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect - google.golang.org/grpc v1.62.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.26.10 // indirect - k8s.io/component-base v0.26.10 // indirect - k8s.io/klog v1.0.0 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect - oras.land/oras-go v1.2.5 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect -) - -replace github.com/antlr/antlr4 => github.com/antlr/antlr4 v0.0.0-20211106181442-e4c1a74c66bd - -replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.11.0 - -replace github.com/Microsoft/hcsshim => github.com/Microsoft/hcsshim v0.12.2 diff --git a/cdq-analysis/go.sum b/cdq-analysis/go.sum deleted file mode 100644 index dbb6c8da9..000000000 --- a/cdq-analysis/go.sum +++ /dev/null @@ -1,625 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.12.2 h1:AcXy+yfRvrx20g9v7qYaJv5Rh+8GaHOS6b8G6Wx/nKs= -github.com/Microsoft/hcsshim v0.12.2/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -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/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= -github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= -github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= -github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -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/devfile/alizer v1.4.0 h1:m1vifzRqeLVWmArwZvnUvZ1HXKCfcdkXbI0xPwuyjtk= -github.com/devfile/alizer v1.4.0/go.mod h1:5kjRnm61+eUU5AQaowgdvVif4oNTWvHqyymoZFga//s= -github.com/devfile/api/v2 v2.2.2 h1:DXRCPWFlZhTIE38Of2jzTRjQHadfbxBC8GS+m+EjoCU= -github.com/devfile/api/v2 v2.2.2/go.mod h1:qp8jcw12y1JdCsxjK/7LJ7uWaJOxcY1s2LUk5PhbkbM= -github.com/devfile/library/v2 v2.2.2 h1:iLtFQ16aYMcB+vUx7NKtKPiTEursxwueu6/qOailubA= -github.com/devfile/library/v2 v2.2.2/go.mod h1:LHgAu9VApI++hE+cr6CWrkj1OlzHOJeKbraqC5PxSec= -github.com/devfile/registry-support/index/generator v0.0.0-20240311135803-6215550f93d4 h1:t09mGdy31tC2YBp6kVD7x8m0jq1CyBUSYMUvrF0iaWw= -github.com/devfile/registry-support/index/generator v0.0.0-20240311135803-6215550f93d4/go.mod h1:3Ngbmm12LW03tAEHpDNymM7zryd5H1Xo3ZAGlBpecf8= -github.com/devfile/registry-support/registry-library v0.0.0-20240404132339-d10fc19d3acc h1:TuOJ37ZnFJtHh55Yi9spX/+sNxYBjDgH2bmQNIyRMeE= -github.com/devfile/registry-support/registry-library v0.0.0-20240404132339-d10fc19d3acc/go.mod h1:2RRLQaOYuzh8n59euz6Bu60hFoX/LLVM9uzFOFDyOZM= -github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= -github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= -github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= -github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= -github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -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 v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -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.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -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-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -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/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -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.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= -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 v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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.1.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-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -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.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.13.1 h1:L8afOFhPq2RPJJSr/VyzbufwID7jquZVB7oFHbPRcPE= -github.com/moby/buildkit v0.13.1/go.mod h1:aNmNQKLBFYAOFuzQjR3VA27/FijlvtBD1pjNwTSN37k= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= -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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/openshift/api v0.0.0-20210503193030-25175d9d392d h1:eKs5lGkavtfolWeUBJCyirqopVSheGPHgikqlfVmq1s= -github.com/openshift/api v0.0.0-20210503193030-25175d9d392d/go.mod h1:dZ4kytOo3svxJHNYd0J55hwe/6IQG5gAUHUE0F3Jkio= -github.com/openshift/build-machinery-go v0.0.0-20210209125900-0da259a2c359/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= -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/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= -github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -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.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -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/lint v0.0.0-20190930215403-16217165b5de/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/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -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-20181221193216-37e7f081c4d4/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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.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-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -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.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= -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.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -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/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -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/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= -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/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -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.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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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.0-20200615113413-eeeca48fe776/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= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -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.21.0-rc.0/go.mod h1:Dkc/ZauWJrgZhjOjeBgW89xZQiTBJA2RaBKYHXPsi2Y= -k8s.io/api v0.26.10 h1:skTnrDR0r8dg4MMLf6YZIzugxNM0BjFsWKPkNc5kOvk= -k8s.io/api v0.26.10/go.mod h1:ou/H3yviqrHtP/DSPVTfsc7qNfmU06OhajytJfYXkXw= -k8s.io/apiextensions-apiserver v0.26.10 h1:wAriTUc6l7gUqJKOxhmXnYo/VNJzk4oh4QLCUR4Uq+k= -k8s.io/apiextensions-apiserver v0.26.10/go.mod h1:N2qhlxkhJLSoC4f0M1/1lNG627b45SYqnOPEVFoQXw4= -k8s.io/apimachinery v0.21.0-rc.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= -k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= -k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/client-go v0.26.10 h1:4mDzl+1IrfRxh4Ro0s65JRGJp14w77gSMUTjACYWVRo= -k8s.io/client-go v0.26.10/go.mod h1:sh74ig838gCckU4ElYclWb24lTesPdEDPnlyg5vcbkA= -k8s.io/code-generator v0.21.0-rc.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= -k8s.io/component-base v0.26.10 h1:vl3Gfe5aC09mNxfnQtTng7u3rnBVrShOK3MAkqEleb0= -k8s.io/component-base v0.26.10/go.mod h1:/IDdENUHG5uGxqcofZajovYXE9KSPzJ4yQbkYQt7oN0= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= -k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= -oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= -sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= -sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= -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/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/cdq-analysis/main.go b/cdq-analysis/main.go deleted file mode 100644 index 1fde720bb..000000000 --- a/cdq-analysis/main.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package main - -import ( - "context" - "flag" - "fmt" - "log" - "os" - "path/filepath" - "strconv" - - "go.uber.org/zap/zapcore" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" -) - -func main() { - gitToken := os.Getenv("GITHUB_TOKEN") - - // Parse all of the possible command-line flags for the tool - var contextPath, URL, name, Revision, namespace, DevfileRegistryURL, createK8sJobStr string - var createK8sJob bool - flag.StringVar(&name, "name", "", "The ComponentDetectionQuery name") - flag.StringVar(&contextPath, "contextPath", "./", "The context path for the cdq analysis") - flag.StringVar(&URL, "URL", "", "The URL for the git repository") - flag.StringVar(&Revision, "revision", "", "The revision of the git repo to run cdq analysis against with") - flag.StringVar(&DevfileRegistryURL, "devfileRegistryURL", pkg.DevfileRegistryEndpoint, "The devfile registry URL") - flag.StringVar(&namespace, "namespace", "", "The namespace from which to fetch resources") - flag.StringVar(&createK8sJobStr, "createK8sJob", "false", "If a kubernetes job need to be created to send back the result") - flag.Parse() - - createK8sJob, err := strconv.ParseBool(createK8sJobStr) - if err != nil { - log.Fatal(fmt.Errorf("Error parse createK8sJob: %v", err)) - createK8sJob = false - } - - if err := validateVariables(name, URL, namespace); err != nil { - log.Fatal(err) - } - - opts := zap.Options{ - TimeEncoder: zapcore.ISO8601TimeEncoder, - } - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - log := ctrl.Log.WithName("cdq-analysis").WithName("CloneAndAnalyze") - var ctx context.Context - var clientset *kubernetes.Clientset - if createK8sJob { - ctx = context.Background() - config, err := rest.InClusterConfig() - if err != nil { - // Couldn't find an InClusterConfig, may be running outside of Kube, so try to find a local kube config file - var kubeconfig string - if os.Getenv("KUBECONFIG") != "" { - kubeconfig = os.Getenv("KUBECONFIG") - } else { - kubeconfig = filepath.Join(os.Getenv("HOME"), ".kube", "config") - } - config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - fmt.Printf("Error creating clientset with config %v: %v", config, err) - os.Exit(1) - } - } - - clientset, err = kubernetes.NewForConfig(config) - if err != nil { - fmt.Printf("Error creating clientset with config %v: %v", config, err) - } - } - k8sInfoClient := pkg.K8sInfoClient{ - Ctx: ctx, - Clientset: clientset, - Log: log, - CreateK8sJob: createK8sJob, - } - - cdqInfo := &pkg.CDQInfo{ - DevfileRegistryURL: DevfileRegistryURL, - GitURL: pkg.GitURL{RepoURL: URL, Revision: Revision, Token: gitToken}, - } - - cdqUtil := pkg.NewCDQUtilClient() - - /* #nosec G104 -- the main.go is triggerred by docker image, and the result as well as the error will be send by the k8s job*/ - pkg.CloneAndAnalyze(k8sInfoClient, namespace, name, contextPath, cdqInfo, cdqUtil) - -} - -// validateVariables ensures that all of the necessary variables passed in are set to valid values -func validateVariables(name, URL, namespace string) error { - - // The namespace flag must be passed in - if namespace == "" { - return fmt.Errorf("usage: --namespace must be set to a Kubernetes namespace") - } - - // Parse the URL - if URL == "" { - return fmt.Errorf("usage: --URL <repository-url> must be passed in as a flag") - } - - // The name flag must be passed in - if name == "" { - return fmt.Errorf("usage: --name <cdq-name> must be passed in as a flag") - } - - return nil -} diff --git a/cdq-analysis/pkg/componentdetectionquery.go b/cdq-analysis/pkg/componentdetectionquery.go deleted file mode 100644 index 063f6af6c..000000000 --- a/cdq-analysis/pkg/componentdetectionquery.go +++ /dev/null @@ -1,340 +0,0 @@ -/* -Copyright 2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package pkg - -import ( - "context" - "encoding/json" - "fmt" - "path" - - "github.com/devfile/alizer/pkg/apis/model" - "github.com/go-logr/logr" - "github.com/spf13/afero" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -type K8sInfoClient struct { - Ctx context.Context - Clientset kubernetes.Interface - Log logr.Logger - CreateK8sJob bool -} - -type ClonedInfo struct { - ClonedPath string // For locally cloned git repos - ComponentPath string // For locally cloned git repos - Fs afero.Afero // For locally cloned git repos -} - -// CDQInfo is a struct that contains the relevant information to 1) clone and search a given git repo for the presence of a devfile or dockerfile and 2) search for matching samples in the devfile -// registry if no devfile or dockerfiles are found. -type CDQInfo struct { - DevfileRegistryURL string - GitURL GitURL - ClonedRepo ClonedInfo - devfilePath string // A resolved devfile; one of DevfileName, HiddenDevfileName, HiddenDevfileDir or HiddenDirHiddenDevfile - dockerfilePath string // A resolved dockerfile -} - -// CDQUtil interface contains all the CDQ utiliy methods that can be implemented by controller and tests -type CDQUtil interface { - // Clone is a method signature to help clone a given repository into a path - Clone(k K8sInfoClient, cdqInfo *CDQInfo, namespace, name, context string) error - - // ValidateDevfile is a method signature to help validate the devfile from the given devfile location - ValidateDevfile(log logr.Logger, devfileLocation string, token string) (shouldIgnoreDevfile bool, devfileBytes []byte, err error) -} -type CDQUtilClient struct { -} - -func NewCDQUtilClient() CDQUtilClient { - return CDQUtilClient{} -} - -func GetDevfileAndDockerFilePaths(client CDQInfo) (string, string) { - return client.devfilePath, client.dockerfilePath -} - -func (cdqUtilClient CDQUtilClient) Clone(k K8sInfoClient, cdqInfo *CDQInfo, namespace, name, context string) error { - return clone(k, cdqInfo, namespace, name, context) -} - -func (cdqUtilClient CDQUtilClient) ValidateDevfile(log logr.Logger, devfileLocation string, token string) (shouldIgnoreDevfile bool, devfileBytes []byte, err error) { - return validateDevfile(log, devfileLocation, token) -} - -// clones the Git repository into a temporary path and sets the corresponding CDQ Info for the cloned repository -func clone(k K8sInfoClient, cdqInfo *CDQInfo, namespace, name, context string) error { - if cdqInfo == nil { - return nil - } - - log := k.Log - var clonePath, componentPath string - var err error - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - componentPortsMap := make(map[string][]int) - revision := cdqInfo.GitURL.Revision - repoURL := cdqInfo.GitURL.RepoURL - gitToken := cdqInfo.GitURL.Token - Fs := NewFilesystem() - cdqInfo.ClonedRepo.Fs = Fs - clonePath, err = CreateTempPath(name, Fs) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to create a temp path %s for cloning %v", clonePath, namespace)) - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, err) - return err - } - - err = CloneRepo(clonePath, GitURL{RepoURL: repoURL, Revision: revision, Token: gitToken}) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to clone repo %s to path %s, exiting reconcile loop %v", repoURL, clonePath, namespace)) - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, err) - return err - } - log.Info(fmt.Sprintf("cloned from %s to path %s... %v", repoURL, clonePath, namespace)) - componentPath = clonePath - if context != "" { - componentPath = path.Join(clonePath, context) - } - - if revision == "" { - revision, err = GetBranchFromRepo(componentPath) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to get branch from cloned repo for component path %s, exiting reconcile loop %v", componentPath, namespace)) - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, err) - return err - } - } - - cdqInfo.ClonedRepo.ClonedPath = clonePath - cdqInfo.ClonedRepo.ComponentPath = componentPath - cdqInfo.GitURL.Revision = revision - return nil -} - -// CloneAndAnalyze clones and analyzes the Git repo with Alizer if necessary and returns -// devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision and an error -func CloneAndAnalyze(k K8sInfoClient, namespace, name, context string, cdqInfo *CDQInfo, cdqUtil CDQUtil) (devfilesMap map[string][]byte, devfilesURLMap map[string]string, dockerfileContextMap map[string]string, componentPortsMap map[string][]int, revision string, err error) { - if cdqInfo == nil { - return nil, nil, nil, nil, "", fmt.Errorf("CDQ cannot clone and analyze because no information was passed to it") - } - - log := k.Log - var clonePath, componentPath string - alizerClient := AlizerClient{} - devfilesMap = make(map[string][]byte) - devfilesURLMap = make(map[string]string) - dockerfileContextMap = make(map[string]string) - componentPortsMap = make(map[string][]int) - var Fs afero.Afero - - var components []model.Component - - repoURL := cdqInfo.GitURL.RepoURL - gitToken := cdqInfo.GitURL.Token - registryURL := cdqInfo.DevfileRegistryURL - - err = cdqUtil.Clone(k, cdqInfo, namespace, name, context) - if err != nil { - return nil, nil, nil, nil, "", err - } - - // search the cloned repo for valid devfile locations - devfileBytes, err := FindValidDevfiles(cdqInfo) - if err != nil { - log.Info(fmt.Sprintf("Unable to find from any known devfile locations from %s ", cdqInfo.GitURL.RepoURL)) - } - - // search the cloned repo for valid dockerfile locations - dockerfileBytes, err := FindValidDockerfile(cdqInfo) - if err != nil { - log.Info(fmt.Sprintf("Unable to find from any known Dockerfile locations from %s ", cdqInfo.GitURL.RepoURL)) - } - - isDevfilePresent := len(devfileBytes) != 0 - isDockerfilePresent := len(dockerfileBytes) != 0 - - // the following values come from the previous step when the repo was cloned - Fs = cdqInfo.ClonedRepo.Fs - componentPath = cdqInfo.ClonedRepo.ComponentPath - clonePath = cdqInfo.ClonedRepo.ClonedPath - revision = cdqInfo.GitURL.Revision - devfilePath := cdqInfo.devfilePath - dockerfilePath := cdqInfo.dockerfilePath - - if context == "" { - context = "./" - } - - //search for devfiles - - isMultiComponent := false - if isDevfilePresent { - // devfilePath is the resolved, valid devfile location set in FindValidDevfiles - updatedLink, err := UpdateGitLink(repoURL, revision, path.Join(context, devfilePath)) - log.Info(fmt.Sprintf("Updating the git link to access devfile: %s ", updatedLink)) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to update the devfile git link for CDQ %v... %v", name, namespace)) - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, err) - return nil, nil, nil, nil, "", err - } - - shouldIgnoreDevfile, devfileBytes, err := cdqUtil.ValidateDevfile(log, updatedLink, gitToken) - if err != nil { - retErr := &InvalidDevfile{Err: err} - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, retErr) - return nil, nil, nil, nil, "", retErr - } - if shouldIgnoreDevfile { - isDevfilePresent = false - } else { - log.Info(fmt.Sprintf("Found a devfile, devfile to be analyzed to see if a Dockerfile is referenced for CDQ %v...%v", name, namespace)) - devfilesMap[context] = devfileBytes - devfilesURLMap[context] = updatedLink - } - } - // recheck if devfile presents, since the devfile may need to be ignored after validation - if !isDevfilePresent && isDockerfilePresent { - log.Info(fmt.Sprintf("Determined that this is a Dockerfile only component for cdq %v... %v", name, namespace)) - dockerfileContextMap[context] = dockerfilePath - } - - if !isDockerfilePresent { - log.Info(fmt.Sprintf("Unable to find devfile, Dockerfile or Containerfile under root directory, run Alizer to detect components... %v", namespace)) - - if !isDevfilePresent { - components, err = alizerClient.DetectComponents(componentPath) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to detect components using Alizer for repo %v, under path %v... %v ", repoURL, componentPath, namespace)) - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, err) - return nil, nil, nil, nil, "", err - } - log.Info(fmt.Sprintf("components detected %v... %v", components, namespace)) - // If no devfile and no Dockerfile or Containerfile present in the root - // case 1: no components been detected by Alizer, might still has subfolders contains Dockerfile or Containerfile. Need to scan repo - // case 2: one or more than 1 compinents been detected by Alizer, and the first one in the list is under sub-folder. Need to scan repo. - if len(components) == 0 || (len(components) != 0 && path.Clean(components[0].Path) != path.Clean(componentPath)) { - isMultiComponent = true - } - } - } - - // Logic to read multiple components in from git - if isMultiComponent { - log.Info(fmt.Sprintf("Since this is a multi-component, attempt will be made to read only level 1 dir for devfiles... %v", namespace)) - devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, err = ScanRepo(log, alizerClient, componentPath, context, *cdqInfo, cdqUtil) - if err != nil { - if _, ok := err.(*NoDevfileFound); !ok { - log.Error(err, fmt.Sprintf("Unable to find devfile(s) in repo %s due to an error %s, exiting reconcile loop %v", repoURL, err.Error(), namespace)) - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, err) - return nil, nil, nil, nil, "", err - } - } - } else { - log.Info(fmt.Sprintf("Since this is not a multi-component, attempt will be made to read devfile at the root dir... %v", namespace)) - err := AnalyzePath(log, alizerClient, componentPath, context, registryURL, devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, isDevfilePresent, isDockerfilePresent) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to analyze path %s for a devfile, Dockerfile or Containerfile %v", componentPath, namespace)) - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, err) - return nil, nil, nil, nil, "", err - } - } - - k.SendBackDetectionResult(devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, name, namespace, clonePath, Fs, nil) - return devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, revision, nil -} - -func (k K8sInfoClient) SendBackDetectionResult(devfilesMap map[string][]byte, devfilesURLMap map[string]string, dockerfileContextMap map[string]string, componentPortsMap map[string][]int, revision, name, namespace, clonePath string, Fs afero.Afero, completeError error) { - log := k.Log - if !k.CreateK8sJob { - log.Info("Skip creating the job...") - // remove the clone path after cdq - if isExist, _ := IsExisting(Fs, clonePath); isExist { - if err := Fs.RemoveAll(clonePath); err != nil { - log.Error(err, fmt.Sprintf("Unable to remove the clonepath %s %v", clonePath, namespace)) - } - } - return - } - log.Info(fmt.Sprintf("Sending back result, devfilesMap %v,devfilesURLMap %v, dockerfileContextMap %v, componentPortsMap %v, error %v ... %v", devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap, completeError, namespace)) - - configMapBinaryData := make(map[string][]byte) - if devfilesMap != nil { - devfilesMapbytes, _ := json.Marshal(devfilesMap) - configMapBinaryData["devfilesMap"] = devfilesMapbytes - } - if devfilesURLMap != nil { - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - } - - if dockerfileContextMap != nil { - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - } - - if componentPortsMap != nil { - componentPortsMapbytes, _ := json.Marshal(componentPortsMap) - configMapBinaryData["componentPortsMap"] = componentPortsMapbytes - } - - configMapBinaryData["revision"] = []byte(revision) - - if completeError != nil { - errorMap := make(map[string]string) - switch completeError.(type) { - case *NoDevfileFound: - errorMap["NoDevfileFound"] = fmt.Sprintf("%v", completeError) - case *NoDockerfileFound: - errorMap["NoDockerfileFound"] = fmt.Sprintf("%v", completeError) - case *RepoNotFound: - errorMap["RepoNotFound"] = fmt.Sprintf("%v", completeError) - case *InvalidDevfile: - errorMap["InvalidDevfile"] = fmt.Sprintf("%v", completeError) - case *InvalidURL: - errorMap["InvalidURL"] = fmt.Sprintf("%v", completeError) - case *AuthenticationFailed: - errorMap["AuthenticationFailed"] = fmt.Sprintf("%v", completeError) - default: - errorMap["InternalError"] = fmt.Sprintf("%v", completeError) - } - errorMapbytes, _ := json.Marshal(errorMap) - configMapBinaryData["errorMap"] = errorMapbytes - } - - configMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - BinaryData: configMapBinaryData, - } - _, err := k.Clientset.CoreV1().ConfigMaps(namespace).Create(k.Ctx, &configMap, metav1.CreateOptions{}) - if err != nil { - log.Error(err, "Error creating configmap") - } -} diff --git a/cdq-analysis/pkg/componentdetectionquery_test.go b/cdq-analysis/pkg/componentdetectionquery_test.go deleted file mode 100644 index fa9dcff1b..000000000 --- a/cdq-analysis/pkg/componentdetectionquery_test.go +++ /dev/null @@ -1,422 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "context" - "encoding/json" - "fmt" - "reflect" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -func TestCloneAndAnalyze(t *testing.T) { - - ctx := context.TODO() - clientset := fake.NewSimpleClientset() - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{}))) - log := ctrl.Log.WithName("TestCloneAndAnalyze") - - k8sClient := K8sInfoClient{ - Ctx: ctx, - Clientset: clientset, - Log: log, - CreateK8sJob: false, - } - - compName := "testComponent" - namespaceName := "testNamespace" - springSampleURL := "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" - privateRepoURL := "https://github.com/devfile-resources/private-repo-test" - multiComponentRepoURL := "https://github.com/devfile-resources/multi-components-dockerfile" - springNoDevfileNoDockerfileURL := "https://github.com/devfile-resources/devfile-sample-java-springboot-basic-no-devfile-no-dockerfile" - springNoDevfileURL := "https://github.com/devfile-resources/devfile-sample-java-springboot-basic-no-devfile" - multiComponentWithNoDevfileAndDockerfileURL := "https://github.com/devfile-resources/quality-dashboard" - authenticationFailedErr := "authentication failed .*" - - springDevfileContext := ` - schemaVersion: 2.2.0 - metadata: - name: java-springboot - version: 1.2.1 - projectType: springboot - provider: Red Hat - language: Java - ` - - pythonDevfileContext := ` - schemaVersion: 2.2.0 - metadata: - name: python - version: 1.0.1 - projectType: Python - provider: Red Hat - language: Python - ` - - nodeJSDevfileContext := ` - schemaVersion: 2.2.0 - metadata: - name: nodejs - version: 2.1.1 - projectType: Node.js - provider: Red Hat - language: JavaScript - ` - - tests := []struct { - testCase string - context string - URL string - Revision string - DevfileRegistryURL string - gitToken string - isDevfilePresent bool - isDockerfilePresent bool - wantErr string - wantDevfilesMap map[string][]byte - wantDevfilesURLMap map[string]string - wantDockerfileContextMap map[string]string - wantComponentsPortMap map[string][]int - wantBranch string - }{ - { - testCase: "repo with devfile - should successfully detect spring component", - URL: springSampleURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - wantDevfilesMap: map[string][]byte{ - "./": []byte(springDevfileContext), - }, - wantDevfilesURLMap: map[string]string{ - "./": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - }, - wantDockerfileContextMap: map[string]string{}, - wantComponentsPortMap: map[string][]int{}, - wantBranch: "main", - }, - { - testCase: "repo without devfile and dockerfile - should successfully detect spring component", - URL: springNoDevfileNoDockerfileURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - wantDevfilesMap: map[string][]byte{ - "./": []byte(springDevfileContext), - }, - wantDevfilesURLMap: map[string]string{ - "./": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - }, - wantDockerfileContextMap: map[string]string{ - "./": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - }, - wantComponentsPortMap: map[string][]int{}, - wantBranch: "main", - }, - { - testCase: "multi-component repo without devfile and dockerfile - should successfully detect dockerfiles", - URL: multiComponentWithNoDevfileAndDockerfileURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - wantDevfilesMap: map[string][]byte{}, - wantDevfilesURLMap: map[string]string{}, - wantDockerfileContextMap: map[string]string{ - "backend": "Dockerfile", - "frontend": "Dockerfile", - }, - wantComponentsPortMap: map[string][]int{}, //empty because repo does not have a dockerfile with a specified port - wantBranch: "main", - }, - { - testCase: "private repo - should error out with no token provided", - URL: privateRepoURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - wantDevfilesMap: map[string][]byte{}, - wantDevfilesURLMap: map[string]string{}, - wantDockerfileContextMap: map[string]string{}, - wantComponentsPortMap: map[string][]int{}, - wantErr: authenticationFailedErr, - }, - { - testCase: "private repo - should error out with invalid token provided", - URL: privateRepoURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - gitToken: "fakeToken", - wantDevfilesMap: map[string][]byte{}, - wantDevfilesURLMap: map[string]string{}, - wantDockerfileContextMap: map[string]string{}, - wantComponentsPortMap: map[string][]int{}, - wantErr: authenticationFailedErr, - }, - { - testCase: "should successfully detect multi-component with dockerfile present", - URL: multiComponentRepoURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - wantDevfilesMap: map[string][]byte{ - "devfile-sample-java-springboot-basic": []byte(springDevfileContext), - "devfile-sample-nodejs-basic": []byte(nodeJSDevfileContext), - "devfile-sample-python-basic": []byte(pythonDevfileContext), - "python-src-none": []byte(pythonDevfileContext), - }, - wantDevfilesURLMap: map[string]string{ - "devfile-sample-java-springboot-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/.devfile/.devfile.yaml", - "devfile-sample-nodejs-basic": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml", - "devfile-sample-python-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-python-basic/.devfile.yaml", - "python-src-none": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - }, - wantDockerfileContextMap: map[string]string{ - "devfile-sample-nodejs-basic": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/Dockerfile", - "devfile-sample-python-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-python-basic/Dockerfile", - "python-src-docker": "Dockerfile", - "python-src-none": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile", - }, - wantComponentsPortMap: map[string][]int{ - "devfile-sample-nodejs-basic": {3000}, - }, - wantBranch: "main", - }, - { - testCase: "should successfully detect single component when context is provided", - context: "devfile-sample-nodejs-basic", - URL: multiComponentRepoURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - wantDevfilesMap: map[string][]byte{ - "devfile-sample-nodejs-basic": []byte(nodeJSDevfileContext), - }, - wantDevfilesURLMap: map[string]string{ - "devfile-sample-nodejs-basic": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml", - }, - wantComponentsPortMap: map[string][]int{ - "devfile-sample-nodejs-basic": {3000}, - }, - wantDockerfileContextMap: map[string]string{ - "devfile-sample-nodejs-basic": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/Dockerfile", - }, - wantBranch: "main", - }, - { - testCase: "repo without devfile, with dockerfile - should successfully detect dockerfile", - URL: springNoDevfileURL, - DevfileRegistryURL: DevfileRegistryEndpoint, - wantDevfilesMap: map[string][]byte{}, - wantDevfilesURLMap: map[string]string{}, - wantDockerfileContextMap: map[string]string{ - "./": "docker/Dockerfile", - }, - wantComponentsPortMap: map[string][]int{ - "./": {8081}, - }, - wantBranch: "main", - }, - } - - for _, tt := range tests { - t.Run(tt.testCase, func(t *testing.T) { - cdqInfo := &CDQInfo{ - DevfileRegistryURL: tt.DevfileRegistryURL, - GitURL: GitURL{RepoURL: tt.URL, Revision: tt.Revision, Token: tt.gitToken}, - } - devfilesMap, devfilesURLMap, dockerfileContextMap, componentsPortMap, branch, err := CloneAndAnalyze(k8sClient, namespaceName, compName, tt.context, cdqInfo, NewCDQUtilClient()) - if (err != nil) != (tt.wantErr != "") { - t.Errorf("got unexpected error %v", err) - } else if err == nil { - // do not bother to check for the content for cdq testing - // check if correct number of devfiles stored in the map - // also check if correct context has been detected - if devfilesMap != nil { - if len(devfilesMap) != len(tt.wantDevfilesMap) { - t.Errorf("Expected devfilesMap length: %+v, Got: %+v, devfileMap is %+v", len(tt.wantDevfilesMap), len(devfilesMap), devfilesMap) - } else { - for key := range tt.wantDevfilesMap { - if _, ok := devfilesMap[key]; !ok { - t.Errorf("Expected devfilesMap contains context: %+v, devfileMap is %+v", key, devfilesMap) - } - } - } - } - if !reflect.DeepEqual(devfilesURLMap, tt.wantDevfilesURLMap) { - t.Errorf("Expected devfilesURLMap: %+v, Got: %+v", tt.wantDevfilesURLMap, devfilesURLMap) - } - if !reflect.DeepEqual(dockerfileContextMap, tt.wantDockerfileContextMap) { - t.Errorf("Expected dockerfileContextMap: %+v, Got: %+v", tt.wantDockerfileContextMap, dockerfileContextMap) - } - if !reflect.DeepEqual(componentsPortMap, tt.wantComponentsPortMap) { - t.Errorf("Expected componentsPortMap: %+v, Got: %+v", tt.wantComponentsPortMap, componentsPortMap) - } - if branch != tt.wantBranch { - t.Errorf("Expected branch: %+v, Got: %+v", tt.wantBranch, branch) - } - } else if err != nil { - assert.Regexp(t, tt.wantErr, err.Error(), "Error message should match") - } - clientset.CoreV1().ConfigMaps(namespaceName).Delete(k8sClient.Ctx, compName, metav1.DeleteOptions{}) - }) - } -} - -func TestSendBackDetectionResult(t *testing.T) { - - ctx := context.TODO() - clientset := fake.NewSimpleClientset() - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{}))) - Fs := afero.Afero{} - log := ctrl.Log.WithName("TestSendBackDetectionResult") - - k8sClient := K8sInfoClient{ - Ctx: ctx, - Clientset: clientset, - Log: log, - CreateK8sJob: true, - } - - compName := "testComponent" - namespaceName := "testNamespace" - revision := "main" - - springDevfileContext := ` -schemaVersion: 2.2.0 -metadata: - name: java-springboot - version: 1.2.1 - projectType: springboot - provider: Red Hat - language: Java -` - devfilesMap := map[string][]byte{ - "./": []byte(springDevfileContext), - } - devfilesURLMap := map[string]string{ - "./": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - } - dockerfileContextMap := map[string]string{ - "./": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - } - - configMapBinaryData := make(map[string][]byte) - devfilesMapbytes, _ := json.Marshal(devfilesMap) - configMapBinaryData["devfilesMap"] = devfilesMapbytes - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - - configMapBinaryData["revision"] = []byte(revision) - - internalErrBinaryData := make(map[string][]byte) - internalErr := fmt.Errorf("dummy internal error") - internalErrMap := make(map[string]string) - internalErrMap["InternalError"] = fmt.Sprintf("%v", internalErr) - errorMapbytes, _ := json.Marshal(internalErrMap) - internalErrBinaryData["errorMap"] = errorMapbytes - internalErrBinaryData["revision"] = []byte(revision) - - devfileNotFoundBinaryData := make(map[string][]byte) - devfileNotFoundErr := NoDevfileFound{"dummy location", fmt.Errorf("dummy NoDevfileFound error")} - devfileNotFoundErrMap := make(map[string]string) - devfileNotFoundErrMap["NoDevfileFound"] = fmt.Sprintf("%v", &devfileNotFoundErr) - devfileNotFoundErrorMapbytes, _ := json.Marshal(devfileNotFoundErrMap) - devfileNotFoundBinaryData["errorMap"] = devfileNotFoundErrorMapbytes - devfileNotFoundBinaryData["revision"] = []byte(revision) - - dockerfileNotFoundBinaryData := make(map[string][]byte) - dockerfileNotFoundErr := NoDockerfileFound{"dummy location", fmt.Errorf("dummy NoDockerfileFound error")} - dockerfileNotFoundErrMap := make(map[string]string) - dockerfileNotFoundErrMap["NoDockerfileFound"] = fmt.Sprintf("%v", &dockerfileNotFoundErr) - dockerfileNotFoundErrorMapbytes, _ := json.Marshal(dockerfileNotFoundErrMap) - dockerfileNotFoundBinaryData["errorMap"] = dockerfileNotFoundErrorMapbytes - dockerfileNotFoundBinaryData["revision"] = []byte(revision) - - tests := []struct { - testCase string - devfilesMap map[string][]byte - devfilesURLMap map[string]string - dockerfileContextMap map[string]string - componentPortsMap map[string][]int - binaryData map[string][]byte - err error - }{ - { - testCase: "without error", - devfilesMap: devfilesMap, - devfilesURLMap: devfilesURLMap, - dockerfileContextMap: dockerfileContextMap, - binaryData: configMapBinaryData, - }, - { - testCase: "with internal error", - binaryData: internalErrBinaryData, - err: internalErr, - }, - { - testCase: "with NoDevfileFound error", - binaryData: devfileNotFoundBinaryData, - err: &devfileNotFoundErr, - }, - { - testCase: "with NoDockerfileFound error", - binaryData: dockerfileNotFoundBinaryData, - err: &dockerfileNotFoundErr, - }, - } - for _, tt := range tests { - t.Run(tt.testCase, func(t *testing.T) { - k8sClient.SendBackDetectionResult(tt.devfilesMap, tt.devfilesURLMap, tt.dockerfileContextMap, tt.componentPortsMap, revision, compName, namespaceName, "", Fs, tt.err) - configMap, err := clientset.CoreV1().ConfigMaps(namespaceName).Get(k8sClient.Ctx, compName, metav1.GetOptions{}) - if err != nil { - t.Errorf("got unexpected error %v", err) - } - if !reflect.DeepEqual(tt.binaryData, configMap.BinaryData) { - t.Errorf("Expected configmap with binaryData: %+v, Got: %+v", tt.binaryData, configMap.BinaryData) - } - clientset.CoreV1().ConfigMaps(namespaceName).Delete(k8sClient.Ctx, compName, metav1.DeleteOptions{}) - }) - } -} - -func TestGetDevfileAndDockerFilePaths(t *testing.T) { - tests := []struct { - testCase string - cdqInfo CDQInfo - wantDevfilePath string - wantDockerfilePath string - }{ - { - testCase: "Unset dockerfilepath and devfilepath", - cdqInfo: CDQInfo{}, - wantDevfilePath: "", - wantDockerfilePath: "", - }, - { - testCase: "Set dockerfilepath and devfilepath", - cdqInfo: CDQInfo{dockerfilePath: "/dockerfile", devfilePath: "devfile.yml"}, - wantDevfilePath: "devfile.yml", - wantDockerfilePath: "/dockerfile", - }, - } - - for _, tt := range tests { - t.Run(tt.testCase, func(t *testing.T) { - devfilePath, dockerfilePath := GetDevfileAndDockerFilePaths(tt.cdqInfo) - if devfilePath != tt.wantDevfilePath || dockerfilePath != tt.wantDockerfilePath { - t.Errorf("devfilepath %s or dockerfile %s path does not match expected values wantDevfilePath %s, wantDockerfilePath %s", devfilePath, dockerfilePath, tt.wantDevfilePath, tt.wantDockerfilePath) - } - }) - } - -} diff --git a/cdq-analysis/pkg/detect.go b/cdq-analysis/pkg/detect.go deleted file mode 100644 index f7448e436..000000000 --- a/cdq-analysis/pkg/detect.go +++ /dev/null @@ -1,397 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "fmt" - "os" - "path" - "reflect" - "strings" - - "github.com/devfile/alizer/pkg/apis/model" - "github.com/devfile/alizer/pkg/apis/recognizer" - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/library/v2/pkg/devfile/parser" - "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - "github.com/go-logr/logr" - "sigs.k8s.io/yaml" -) - -type Alizer interface { - SelectDevFileFromTypes(path string, devFileTypes []model.DevfileType) (model.DevfileType, error) - DetectComponents(path string) ([]model.Component, error) -} - -type AlizerClient struct { -} - -// search attempts to read and return devfiles and Dockerfiles/Containerfiles from the local path upto the specified depth -// If no devfile(s) or Dockerfile(s)/Containerfile(s) are found, then the Alizer tool is used to detect and match a devfile/Dockerfile from the devfile registry -// search returns 3 maps and an error: -// Map 1 returns a context to the devfile bytes if present. -// Map 2 returns a context to the matched devfileURL from the github repository. If no devfile was present, then a link to a matching devfile in the devfile registry will be used instead. -// Map 3 returns a context to the Dockerfile uri or a matched DockerfileURL from the devfile registry if no Dockerfile is present in the context -// Map 4 returns a context to the list of ports that were detected by alizer in the source code, at that given context -func search(log logr.Logger, a Alizer, localpath string, srcContext string, cdqInfo CDQInfo, cdqUtil CDQUtil) (map[string][]byte, map[string]string, map[string]string, map[string][]int, error) { - - devfileMapFromRepo := make(map[string][]byte) - devfilesURLMapFromRepo := make(map[string]string) - dockerfileContextMapFromRepo := make(map[string]string) - componentPortsMapFromRepo := make(map[string][]int) - - URL := cdqInfo.GitURL.RepoURL - revision := cdqInfo.GitURL.Revision - token := cdqInfo.GitURL.Token - devfileRegistryURL := cdqInfo.DevfileRegistryURL - - files, err := os.ReadDir(localpath) - if err != nil { - return nil, nil, nil, nil, err - } - - for _, f := range files { - if f.IsDir() { - isDevfilePresent := false - isDockerfilePresent := false - curPath := path.Join(localpath, f.Name()) - context := path.Join(srcContext, f.Name()) - files, err := os.ReadDir(curPath) - if err != nil { - return nil, nil, nil, nil, err - } - for _, f := range files { - lowerCaseFileName := strings.ToLower(f.Name()) - if lowerCaseFileName == Devfile || lowerCaseFileName == HiddenDevfile || - lowerCaseFileName == DevfileYml || lowerCaseFileName == HiddenDirDevfileYml { - // Check for devfile.yaml or .devfile.yaml - /* #nosec G304 -- false positive, filename is not based on user input*/ - devfilePath := path.Join(curPath, f.Name()) - // Set the proper devfile URL for the detected devfile - updatedLink, err := UpdateGitLink(URL, revision, path.Join(context, f.Name())) - if err != nil { - return nil, nil, nil, nil, err - } - shouldIgnoreDevfile, devfileBytes, err := cdqUtil.ValidateDevfile(log, devfilePath, token) - if err != nil { - retErr := &InvalidDevfile{Err: err} - return nil, nil, nil, nil, retErr - } - if shouldIgnoreDevfile { - isDevfilePresent = false - } else { - devfileMapFromRepo[context] = devfileBytes - devfilesURLMapFromRepo[context] = updatedLink - isDevfilePresent = true - } - } else if f.IsDir() && f.Name() == HiddenDevfileDir { - // Check for .devfile/devfile.yaml, .devfile/.devfile.yaml, .devfile/devfile.yml or .devfile/.devfile.yml - // if the dir is .devfile, we dont increment currentLevel - // consider devfile.yaml and .devfile/devfile.yaml as the same level, for example - hiddenDirPath := path.Join(curPath, HiddenDevfileDir) - hiddenfiles, err := os.ReadDir(hiddenDirPath) - if err != nil { - return nil, nil, nil, nil, err - } - for _, f := range hiddenfiles { - lowerCaseFileName := strings.ToLower(f.Name()) - if lowerCaseFileName == Devfile || lowerCaseFileName == HiddenDevfile || - lowerCaseFileName == DevfileYml || lowerCaseFileName == HiddenDirDevfileYml { - // Check for devfile.yaml , .devfile.yaml, devfile.yml or .devfile.yml - /* #nosec G304 -- false positive, filename is not based on user input*/ - devfilePath := path.Join(hiddenDirPath, f.Name()) - // Set the proper devfile URL for the detected devfile - updatedLink, err := UpdateGitLink(URL, revision, path.Join(context, HiddenDevfileDir, f.Name())) - if err != nil { - return nil, nil, nil, nil, err - } - shouldIgnoreDevfile, devfileBytes, err := cdqUtil.ValidateDevfile(log, devfilePath, token) - if err != nil { - retErr := &InvalidDevfile{Err: err} - return nil, nil, nil, nil, retErr - } - - if shouldIgnoreDevfile { - isDevfilePresent = false - } else { - devfileMapFromRepo[context] = devfileBytes - devfilesURLMapFromRepo[context] = updatedLink - - isDevfilePresent = true - } - } - } - } else if lowerCaseFileName == strings.ToLower(DockerfileName) { - // Check for Dockerfile or dockerfile - // NOTE: if a Dockerfile is named differently, for example, Dockerfile.jvm; - // thats ok. As we finish iterating through all the files in the localpath - // we will read the devfile to ensure a Dockerfile has been referenced. - // However, if a Dockerfile is named differently and not referenced in the devfile - // it will go undetected - dockerfileContextMapFromRepo[context] = f.Name() - isDockerfilePresent = true - } else if lowerCaseFileName == strings.ToLower(ContainerfileName) { - // Check for Containerfile - dockerfileContextMapFromRepo[context] = ContainerfileName - isDockerfilePresent = true - } else if f.IsDir() && (f.Name() == DockerDir || f.Name() == HiddenDockerDir || f.Name() == BuildDir) { - // Check for docker/Dockerfile, .docker/Dockerfile and build/Dockerfile - // OR docker/dockerfile, .docker/dockerfile and build/dockerfile - // OR docker/Containerfile, .docker/Containerfile and build/Containerfile - dirName := f.Name() - dirPath := path.Join(curPath, dirName) - files, err := os.ReadDir(dirPath) - if err != nil { - return nil, nil, nil, nil, err - } - for _, f := range files { - lowerCaseFileName := strings.ToLower(f.Name()) - if lowerCaseFileName == strings.ToLower(DockerfileName) || lowerCaseFileName == strings.ToLower(ContainerfileName) { - dockerfileContextMapFromRepo[context] = path.Join(dirName, f.Name()) - isDockerfilePresent = true - } - } - } - } - // unset the Dockerfile context if we have both devfile and Dockerfile - // at this stage, we need to ensure the Dockerfile has been referenced - // in the devfile image component even if we detect both devfile and Dockerfile - if isDevfilePresent && isDockerfilePresent { - delete(dockerfileContextMapFromRepo, context) - isDockerfilePresent = false - } - - if (!isDevfilePresent && !isDockerfilePresent) || (isDevfilePresent && !isDockerfilePresent) { - err := AnalyzePath(log, a, curPath, context, devfileRegistryURL, devfileMapFromRepo, devfilesURLMapFromRepo, dockerfileContextMapFromRepo, componentPortsMapFromRepo, isDevfilePresent, isDockerfilePresent) - if err != nil { - return nil, nil, nil, nil, err - } - } - } - } - - if len(devfilesURLMapFromRepo) == 0 && len(devfileMapFromRepo) == 0 && len(dockerfileContextMapFromRepo) == 0 { - // if we didnt find any devfile or Dockerfile we should return an err - log.Info(fmt.Sprintf("no devfile or Dockerfile found in the specified location %s", localpath)) - } - - return devfileMapFromRepo, devfilesURLMapFromRepo, dockerfileContextMapFromRepo, componentPortsMapFromRepo, err -} - -// AnalyzePath checks if a devfile or a Dockerfile can be found in the localpath for the given context, this is a helper func used by the CDQ controller -// In addition to returning an error, the following maps may be updated: -// devfileMapFromRepo: a context to the devfile bytes if present -// devfilesURLMapFromRepo: a context to the matched devfileURL from the github repository. If no devfile was present, then a link to a matching devfile in the devfile registry will be used instead. -// dockerfileContextMapFromRepo: a context to the Dockerfile uri or a matched DockerfileURL from the devfile registry if no Dockerfile is present in the context -// componentPortsMapFromRepo: a context to the list of ports that were detected by alizer in the source code, at that given context -func AnalyzePath(log logr.Logger, a Alizer, localpath, context, devfileRegistryURL string, devfileMapFromRepo map[string][]byte, devfilesURLMapFromRepo, dockerfileContextMapFromRepo map[string]string, componentPortsMapFromRepo map[string][]int, isDevfilePresent, isDockerfilePresent bool) error { - if isDevfilePresent { - // If devfile is present, check to see if we can determine a Dockerfile from it - devfileBytes := devfileMapFromRepo[context] - dockerfileImage, err := SearchForDockerfile(devfileBytes) - if err != nil { - return err - } - if dockerfileImage != nil { - // if it is an absolute uri, add it to the Dockerfile context map - // If it's relative URI, leave it out, as the build will process the devfile and find the Dockerfile - if strings.HasPrefix(dockerfileImage.Uri, "http") { - dockerfileContextMapFromRepo[context] = dockerfileImage.Uri - } - isDockerfilePresent = true - } - } - - if !isDockerfilePresent { - // if we didnt find any devfile/Dockerfile/Containerfile upto our desired depth, then use alizer - detectedDevfile, detectedDevfileEndpoint, detectedSampleName, detectedPorts, err := AnalyzeAndDetectDevfile(a, localpath, devfileRegistryURL) - if err != nil { - if _, ok := err.(*NoDevfileFound); !ok { - return err - } - } - - if len(detectedDevfile) > 0 { - if !isDevfilePresent { - // If a devfile is not present at this stage, just update devfileMapFromRepo and devfilesURLMapFromRepo - // Dockerfile is not needed because all the devfile registry samples will have a Dockerfile entry - devfileMapFromRepo[context] = detectedDevfile - devfilesURLMapFromRepo[context] = detectedDevfileEndpoint - } - // 1. If a devfile is present but we could not determine a Dockerfile or, - // 2. If a devfile is not present and we matched from the registry with Alizer - // update dockerfileContextMapFromRepo with the Dockerfile full uri - // by looking up the devfile from the detected alizer sample from the devfile registry - sampleRepoURL, err := GetRepoFromRegistry(detectedSampleName, devfileRegistryURL) - if err != nil { - return err - } - - dockerfileImage, err := SearchForDockerfile(detectedDevfile) - if err != nil { - return err - } - - var dockerfileUri string - if dockerfileImage != nil { - dockerfileUri = dockerfileImage.Uri - } - link, err := UpdateGitLink(sampleRepoURL, "", dockerfileUri) - if err != nil { - return err - } - - dockerfileContextMapFromRepo[context] = link - // only set if not empty - if detectedPorts != nil && !reflect.DeepEqual(detectedPorts, []int{}) { - componentPortsMapFromRepo[context] = detectedPorts - } - isDockerfilePresent = true - } - } - - if !isDevfilePresent && isDockerfilePresent { - // Still invoke alizer to detect the ports from the component - _, _, _, detectedPorts, err := AnalyzeAndDetectDevfile(a, localpath, devfileRegistryURL) - if err == nil { - if detectedPorts != nil && !reflect.DeepEqual(detectedPorts, []int{}) { - componentPortsMapFromRepo[context] = detectedPorts - } - } else { - log.Info(fmt.Sprintf("failed to detect port from context: %v, error: %v", context, err)) - } - } - return nil -} - -// SearchForDockerfile searches for a Dockerfile from a devfile image component. -// If no Dockerfile is found, nil will be returned. -// token is required if the devfile has a parent reference to a private repo -func SearchForDockerfile(devfileBytes []byte) (*v1alpha2.DockerfileImage, error) { - if len(devfileBytes) == 0 { - return nil, nil - } - devfileData, err := ParseDevfileWithParserArgs(&parser.ParserArgs{Data: devfileBytes}) - - if err != nil { - retErr := &InvalidDevfile{Err: err} - return nil, retErr - } - devfileImageComponents, err := devfileData.GetComponents(common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: v1alpha2.ImageComponentType, - }, - }) - if err != nil { - return nil, err - } - - for _, component := range devfileImageComponents { - // Only check for the Dockerfile Uri at this point, in later stages we need to account for Dockerfile from Git & the Registry - if component.Image != nil && component.Image.Dockerfile != nil && component.Image.Dockerfile.DockerfileSrc.Uri != "" { - return component.Image.Dockerfile, nil - } - } - - return nil, nil -} - -// Analyze is a wrapper call to Alizer's Analyze() -func (a AlizerClient) Analyze(path string) ([]model.Language, error) { - return recognizer.Analyze(path) -} - -// SelectDevFileFromTypes is a wrapper call to Alizer's SelectDevFileFromTypes() -func (a AlizerClient) SelectDevFileFromTypes(path string, devFileTypes []model.DevfileType) (model.DevfileType, error) { - index, err := recognizer.SelectDevFileFromTypes(path, devFileTypes) - if err != nil { - return model.DevfileType{}, err - } - return devFileTypes[index], err -} - -func (a AlizerClient) DetectComponents(path string) ([]model.Component, error) { - return recognizer.DetectComponents(path) -} - -// AnalyzeAndDetectDevfile analyzes and attempts to detect a devfile from the devfile registry for a given local path -// The following values are returned, in addition to an error -// 1. the detected devfile, in bytes -// 2. the detected endpoints in the devfile -// 3. the detected type of the source code -// 4. the detected ports found in the source code -func AnalyzeAndDetectDevfile(a Alizer, path, devfileRegistryURL string) ([]byte, string, string, []int, error) { - var devfileBytes []byte - alizerDevfileTypes, err := getAlizerDevfileTypes(devfileRegistryURL) - if err != nil { - return nil, "", "", nil, err - } - - alizerComponents, err := a.DetectComponents(path) - if err != nil { - return nil, "", "", nil, err - } - - if len(alizerComponents) == 0 { - return nil, "", "", nil, &NoDevfileFound{Location: path} - } - - // Assuming it's a single component. as multi-component should be handled before - for _, language := range alizerComponents[0].Languages { - if language.CanBeComponent { - // if we get one language analysis that can be a component - // we can then determine a devfile from the registry and return - - // The highest rank is the most suggested component. priorty: configuration file > high % - - detectedType, err := a.SelectDevFileFromTypes(path, alizerDevfileTypes) - if err != nil && err.Error() != fmt.Sprintf("No valid devfile found for project in %s", path) { - // No need to check for err, if a path does not have a detected devfile, ignore err - // if a dir can be a component but we get an unrelated err, err out - return nil, "", "", nil, err - } else if !reflect.DeepEqual(detectedType, model.DevfileType{}) { - // Note: Do not use the Devfile registry endpoint devfileRegistry/devfiles/detectedType.Name - // until the Devfile registry support uploads the Devfile Kubernetes component relative uri file - // as an artifact and made accessible via devfile/library or devfile/registry-support - sampleRepoURL, err := GetRepoFromRegistry(detectedType.Name, devfileRegistryURL) - if err != nil { - return nil, "", "", nil, err - } - detectedDevfileEndpoint, err := UpdateGitLink(sampleRepoURL, "", Devfile) - if err != nil { - return nil, "", "", nil, err - } - - // This is the community registry we are parsing the sample from, so we don't need to pass in the git token - compDevfileData, err := ParseDevfileWithParserArgs(&parser.ParserArgs{URL: detectedDevfileEndpoint}) - - if err != nil { - return nil, "", "", nil, err - } - devfileBytes, err = yaml.Marshal(compDevfileData) - if err != nil { - return nil, "", "", nil, err - } - - if len(devfileBytes) > 0 { - return devfileBytes, detectedDevfileEndpoint, detectedType.Name, alizerComponents[0].Ports, nil - } - } - } - } - - return nil, "", "", nil, &NoDevfileFound{Location: path} -} diff --git a/cdq-analysis/pkg/detect_mock.go b/cdq-analysis/pkg/detect_mock.go deleted file mode 100644 index a5e22470e..000000000 --- a/cdq-analysis/pkg/detect_mock.go +++ /dev/null @@ -1,268 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/devfile/alizer/pkg/apis/model" -) - -type MockAlizerClient struct { -} - -// DetectComponents is a wrapper call to Alizer's DetectComponents() -func (a MockAlizerClient) DetectComponents(path string) ([]model.Component, error) { - if strings.Contains(path, "errorAnalyze") { - return nil, fmt.Errorf("dummy DetectComponents err") - } else if strings.Contains(path, "empty") { - return []model.Component{}, nil - } else if strings.Contains(path, "devfile-sample-nodejs-basic") { - return []model.Component{ - { - Path: path, - Languages: []model.Language{ - { - Name: "nodejs", - Weight: 60.4, - CanBeComponent: true, - }, - }, - Ports: []int{8080}, - }, - }, nil - } else if strings.Contains(path, "nodejs-no-dockerfile") { - return []model.Component{ - { - Path: path, - Languages: []model.Language{ - { - Name: "JavaScript", - Aliases: []string{ - "js", - "node", - "nodejs", - }, - Frameworks: []string{ - "Express", - }, - Tools: []string{ - "NodeJs", - "Node.js", - }, - Weight: 100, - CanBeComponent: true, - }, - }, - }, - }, nil - } else if strings.Contains(path, "dockerfile-node-sample") { - return []model.Component{ - { - Path: path, - Ports: []int{5050}, - Languages: []model.Language{ - { - Name: "JavaScript", - Aliases: []string{ - "js", - "node", - "nodejs", - }, - Frameworks: []string{ - "Express", - }, - Tools: []string{ - "NodeJs", - "Node.js", - }, - Weight: 100, - CanBeComponent: true, - }, - }, - }, - }, nil - } else if strings.Contains(path, "python-src-none") { - return []model.Component{ - { - Path: path, - Languages: []model.Language{ - { - Name: "python", - Weight: 99, - CanBeComponent: true, - }, - }, - }, - }, nil - } else if strings.Contains(path, "python-src-docker") { - return []model.Component{ - { - Path: path, - Languages: []model.Language{ - { - Name: "python", - Weight: 100, - CanBeComponent: true, - }, - }, - }, - }, nil - } else if strings.Contains(path, "spring-boot-root-component") { - return []model.Component{ - { - Path: path, - Languages: []model.Language{ - { - Name: "Java", - Frameworks: []string{ - "Spring", - }, - Tools: []string{ - "Maven", - }, - Weight: 100, - CanBeComponent: true, - }, - }, - }, - { - Path: filepath.Join(path, "src/main/resources/static"), - Languages: []model.Language{ - { - Name: "JavaScript", - Aliases: []string{ - "js", - "node", - "nodejs", - "TypeScript", - }, - Frameworks: []string{ - "Vue", - }, - Tools: []string{ - "NodeJs", - "Node.js", - }, - Weight: 100, - CanBeComponent: true, - }, - }, - }, - }, nil - } else if strings.Contains(path, "quality-dashboard") { - return []model.Component{ - { - Path: filepath.Join(path, "backend"), - Languages: []model.Language{ - { - Name: "Go", - Aliases: []string{ - "golang", - }, - Frameworks: []string{ - "Mux", - }, - Tools: []string{ - "1.19", - }, - Weight: 100, - CanBeComponent: true, - }, - }, - }, - { - Path: filepath.Join(path, "frontend"), - Languages: []model.Language{ - { - Name: "Typescript", - Aliases: []string{ - "ts", - "Javascript", - }, - Frameworks: []string{ - "React", - }, - Tools: []string{ - "NodeJs", - "Node.js", - }, - Weight: 100, - CanBeComponent: true, - }, - }, - }, - }, nil - } else if !strings.Contains(path, "springboot") && !strings.Contains(path, "python") { - return nil, nil - } - - return []model.Component{ - { - Path: path, - Languages: []model.Language{ - { - Name: "springboot", - Weight: 60.4, - CanBeComponent: true, - }, - { - Name: "python", - Weight: 22.4, - CanBeComponent: true, - }, - }, - }, - }, nil -} - -// SelectDevFileFromTypes is a wrapper call to Alizer's SelectDevFileFromTypes() -func (a MockAlizerClient) SelectDevFileFromTypes(path string, devFileTypes []model.DevfileType) (model.DevfileType, error) { - if strings.Contains(path, "/errorSelectDevFileFromTypes") { - return model.DevfileType{}, fmt.Errorf("dummy SelectDevFileFromTypes err") - } else if strings.Contains(path, "/error/devfileendpoint") { - return model.DevfileType{ - Name: "fake", - }, nil - } else if strings.Contains(path, "java-springboot-basic") || strings.Contains(path, "springboot") || strings.Contains(path, "spring-boot-root-component") { - return model.DevfileType{ - Name: "java-springboot-basic", - }, nil - } else if strings.Contains(path, "devfile-sample-nodejs-basic") { - return model.DevfileType{ - Name: "nodejs-basic", - }, nil - } else if strings.Contains(path, "python-basic") || strings.Contains(path, "python-src-none") || strings.Contains(path, "python-src-docker") { - return model.DevfileType{ - Name: "python-basic", - }, nil - } else if strings.Contains(path, "nodejs-no-dockerfile") || strings.Contains(path, "dockerfile-node-sample") { - return model.DevfileType{ - Name: "nodejs-basic", - Language: "JavaScript", - ProjectType: "Node.js", - Tags: []string{ - "Node.js", - "Express", - "ubi8", - }, - }, nil - } - - return model.DevfileType{}, nil -} diff --git a/cdq-analysis/pkg/detect_test.go b/cdq-analysis/pkg/detect_test.go deleted file mode 100644 index ad29b853c..000000000 --- a/cdq-analysis/pkg/detect_test.go +++ /dev/null @@ -1,268 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "os" - "reflect" - "testing" - - "github.com/devfile/alizer/pkg/apis/model" -) - -func TestAnalyzeAndDetectDevfile(t *testing.T) { - - var mockClient MockAlizerClient - - tests := []struct { - name string - clonePath string - repo string - revision string - token string - registryURL string - wantDevfile bool - wantDevfileEndpoint string - wantDetectedPorts []int - wantErr bool - }{ - { - name: "Successfully detect a devfile from the registry", - clonePath: "/tmp/java-springboot-basic", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - registryURL: DevfileStageRegistryEndpoint, - wantDevfile: true, - wantDevfileEndpoint: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - }, - { - name: "Successfully detect a devfile from the registry using an alternate branch", - clonePath: "/tmp/java-springboot-basic", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - revision: "testbranch", - registryURL: DevfileStageRegistryEndpoint, - wantDevfile: true, - wantDevfileEndpoint: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - }, - { - name: "Cannot detect a devfile for a Scala repository", - clonePath: "/tmp/testscala", - repo: "https://github.com/devfile-resources/scalatemplate", - registryURL: DevfileStageRegistryEndpoint, - wantErr: true, - }, - { - name: "Test err condition for Alizer Analyze", - clonePath: "/tmp/errorAnalyze", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - registryURL: DevfileStageRegistryEndpoint, - wantErr: true, - }, - { - name: "Test with a fake Devfile Registry", - clonePath: "/tmp/path", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - registryURL: "FakeDevfileRegistryEndpoint", - wantErr: true, - }, - { - name: "Test err condition for Alizer SelectDevFileFromTypes", - clonePath: "/tmp/springboot/errorSelectDevFileFromTypes", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - registryURL: DevfileStageRegistryEndpoint, - wantErr: true, - }, - { - name: "Test err condition for failing to hit the devfile endpoint", - clonePath: "/tmp/springboot/error/devfileendpoint", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - registryURL: DevfileStageRegistryEndpoint, - wantErr: true, - }, - { - name: "Component detected successfully with Port(s) detected", - clonePath: "/tmp/nodejsports-devfile-sample-nodejs-basic", - repo: "https://github.com/devfile-resources/node-express-hello-no-devfile", - registryURL: DevfileStageRegistryEndpoint, - wantDevfile: true, - wantDevfileEndpoint: "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml", - wantDetectedPorts: []int{8080}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := CloneRepo(tt.clonePath, GitURL{ - RepoURL: tt.repo, - Revision: tt.revision, - Token: tt.token, - }) - if err != nil { - t.Errorf("got unexpected error %v", err) - } else { - devfileBytes, detectedDevfileEndpoint, _, detectedPorts, err := AnalyzeAndDetectDevfile(mockClient, tt.clonePath, tt.registryURL) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } else if !reflect.DeepEqual(len(devfileBytes) > 0, tt.wantDevfile) { - t.Errorf("Expected devfile: %+v, Got: %+v, devfile %+v", tt.wantDevfile, len(devfileBytes) > 0, string(devfileBytes)) - } else if !reflect.DeepEqual(detectedDevfileEndpoint, tt.wantDevfileEndpoint) { - t.Errorf("Expected devfile endpoint: %+v, Got: %+v", tt.wantDevfileEndpoint, detectedDevfileEndpoint) - } else if !reflect.DeepEqual(detectedPorts, tt.wantDetectedPorts) { - t.Errorf("Expected detected ports: %+v, Got: %+v", tt.wantDetectedPorts, detectedPorts) - } - } - os.RemoveAll(tt.clonePath) - }) - } -} - -func TestSelectDevfileFromTypes(t *testing.T) { - - var alizerClient AlizerClient - - tests := []struct { - name string - clonePath string - repo string - devfileTypes []model.DevfileType - wantErr bool - wantDevfileType model.DevfileType - }{ - { - name: "Successfully detect a devfile from the registry", - clonePath: "/tmp/test-selected-devfile", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - devfileTypes: []model.DevfileType{ - { - Name: "nodejs-basic", Language: "JavaScript", ProjectType: "Node.js", Tags: []string{"Node.js", "Express"}, - }, - { - Name: "code-with-quarkus", Language: "Java", ProjectType: "Quarkus", Tags: []string{"Java", "Quarkus"}, - }, - { - Name: "java-springboot-basic", Language: "Java", ProjectType: "springboot", Tags: []string{"Java", "Spring"}, - }, - { - Name: "python-basic", Language: "Python", ProjectType: "Python", Tags: []string{"Python", "Pip", "Flask"}, - }, - { - Name: "go-basic", Language: "Go", ProjectType: "Go", Tags: []string{"Go"}, - }, - { - Name: "dotnet-basic", Language: ".NET", ProjectType: "dotnet", Tags: []string{".NET"}, - }, - }, - wantErr: false, - wantDevfileType: model.DevfileType{ - Name: "java-springboot-basic", Language: "Java", ProjectType: "springboot", Tags: []string{"Java", "Spring"}, - }, - }, - { - name: "Unable to detect a devfile from the registry", - clonePath: "/tmp/test-no-devfiles-selected", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - devfileTypes: []model.DevfileType{ - { - Name: "python-basic", Language: "Python", ProjectType: "Python", Tags: []string{"Python", "Pip", "Flask"}, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - os.RemoveAll(tt.clonePath) - err := CloneRepo(tt.clonePath, GitURL{ - RepoURL: tt.repo, - Revision: "main", - Token: "", - }) - if err != nil { - t.Errorf("got unexpected error %v", err) - } - - devfileType, err := alizerClient.SelectDevFileFromTypes(tt.clonePath, tt.devfileTypes) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } - - if !tt.wantErr { - if !reflect.DeepEqual(devfileType, tt.wantDevfileType) { - t.Errorf("Expected devfileType: %v, got %v", tt.wantDevfileType, devfileType) - } - } - }) - } -} - -func TestSearchForDockerfile(t *testing.T) { - - tests := []struct { - name string - devfileString string - found bool - wantErr bool - }{ - { - name: "Successfully get the Devfile Uri", - devfileString: ` -schemaVersion: 2.2.0 -metadata: - name: nodejs -components: - - name: outerloop-build - image: - imageName: nodejs-image:latest - dockerfile: - uri: "myuri"`, - found: true, - }, - { - name: "No Devfile Uri", - devfileString: ` -schemaVersion: 2.2.0 -metadata: - name: nodejs -components: - - name: outerloop-build - image: - imageName: nodejs-image:latest - dockerfile: - uri: ""`, - found: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - devfileBytes := []byte(tt.devfileString) - dockerfileImage, err := SearchForDockerfile(devfileBytes) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } else if tt.found && dockerfileImage == nil { - t.Errorf("dockerfile should be found, but got %v", dockerfileImage) - } else if !tt.found && dockerfileImage != nil { - t.Errorf("dockerfile should be found, but got %v", dockerfileImage) - } - }) - } -} diff --git a/cdq-analysis/pkg/devfile.go b/cdq-analysis/pkg/devfile.go deleted file mode 100644 index 68e7bcaf2..000000000 --- a/cdq-analysis/pkg/devfile.go +++ /dev/null @@ -1,356 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "fmt" - "net/url" - "path" - "strings" - - "github.com/pkg/errors" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - devfileValidation "github.com/devfile/api/v2/pkg/validation" - devfilePkg "github.com/devfile/library/v2/pkg/devfile" - "github.com/devfile/library/v2/pkg/devfile/parser" - "github.com/devfile/library/v2/pkg/devfile/parser/data" - "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - parserUtil "github.com/devfile/library/v2/pkg/util" - "github.com/go-logr/logr" - "github.com/hashicorp/go-multierror" - "sigs.k8s.io/yaml" -) - -const ( - DevfileName = "devfile" - HiddenDevfileName = ".devfile" - YamlExtension = ".yaml" - YmlExtension = ".yml" - HiddenDevfileDir = ".devfile" - DockerfileName = "Dockerfile" - AlternateDockerfileName = "dockerfile" - ContainerfileName = "Containerfile" - HiddenDockerDir = ".docker" - DockerDir = "docker" - BuildDir = "build" - - Devfile = DevfileName + YamlExtension // devfile.yaml - HiddenDevfile = HiddenDevfileName + YamlExtension // .devfile.yaml - DevfileYml = DevfileName + YmlExtension // devfile.yml - HiddenDevfilYml = HiddenDevfileName + YmlExtension // .devfile.yml - HiddenDirDevfile = HiddenDevfileDir + "/" + DevfileName + YamlExtension // .devfile/devfile.yaml - HiddenDirHiddenDevfile = HiddenDevfileDir + "/" + HiddenDevfileName + YamlExtension // .devfile/.devfile.yaml - HiddenDirDevfileYml = HiddenDevfileDir + "/" + DevfileName + YmlExtension // .devfile/devfile.yml - HiddenDirHiddenDevfileYml = HiddenDevfileDir + "/" + HiddenDevfileName + YmlExtension // .devfile/.devfile.yml - - Dockerfile = DockerfileName // Dockerfile - HiddenDirDockerfile = HiddenDockerDir + "/" + DockerfileName // .docker/Dockerfile - DockerDirDockerfile = DockerDir + "/" + DockerfileName // docker/Dockerfile - BuildDirDockerfile = BuildDir + "/" + DockerfileName // build/Dockerfile - AlternateDockerfile = AlternateDockerfileName // dockerfile - HiddenDirAlternateDockerfile = HiddenDockerDir + "/" + AlternateDockerfileName // .docker/dockerfile - DockerDirAlternateDockerfile = DockerDir + "/" + AlternateDockerfileName // docker/dockerfile - BuildDirAlternateDockerfile = BuildDir + "/" + AlternateDockerfileName // build/dockerfile - - Containerfile = ContainerfileName // Containerfile - HiddenDirContainerfile = HiddenDockerDir + "/" + ContainerfileName // .docker/Containerfile - DockerDirContainerfile = DockerDir + "/" + ContainerfileName // docker/Containerfile - BuildDirContainerfile = BuildDir + "/" + ContainerfileName // build/Containerfile - - // DevfileRegistryEndpoint is the endpoint of the devfile registry - DevfileRegistryEndpoint = "https://registry.devfile.io" - - // DevfileStageRegistryEndpoint is the endpoint of the staging devfile registry - DevfileStageRegistryEndpoint = "https://registry.stage.devfile.io" - - // CDQAnalysisImage is the image of the go module, to spwan the k8s job - CDQAnalysisImage = "quay.io/redhat-appstudio/cdq-analysis:latest" -) - -var ValidDevfileLocations = []string{Devfile, HiddenDevfile, DevfileYml, HiddenDevfilYml, HiddenDirDevfile, - HiddenDirHiddenDevfile, HiddenDirDevfileYml, HiddenDirHiddenDevfileYml} - -var ValidDockerfileLocations = []string{Dockerfile, DockerDirDockerfile, HiddenDirDockerfile, BuildDirDockerfile, - AlternateDockerfile, DockerDirAlternateDockerfile, HiddenDirAlternateDockerfile, BuildDirAlternateDockerfile, - Containerfile, DockerDirContainerfile, HiddenDirContainerfile, BuildDirContainerfile} - -// ScanRepo attempts to read and return devfiles and dockerfiles from the local path upto the specified depth -// Iterate through each sub-folder under first level, and scan for component. (devfile, dockerfile, then Alizer) -// If no devfile(s) or dockerfile(s) are found in sub-folders of the root directory, then the Alizer tool is used to detect and match a devfile/dockerfile from the devfile registry -// ScanRepo returns 3 maps and an error: -// Map 1 returns a context to the devfile bytes if present. -// Map 2 returns a context to the matched devfileURL from the devfile registry if no devfile is present in the context. -// Map 3 returns a context to the Dockerfile uri or a matched DockerfileURL from the devfile registry if no Dockerfile/Containerfile is present in the context -// Map 4 returns a context to the list of ports that were detected by alizer in the source code, at that given context -func ScanRepo(log logr.Logger, a Alizer, localpath string, srcContext string, cdqInfo CDQInfo, cdqUtil CDQUtil) (map[string][]byte, map[string]string, map[string]string, map[string][]int, error) { - return search(log, a, localpath, srcContext, cdqInfo, cdqUtil) -} - -// ValidateDevfile parse and validate a devfile from it's URL, returns if the devfile should be ignored, the devfile raw content and an error if devfile is invalid -// If the devfile failed to parse, or the kubernetes uri is invalid or kubernetes file content is invalid. return an error. -// If no kubernetes components being defined in devfile, then it's not a valid outerloop devfile, the devfile should be ignored. -// If more than one kubernetes components in the devfile, but no deploy commands being defined. return an error -// If more than one image components in the devfile, but no apply commands being defined. return an error -func validateDevfile(log logr.Logger, devfileLocation string, token string) (shouldIgnoreDevfile bool, devfileBytes []byte, err error) { - log.Info(fmt.Sprintf("Validating the devfile from location: %s...", devfileLocation)) - shouldIgnoreDevfile = false - parserArgs := &parser.ParserArgs{Token: token} - if strings.HasPrefix(devfileLocation, "http://") || strings.HasPrefix(devfileLocation, "https://") { - parserArgs.URL = devfileLocation - } else { - parserArgs.Path = devfileLocation - } - - devfileData, err := ParseDevfileWithParserArgs(parserArgs) - - if err != nil { - var newErr error - if merr, ok := err.(*multierror.Error); ok { - for i := range merr.Errors { - switch merr.Errors[i].(type) { - case *devfileValidation.MissingDefaultCmdWarning: - log.Info(fmt.Sprintf("devfile is missing default command, found a warning: %v", merr.Errors[i])) - default: - newErr = multierror.Append(newErr, merr.Errors[i]) - } - } - } else { - newErr = err - } - if newErr != nil { - if merr, ok := newErr.(*multierror.Error); !ok || len(merr.Errors) != 0 { - log.Error(err, fmt.Sprintf("failed to parse the devfile content from %s", devfileLocation)) - return shouldIgnoreDevfile, nil, fmt.Errorf(fmt.Sprintf("err: %v, failed to parse the devfile content from %s", err, devfileLocation)) - } - } - } - deployCompMap, err := parser.GetDeployComponents(devfileData) - if err != nil { - log.Error(err, fmt.Sprintf("failed to get deploy components from %s", devfileLocation)) - return shouldIgnoreDevfile, nil, fmt.Errorf(fmt.Sprintf("err: %v, failed to get deploy components from %s", err, devfileLocation)) - } - devfileBytes, err = yaml.Marshal(devfileData) - if err != nil { - return shouldIgnoreDevfile, nil, err - } - kubeCompFilter := common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: v1alpha2.KubernetesComponentType, - }, - } - kubeComp, err := devfileData.GetComponents(kubeCompFilter) - if err != nil { - log.Error(err, fmt.Sprintf("failed to get kubernetes component from %s", devfileLocation)) - shouldIgnoreDevfile = true - return shouldIgnoreDevfile, nil, nil - } - if len(kubeComp) == 0 { - log.Info(fmt.Sprintf("Found 0 kubernetes components being defined in devfile from %s, it is not a valid outerloop definition, the devfile will be ignored. A devfile will be matched from the devfile registry...", devfileLocation)) - shouldIgnoreDevfile = true - return shouldIgnoreDevfile, nil, nil - } else { - if len(kubeComp) > 1 { - found := false - for _, component := range kubeComp { - if _, ok := deployCompMap[component.Name]; ok { - found = true - break - } - } - if !found { - err = fmt.Errorf("found more than one kubernetes components, but no deploy command associated with any being defined in the devfile from %s", devfileLocation) - log.Error(err, "failed to validate devfile") - return shouldIgnoreDevfile, nil, err - } - } - // TODO: if only one kube component, should return a warning that no deploy command being defined - } - imageCompFilter := common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: v1alpha2.ImageComponentType, - }, - } - imageComp, err := devfileData.GetComponents(imageCompFilter) - if err != nil { - log.Error(err, fmt.Sprintf("failed to get image component from %s", devfileLocation)) - return shouldIgnoreDevfile, nil, fmt.Errorf(fmt.Sprintf("err: %v, failed to get image component from %s", err, devfileLocation)) - } - if len(imageComp) == 0 { - log.Info(fmt.Sprintf("Found 0 image components being defined in devfile from %s, it is not a valid outerloop definition, the devfile will be ignored. A devfile will be matched from the devfile registry...", devfileLocation)) - shouldIgnoreDevfile = true - return shouldIgnoreDevfile, nil, nil - } else if len(imageComp) > 1 { - found := false - for _, component := range imageComp { - err = validateImageComponentDockerfile(log, component, devfileLocation, token) - if err != nil { - log.Error(err, fmt.Sprintf("failed to validate the Dockerfile from the Image Component %s in the devfile", component.Name)) - return shouldIgnoreDevfile, nil, err - } - - if _, ok := deployCompMap[component.Name]; ok { - found = true - break - } - } - if !found { - err = fmt.Errorf("found more than one image components, but no deploy command associated with any being defined in the devfile from %s", devfileLocation) - log.Error(err, "failed to validate devfile") - return shouldIgnoreDevfile, nil, err - } - } else if len(imageComp) == 1 { - // TODO: if only one image component, should return a warning that no apply command being defined - component := imageComp[0] - err = validateImageComponentDockerfile(log, component, devfileLocation, token) - if err != nil { - log.Error(err, fmt.Sprintf("failed to validate the Dockerfile from the Image Component %s in the devfile", component.Name)) - return shouldIgnoreDevfile, nil, err - } - } - - return shouldIgnoreDevfile, devfileBytes, nil -} - -// validateImageComponentDockerfile validates the given image component dockerfile for a devfile location -func validateImageComponentDockerfile(log logr.Logger, component v1alpha2.Component, devfileLocation, token string) (err error) { - if component.Image != nil && component.Image.Dockerfile != nil && component.Image.Dockerfile.DockerfileSrc.Uri != "" { - dockerfileURI := component.Image.Dockerfile.DockerfileSrc.Uri - absoluteDockerfileURI := strings.HasPrefix(dockerfileURI, "http://") || strings.HasPrefix(dockerfileURI, "https://") - absoluteDevfileLocation := strings.HasPrefix(devfileLocation, "http://") || strings.HasPrefix(devfileLocation, "https://") - - if absoluteDockerfileURI { - // absolute Dockerfile uri - log.Info(fmt.Sprintf("Checking if the Dockerfile location %s is reachable", dockerfileURI)) - _, err = CurlEndpoint(dockerfileURI, token) - } else { - if !absoluteDevfileLocation { - // local devfile src with relative Dockerfile uri - dockerfileURI = path.Join(path.Dir(devfileLocation), dockerfileURI) - log.Info(fmt.Sprintf("Checking if the Dockerfile location %s is reachable", dockerfileURI)) - err = parserUtil.ValidateFile(dockerfileURI) - } else { - // remote devfile src with relative Dockerfile uri - var u *url.URL - u, err = url.Parse(devfileLocation) - if err != nil { - log.Error(err, fmt.Sprintf("failed to parse URL from %s", devfileLocation)) - return fmt.Errorf(fmt.Sprintf("failed to parse URL from %s", devfileLocation)) - } - u.Path = path.Join(path.Dir(u.Path), dockerfileURI) - dockerfileURI = u.String() - log.Info(fmt.Sprintf("Checking if the Dockerfile location %s is reachable", dockerfileURI)) - _, err = CurlEndpoint(dockerfileURI, token) - } - } - if err != nil { - errMsg := fmt.Sprintf("failed to get Dockerfile from the location %s for the image component: %s", dockerfileURI, component.Name) - log.Error(err, errMsg) - return fmt.Errorf(errMsg) - } - } - - return nil -} - -// DevfileSrc specifies the src of the Devfile -type DevfileSrc struct { - Data string - URL string - Path string -} - -// ParseDevfileWithParserArgs is an alternative implementation of ParseDevfile which gives the client the flexibility to call the devfile parser with custom parser arguments. -// The default httpTimeout is set to 10. To override this value, specify the preferred value in parserArgs.HTTPTimeout -func ParseDevfileWithParserArgs(parserArgs *parser.ParserArgs) (data.DevfileData, error) { - - if parserArgs.HTTPTimeout == nil { - httpTimeout := 10 - parserArgs.HTTPTimeout = &httpTimeout - } - - if len(parserArgs.Data) == 0 && parserArgs.URL == "" && parserArgs.Path == "" { - return nil, fmt.Errorf("cannot parse devfile without a src") - } - - devfileObj, _, err := devfilePkg.ParseDevfileAndValidate(*parserArgs) - return devfileObj.Data, err -} - -// ParseDevfile calls the devfile library's parse and returns the devfile data. -// Provide either a Data src or the URL src -// Deprecated, use ParseDevfileWithParserArgs instead -func ParseDevfile(src DevfileSrc) (data.DevfileData, error) { - - httpTimeout := 10 - convert := true - parserArgs := parser.ParserArgs{ - HTTPTimeout: &httpTimeout, - ConvertKubernetesContentInUri: &convert, - } - - if src.Data != "" { - parserArgs.Data = []byte(src.Data) - } else if src.URL != "" { - parserArgs.URL = src.URL - } else if src.Path != "" { - parserArgs.Path = src.Path - } else { - return nil, fmt.Errorf("cannot parse devfile without a src") - } - devfileObj, _, err := devfilePkg.ParseDevfileAndValidate(parserArgs) - return devfileObj.Data, err -} - -// FindValidDevfiles will search through the list of valid devfile locations and update the DevfileInfo object if a valid devfilepath is found -func FindValidDevfiles(cdqInfo *CDQInfo) ([]byte, error) { - var devfileBytes []byte - Fs := cdqInfo.ClonedRepo.Fs - if isExist, _ := IsExisting(Fs, cdqInfo.ClonedRepo.ClonedPath); isExist { - for _, path := range ValidDevfileLocations { - devfileTempPath := cdqInfo.ClonedRepo.ComponentPath + "/" + path - if isExist, _ := IsExisting(Fs, devfileTempPath); isExist { - cdqInfo.devfilePath = path - //read contents - devfileBytes, err := Fs.ReadFile(devfileTempPath) - if err != nil { - return nil, errors.Wrapf(err, "failed to read yaml from path %q", devfileTempPath) - } - return devfileBytes, nil - } - } - } - return devfileBytes, &NoDevfileFound{Location: cdqInfo.ClonedRepo.ClonedPath} -} - -func FindValidDockerfile(cdqInfo *CDQInfo) ([]byte, error) { - var dockerfileBytes []byte - Fs := cdqInfo.ClonedRepo.Fs - for _, path := range ValidDockerfileLocations { - dockerfileTempPath := cdqInfo.ClonedRepo.ComponentPath + "/" + path - if isExist, _ := IsExisting(Fs, dockerfileTempPath); isExist { - cdqInfo.dockerfilePath = path - //read contents - dockerfileBytes, err := Fs.ReadFile(dockerfileTempPath) - if err != nil { - return nil, errors.Wrapf(err, "failed to read yaml from path %q", dockerfileTempPath) - } - return dockerfileBytes, nil - } - } - - return dockerfileBytes, &NoDockerfileFound{Location: cdqInfo.ClonedRepo.ClonedPath} -} diff --git a/cdq-analysis/pkg/devfile_test.go b/cdq-analysis/pkg/devfile_test.go deleted file mode 100644 index c60bce993..000000000 --- a/cdq-analysis/pkg/devfile_test.go +++ /dev/null @@ -1,652 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "fmt" - "net" - "net/http" - "net/http/httptest" - "os" - "path" - "reflect" - "strings" - "testing" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/api/v2/pkg/attributes" - "github.com/devfile/api/v2/pkg/devfile" - devfilePkg "github.com/devfile/library/v2/pkg/devfile" - "github.com/devfile/library/v2/pkg/devfile/parser" - "github.com/devfile/library/v2/pkg/devfile/parser/data" - v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" - devfileParserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util" - "github.com/go-logr/logr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/yaml" -) - -func TestParseDevfileModel(t *testing.T) { - testServerURL := "127.0.0.1:9080" - - simpleDevfile := ` -metadata: - attributes: - appModelRepository.url: https://github.com/testorg/petclinic-app - gitOpsRepository.url: https://github.com/testorg/petclinic-gitops - name: petclinic -schemaVersion: 2.2.0` - - testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte(simpleDevfile)) - if err != nil { - t.Errorf("TestParseDevfileModel() unexpected error while writing data: %v", err) - } - })) - // create a listener with the desired port. - l, err := net.Listen("tcp", testServerURL) - if err != nil { - t.Errorf("TestParseDevfileModel() unexpected error while creating listener: %v", err) - return - } - - // NewUnstartedServer creates a listener. Close that listener and replace - // with the one we created. - testServer.Listener.Close() - testServer.Listener = l - - testServer.Start() - defer testServer.Close() - - localPath := "/tmp/testDir" - localDevfilePath := path.Join(localPath, "devfile.yaml") - // prepare for local file - err = os.MkdirAll(localPath, 0755) - if err != nil { - t.Errorf("TestParseDevfileModel() error: failed to create folder: %v, error: %v", localPath, err) - } - err = os.WriteFile(localDevfilePath, []byte(simpleDevfile), 0644) - if err != nil { - t.Errorf("TestParseDevfileModel() error: fail to write to file: %v", err) - } - - if err != nil { - t.Error(err) - } - - defer os.RemoveAll(localPath) - - tests := []struct { - name string - devfileString string - devfileURL string - devfilePath string - wantDevfile *v2.DevfileV2 - wantMetadata devfile.DevfileMetadata - wantSchemaVersion string - }{ - { - name: "Simple devfile from data", - devfileString: simpleDevfile, - wantMetadata: devfile.DevfileMetadata{ - Name: "petclinic", - Attributes: attributes.Attributes{}.PutString("gitOpsRepository.url", "https://github.com/testorg/petclinic-gitops").PutString("appModelRepository.url", "https://github.com/testorg/petclinic-app"), - }, - wantSchemaVersion: string(data.APISchemaVersion220), - }, - { - name: "Simple devfile from URL", - devfileURL: "http://" + testServerURL, - wantMetadata: devfile.DevfileMetadata{ - Name: "petclinic", - Attributes: attributes.Attributes{}.PutString("gitOpsRepository.url", "https://github.com/testorg/petclinic-gitops").PutString("appModelRepository.url", "https://github.com/testorg/petclinic-app"), - }, - wantSchemaVersion: string(data.APISchemaVersion220), - }, - { - name: "Simple devfile from PATH", - devfilePath: localDevfilePath, - wantMetadata: devfile.DevfileMetadata{ - Name: "petclinic", - Attributes: attributes.Attributes{}.PutString("gitOpsRepository.url", "https://github.com/testorg/petclinic-gitops").PutString("appModelRepository.url", "https://github.com/testorg/petclinic-app"), - }, - wantSchemaVersion: string(data.APISchemaVersion220), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - parserArgs := &parser.ParserArgs{} - if tt.devfileString != "" { - parserArgs.Data = []byte(tt.devfileString) - } else if tt.devfileURL != "" { - parserArgs.URL = tt.devfileURL - } else if tt.devfilePath != "" { - parserArgs.Path = tt.devfilePath - } - devfile, err := ParseDevfileWithParserArgs(parserArgs) - if err != nil { - t.Errorf("TestParseDevfileModel() unexpected error: %v", err) - } else { - gotMetadata := devfile.GetMetadata() - if !reflect.DeepEqual(gotMetadata, tt.wantMetadata) { - t.Errorf("TestParseDevfileModel() metadata is different") - } - - gotSchemaVersion := devfile.GetSchemaVersion() - if gotSchemaVersion != tt.wantSchemaVersion { - t.Errorf("TestParseDevfileModel() schema version is different") - } - } - }) - } -} - -func TestScanRepo(t *testing.T) { - - var logger logr.Logger - var alizerClient AlizerClient // Use actual client because this is a huge wrapper function and mocking so many possibilities is pretty tedious when everything is changing frequently - - tests := []struct { - name string - clonePath string - repo string - revision string - token string - wantErr bool - expectedDevfileContext []string - expectedDevfileURLContextMap map[string]string - expectedDockerfileContextMap map[string]string - expectedPortsMap map[string][]int - }{ - { - name: "Should return 2 devfile contexts, and 2 devfileURLs as this is a multi comp devfile", - clonePath: "/tmp/testclone", - repo: "https://github.com/devfile-resources/multi-components-deep", - expectedDevfileContext: []string{"python", "devfile-sample-java-springboot-basic"}, - expectedDevfileURLContextMap: map[string]string{ - "devfile-sample-java-springboot-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-deep/main/devfile-sample-java-springboot-basic/.devfile/.devfile.yaml", - "python": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - }, - expectedDockerfileContextMap: map[string]string{ - "devfile-sample-java-springboot-basic": "devfile-sample-java-springboot-basic/docker/Dockerfile", - "python": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile"}, - expectedPortsMap: map[string][]int{ - "python": {8081}, - }, - }, - { - name: "Should return 2 devfile contexts, and 2 devfileURLs as this is a multi comp devfile - with revision specified", - clonePath: "/tmp/testclone", - repo: "https://github.com/devfile-resources/multi-components-deep", - revision: "main", - expectedDevfileContext: []string{"python", "devfile-sample-java-springboot-basic"}, - expectedDevfileURLContextMap: map[string]string{ - "devfile-sample-java-springboot-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-deep/main/devfile-sample-java-springboot-basic/.devfile/.devfile.yaml", - "python": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - }, - expectedDockerfileContextMap: map[string]string{ - "devfile-sample-java-springboot-basic": "devfile-sample-java-springboot-basic/docker/Dockerfile", - "python": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile"}, - expectedPortsMap: map[string][]int{ - "python": {8081}, - }, - }, - { - name: "Should return 2 devfile contexts, and 2 devfileURLs with multi-component but no outerloop definition", - clonePath: "/tmp/testclone", - repo: "https://github.com/devfile-resources/multi-components-with-no-kubecomps", - expectedDevfileContext: []string{"python", "devfile-sample-java-springboot-basic"}, - expectedDevfileURLContextMap: map[string]string{ - "devfile-sample-java-springboot-basic": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - "python": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - }, - expectedDockerfileContextMap: map[string]string{ - "devfile-sample-java-springboot-basic": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - "python": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile"}, - }, - { - name: "Should return 4 devfiles, 5 devfile url and 5 Dockerfile uri as this is a multi comp devfile", - clonePath: "/tmp/testclone", - repo: "https://github.com/devfile-resources/multi-components-dockerfile", - expectedDevfileContext: []string{"devfile-sample-java-springboot-basic", "devfile-sample-nodejs-basic", "devfile-sample-python-basic", "python-src-none"}, - expectedDevfileURLContextMap: map[string]string{ - "devfile-sample-java-springboot-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/.devfile/.devfile.yaml", - "devfile-sample-nodejs-basic": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml", - "devfile-sample-python-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-python-basic/.devfile.yaml", - "python-src-none": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - "python-src-docker": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - }, - expectedDockerfileContextMap: map[string]string{ - "python-src-docker": "Dockerfile", - "devfile-sample-nodejs-basic": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/Dockerfile", - "devfile-sample-java-springboot-basic": "devfile-sample-java-springboot-basic/docker/Dockerfile", - "python-src-none": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile", - "devfile-sample-python-basic": "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-python-basic/Dockerfile"}, - expectedPortsMap: map[string][]int{ - "devfile-sample-nodejs-basic": {3000}, - }, - }, - { - name: "Should return 4 Dockerfile contexts with Dockerfile/Containerfile path, and 4 devfileURLs ", - clonePath: "/tmp/testclone", - repo: "https://github.com/devfile-resources/multi-components-dockerfile", - revision: "containerfile", - expectedDevfileURLContextMap: map[string]string{ - "java-springboot-containerfile": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - "java-springboot-dockerfile": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - "python-dockerfile": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - "python-containerfile": "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml", - }, - expectedDockerfileContextMap: map[string]string{ - "java-springboot-dockerfile": "docker/Dockerfile", - "java-springboot-containerfile": "docker/Containerfile", - "python-dockerfile": "docker/Dockerfile", - "python-containerfile": "Containerfile"}, - }, - { - name: "Should return one context with one devfile, along with one port detected", - clonePath: "/tmp/testclonenode-devfile-sample-nodejs-basic", - repo: "https://github.com/devfile-resources/single-component-port-detected", - expectedDevfileContext: []string{"nodejs"}, - expectedDevfileURLContextMap: map[string]string{ - "nodejs": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml", - }, - expectedDockerfileContextMap: map[string]string{ - "nodejs": "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/Dockerfile", - }, - expectedPortsMap: map[string][]int{ - "nodejs": {8080}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - logger = ctrl.Log.WithName("TestScanRepo") - err := CloneRepo(tt.clonePath, GitURL{ - RepoURL: tt.repo, - Revision: tt.revision, - Token: tt.token, - }) - URL := tt.repo - if err != nil { - t.Errorf("got unexpected error %v", err) - } else { - devfileInfo := CDQInfo{ - DevfileRegistryURL: DevfileStageRegistryEndpoint, - GitURL: GitURL{RepoURL: URL}, - } - devfileMap, devfileURLMap, dockerfileMap, portsMap, err := ScanRepo(logger, alizerClient, tt.clonePath, "", devfileInfo, NewCDQUtilClient()) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else { - for actualContext := range devfileMap { - matched := false - for _, expectedContext := range tt.expectedDevfileContext { - if expectedContext == actualContext { - matched = true - break - } - } - - if !matched { - t.Errorf("found devfile at context %v but expected none", actualContext) - } - } - - for actualContext := range devfileURLMap { - if devfileURLMap[actualContext] != tt.expectedDevfileURLContextMap[actualContext] { - t.Errorf("expected devfile URL %v but got %v", tt.expectedDevfileURLContextMap[actualContext], devfileURLMap[actualContext]) - } - - } - - for actualContext := range dockerfileMap { - if tt.expectedDockerfileContextMap[actualContext] != dockerfileMap[actualContext] { - t.Errorf("found Dockerfile uri at context %v:%v but expected %v", actualContext, dockerfileMap[actualContext], tt.expectedDockerfileContextMap[actualContext]) - } - } - - for actualContext := range portsMap { - if !reflect.DeepEqual(tt.expectedPortsMap[actualContext], portsMap[actualContext]) { - t.Errorf("found port(s) at context %v:%v but expected %v", actualContext, portsMap[actualContext], tt.expectedPortsMap[actualContext]) - } - } - } - } - os.RemoveAll(tt.clonePath) - }) - } -} - -func TestValidateDevfile(t *testing.T) { - logger := ctrl.Log.WithName("TestValidateDevfile") - httpTimeout := 10 - convert := true - parserArgs := parser.ParserArgs{ - HTTPTimeout: &httpTimeout, - ConvertKubernetesContentInUri: &convert, - } - - mockDevfileUtilsClient := devfileParserUtil.NewMockDevfileUtilsClient() - parserArgs.DevfileUtilsClient = &mockDevfileUtilsClient - - springDevfileParser := parserArgs - springDevfileParser.URL = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml" - - springDevfileObj, _, err := devfilePkg.ParseDevfileAndValidate(springDevfileParser) - if err != nil { - t.Errorf("TestValidateDevfile() unexpected error: %v", err) - } - springDevfileBytes, err := yaml.Marshal(springDevfileObj.Data) - if err != nil { - t.Errorf("TestValidateDevfile() unexpected error: %v", err) - } - - springDevfileWithAbsoluteDockerfileParser := parserArgs - springDevfileWithAbsoluteDockerfileParser.URL = "https://raw.githubusercontent.com/devfile-resources/spring-sample-with-absolute-dockerfileURI/main/devfile.yaml" - springDevfileObjWithAbsoluteDockerfile, _, err := devfilePkg.ParseDevfileAndValidate(springDevfileWithAbsoluteDockerfileParser) - if err != nil { - t.Errorf("TestValidateDevfile() unexpected error: %v", err) - } - springDevfileWithAbsoluteDockerfileBytes, err := yaml.Marshal(springDevfileObjWithAbsoluteDockerfile.Data) - if err != nil { - t.Errorf("TestValidateDevfile() unexpected error: %v", err) - } - - tests := []struct { - name string - url string - wantDevfileBytes []byte - wantIgnore bool - wantErr bool - }{ - { - name: "should success with valid deploy.yaml URI and relative Dockerfile URI references", - url: springDevfileParser.URL, - wantDevfileBytes: springDevfileBytes, - wantIgnore: false, - wantErr: false, - }, - { - name: "should success with valid Dockerfile absolute URL references", - url: springDevfileWithAbsoluteDockerfileParser.URL, - wantDevfileBytes: springDevfileWithAbsoluteDockerfileBytes, - wantIgnore: false, - wantErr: false, - }, - { - name: "devfile.yaml with invalid deploy.yaml reference", - url: "https://raw.githubusercontent.com/devfile-resources/go-basic-no-deploy-file/main/devfile.yaml", - wantIgnore: false, - wantErr: true, - }, - { - name: "devfile.yaml should be ignored if no kubernetes components defined", - url: "https://raw.githubusercontent.com/devfile/registry/main/stacks/java-springboot/1.2.0/devfile.yaml", - wantIgnore: true, - wantErr: false, - }, - { - name: "devfile.yaml should be ignored if no image components defined", - url: "https://raw.githubusercontent.com/devfile-resources/spring-sample-no-image-comp/main/devfile.yaml", - wantIgnore: true, - wantErr: false, - }, - { - name: "devfile.yaml with no outerloop definition and missing command group", - url: "https://raw.githubusercontent.com/devfile-resources/missing-cmd-group/main/devfile.yaml", - wantIgnore: true, - wantErr: false, - }, - { - name: "should error out with multiple kubernetes components but no deploy command", - url: "https://raw.githubusercontent.com/devfile-resources/spring-multi-kubecomps-no-deploycmd/main/devfile.yaml", - wantIgnore: false, - wantErr: true, - }, - { - name: "should error out with multiple image components but no apply command", - url: "https://raw.githubusercontent.com/devfile-resources/spring-multi-imagecomps-no-applycmd/main/devfile.yaml", - wantIgnore: false, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cdqUtil := NewCDQUtilClient() - shouldIgnoreDevfile, devfileBytes, err := cdqUtil.ValidateDevfile(logger, tt.url, "") - if (err != nil) != tt.wantErr { - t.Errorf("TestValidateDevfile() unexpected error: %v", err) - } - if !tt.wantErr { - if shouldIgnoreDevfile != tt.wantIgnore { - t.Errorf("TestValidateDevfile() wantIgnore is %v, got %v", tt.wantIgnore, shouldIgnoreDevfile) - } - if !tt.wantIgnore && !reflect.DeepEqual(devfileBytes, tt.wantDevfileBytes) { - t.Errorf("devfile content did not match, got %v, wanted %v", string(devfileBytes), string(tt.wantDevfileBytes)) - } - } - - }) - } -} - -func TestValidateImageComponentDockerfile(t *testing.T) { - logger := ctrl.Log.WithName("TestValidateImageComponentDockerfile") - - testServerURL := "127.0.0.1:9080" - - simpleDockerfile := ` -FROM registry.access.redhat.com/ubi9/go-toolset:1.18.9-14 - -COPY . . -RUN go mod download - -RUN go build -o ./main - -ENV PORT 8081 -EXPOSE 8081 - -CMD [ "./main" ]` - - testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.Contains(r.URL.Path, "docker/Dockerfile") { - if _, err := w.Write([]byte(simpleDockerfile)); err != nil { - t.Errorf("TestvalidateImageComponentDockerfile() unexpected error while writing data: %v", err) - } - } - })) - // create a listener with the desired port. - l, err := net.Listen("tcp", testServerURL) - if err != nil { - t.Errorf("TestvalidateImageComponentDockerfile() unexpected error while creating listener: %v", err) - return - } - - // NewUnstartedServer creates a listener. Close that listener and replace - // with the one we created. - testServer.Listener.Close() - testServer.Listener = l - - testServer.Start() - defer testServer.Close() - - Fs := NewFilesystem() - localPath, err := CreateTempPath("validateImageComponentDockerfile", Fs) - if err != nil { - t.Error(err, fmt.Sprintf("Unable to create a temp path %s", localPath)) - return - } - - localDockerfilePath := path.Join(localPath, "Dockerfile") - localDevfilePath := path.Join(localPath, "devfile.yaml") - // prepare for local file - err = os.MkdirAll(localPath, 0755) - if err != nil { - t.Errorf("TestvalidateImageComponentDockerfile() error: failed to create folder: %v, error: %v", localPath, err) - } - err = os.WriteFile(localDockerfilePath, []byte(simpleDockerfile), 0644) - if err != nil { - t.Errorf("TestvalidateImageComponentDockerfile() error: fail to write to file: %v", err) - } - - defer os.RemoveAll(localPath) - - tests := []struct { - name string - devfileLocation string - component v1alpha2.Component - wantErr bool - }{ - { - name: "Simple Dockerfile from an absolute URL", - component: v1alpha2.Component{ - Name: "comp1", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "http://" + testServerURL + "/docker/Dockerfile", - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "Simple Dockerfile from an absolute URL is in err condition", - component: v1alpha2.Component{ - Name: "comp1", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "http://dummyurl", - }, - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Simple Dockerfile from an absolute file path", - component: v1alpha2.Component{ - Name: "comp1", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "Dockerfile", - }, - }, - }, - }, - }, - }, - }, - devfileLocation: localDevfilePath, - }, - { - name: "Simple Dockerfile from an absolute file path is in err condition", - component: v1alpha2.Component{ - Name: "comp1", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "docker/Dockerfile", - }, - }, - }, - }, - }, - }, - }, - devfileLocation: localDevfilePath, - wantErr: true, - }, - { - name: "Simple Dockerfile from a relative file path", - component: v1alpha2.Component{ - Name: "comp1", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "docker/Dockerfile", - }, - }, - }, - }, - }, - }, - }, - devfileLocation: "http://" + testServerURL + "/devfile.yaml", - }, - { - name: "Simple Dockerfile from a relative file path in an err condition", - component: v1alpha2.Component{ - Name: "comp1", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "docker/Dockerfile", - }, - }, - }, - }, - }, - }, - }, - devfileLocation: "http://notvalid/devfile.yaml", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := validateImageComponentDockerfile(logger, tt.component, tt.devfileLocation, "") - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } - }) - } -} diff --git a/cdq-analysis/pkg/errors.go b/cdq-analysis/pkg/errors.go deleted file mode 100644 index 884cf5c5f..000000000 --- a/cdq-analysis/pkg/errors.go +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import "fmt" - -// NoDevfileFound returns an error if no devfile was found -type NoDevfileFound struct { - Location string - Err error -} - -func (e *NoDevfileFound) Error() string { - errMsg := fmt.Sprintf("unable to find devfile in the specified location %s", e.Location) - if e.Err != nil { - errMsg = fmt.Sprintf("%s due to %v", errMsg, e.Err) - } - return errMsg -} - -// NoDockerfileFound returns an error if no dockerfile was found -type NoDockerfileFound struct { - Location string - Err error -} - -func (e *NoDockerfileFound) Error() string { - errMsg := fmt.Sprintf("unable to find dockerfile in the specified location %s", e.Location) - if e.Err != nil { - errMsg = fmt.Sprintf("%s due to %v", errMsg, e.Err) - } - return errMsg -} - -// RepoNotFound returns an error if no git repo was found -type RepoNotFound struct { - URL string - Revision string - Err error -} - -func (e *RepoNotFound) Error() string { - errMsg := fmt.Sprintf("unable to find git repository %s %s", e.URL, e.Revision) - if e.Err != nil { - errMsg = fmt.Sprintf("%s due to %v", errMsg, e.Err) - } - return errMsg -} - -// InvalidDevfile returns an error if no devfile is invalid -type InvalidDevfile struct { - Err error -} - -func (e *InvalidDevfile) Error() string { - var errMsg string - if e.Err != nil { - errMsg = fmt.Sprintf("invalid devfile due to %v", e.Err) - } - return errMsg -} - -// InvalidURL returns an error if URL is invalid to be parsed -type InvalidURL struct { - URL string - Err error -} - -func (e *InvalidURL) Error() string { - var errMsg string - if e.Err != nil { - errMsg = fmt.Sprintf("invalid URL %v due to %v", e.URL, e.Err) - } - return errMsg -} - -// AuthenticationFailed returns an error if authenticated failed when cloning the repository -// indicates the token is not valid -type AuthenticationFailed struct { - URL string - Err error -} - -func (e *AuthenticationFailed) Error() string { - var errMsg string - if e.Err != nil { - errMsg = fmt.Sprintf("authentication failed to URL %v: %v", e.URL, e.Err) - } - return errMsg -} - -// InternalError returns cdq errors other than user error -type InternalError struct { - Err error -} - -func (e *InternalError) Error() string { - errMsg := fmt.Sprintf("internal error: %v", e.Err) - return errMsg -} diff --git a/cdq-analysis/pkg/errors_test.go b/cdq-analysis/pkg/errors_test.go deleted file mode 100644 index 15ae8a951..000000000 --- a/cdq-analysis/pkg/errors_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNoDevfileFoundErr(t *testing.T) { - - tests := []struct { - name string - args NoDevfileFound - wantErrString string - }{ - { - name: "No Devfile Found at location", - args: NoDevfileFound{ - Location: "/path", - }, - wantErrString: "unable to find devfile in the specified location /path", - }, - { - name: "No Devfile Found at location due to an err", - args: NoDevfileFound{ - Location: "/path", - Err: fmt.Errorf("a dummy err"), - }, - wantErrString: "unable to find devfile in the specified location /path due to a dummy err", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - errString := tt.args.Error() - assert.Equal(t, tt.wantErrString, errString, "the err string should be equal") - }) - } -} - -func TestNoDockerfileFoundErr(t *testing.T) { - - tests := []struct { - name string - args NoDockerfileFound - wantErrString string - }{ - { - name: "No Dockerfile Found at location", - args: NoDockerfileFound{ - Location: "/path", - }, - wantErrString: "unable to find dockerfile in the specified location /path", - }, - { - name: "No Dockerfile Found at location due to an err", - args: NoDockerfileFound{ - Location: "/path", - Err: fmt.Errorf("a dummy err"), - }, - wantErrString: "unable to find dockerfile in the specified location /path due to a dummy err", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - errString := tt.args.Error() - assert.Equal(t, tt.wantErrString, errString, "the err string should be equal") - }) - } -} diff --git a/cdq-analysis/pkg/ioutils.go b/cdq-analysis/pkg/ioutils.go deleted file mode 100644 index feb32befe..000000000 --- a/cdq-analysis/pkg/ioutils.go +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Originally from https://github.com/redhat-developer/kam/blob/master/pkg/pipelines/ioutils/file_utils.go - -package pkg - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/spf13/afero" -) - -// NewFilesystem returns a local filesystem based afero FS implementation. -func NewFilesystem() afero.Afero { - return afero.Afero{Fs: afero.NewOsFs()} -} - -// NewMemoryFilesystem returns an in-memory afero FS implementation. -func NewMemoryFilesystem() afero.Afero { - return afero.Afero{Fs: afero.NewMemMapFs()} -} - -// NewReadOnlyFs returns a read-only file system -func NewReadOnlyFs() afero.Afero { - return afero.Afero{Fs: afero.NewReadOnlyFs(afero.NewOsFs())} -} - -// IsExisting returns bool whether path exists -func IsExisting(fs afero.Fs, path string) (bool, error) { - fileInfo, err := fs.Stat(path) - if err != nil { - return false, err - } - if fileInfo.IsDir() { - return true, fmt.Errorf("%q: Dir already exists at %s", filepath.Base(path), path) - } - return true, fmt.Errorf("%q: File already exists at %s", filepath.Base(path), path) -} - -// CreateTempPath creates a temp path with the prefix using the Afero FS -func CreateTempPath(prefix string, appFs afero.Afero) (string, error) { - return appFs.TempDir(os.TempDir(), prefix) -} diff --git a/cdq-analysis/pkg/ioutils_test.go b/cdq-analysis/pkg/ioutils_test.go deleted file mode 100644 index a1b07e29c..000000000 --- a/cdq-analysis/pkg/ioutils_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "os" - "strings" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" -) - -func TestIsExisting(t *testing.T) { - fs := NewFilesystem() - inmemoryFs := NewMemoryFilesystem() - readOnlyFs := NewReadOnlyFs() - dirName := "/tmp/test-dir" - fileName := "/tmp/test-file" - secondFile := "/tmp/test-two" - - // Make sure at least one file and one dir exists in each file system for testing - fs.Create(fileName) - // fs.Mkdir(dirName, 0755) - inmemoryFs.Create(fileName) - inmemoryFs.Mkdir(dirName, 0755) - - tests := []struct { - name string - path string - want bool - wantErrString string - fs afero.Afero - }{ - { - name: "Simple file does not exist, inmemory fs", - path: secondFile, - want: false, - wantErrString: "open /tmp/test-two: file does not exist", - fs: inmemoryFs, - }, - { - name: "File exists, inmemory fs", - path: fileName, - want: true, - wantErrString: "\"test-file\": File already exists at /tmp/test-file", - fs: inmemoryFs, - }, - { - name: "Dir already exists, inmemory fs", - path: dirName, - want: true, - wantErrString: "\"test-dir\": Dir already exists at /tmp/test-dir", - fs: inmemoryFs, - }, - { - name: "File does not exist, regular fs", - path: secondFile, - want: false, - wantErrString: "stat /tmp/test-two: no such file or directory", - fs: fs, - }, - { - name: "Dir already exists, readonly fs", - path: "/", - want: true, - wantErrString: "\"/\": Dir already exists at /", - fs: readOnlyFs, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - exists, err := IsExisting(tt.fs, tt.path) - if tt.wantErrString != "" { - if err == nil { - t.Errorf("TestIsExisting() expected error: %v, got: %v", tt.wantErrString, nil) - } else if err.Error() != tt.wantErrString { - t.Errorf("TestIsExisting() expected error: %v, got: %v", tt.wantErrString, err.Error()) - } - } else if tt.wantErrString == "" && err != nil { - t.Errorf("TestIsExisting() unexpected error: %v, got: %v", tt.wantErrString, err) - } - - if exists != tt.want { - t.Errorf("TestIsExisting() expected: %v, got: %v", tt.want, exists) - } - - }) - } -} - -func TestCreateTempPath(t *testing.T) { - fs := NewFilesystem() - inmemoryFs := NewMemoryFilesystem() - readOnlyFs := NewReadOnlyFs() - - tests := []struct { - name string - fs afero.Afero - wantErr bool - }{ - { - name: "inmemory fs", - fs: inmemoryFs, - }, - { - name: "read only fs", - fs: readOnlyFs, - wantErr: true, - }, - { - name: "local fs", - fs: fs, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - path, err := CreateTempPath("TestCreateTempPath", tt.fs) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error: %v", err) - } else if err == nil { - if !strings.Contains(path, os.TempDir()) { - t.Errorf("TestCreateTempPath error: the temp path should be in the OS temp dir") - } - - if !strings.Contains(path, "TestCreateTempPath") { - t.Errorf("TestCreateTempPath error: the temp path should contain the prefix") - } - - if isExist, err := IsExisting(tt.fs, path); isExist { - assert.NoError(t, tt.fs.RemoveAll(path), "unable to delete the temp path") - } else if err != nil { - t.Errorf("TestCreateTempPath unexpected error: %v", err) - } - } - }) - } -} diff --git a/cdq-analysis/pkg/mock.go b/cdq-analysis/pkg/mock.go deleted file mode 100644 index 8254fa42a..000000000 --- a/cdq-analysis/pkg/mock.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package pkg - -import "github.com/go-logr/logr" - -type CDQUtilMockClient struct { -} - -func NewCDQUtilMockClient() CDQUtilMockClient { - return CDQUtilMockClient{} -} - -func (cdqUtilMockClient CDQUtilMockClient) Clone(k K8sInfoClient, cdqInfo *CDQInfo, namespace, name, context string) error { - - if cdqInfo == nil { - return nil - } - - if cdqInfo.GitURL.Token == "valid-mock-token" { - // This is a private repository case - // Since we are not actually testing with a valid token, - // mock the clone by calling a valid public repository instead. - // This way, we can plug into the existing code where - // CDQ can search for a devfile or a Dockerfile and - // possibly use Alizer. By doing this check, - // CDQ now assumes its a public repository path - // by working around it and we can proceed - // with the private repository test - - cdqInfo.GitURL.Token = "" - } - - return clone(k, cdqInfo, namespace, name, context) -} - -func (cdqUtilMockClient CDQUtilMockClient) ValidateDevfile(log logr.Logger, devfileLocation string, token string) (shouldIgnoreDevfile bool, devfileBytes []byte, err error) { - - if token == "valid-mock-token" { - // This is a private repository case - // Since we are not actually testing with a valid token, - // mock the clone by calling a valid public repository instead. - // This way, we can plug into the existing code where - // CDQ can search for a devfile or a Dockerfile and - // possibly use Alizer. By doing this check, - // CDQ now assumes its a public repository path - // by working around it and we can proceed - // with the private repository test - - token = "" - } - - return validateDevfile(log, devfileLocation, token) -} diff --git a/cdq-analysis/pkg/util.go b/cdq-analysis/pkg/util.go deleted file mode 100644 index e17ce8acd..000000000 --- a/cdq-analysis/pkg/util.go +++ /dev/null @@ -1,281 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "fmt" - "io" - "net/http" - "net/url" - "os" - "os/exec" - "path" - "path/filepath" - "regexp" - "strings" - "time" - - "github.com/devfile/alizer/pkg/apis/model" - "github.com/devfile/registry-support/index/generator/schema" - registryLibrary "github.com/devfile/registry-support/registry-library/library" -) - -const ( - HTTPRequestResponseTimeout = 30 * time.Second -) - -type GitURL struct { - RepoURL string // the repo URL where the devfile is located - Revision string - Token string // TODO: Token should not be exported/exposed via GitURL. CRUD ops should be used to access token -} - -const ( - RepoNotFoundMsg = "repository .* not found" - RevisionNotFoundMsg = "pathspec .* did not match any file(s) known to git" - AuthenticationFailedMsg = "Authentication failed .*" -) - -// CloneRepo clones the repoURL to specfied clonePath -func CloneRepo(clonePath string, gitURL GitURL) error { - exist, err := IsExist(clonePath) - if !exist || err != nil { - os.MkdirAll(clonePath, 0750) - } - cloneURL := gitURL.RepoURL - // Execute does an exec.Command on the specified command - if gitURL.Token != "" { - tempStr := strings.Split(gitURL.RepoURL, "https://") - - // e.g. https://token:<token>@github.com/owner/repoName.git - cloneURL = fmt.Sprintf("https://token:%s@%s", gitURL.Token, tempStr[1]) - } - c := exec.Command("git", "clone", cloneURL, clonePath) - c.Dir = clonePath - - // set env to skip authentication prompt and directly error out - c.Env = os.Environ() - c.Env = append(c.Env, "GIT_TERMINAL_PROMPT=0", "GIT_ASKPASS=/bin/echo") - - output, err := c.CombinedOutput() - if err != nil { - - if matched, _ := regexp.MatchString(RepoNotFoundMsg, string(output)); matched { - return &RepoNotFound{URL: cloneURL, Err: err} - } else if matched, _ := regexp.MatchString(AuthenticationFailedMsg, string(output)); matched { - return &AuthenticationFailed{URL: cloneURL, Err: err} - } - - return fmt.Errorf("failed to clone the repo: %v", err) - } - - if gitURL.Revision != "" { - c = exec.Command("git", "checkout", gitURL.Revision) - c.Dir = clonePath - - _, err = c.CombinedOutput() - if err != nil { - if matched, _ := regexp.MatchString(RevisionNotFoundMsg, string(output)); matched { - return &RepoNotFound{URL: cloneURL, Revision: gitURL.Revision, Err: err} - } - - return fmt.Errorf("failed to checkout the revision %q: %v", gitURL.Revision, err) - } - } - - return nil -} - -// GetBranchFromRepo gets the current branch from the cloned repository -func GetBranchFromRepo(clonePath string) (string, error) { - // Command we want to run is: git rev-parse --abbrev-ref HEAD - c := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") - c.Dir = clonePath - - // Get the output of the command - branchBytes, err := c.CombinedOutput() - if err != nil { - return "", fmt.Errorf("failed to get the branch from the repo: %v", err) - } - branch := string(branchBytes) - - // Remove newline characters potentially present - branch = strings.Split(branch, "\n")[0] - return branch, nil -} - -// CurlEndpoint curls the endpoint and returns the response or an error if the response is a non-200 status -func CurlEndpoint(endpoint, token string) ([]byte, error) { - var respBytes []byte - - req, err := http.NewRequest("GET", endpoint, nil) - if err != nil { - return nil, err - } - - if token != "" { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) - } - - client := &http.Client{ - Transport: &http.Transport{ - ResponseHeaderTimeout: HTTPRequestResponseTimeout, - }, - Timeout: HTTPRequestResponseTimeout, - } - - /* #nosec G107 -- The URL is validated by the CDQ if the request is coming from the UI. If we do happen to download invalid bytes, the devfile parser will catch this and fail. */ - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - respBytes, err = io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return respBytes, nil - } - - return nil, fmt.Errorf("received a non-200 status when curling %s", endpoint) -} - -// ConvertGitHubURL converts a git url to its raw format -// adapted from https://github.com/redhat-developer/odo/blob/e63773cc156ade6174a533535cbaa0c79506ffdb/pkg/catalog/catalog.go#L72 -func ConvertGitHubURL(URL string, revision string, context string) (string, error) { - // If the URL ends with .git, remove it - // The regex will only instances of '.git' if it is at the end of the given string - reg := regexp.MustCompile(".git$") - URL = reg.ReplaceAllString(URL, "") - - // If the URL has a trailing / suffix, trim it - URL = strings.TrimSuffix(URL, "/") - - url, err := url.Parse(URL) - if err != nil { - return "", &InvalidURL{URL: URL, Err: err} - } - - if strings.Contains(url.Host, "github") && !strings.Contains(url.Host, "raw") { - // Convert path part of the URL - URLSlice := strings.Split(URL, "/") - if len(URLSlice) > 2 && URLSlice[len(URLSlice)-2] == "tree" { - // GitHub raw URL doesn't have "tree" structure in the URL, need to remove it - URL = strings.Replace(URL, "/tree", "", 1) - } else if revision != "" { - // Add revision for GitHub raw URL - URL = URL + "/" + revision - } else { - // Add "main" branch for GitHub raw URL by default if revision is not specified - URL = URL + "/main" - } - if context != "" && context != "./" && context != "." { - // trim the prefix / in context - context = strings.TrimPrefix(context, "/") - URL = URL + "/" + context - } - - // Convert host part of the URL - if url.Host == "github.com" { - URL = strings.Replace(URL, "github.com", "raw.githubusercontent.com", 1) - } - } - - return URL, nil -} - -// IsExist returns whether the given file or directory exists -func IsExist(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return false, err -} - -// getAlizerDevfileTypes gets the Alizer devfile types for a specified registry -func getAlizerDevfileTypes(registryURL string) ([]model.DevfileType, error) { - types := []model.DevfileType{} - registryIndex, err := registryLibrary.GetRegistryIndex(registryURL, registryLibrary.RegistryOptions{ - Telemetry: registryLibrary.TelemetryData{}, - }, schema.SampleDevfileType) - if err != nil { - return nil, err - } - - for _, index := range registryIndex { - types = append(types, model.DevfileType{ - Name: index.Name, - Language: index.Language, - ProjectType: index.ProjectType, - Tags: index.Tags, - }) - } - - return types, nil -} - -// GetRepoFromRegistry gets the sample repo link from the devfile registry -func GetRepoFromRegistry(name, registryURL string) (string, error) { - registryIndex, err := registryLibrary.GetRegistryIndex(registryURL, registryLibrary.RegistryOptions{ - Telemetry: registryLibrary.TelemetryData{}, - }, schema.SampleDevfileType) - if err != nil { - return "", err - } - - for _, index := range registryIndex { - if index.Name == name && index.Git != nil && index.Git.Remotes["origin"] != "" { - return index.Git.Remotes["origin"], nil - } - } - - return "", fmt.Errorf("unable to find sample with a name %s in the registry", name) -} - -// getContext returns the context backtracking from the end of the localpath -func getContext(localpath string, currentLevel int) string { - context := "./" - currentPath := localpath - for i := 0; i < currentLevel; i++ { - context = path.Join(filepath.Base(currentPath), context) - currentPath = filepath.Dir(currentPath) - } - - return context -} - -// UpdateGitLink updates the relative uri -// to a full URL link with the context & revision -func UpdateGitLink(repo, revision, context string) (string, error) { - var rawGitURL string - var err error - if !strings.HasPrefix(context, "http") { - rawGitURL, err = ConvertGitHubURL(repo, revision, context) - if err != nil { - return "", err - } - - } else { - return context, nil - } - return rawGitURL, nil -} diff --git a/cdq-analysis/pkg/util_test.go b/cdq-analysis/pkg/util_test.go deleted file mode 100644 index 42a967fb7..000000000 --- a/cdq-analysis/pkg/util_test.go +++ /dev/null @@ -1,637 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pkg - -import ( - "encoding/json" - "net" - "net/http" - "net/http/httptest" - "os" - "reflect" - "testing" - - "github.com/devfile/alizer/pkg/apis/model" - indexSchema "github.com/devfile/registry-support/index/generator/schema" -) - -func TestISExist(t *testing.T) { - tests := []struct { - name string - path string - exist bool - wantErr bool - }{ - { - name: "Path Exist", - path: "/tmp", - exist: true, - }, - { - name: "Path Does Not Exist", - path: "/pathdoesnotexist", - exist: false, - }, - { - name: "Error Case", - path: "\000x", - exist: false, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - isExist, err := IsExist(tt.path) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if isExist != tt.exist { - t.Errorf("IsExist; expected %v got %v", tt.exist, isExist) - } - }) - } -} - -func TestCurlEndpoint(t *testing.T) { - tests := []struct { - name string - url string - wantErr bool - }{ - { - name: "Valid Endpoint", - url: "https://google.ca", - }, - { - name: "Invalid Endpoint", - url: "https://google.ca/somepath", - wantErr: true, - }, - { - name: "Invalid URL", - url: "\000x", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - contents, err := CurlEndpoint(tt.url, "") - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if err == nil && contents == nil { - t.Errorf("unable to read body") - } - }) - } -} - -func TestCloneRepo(t *testing.T) { - os.Mkdir("/tmp/alreadyexistingdir", 0755) - - tests := []struct { - name string - clonePath string - repo string - revision string - token string - wantErr bool - }{ - { - name: "Clone Successfully", - clonePath: "/tmp/testspringboot", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - }, - { - name: "Invalid Repo", - clonePath: "/tmp/testclone", - repo: "https://invalid.url", - wantErr: true, - }, - { - name: "Invalid Clone Path", - clonePath: "\000x", - wantErr: true, - }, - { - name: "Clone path, already existing folder", - clonePath: "/tmp/alreadyexistingdir", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - wantErr: false, - }, - { - name: "Invalid token, should err out", - clonePath: "/tmp/alreadyexistingdir", - repo: "https://github.com/devfile-resources/multi-components-private/", - token: "fake-token", - wantErr: true, - }, - { - name: "Clone Successfully - branch specified as revision", - clonePath: "/tmp/testspringboot", - repo: "https://github.com/devfile-resources/node-express-hello-no-devfile", - revision: "testbranch", - }, - { - name: "Clone Successfully - commit specified as revision", - clonePath: "/tmp/nodeexpressrevision", - repo: "https://github.com/devfile-resources/node-express-hello-no-devfile", - revision: "22d213a42091199bc1f85a8eac60a5ff82371df3", - }, - { - name: "Invalid revision, should err out", - clonePath: "/tmp/nodeexpressrevisioninvalidrevision", - repo: "https://github.com/devfile-resources/node-express-hello-no-devfile", - revision: "fasdfasdfasdfdsklafj2w23", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := CloneRepo(tt.clonePath, GitURL{ - RepoURL: tt.repo, - Revision: tt.revision, - Token: tt.token, - }) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } - os.RemoveAll(tt.clonePath) - }) - } -} - -func TestGetBranchFromRepo(t *testing.T) { - os.Mkdir("/tmp/alreadyexistingdir", 0755) - - tests := []struct { - name string - clonePath string - repo string - revision string - token string - wantErr bool - want string - }{ - { - name: "Detect Successfully", - clonePath: "/tmp/testspringbootclone", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - want: "main", - }, - { - name: "Detect alternate branch Successfully", - clonePath: "/tmp/testspringbootclonealt", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - revision: "testbranch", - want: "testbranch", - }, - { - name: "Repo not exist", - clonePath: "FDSFSDFSDFSDFjsdklfjsdklfjs", - repo: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - os.RemoveAll(tt.clonePath) - if tt.name != "Repo not exist" { - CloneRepo(tt.clonePath, GitURL{ - RepoURL: tt.repo, - Revision: tt.revision, - Token: tt.token, - }) - } - - branch, err := GetBranchFromRepo(tt.clonePath) - if (err != nil) != tt.wantErr { - t.Errorf("TestGetBranchFromRepo() unexpected error: %v", err) - } - if err != nil { - if branch != tt.want { - t.Errorf("TestGetBranchFromRepo() unexpected branch, expected %v got %v", tt.want, branch) - } - } - }) - } -} - -func TestConvertGitHubURL(t *testing.T) { - tests := []struct { - name string - url string - revision string - context string - useAPI bool - wantUrl string - wantErr bool - }{ - { - name: "Successfully convert a github url to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main", - }, - { - name: "Successfully convert a github url with revision to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic", - revision: "testbranch", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/testbranch", - }, - { - name: "Successfully convert a github url with a trailing / suffix to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic/", - context: "./", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main", - }, - { - name: "Successfully convert a github url with a context to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic/", - context: "testfolder", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/testfolder", - }, - { - name: "Successfully convert a github url with a context with a prefix / to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic/", - context: "/testfolder", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/testfolder", - }, - { - name: "Successfully convert a github url with revision and a trailing / suffix and a context to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic/", - revision: "testbranch", - context: "testfolder", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/testbranch/testfolder", - }, - { - name: "Successfully convert a github url with .git to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic.git", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main", - }, - { - name: "Successfully convert a github url with revision and .git and a context with prefix / to raw url", - url: "https://github.com/devfile-samples/devfile-sample-java-springboot-basic.git", - revision: "testbranch", - context: "/testfolder", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/testbranch/testfolder", - }, - { - name: "A non github url", - url: "https://some.url", - wantUrl: "https://some.url", - }, - { - name: "A raw github url", - url: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - }, - { - name: "A raw github url with revision", - url: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/testbranch/devfile.yaml", - revision: "testbranch", - wantUrl: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/testbranch/devfile.yaml", - }, - { - name: "A non-main branch github url", - url: "https://github.com/devfile/api/tree/2.1.x", - wantUrl: "https://raw.githubusercontent.com/devfile/api/2.1.x", - }, - { - name: "A non url", - url: "\000x", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - convertedUrl, err := ConvertGitHubURL(tt.url, tt.revision, tt.context) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if convertedUrl != tt.wantUrl { - t.Errorf("ConvertGitHubURL; expected %v got %v", tt.wantUrl, convertedUrl) - } - }) - } -} - -func TestGetContext(t *testing.T) { - - localpath := "/tmp/path/to/a/dir" - - tests := []struct { - name string - currentLevel int - wantContext string - }{ - { - name: "1 level", - currentLevel: 1, - wantContext: "dir", - }, - { - name: "2 levels", - currentLevel: 2, - wantContext: "a/dir", - }, - { - name: "0 levels", - currentLevel: 0, - wantContext: "./", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - context := getContext(localpath, tt.currentLevel) - if tt.wantContext != context { - t.Errorf("expected %s got %s", tt.wantContext, context) - } - }) - } -} - -func TestGetAlizerDevfileTypes(t *testing.T) { - const serverIP = "127.0.0.1:9080" - - sampleFilteredIndex := []indexSchema.Schema{ - { - Name: "sampleindex1", - ProjectType: "project1", - Language: "language1", - }, - { - Name: "sampleindex2", - ProjectType: "project2", - Language: "language2", - }, - } - - stackFilteredIndex := []indexSchema.Schema{ - { - Name: "stackindex1", - }, - { - Name: "stackindex2", - }, - } - - notFilteredIndex := []indexSchema.Schema{ - { - Name: "index1", - }, - { - Name: "index2", - }, - } - - // Mocking the registry REST endpoints on a very basic level - testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - var data []indexSchema.Schema - var err error - - if r.URL.Path == "/index/sample" { - data = sampleFilteredIndex - } else if r.URL.Path == "/index/stack" || r.URL.Path == "/index" { - data = stackFilteredIndex - } else if r.URL.Path == "/index/all" { - data = notFilteredIndex - } - - bytes, err := json.MarshalIndent(&data, "", " ") - if err != nil { - t.Errorf("Unexpected error while doing json marshal: %v", err) - return - } - - _, err = w.Write(bytes) - if err != nil { - t.Errorf("Unexpected error while writing data: %v", err) - } - })) - // create a listener with the desired port. - l, err := net.Listen("tcp", serverIP) - if err != nil { - t.Errorf("Unexpected error while creating listener: %v", err) - return - } - - // NewUnstartedServer creates a listener. Close that listener and replace - // with the one we created. - testServer.Listener.Close() - testServer.Listener = l - - testServer.Start() - defer testServer.Close() - - tests := []struct { - name string - url string - wantTypes []model.DevfileType - wantErr bool - }{ - { - name: "Get the Sample Devfile Types", - url: "http://" + serverIP, - wantTypes: []model.DevfileType{ - { - Name: "sampleindex1", - ProjectType: "project1", - Language: "language1", - }, - { - Name: "sampleindex2", - ProjectType: "project2", - Language: "language2", - }, - }, - }, - { - name: "Not a URL", - url: serverIP, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - types, err := getAlizerDevfileTypes(tt.url) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } else if !reflect.DeepEqual(types, tt.wantTypes) { - t.Errorf("Expected: %+v, \nGot: %+v", tt.wantTypes, types) - } - }) - } -} - -func TestGetRepoFromRegistry(t *testing.T) { - const serverIP = "127.0.0.1:9080" - - index := []indexSchema.Schema{ - { - Name: "index1", - ProjectType: "project1", - Language: "language1", - Git: &indexSchema.Git{ - Remotes: map[string]string{ - "origin": "repo", - }, - }, - }, - { - Name: "index2", - ProjectType: "project2", - Language: "language2", - }, - } - - // Mocking the registry REST endpoints on a very basic level - testServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - var data []indexSchema.Schema - var err error - - if r.URL.Path == "/index/sample" { - data = index - } else if r.URL.Path == "/index/all" { - data = index - } - - bytes, err := json.MarshalIndent(&data, "", " ") - if err != nil { - t.Errorf("Unexpected error while doing json marshal: %v", err) - return - } - - _, err = w.Write(bytes) - if err != nil { - t.Errorf("Unexpected error while writing data: %v", err) - } - })) - // create a listener with the desired port. - l, err := net.Listen("tcp", serverIP) - if err != nil { - t.Errorf("Unexpected error while creating listener: %v", err) - return - } - - // NewUnstartedServer creates a listener. Close that listener and replace - // with the one we created. - testServer.Listener.Close() - testServer.Listener = l - - testServer.Start() - defer testServer.Close() - - tests := []struct { - name string - url string - sampleName string - wantURL string - wantErr bool - }{ - { - name: "Get the Repo URL from the sample", - url: "http://" + serverIP, - sampleName: "index1", - wantURL: "repo", - }, - { - name: "Sample does not have a Repo URL", - url: "http://" + serverIP, - sampleName: "index2", - wantErr: true, - }, - { - name: "Not a URL", - url: serverIP, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotURL, err := GetRepoFromRegistry(tt.sampleName, tt.url) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } else if !reflect.DeepEqual(gotURL, tt.wantURL) { - t.Errorf("Expected: %+v, \nGot: %+v", tt.wantURL, gotURL) - } - }) - } -} - -func TestUpdateGitLink(t *testing.T) { - - tests := []struct { - name string - repo string - context string - wantLink string - wantErr bool - }{ - { - name: "context has no http", - repo: "https://github.com/devfile-resources/multi-components-dockerfile/", - context: "devfile-sample-java-springboot-basic/docker/Dockerfile", - wantLink: "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/docker/Dockerfile", - }, - { - name: "context has http", - repo: "https://github.com/devfile-resources/multi-components-dockerfile/", - context: "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/docker/Dockerfile", - wantLink: "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/docker/Dockerfile", - }, - { - name: "err case", - repo: "\000x", - context: "test/dir", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - gotLink, err := UpdateGitLink(tt.repo, "", tt.context) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } else if gotLink != tt.wantLink { - t.Errorf("Expected: %+v, Got: %+v", tt.wantLink, gotLink) - } - - }) - } -} diff --git a/contracts/pact_state_handlers_methods.go b/contracts/pact_state_handlers_methods.go deleted file mode 100644 index ddb27c4f9..000000000 --- a/contracts/pact_state_handlers_methods.go +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package contracts - -import ( - "context" - "fmt" - "os" - "reflect" - "strings" - "time" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - "github.com/onsi/ginkgo" - gomega "github.com/onsi/gomega" - models "github.com/pact-foundation/pact-go/v2/models" - - core "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" -) - -var ( - k8sClient client.Client - testEnv *envtest.Environment - ctx context.Context - cancel context.CancelFunc -) - -const timeout = 10 * time.Second -const interval = 250 * time.Millisecond - -func createApp(setup bool, state models.ProviderState) (models.ProviderStateResponse, error) { - if !setup { - err := os.Setenv("SETUP", "false") - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Setting up env var failed: %s", err)) - return nil, nil - } - err := os.Setenv("SETUP", "true") - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Setting up env var failed: %s", err)) - - params := parseApplication(state.Parameters) - hasApp := getApplicationSpec(params.appName, params.namespace) - hasAppLookupKey := types.NamespacedName{Name: params.appName, Namespace: params.namespace} - createdHasApp := &appstudiov1alpha1.Application{} - - // create app - err = k8sClient.Create(ctx, hasApp) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to create application: %s", err)) - - // check it is created - gomega.Eventually(func() bool { - err := k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to get application: %s", err)) - return len(createdHasApp.Status.Conditions) > 0 - }, timeout, interval).Should(gomega.BeTrue()) - - return nil, nil - -} - -func createComponents(setup bool, state models.ProviderState) (models.ProviderStateResponse, error) { - if !setup { - err := os.Setenv("SETUP", "false") - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Setting up env var failed: %s", err)) - return nil, nil - } - err := os.Setenv("SETUP", "true") - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Setting up env var failed: %s", err)) - - components := parseComponents(state.Parameters) - for _, comp := range components { - ghComp := getGhComponentSpec(comp.name, comp.app.namespace, comp.app.appName, comp.repo) - - hasAppLookupKey := types.NamespacedName{Name: comp.app.appName, Namespace: comp.app.namespace} - createdHasApp := &appstudiov1alpha1.Application{} - - //create gh component - err := k8sClient.Create(ctx, ghComp) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to craete component: %s", err)) - hasCompLookupKey := types.NamespacedName{Name: comp.name, Namespace: comp.app.namespace} - createdHasComp := &appstudiov1alpha1.Component{} - - // wait until component is created - gomega.Eventually(func() bool { - err := k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to get component: %s", err)) - return len(createdHasComp.Status.Conditions) > 1 - }, timeout, interval).Should(gomega.BeTrue()) - - // wait until component is ready - gomega.Eventually(func() bool { - err := k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to get application: %s", err)) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, comp.name) - }, timeout, interval).Should(gomega.BeTrue()) - } - return nil, nil - -} - -func removeAllInstacesInNamespace(namespace string, myInstance client.Object) { - objectKind := strings.Split(reflect.TypeOf(myInstance).String(), ".")[1] - remainingCount := getObjectCountInNamespace(objectKind, namespace) - if remainingCount == 0 { - return - } - // remove resources in namespace - err := k8sClient.DeleteAllOf(context.Background(), myInstance, client.InNamespace(namespace)) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to delete %s: %s", myInstance, err)) - - // watch number of resources existing - gomega.Eventually(func() bool { - objectKind := strings.Split(reflect.TypeOf(myInstance).String(), ".")[1] - remainingCount := getObjectCountInNamespace(objectKind, namespace) - fmt.Fprintf(ginkgo.GinkgoWriter, "Removing %s instance from %s namespace. Remaining: %d", objectKind, namespace, remainingCount) - return remainingCount == 0 - }, timeout, interval).Should(gomega.BeTrue()) -} - -func getObjectCountInNamespace(objectKind string, namespace string) int { - unstructuredObject := &unstructured.Unstructured{} - - unstructuredObject.SetGroupVersionKind(schema.GroupVersionKind{ - Group: appstudiov1alpha1.GroupVersion.Group, - Version: appstudiov1alpha1.GroupVersion.Version, - Kind: objectKind, - }) - - err := k8sClient.List(context.Background(), unstructuredObject, &client.ListOptions{Namespace: namespace}) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to get list of %s: %s", objectKind, err)) - - listOfObjects, _ := unstructuredObject.ToList() - return len(listOfObjects.Items) -} - -func cleanUpNamespaces() { - fmt.Fprint(ginkgo.GinkgoWriter, "clean up namespaces") - removeAllInstances(&appstudiov1alpha1.Component{}) - removeAllInstances(&appstudiov1alpha1.Application{}) - removeAllInstances(&appstudiov1alpha1.ComponentDetectionQuery{}) -} - -// remove all instances of the given type within the whole cluster -func removeAllInstances(myInstance client.Object) { - listOfNamespaces := getListOfNamespaces() - for _, item := range listOfNamespaces.Items { - removeAllInstacesInNamespace(item.Name, myInstance) - } -} - -// return all namespaces where the instances of the specified object kind exist -func getListOfNamespaces() core.NamespaceList { - namespaceList := &core.NamespaceList{} - err := k8sClient.List(context.Background(), namespaceList, &client.ListOptions{Namespace: ""}) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), fmt.Sprintf("Failed to get list of namespaces: %s", err)) - return *namespaceList -} diff --git a/contracts/pact_state_params.go b/contracts/pact_state_params.go deleted file mode 100644 index a4751006d..000000000 --- a/contracts/pact_state_params.go +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package contracts - -// !!! -// -// Parameters have to correspond with the https://github.com/openshift/hac-dev/blob/main/pact-tests/states/state-params.ts -// -// !!! - -// ComponentParams are the Pact parameters used for component manipulation. -// app - describes the Application tnat component belongs to -// repo - Git repository for the component -// name - name of the component -type ComponentParams struct { - app ApplicationParams - repo string - name string -} - -// ApplicationParams are the Pact parameters used for application manipulation. -// appName - name of the application -// namespace - namespace where the application should live, currently only "default" is supported -type ApplicationParams struct { - appName string - namespace string -} - -func parseApplication(params map[string]interface{}) ApplicationParams { - return ApplicationParams{ - params["params"].(map[string]interface{})["appName"].(string), - params["params"].(map[string]interface{})["namespace"].(string), - } -} - -func parseComponents(params map[string]interface{}) []ComponentParams { - tmp := params["params"].(map[string]interface{})["components"].([]interface{}) - var components []ComponentParams - for _, compToParse := range tmp { - component := compToParse.(map[string]interface{}) - appParsed := ApplicationParams{component["app"].(map[string]interface{})["appName"].(string), - component["app"].(map[string]interface{})["namespace"].(string)} - compParsed := ComponentParams{appParsed, component["repo"].(string), component["compName"].(string)} - components = append(components, compParsed) - } - return components -} diff --git a/contracts/pact_states.go b/contracts/pact_states.go deleted file mode 100644 index 474cf2424..000000000 --- a/contracts/pact_states.go +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package contracts - -// The list of used states and their params can be found there: https://github.com/openshift/hac-dev/blob/main/pact-tests/states/states.ts#L18 - -import ( - models "github.com/pact-foundation/pact-go/v2/models" -) - -func setupStateHandler() models.StateHandlers { - return models.StateHandlers{ - "Application exists": createApp, - "Application has components": createComponents, - } -} diff --git a/contracts/pact_test.go b/contracts/pact_test.go deleted file mode 100644 index 476c87f6b..000000000 --- a/contracts/pact_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package contracts - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "os" - "path/filepath" - "testing" - "time" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - provider "github.com/pact-foundation/pact-go/v2/provider" - "github.com/redhat-appstudio/application-service/controllers" -) - -func TestContracts(t *testing.T) { - // Skip tests if "SKIP_PACT_TESTS" env var is set - // or if the unit tests are running during PR check job - if os.Getenv("SKIP_PACT_TESTS") == "true" || (os.Getenv("PR_CHECK") == "true" && os.Getenv("COMMIT_SHA") == "") { - t.Skip("Skipping Pact tests.") - } - - // Register fail handler and setup test environment (same as during unit tests) - RegisterFailHandler(Fail) - k8sClient, testEnv, ctx, cancel = controllers.SetupTestEnv() - - // Create and setup Pact Verifier - verifyRequest := createVerifier(t) - - // Run pact tests - err := provider.NewVerifier().VerifyProvider(t, verifyRequest) - if err != nil { - t.Errorf("Error while verifying tests. \n %+v", err) - } - - println("cleanup") - cleanUpNamespaces() - - cancel() - - err = testEnv.Stop() - if err != nil { - fmt.Println("Stopping failed") - fmt.Printf("%+v", err) - panic("Cleanup failed") - } -} - -func createVerifier(t *testing.T) provider.VerifyRequest { - verifyRequest := provider.VerifyRequest{ - Provider: "HAS", - RequestTimeout: 60 * time.Second, - ProviderBaseURL: testEnv.Config.Host, - // Default selector should include environments, but as they are not in place yet, using just main branch - ConsumerVersionSelectors: []provider.Selector{&provider.ConsumerVersionSelector{Branch: "main"}}, - BrokerURL: "https://pact-broker-hac-pact-broker.apps.hac-devsandbox.5unc.p1.openshiftapps.com", - PublishVerificationResults: false, - EnablePending: true, - ProviderVersion: "local", - ProviderBranch: "main", - } - - // clean up test env before every test - verifyRequest.BeforeEach = func() error { - // workaround for https://github.com/pact-foundation/pact-go/issues/359 - if os.Getenv("SETUP") == "true" { - return nil - } - cleanUpNamespaces() - return nil - } - - // setup credentials and publishing - if os.Getenv("PR_CHECK") != "true" { - if os.Getenv("PACT_BROKER_USERNAME") == "" { - // To run Pact tests against local contract files, set LOCAL_PACT_FILES_FOLDER to the folder with pact jsons - var pactDir, useLocalFiles = os.LookupEnv("LOCAL_PACT_FILES_FOLDER") - if useLocalFiles { - verifyRequest.BrokerPassword = "" - verifyRequest.BrokerUsername = "" - verifyRequest.BrokerURL = "" - verifyRequest.PactFiles = []string{filepath.ToSlash(fmt.Sprintf("%s/HACdev-HAS.json", pactDir))} - t.Log("Running tests locally. Verifying tests from local folder: ", pactDir) - } else { - t.Log("Running tests locally. Verifying against main branch, not pushing results to broker.") - verifyRequest.ConsumerVersionSelectors = []provider.Selector{&provider.ConsumerVersionSelector{Branch: "main"}} - // to test against changes in specific HAC-dev PR, use Tag: - // verifyRequest.ConsumerVersionSelectors = []provider.Selector{&provider.ConsumerVersionSelector{Tag: "PR808", Latest: true}} - } - } else { - t.Log("Running tests post-merge. Verifying against main branch and all environments. Pushing results to Pact broker with the branch \"main\".") - verifyRequest.BrokerUsername = os.Getenv("PACT_BROKER_USERNAME") - verifyRequest.BrokerPassword = os.Getenv("PACT_BROKER_PASSWORD") - verifyRequest.ProviderBranch = os.Getenv("PROVIDER_BRANCH") - verifyRequest.ProviderVersion = os.Getenv("COMMIT_SHA") - verifyRequest.PublishVerificationResults = true - } - } - - // setup state handlers - verifyRequest.StateHandlers = setupStateHandler() - - // Certificate magic - for the mocked service to be able to communicate with kube-apiserver & for authorization - verifyRequest.CustomTLSConfig = createTlsConfig() - - return verifyRequest -} - -func createTlsConfig() *tls.Config { - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(testEnv.Config.CAData) - certs, err := tls.X509KeyPair(testEnv.Config.CertData, testEnv.Config.KeyData) - if err != nil { - panic(err) - } - return &tls.Config{ - RootCAs: caCertPool, - Certificates: []tls.Certificate{certs}, - } -} diff --git a/contracts/pact_utils.go b/contracts/pact_utils.go deleted file mode 100644 index c3ff6a99f..000000000 --- a/contracts/pact_utils.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package contracts - -import ( - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var replicas int = 1 - -func getApplicationSpec(name string, namespace string) *appstudiov1alpha1.Application { - - return &appstudiov1alpha1.Application{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Application", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ApplicationSpec{ - DisplayName: name, - Description: "Some description", - }, - } -} - -func getGhComponentSpec(name string, namespace string, appname string, repo string) *appstudiov1alpha1.Component { - return &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: name, - Application: appname, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: repo, - }, - }, - }, - Replicas: &replicas, - TargetPort: 1111, - Route: "route-endpoint-url", - }, - } -} diff --git a/controllers/application_controller.go b/controllers/application_controller.go deleted file mode 100644 index de1607b11..000000000 --- a/controllers/application_controller.go +++ /dev/null @@ -1,267 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "fmt" - "reflect" - "time" - - "github.com/devfile/library/v2/pkg/devfile/parser" - - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "github.com/redhat-appstudio/application-service/pkg/metrics" - - gofakeit "github.com/brianvoe/gofakeit/v6" - "github.com/go-logr/logr" - - k8sErrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/util/retry" - "k8s.io/client-go/util/workqueue" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" - "sigs.k8s.io/yaml" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - devfile "github.com/redhat-appstudio/application-service/pkg/devfile" - logutil "github.com/redhat-appstudio/application-service/pkg/log" -) - -const appFinalizerName = "application.appstudio.redhat.com/finalizer" - -// ApplicationReconciler reconciles a Application object -type ApplicationReconciler struct { - client.Client - Scheme *runtime.Scheme - Log logr.Logger -} - -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=applications,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=applications/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=applications/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Application object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile -func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := ctrl.LoggerFrom(ctx) - - // Get the Application resource - var application appstudiov1alpha1.Application - err := r.Get(ctx, req.NamespacedName, &application) - if err != nil { - if k8sErrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, err - } - - // If the resource still has the finalizer attached to it, just remove it so deletion doesn't get blocked - if containsString(application.GetFinalizers(), appFinalizerName) { - // remove the finalizer from the list and update it. - err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - var currentApplication appstudiov1alpha1.Application - err := r.Get(ctx, req.NamespacedName, ¤tApplication) - if err != nil { - return err - } - - controllerutil.RemoveFinalizer(¤tApplication, appFinalizerName) - - err = r.Update(ctx, ¤tApplication) - return err - }) - if err != nil { - return ctrl.Result{}, err - } - } - log.Info(fmt.Sprintf("Starting reconcile loop for %v", req.NamespacedName)) - // If devfile hasn't been generated yet, generate it - // If the devfile hasn't been generated, the CR was just created. - if application.Status.Devfile == "" { - metrics.ApplicationCreationTotalReqs.Inc() - - // Convert the devfile string to a devfile object - devfileData, err := devfile.ConvertApplicationToDevfile(application) - if err != nil { - metrics.ApplicationCreationFailed.Inc() - log.Error(err, fmt.Sprintf("Unable to convert Application CR to devfile, exiting reconcile loop %v", req.NamespacedName)) - r.SetCreateConditionAndUpdateCR(ctx, req, &application, err) - return reconcile.Result{}, err - } - - // Find all components owned by the application - err = r.getAndAddComponentApplicationsToModel(log, req, application.Name, devfileData.GetDevfileWorkspaceSpec()) - if err != nil { - r.SetCreateConditionAndUpdateCR(ctx, req, &application, err) - log.Error(err, fmt.Sprintf("Unable to add components to application model for %v", req.NamespacedName)) - return ctrl.Result{}, err - } - - yamlData, err := yaml.Marshal(devfileData) - if err != nil { - metrics.ApplicationCreationFailed.Inc() - log.Error(err, fmt.Sprintf("Unable to marshall Application devfile, exiting reconcile loop %v", req.NamespacedName)) - r.SetCreateConditionAndUpdateCR(ctx, req, &application, err) - return reconcile.Result{}, err - } - - application.Status.Devfile = string(yamlData) - - // Update the status of the CR - metrics.ApplicationCreationSucceeded.Inc() - r.SetCreateConditionAndUpdateCR(ctx, req, &application, nil) - } else { - // If the model already exists, see if either the displayname or description need updating - // Get the devfile of the hasApp CR - - // Token can be empty since we are passing in generated devfile data, so we won't be dealing with private repos - devfileData, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(application.Status.Devfile)}) - if err != nil { - r.SetUpdateConditionAndUpdateCR(ctx, req, &application, err) - log.Error(err, fmt.Sprintf("Unable to parse devfile model, exiting reconcile loop %v", req.NamespacedName)) - return ctrl.Result{}, err - } - - updateRequired := false - // nil out the attributes and projects for the application devfile - // The Attributes contain any image components for the application - // And the projects contains any git components for the application - devWorkspacesSpec := devfileData.GetDevfileWorkspaceSpec().DeepCopy() - devWorkspacesSpec.Attributes = nil - devWorkspacesSpec.Projects = nil - - err = r.getAndAddComponentApplicationsToModel(log, req, application.Name, devWorkspacesSpec) - if err != nil { - r.SetUpdateConditionAndUpdateCR(ctx, req, &application, err) - log.Error(err, fmt.Sprintf("Unable to add components to application model for %v", req.NamespacedName)) - return ctrl.Result{}, err - } - // Update any specific fields that changed - displayName := application.Spec.DisplayName - description := application.Spec.Description - devfileMeta := devfileData.GetMetadata() - if devfileMeta.Name != displayName { - devfileMeta.Name = displayName - updateRequired = true - } - if devfileMeta.Description != description { - devfileMeta.Description = description - updateRequired = true - } - - oldDevSpec := devfileData.GetDevfileWorkspaceSpec() - if !reflect.DeepEqual(oldDevSpec.Attributes, devWorkspacesSpec.Attributes) || !reflect.DeepEqual(oldDevSpec.Projects, devWorkspacesSpec.Projects) { - devfileData.SetDevfileWorkspaceSpec(*devWorkspacesSpec) - updateRequired = true - } - - if updateRequired { - devfileData.SetMetadata(devfileMeta) - - // Update the Application CR with the new devfile - yamlData, err := yaml.Marshal(devfileData) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to marshall Application devfile, exiting reconcile loop %v", req.NamespacedName)) - r.SetUpdateConditionAndUpdateCR(ctx, req, &application, err) - return reconcile.Result{}, err - } - - application.Status.Devfile = string(yamlData) - r.SetUpdateConditionAndUpdateCR(ctx, req, &application, nil) - } - - } - - log.Info(fmt.Sprintf("Finished reconcile loop for %v", req.NamespacedName)) - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *ApplicationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { - gofakeit.New(0) - log := ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Application") - - return ctrl.NewControllerManagedBy(mgr). - For(&appstudiov1alpha1.Application{}). - WithOptions(controller.Options{ - RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(time.Duration(1*time.Second), time.Duration(1000*time.Second)), - }). - WithEventFilter(predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - log := log.WithValues("namespace", e.Object.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.Object.GetName(), "Application", logutil.ResourceCreate, nil) - return true - }, - UpdateFunc: func(e event.UpdateEvent) bool { - log := log.WithValues("namespace", e.ObjectNew.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.ObjectNew.GetName(), "Application", logutil.ResourceUpdate, nil) - return true - }, - DeleteFunc: func(e event.DeleteEvent) bool { - log := log.WithValues("namespace", e.Object.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.Object.GetName(), "Application", logutil.ResourceDelete, nil) - return false - }, - }). - // Watch Components (Create and Delete events only) as a secondary resource - Watches(&source.Kind{Type: &appstudiov1alpha1.Component{}}, handler.EnqueueRequestsFromMapFunc(MapComponentToApplication()), builder.WithPredicates(predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - return true - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return false - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return true - }, - GenericFunc: func(e event.GenericEvent) bool { - return false - }, - })). - Complete(r) -} - -// Helper functions to check and remove string from a slice of strings. -func containsString(slice []string, s string) bool { - for _, item := range slice { - if item == s { - return true - } - } - return false -} diff --git a/controllers/application_controller_conditions.go b/controllers/application_controller_conditions.go deleted file mode 100644 index f39d31c25..000000000 --- a/controllers/application_controller_conditions.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "fmt" - - logutil "github.com/redhat-appstudio/application-service/pkg/log" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" -) - -func (r *ApplicationReconciler) SetCreateConditionAndUpdateCR(ctx context.Context, req ctrl.Request, application *appstudiov1alpha1.Application, createError error) { - log := ctrl.LoggerFrom(ctx) - - condition := metav1.Condition{} - var currentApplication appstudiov1alpha1.Application - err := r.Get(ctx, req.NamespacedName, ¤tApplication) - if err != nil { - log.Error(err, "Unable to get current Application status") - return - } - patch := client.MergeFrom(currentApplication.DeepCopy()) - - if createError == nil { - condition = metav1.Condition{ - Type: "Created", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "Application has been successfully created", - } - } else { - condition = metav1.Condition{ - Type: "Created", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("Application create failed: %v", createError), - } - logutil.LogAPIResourceChangeEvent(log, application.Name, "Application", logutil.ResourceCreate, createError) - } - meta.SetStatusCondition(¤tApplication.Status.Conditions, condition) - currentApplication.Status.Devfile = application.Status.Devfile - err = r.Client.Status().Patch(ctx, ¤tApplication, patch) - if err != nil { - log.Error(err, "Unable to update Application status") - } -} - -func (r *ApplicationReconciler) SetUpdateConditionAndUpdateCR(ctx context.Context, req ctrl.Request, application *appstudiov1alpha1.Application, updateError error) { - log := ctrl.LoggerFrom(ctx) - condition := metav1.Condition{} - var currentApplication appstudiov1alpha1.Application - err := r.Get(ctx, req.NamespacedName, ¤tApplication) - if err != nil { - log.Error(err, "Unable to get current Application status") - return - } - patch := client.MergeFrom(currentApplication.DeepCopy()) - if updateError == nil { - condition = metav1.Condition{ - Type: "Updated", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "Application has been successfully updated", - } - } else { - condition = metav1.Condition{ - Type: "Updated", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("Application updated failed: %v", updateError), - } - logutil.LogAPIResourceChangeEvent(log, application.Name, "Application", logutil.ResourceUpdate, updateError) - } - - meta.SetStatusCondition(¤tApplication.Status.Conditions, condition) - currentApplication.Status.Devfile = application.Status.Devfile - err = r.Client.Status().Patch(ctx, ¤tApplication, patch) - if err != nil { - log.Error(err, "Unable to update Application status") - } -} diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go deleted file mode 100644 index dddb2cd6c..000000000 --- a/controllers/application_controller_test.go +++ /dev/null @@ -1,197 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "time" - - "github.com/devfile/library/v2/pkg/devfile/parser" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - //+kubebuilder:scaffold:imports -) - -const ( - timeout = time.Second * 10 - timeout20s = time.Second * 20 - timeout40s = time.Second * 40 - duration = time.Second * 10 - interval = time.Millisecond * 250 -) - -var _ = Describe("Application controller", func() { - - // Define utility constants for object names and testing timeouts/durations and intervals. - const ( - HASAppName = "test-application" - HASAppNamespace = "default" - DisplayName = "petclinic" - Description = "Simple petclinic app" - ) - - Context("Create Application CR", func() { - It("Should create successfully and have devfile in status", func() { - ctx := context.Background() - - hasApp := &appstudiov1alpha1.Application{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Application", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: HASAppName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ApplicationSpec{ - DisplayName: DisplayName, - Description: Description, - }, - } - - Expect(k8sClient.Create(ctx, hasApp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasAppLookupKey := types.NamespacedName{Name: HASAppName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Delete the specified resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Update Application CR fields", func() { - It("Should update successfully with updated description", func() { - ctx := context.Background() - - applicationName := HASAppName + "4" - - hasApp := &appstudiov1alpha1.Application{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Application", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: applicationName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ApplicationSpec{ - DisplayName: DisplayName, - Description: Description, - }, - } - - Expect(k8sClient.Create(ctx, hasApp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - fetchedHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, fetchedHasApp) - return len(fetchedHasApp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Update the name and description of the hasApp resource - fetchedHasApp.Spec.DisplayName = "newname" - fetchedHasApp.Spec.Description = "New Description" - Expect(k8sClient.Update(context.Background(), fetchedHasApp)).Should(Succeed()) - - // Make sure the devfile model was properly set - Expect(fetchedHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Get the updated resource - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, fetchedHasApp) - // Return true if the most recent condition on the CR is updated - return fetchedHasApp.Status.Conditions[len(fetchedHasApp.Status.Conditions)-1].Type == "Updated" - }, timeout, interval).Should(BeTrue()) - - devfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(fetchedHasApp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - Expect(string(devfile.GetMetadata().Name)).Should(Equal("newname")) - Expect(string(devfile.GetMetadata().Description)).Should(Equal("New Description")) - - // Delete the specified resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Application CR with finalizer", func() { - It("Should delete successfully", func() { - ctx := context.Background() - - applicationName := HASAppName + "5" - - hasApp := &appstudiov1alpha1.Application{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Application", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: applicationName, - Namespace: HASAppNamespace, - Finalizers: []string{appFinalizerName}, - }, - Spec: appstudiov1alpha1.ApplicationSpec{ - DisplayName: DisplayName, - Description: Description, - }, - } - - Expect(k8sClient.Create(ctx, hasApp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - // Delete the specified resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - -}) - -// deleteHASAppCR deletes the specified hasApp resource and verifies it was properly deleted -func deleteHASAppCR(hasAppLookupKey types.NamespacedName) { - // Delete - Eventually(func() error { - f := &appstudiov1alpha1.Application{} - k8sClient.Get(context.Background(), hasAppLookupKey, f) - return k8sClient.Delete(context.Background(), f) - }, timeout, interval).Should(Succeed()) - - // Wait for delete to finish - Eventually(func() error { - f := &appstudiov1alpha1.Application{} - return k8sClient.Get(context.Background(), hasAppLookupKey, f) - }, timeout, interval).ShouldNot(Succeed()) -} diff --git a/controllers/component_controller.go b/controllers/component_controller.go deleted file mode 100644 index b58829a73..000000000 --- a/controllers/component_controller.go +++ /dev/null @@ -1,532 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "fmt" - "os" - "reflect" - "time" - - corev1 "k8s.io/api/core/v1" - - "github.com/prometheus/client_golang/prometheus" - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "github.com/redhat-appstudio/application-service/pkg/metrics" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/retry" - "k8s.io/client-go/util/workqueue" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/yaml" - - devfileParser "github.com/devfile/library/v2/pkg/devfile/parser" - data "github.com/devfile/library/v2/pkg/devfile/parser/data" - parserErrPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors" - devfileParserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util" - "github.com/go-logr/logr" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - devfile "github.com/redhat-appstudio/application-service/pkg/devfile" - "github.com/redhat-appstudio/application-service/pkg/github" - logutil "github.com/redhat-appstudio/application-service/pkg/log" - "github.com/redhat-appstudio/application-service/pkg/spi" - "github.com/redhat-appstudio/application-service/pkg/util" - "github.com/spf13/afero" -) - -const compFinalizerName = "component.appstudio.redhat.com/finalizer" - -// ComponentReconciler reconciles a Component object -type ComponentReconciler struct { - client.Client - Scheme *runtime.Scheme - Log logr.Logger - AppFS afero.Afero - SPIClient spi.SPI - GitHubTokenClient github.GitHubToken - DevfileUtilsClient devfileParserUtil.DevfileUtils -} - -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=components,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=components/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=components/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=spifilecontentrequests,verbs=get;list;create -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=spifilecontentrequests/status,verbs=get -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=spifilecontentrequests/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Component object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile -func (r *ComponentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := ctrl.LoggerFrom(ctx) - - // Fetch the Component instance - var component appstudiov1alpha1.Component - err := r.Get(ctx, req.NamespacedName, &component) - if err != nil { - if errors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - return ctrl.Result{}, nil - } - // Error reading the object - requeue the request. - return ctrl.Result{}, err - } - - // If a resource still has the finalizer attached from it, just remove it so deletion doesn't get blocked - if containsString(component.GetFinalizers(), compFinalizerName) { - // remove the finalizer from the list and update it. - err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - var currentComponent appstudiov1alpha1.Component - err := r.Get(ctx, req.NamespacedName, ¤tComponent) - if err != nil { - return err - } - - controllerutil.RemoveFinalizer(¤tComponent, compFinalizerName) - - err = r.Update(ctx, ¤tComponent) - return err - }) - if err != nil { - return ctrl.Result{}, err - } - } - - _, prevErrCondition := checkForCreateReconcile(component) - - // Get the Application CR - hasApplication := appstudiov1alpha1.Application{} - err = r.Get(ctx, types.NamespacedName{Name: component.Spec.Application, Namespace: component.Namespace}, &hasApplication) - if err != nil { - return ctrl.Result{}, err - } - - var gitToken string - //get the token to pass into the parser - if component.Spec.Secret != "" { - gitSecret := corev1.Secret{} - namespacedName := types.NamespacedName{ - Name: component.Spec.Secret, - Namespace: component.Namespace, - } - - err = r.Client.Get(ctx, namespacedName, &gitSecret) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to retrieve Git secret %v, exiting reconcile loop %v", component.Spec.Secret, req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - gitToken = string(gitSecret.Data["password"]) - } - - ghClient, err := r.GitHubTokenClient.GetNewGitHubClient(gitToken) - if err != nil { - log.Error(err, "Unable to create Go-GitHub client due to error") - return reconcile.Result{}, err - } - - // Add the Go-GitHub client name to the context - ctx = context.WithValue(ctx, github.GHClientKey, ghClient.TokenName) - - log.Info(fmt.Sprintf("Starting reconcile loop for %v", req.NamespacedName)) - - // If the devfile hasn't been populated, the CR was just created - if component.Status.Devfile == "" { - - source := component.Spec.Source - gitSourceFromGitlab := false - - var compDevfileData data.DevfileData - var devfileLocation string - var devfileBytes []byte - - if source.GitSource != nil && source.GitSource.URL != "" { - if err := util.ValidateGithubURL(source.GitSource.URL); err != nil { - // User error - the git url provided is not from github - log.Error(err, "unable to validate github url") - gitSourceFromGitlab = true - } - context := source.GitSource.Context - // If a Git secret was passed in, retrieve it for use in our Git operations - // The secret needs to be in the same namespace as the Component - - if source.GitSource.Revision == "" { - sourceURL := source.GitSource.URL - // If the repository URL ends in a forward slash, remove it to avoid issues with default branch lookup - if string(sourceURL[len(sourceURL)-1]) == "/" { - sourceURL = sourceURL[0 : len(sourceURL)-1] - } - log.Info(fmt.Sprintf("Looking for the default branch of the repo %s... %v", source.GitSource.URL, req.NamespacedName)) - metricsLabel := prometheus.Labels{"controller": cdqName, "tokenName": ghClient.TokenName, "operation": "GetDefaultBranchFromURL"} - metrics.ControllerGitRequest.With(metricsLabel).Inc() - source.GitSource.Revision, err = ghClient.GetDefaultBranchFromURL(sourceURL, ctx) - metrics.HandleRateLimitMetrics(err, metricsLabel) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to get default branch of Github Repo %v, try to fall back to main branch... %v", source.GitSource.URL, req.NamespacedName)) - metricsLabel := prometheus.Labels{"controller": cdqName, "tokenName": ghClient.TokenName, "operation": "GetBranchFromURL"} - metrics.ControllerGitRequest.With(metricsLabel).Inc() - _, err := ghClient.GetBranchFromURL(sourceURL, ctx, "main") - if err != nil { - metrics.HandleRateLimitMetrics(err, metricsLabel) - _, ok := err.(*github.GitHubUserErr) - if ok || gitSourceFromGitlab { - // User error, so increment the "success" metric since we're tracking only system errors - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - } else { - // Not a user error, increment fail metric - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - } - log.Error(err, fmt.Sprintf("Unable to get main branch of Github Repo %v ... %v", source.GitSource.URL, req.NamespacedName)) - retErr := fmt.Errorf("unable to get default branch of Github Repo %v, try to fall back to main branch, failed to get main branch... %v", source.GitSource.URL, req.NamespacedName) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, retErr) - return ctrl.Result{}, retErr - } else { - source.GitSource.Revision = "main" - } - } - } - - var gitURL string - if source.GitSource.DevfileURL == "" && source.GitSource.DockerfileURL == "" { - metrics.ImportGitRepoTotalReqs.Inc() - - if gitToken == "" { - gitURL, err = cdqanalysis.ConvertGitHubURL(source.GitSource.URL, source.GitSource.Revision, context) - if err != nil { - // ConvertGitHubURL only returns user error - metrics.ImportGitRepoSucceeded.Inc() - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to convert Github URL to raw format, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - devfileBytes, devfileLocation, err = devfile.FindAndDownloadDevfile(gitURL, gitToken) - // FindAndDownloadDevfile only returns user error - metrics.ImportGitRepoSucceeded.Inc() - if err != nil { - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to read the devfile from dir %s %v", gitURL, req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - devfileLocation = gitURL + string(os.PathSeparator) + devfileLocation - } else { - //cannot use converted URLs in SPI because it's not supported. Need to convert later for parsing - gitURL = source.GitSource.URL - // Use SPI to retrieve the devfile from the private repository - devfileBytes, devfileLocation, err = spi.DownloadDevfileUsingSPI(r.SPIClient, ctx, component, gitURL, source.GitSource.Revision, context) - if err != nil { - // Increment the import git repo and component create failed metric on non-user errors - // Exclude errors from gitlab urls - _, ok := err.(*cdqanalysis.NoDevfileFound) - if !ok && !gitSourceFromGitlab { - metrics.ImportGitRepoFailed.Inc() - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - } else { - metrics.ImportGitRepoSucceeded.Inc() - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - } - log.Error(err, fmt.Sprintf("Unable to download from any known devfile locations from %s %v", gitURL, req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - metrics.ImportGitRepoSucceeded.Inc() - - gitURL, err := cdqanalysis.ConvertGitHubURL(source.GitSource.URL, source.GitSource.Revision, context) - if err != nil { - // User error - so increment the "success" metric - since we're tracking only system errors - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to convert Github URL to raw format, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - devfileLocation = gitURL + string(os.PathSeparator) + devfileLocation - } - - } else if source.GitSource.DevfileURL != "" { - devfileLocation = source.GitSource.DevfileURL - } else if source.GitSource.DockerfileURL != "" { - devfileData, err := devfile.CreateDevfileForDockerfileBuild(source.GitSource.DockerfileURL, "./", component.Name, component.Spec.Application) - if err != nil { - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to create devfile for Dockerfile build %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - devfileBytes, err = yaml.Marshal(devfileData) - if err != nil { - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to marshal devfile, exiting reconcile loop %v", req.NamespacedName)) - err = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - if err != nil { - return ctrl.Result{}, err - } - return ctrl.Result{}, nil - } - } - } else { - // An image component was specified - // Generate a stub devfile for the component - devfileData, err := devfile.ConvertImageComponentToDevfile(component) - if err != nil { - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to convert the Image Component to a devfile %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - component.Status.ContainerImage = component.Spec.ContainerImage - - devfileBytes, err = yaml.Marshal(devfileData) - if err != nil { - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to marshal devfile, exiting reconcile loop %v", req.NamespacedName)) - err = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - if err != nil { - return ctrl.Result{}, err - } - return ctrl.Result{}, nil - } - } - - if devfileLocation != "" { - log.Info(fmt.Sprintf("Parsing Devfile from the Devfile location %s... %v", devfileLocation, req.NamespacedName)) - // Parse the Component CR Devfile - // Pass in a Token and a DevfileUtils client because we need to - // 1. Flatten the Devfile and access a private parent if necessary - // 2. Convert the Kubernetes Uri to Inline by default - // 3. Provide a way to mock output for Component controller tests - compDevfileData, err = cdqanalysis.ParseDevfileWithParserArgs(&devfileParser.ParserArgs{URL: devfileLocation, Token: gitToken, DevfileUtilsClient: r.DevfileUtilsClient}) - if err != nil { - if _, ok := err.(*parserErrPkg.NonCompliantDevfile); ok { - // user error in devfile, increment success metric - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - } else { - // not a user error, increment fail metric - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - } - log.Error(err, fmt.Sprintf("Unable to parse the devfile from Component devfile location, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - } else { - log.Info(fmt.Sprintf("Parsing Devfile from the Devfile bytes %v... %v", len(devfileBytes), req.NamespacedName)) - // Parse the Component CR Devfile - // Not necessary to pass in a Token or a DevfileUtils client to the parser here on devfileBytes, since: - // 1. devfileBytes are only used from a DockerfileURL or an Image, Component CR source (check if conditions above on Component CR sources) - // 2. We dont access any resources for either of these cases in devfile/library - compDevfileData, err = cdqanalysis.ParseDevfileWithParserArgs(&devfileParser.ParserArgs{Data: devfileBytes}) - if err != nil { - if _, ok := err.(*parserErrPkg.NonCompliantDevfile); ok { - // user error in devfile, increment success metric - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - } else { - // not a user error, increment fail metric - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - } - log.Error(err, fmt.Sprintf("Unable to parse the devfile from Component, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - } - - err = r.updateComponentDevfileModel(req, compDevfileData, component) - if err != nil { - // Increment the Component create failed metric only on non-user errors - if _, ok := err.(*NotSupported); ok { - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - } else if _, ok := err.(*devfile.DevfileAttributeParse); ok { - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - } else if _, ok := err.(*parserErrPkg.NonCompliantDevfile); ok { - metrics.IncrementComponentCreationSucceeded(prevErrCondition, err.Error()) - } else { - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - } - log.Error(err, fmt.Sprintf("Unable to update the Component Devfile model %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - yamlHASCompData, err := yaml.Marshal(compDevfileData) - if err != nil { - metrics.IncrementComponentCreationFailed(prevErrCondition, err.Error()) - log.Error(err, fmt.Sprintf("Unable to marshall the Component devfile, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetCreateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - component.Status.Devfile = string(yamlHASCompData) - - // Set the container image in the status - component.Status.ContainerImage = component.Spec.ContainerImage - - err = r.SetCreateConditionAndUpdateCR(ctx, req, &component, nil) - if err != nil { - return ctrl.Result{}, err - } - metrics.IncrementComponentCreationSucceeded("", "") - } else { - - // If the model already exists, see if fields have been updated - log.Info(fmt.Sprintf("Checking if the Component has been updated %v", req.NamespacedName)) - - // Parse the Component CR Devfile - // Not necessary to pass in a Token or DevfileUtils client to the parser here since the devfileBytes has: - // 1. Already been flattened on the create reconcile, so private parents are already expanded - // 2. Kubernetes Component Uri has already been converted to inlined content with a Token if required by default on the first parse - hasCompDevfileData, err := cdqanalysis.ParseDevfileWithParserArgs(&devfileParser.ParserArgs{Data: []byte(component.Status.Devfile)}) - - if err != nil { - log.Error(err, fmt.Sprintf("Unable to parse the devfile from Component status, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - err = r.updateComponentDevfileModel(req, hasCompDevfileData, component) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to update the Component Devfile model %v", req.NamespacedName)) - _ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - // Parse the Component CR Devfile again to compare it with any updates - // Not necessary to pass in a Token or DevfileUtils client to the parser here since the devfileBytes has: - // 1. Already been flattened on the create reconcile, so private parents are already expanded - // 2. Kubernetes Component Uri has already been converted to inlined content with a Token if required by default on the first parse - oldCompDevfileData, err := cdqanalysis.ParseDevfileWithParserArgs(&devfileParser.ParserArgs{Data: []byte(component.Status.Devfile)}) - - if err != nil { - log.Error(err, fmt.Sprintf("Unable to parse the devfile from Component status, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - containerImage := component.Spec.ContainerImage - isUpdated := !reflect.DeepEqual(oldCompDevfileData, hasCompDevfileData) || containerImage != component.Status.ContainerImage - if isUpdated { - log.Info(fmt.Sprintf("The Component was updated %v", req.NamespacedName)) - yamlHASCompData, err := yaml.Marshal(hasCompDevfileData) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to marshall the Component devfile, exiting reconcile loop %v", req.NamespacedName)) - _ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - component.Status.ContainerImage = component.Spec.ContainerImage - component.Status.Devfile = string(yamlHASCompData) - err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - var currentComponent appstudiov1alpha1.Component - err := r.Get(ctx, req.NamespacedName, ¤tComponent) - if err != nil { - return err - } - currentComponent.Status.Devfile = component.Status.Devfile - currentComponent.Status.ContainerImage = component.Status.ContainerImage - currentComponent.Status.Conditions = component.Status.Conditions - err = r.Client.Status().Update(ctx, ¤tComponent) - return err - }) - if err != nil { - log.Error(err, "Unable to update Component status") - // if we're unable to update the Component CR status, then we need to err out - // since we need the reference of the devfile in Component to be always accessible - _ = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, err) - return ctrl.Result{}, err - } - - err = r.SetUpdateConditionAndUpdateCR(ctx, req, &component, nil) - if err != nil { - return ctrl.Result{}, err - } - - } else { - log.Info(fmt.Sprintf("The Component devfile data was not updated %v", req.NamespacedName)) - } - } - - log.Info(fmt.Sprintf("Finished reconcile loop for %v", req.NamespacedName)) - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *ComponentReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { - log := ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Component") - return ctrl.NewControllerManagedBy(mgr). - For(&appstudiov1alpha1.Component{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). - WithOptions(controller.Options{ - RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(time.Duration(500*time.Millisecond), time.Duration(1000*time.Second)), - }).WithEventFilter(predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - log := log.WithValues("namespace", e.Object.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.Object.GetName(), "Component", logutil.ResourceCreate, nil) - return true - }, - UpdateFunc: func(e event.UpdateEvent) bool { - log := log.WithValues("namespace", e.ObjectNew.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.ObjectNew.GetName(), "Component", logutil.ResourceUpdate, nil) - return true - }, - DeleteFunc: func(e event.DeleteEvent) bool { - log := log.WithValues("namespace", e.Object.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.Object.GetName(), "Component", logutil.ResourceDelete, nil) - return false - }, - }). - Complete(r) -} - -// checkForCreateReconcile checks if the Component is in Create state or an Update state. -// The err condition message is returned if it is in Create state. -func checkForCreateReconcile(component appstudiov1alpha1.Component) (bool, string) { - var errCondition string - // Determine if this is a Create reconcile or an Update reconcile based on Conditions - for _, condition := range component.Status.Conditions { - if condition.Type == "Updated" { - // If an Updated Condition is present, it means this is an Update reconcile - return false, "" - } else if condition.Type == "Created" && condition.Reason == "Error" && condition.Status == metav1.ConditionFalse { - errCondition = condition.Message - } - } - - // If there are no Conditions or no Updated Condition, then this is a Create reconcile - return true, errCondition -} diff --git a/controllers/component_controller_conditions.go b/controllers/component_controller_conditions.go deleted file mode 100644 index 0eed30167..000000000 --- a/controllers/component_controller_conditions.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/util/retry" - ctrl "sigs.k8s.io/controller-runtime" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - logutil "github.com/redhat-appstudio/application-service/pkg/log" -) - -func (r *ComponentReconciler) SetCreateConditionAndUpdateCR(ctx context.Context, req ctrl.Request, component *appstudiov1alpha1.Component, createError error) error { - log := ctrl.LoggerFrom(ctx) - - condition := metav1.Condition{} - - if createError == nil { - condition = metav1.Condition{ - Type: "Created", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "Component has been successfully created", - } - } else { - condition = metav1.Condition{ - Type: "Created", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("Component create failed: %v", createError), - } - logutil.LogAPIResourceChangeEvent(log, component.Name, "Component", logutil.ResourceCreate, createError) - } - err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - var currentComponent appstudiov1alpha1.Component - err := r.Get(ctx, req.NamespacedName, ¤tComponent) - if err != nil { - return err - } - meta.SetStatusCondition(¤tComponent.Status.Conditions, condition) - currentComponent.Status.Devfile = component.Status.Devfile - currentComponent.Status.ContainerImage = component.Status.ContainerImage - currentComponent.Status.GitOps = component.Status.GitOps - err = r.Client.Status().Update(ctx, ¤tComponent) - return err - }) - if err != nil { - log.Error(err, "Unable to update Component") - return err - } - - return nil -} - -func (r *ComponentReconciler) SetUpdateConditionAndUpdateCR(ctx context.Context, req ctrl.Request, component *appstudiov1alpha1.Component, updateError error) error { - log := ctrl.LoggerFrom(ctx) - - condition := metav1.Condition{} - if updateError == nil { - condition = metav1.Condition{ - Type: "Updated", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "Component has been successfully updated", - } - } else { - condition = metav1.Condition{ - Type: "Updated", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("Component updated failed: %v", updateError), - } - logutil.LogAPIResourceChangeEvent(log, component.Name, "Component", logutil.ResourceUpdate, updateError) - } - err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - var currentComponent appstudiov1alpha1.Component - err := r.Get(ctx, req.NamespacedName, ¤tComponent) - if err != nil { - return err - } - meta.SetStatusCondition(¤tComponent.Status.Conditions, condition) - currentComponent.Status.Devfile = component.Status.Devfile - currentComponent.Status.ContainerImage = component.Status.ContainerImage - currentComponent.Status.GitOps = component.Status.GitOps - err = r.Client.Status().Update(ctx, ¤tComponent) - return err - }) - if err != nil { - log.Error(err, "Unable to update Component") - return err - } - - return nil -} diff --git a/controllers/component_controller_test.go b/controllers/component_controller_test.go deleted file mode 100644 index b0ad86d70..000000000 --- a/controllers/component_controller_test.go +++ /dev/null @@ -1,2265 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "strings" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "github.com/redhat-appstudio/application-service/pkg/metrics" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/library/v2/pkg/devfile/parser" - data "github.com/devfile/library/v2/pkg/devfile/parser/data" - "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - devfilePkg "github.com/redhat-appstudio/application-service/pkg/devfile" - - spiapi "github.com/redhat-appstudio/service-provider-integration-operator/api/v1beta1" - corev1 "k8s.io/api/core/v1" - - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - //+kubebuilder:scaffold:imports -) - -var _ = Describe("Component controller", func() { - - // Define utility constants for object names and testing timeouts/durations and intervals. - const ( - HASAppName = "test-application" - HASCompName = "test-component" - HASAppNamespace = "default" - DisplayName = "petclinic" - Description = "Simple petclinic app" - ComponentName = "backend" - SampleRepoLink = "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" - SampleGitlabRepoLink = "https://gitlab.com/devfile-samples/devfile-sample-java-springboot-basic" - gitToken = "" //empty for public repo test - ) - - prometheus.MustRegister(metrics.GetComponentCreationTotalReqs(), metrics.GetComponentCreationFailed(), metrics.GetComponentCreationSucceeded()) - - Context("Create Component with basic field set", func() { - It("Should create successfully and update the Application", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "1" - componentName := HASCompName + "1" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - _, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - Expect(err).Should(Not(HaveOccurred())) - - // Check the HAS Application devfile - hasAppDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasApp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - hasProjects, err := hasAppDevfile.GetProjects(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(hasProjects)).ShouldNot(Equal(0)) - - nameMatched := false - repoLinkMatched := false - for _, project := range hasProjects { - if project.Name == ComponentName { - nameMatched = true - } - if project.Git != nil && project.Git.GitLikeProjectSource.Remotes["origin"] == SampleRepoLink { - repoLinkMatched = true - } - } - Expect(nameMatched).Should(Equal(true)) - Expect(repoLinkMatched).Should(Equal(true)) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with basic field set including devfileURL", func() { - It("Should create successfully on a valid url", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "2" - componentName := HASCompName + "2" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://raw.githubusercontent.com/devfile/registry/main/stacks/java-openliberty/devfile.yaml", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - hasCompDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - // Check if its Liberty - Expect(string(hasCompDevfile.GetMetadata().DisplayName)).Should(ContainSubstring("Liberty")) - - // Check the HAS Application devfile - hasAppDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasApp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - hasProjects, err := hasAppDevfile.GetProjects(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(hasProjects)).ShouldNot(Equal(0)) - - nameMatched := false - repoLinkMatched := false - for _, project := range hasProjects { - if project.Name == ComponentName { - nameMatched = true - } - if project.Git != nil && project.Git.GitLikeProjectSource.Remotes["origin"] == SampleRepoLink { - repoLinkMatched = true - } - } - Expect(nameMatched).Should(Equal(true)) - Expect(repoLinkMatched).Should(Equal(true)) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with basic field set including devfileURL", func() { - It("Should error out on a bad url", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "3" - componentName := HASCompName + "3" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://bad/devfile.yaml", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasComp.Status.Devfile).Should(Equal("")) - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Reason).Should(Equal("Error")) - Expect(strings.ToLower(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message)).Should(ContainSubstring("error getting devfile")) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) == beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) > beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with other field set", func() { - It("Should create successfully and update the Application", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "5" - componentName := HASCompName + "5" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - originalRoute := "route-endpoint-url" - updatedRoute := "route-endpoint-url-2" - - originalReplica := 1 - updatedReplica := 2 - - originalPort := 1111 - updatedPort := 2222 - - storage1GiResource, err := resource.ParseQuantity("1Gi") - Expect(err).Should(Not(HaveOccurred())) - - storage2GiResource, err := resource.ParseQuantity("2Gi") - Expect(err).Should(Not(HaveOccurred())) - - core500mResource, err := resource.ParseQuantity("500m") - Expect(err).Should(Not(HaveOccurred())) - - core800mResource, err := resource.ParseQuantity("800m") - Expect(err).Should(Not(HaveOccurred())) - - originalEnv := []corev1.EnvVar{ - { - Name: "FOO", - Value: "foo", - }, - { - Name: "BAR", - Value: "bar", - }, - } - - updatedEnv := []corev1.EnvVar{ - { - Name: "FOO", - Value: "foo1", - }, - { - Name: "BAR", - Value: "bar1", - }, - } - - originalResources := corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: core500mResource, - corev1.ResourceMemory: storage1GiResource, - corev1.ResourceStorage: storage1GiResource, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: core500mResource, - corev1.ResourceMemory: storage1GiResource, - corev1.ResourceStorage: storage1GiResource, - }, - } - - updatedResources := corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: core800mResource, - corev1.ResourceMemory: storage2GiResource, - corev1.ResourceStorage: storage2GiResource, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: core800mResource, - corev1.ResourceMemory: storage2GiResource, - corev1.ResourceStorage: storage2GiResource, - }, - } - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - ContainerImage: "quay.io/test/test-image:latest", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - }, - }, - }, - Replicas: &originalReplica, - TargetPort: originalPort, - Route: originalRoute, - Env: originalEnv, - Resources: originalResources, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the component resource that was created. - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout40s, interval).Should(BeTrue()) - - // Validate that the built container image was set in the status - Expect(createdHasComp.Status.ContainerImage).Should(Equal("quay.io/test/test-image:latest")) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - hasCompDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - Expect(err).Should(Not(HaveOccurred())) - - checklist := updateChecklist{ - route: originalRoute, - replica: originalReplica, - port: originalPort, - env: originalEnv, - resources: originalResources, - } - - verifyHASComponentUpdates(hasCompDevfile, checklist, nil) - - // Check the HAS Application devfile - hasAppDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasApp.Status.Devfile)}) - Expect(err).Should(Not(HaveOccurred())) - - // project should be set in hasApp - hasProjects, err := hasAppDevfile.GetProjects(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(hasProjects)).ShouldNot(Equal(0)) - - nameMatched := false - repoLinkMatched := false - for _, project := range hasProjects { - if project.Name == ComponentName { - nameMatched = true - } - if project.Git != nil && project.Git.GitLikeProjectSource.Remotes["origin"] == SampleRepoLink { - repoLinkMatched = true - } - } - Expect(nameMatched).Should(Equal(true)) - Expect(repoLinkMatched).Should(Equal(true)) - - // update the hasComp and apply - createdHasComp.Spec.Replicas = &updatedReplica - createdHasComp.Spec.Route = updatedRoute - createdHasComp.Spec.TargetPort = updatedPort - createdHasComp.Spec.Env = updatedEnv - createdHasComp.Spec.Resources = updatedResources - createdHasComp.Spec.ContainerImage = "quay.io/newimage/newimage:latest" - - Expect(k8sClient.Update(ctx, createdHasComp)).Should(Succeed()) - - updatedHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, updatedHasComp) - return updatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Type == "Updated" - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(updatedHasComp.Status.ContainerImage).Should(Equal("quay.io/newimage/newimage:latest")) - - // Make sure the devfile model was properly set in Component - Expect(updatedHasComp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component updated devfile - hasCompUpdatedDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(updatedHasComp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - checklist = updateChecklist{ - route: updatedRoute, - replica: updatedReplica, - port: updatedPort, - env: updatedEnv, - resources: updatedResources, - } - - verifyHASComponentUpdates(hasCompUpdatedDevfile, checklist, nil) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with built container image set", func() { - It("Should create successfully", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - beforeImportGitRepoTotalReqs := testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) - beforeImportGitRepoSucceeded := testutil.ToFloat64(metrics.ImportGitRepoSucceeded) - beforeImportGitRepoFailed := testutil.ToFloat64(metrics.ImportGitRepoFailed) - - applicationName := HASAppName + "6" - componentName := HASCompName + "6" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - ContainerImage: "quay.io/test/testimage:latest", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 && createdHasComp.Status.ContainerImage != "" - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - _, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - // Make sure the component's built image is included in the status - Expect(createdHasComp.Status.ContainerImage).Should(Equal("quay.io/test/testimage:latest")) - - Expect(testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) > beforeImportGitRepoTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoSucceeded) > beforeImportGitRepoSucceeded).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoFailed) == beforeImportGitRepoFailed).To(BeTrue()) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Component with invalid devfile", func() { - It("Should fail and have proper failure condition set", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "7" - componentName := HASCompName + "7" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - ContainerImage: "quay.io/test/testimage:latest", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the component resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Remove the component's devfile and update a field in the spec to trigger a reconcile - createdHasComp.Status.Devfile = "a" - Expect(k8sClient.Status().Update(ctx, createdHasComp)).Should(Succeed()) - - createdHasComp.Spec.ContainerImage = "test" - Expect(k8sClient.Update(ctx, createdHasComp)).Should(Succeed()) - - updatedHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, updatedHasComp) - return updatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Type == "Updated" - }, timeout, interval).Should(BeTrue()) - - errCondition := updatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1] - Expect(errCondition.Status).Should(Equal(metav1.ConditionFalse)) - Expect(errCondition.Message).Should(ContainSubstring("cannot unmarshal string into Go value of type map[string]interface")) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with invalid git url", func() { - It("Should fail with error", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "11" - componentName := HASCompName + "11" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "http://fds df &#%&%*$ jdnc/\\", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - errCondition := createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1] - Expect(errCondition.Status).Should(Equal(metav1.ConditionFalse)) - Expect(errCondition.Message).Should(ContainSubstring("Component create failed: unable to get default branch of Github Repo")) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with non-exist git url", func() { - It("Should fail with error", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - beforeImportGitRepoTotalReqs := testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) - beforeImportGitRepoSucceeded := testutil.ToFloat64(metrics.ImportGitRepoSucceeded) - beforeImportGitRepoFailed := testutil.ToFloat64(metrics.ImportGitRepoFailed) - - applicationName := HASAppName + "-test-import-user-error" - componentName := HASCompName + "-test-import-user-error" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "http://github.com/non-exist-git-repo", - Revision: "main", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - errCondition := createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1] - Expect(errCondition.Status).Should(Equal(metav1.ConditionFalse)) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) > beforeImportGitRepoTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoSucceeded) > beforeImportGitRepoSucceeded).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoFailed) == beforeImportGitRepoFailed).To(BeTrue()) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with invalid devfile url", func() { - It("Should fail with error that devfile couldn't be unmarshalled", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "12" - componentName := HASCompName + "12" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://gist.githubusercontent.com/johnmcollier/f322819abaef77a4646a5d27279acb1a/raw/04cfa05bdd8a2f960fffd3cb2fe007efd597f059/component.yaml", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - errCondition := createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1] - Expect(errCondition.Status).Should(Equal(metav1.ConditionFalse)) - Expect(errCondition.Message).Should(ContainSubstring("schemaVersion not present in devfile")) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - // Private Git Repo tests - Context("Create Component with git secret field set to non-existent secret", func() { - It("Should error out since the secret doesn't exist", func() { - ctx := context.Background() - - applicationName := HASAppName + "13" - componentName := HASCompName + "13" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Secret: "fake-secret", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://github.com/test/repo", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasComp.Status.Devfile).Should(Equal("")) - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Reason).Should(Equal("Error")) - Expect(strings.ToLower(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message)).Should(ContainSubstring("component create failed: secret \"fake-secret\" not found")) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with git secret field set to an invalid secret", func() { - It("Should error out due parse error", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - // the secret exists but it's not a real one that we can use to access a live repo - ctx := context.Background() - - applicationName := HASAppName + "14" - componentName := HASCompName + "14" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - StringData: map[string]string{ - "password": "sometoken", - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Secret: componentName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 2 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) == 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasComp.Status.Devfile).Should(Equal("")) - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Status).Should(Equal(metav1.ConditionFalse)) - // This test case uses an invalid token with a public URL. The Devfile library expects an unset token and will error out trying to retrieve the devfile since it assumes it's from a private repo - Expect(strings.ToLower(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message)).Should(ContainSubstring("error getting devfile info from url: failed to retrieve")) - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) == beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) > beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with private repo, but no devfile", func() { - It("Should error out since no devfile exists", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "15" - componentName := HASCompName + "15" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - StringData: map[string]string{ - "password": "sometoken", - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Secret: componentName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/test-error-response", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasComp.Status.Devfile).Should(Equal("")) - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(strings.ToLower(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message)).Should(ContainSubstring("component create failed: unable to get default branch of github repo")) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) == beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) > beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with with context folder containing no devfile", func() { - It("Should error out because a devfile cannot be found", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "16" - componentName := HASCompName + "16" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/devfile-sample-python-basic", - Context: "docker", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Reason).Should(Equal("Error")) - Expect(strings.ToLower(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message)).Should(ContainSubstring("unable to find devfile in the specified location https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker")) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with basic field set and test updates to replicas", func() { - It("Should complete successfully", func() { - ctx := context.Background() - - applicationName := HASAppName + "17" - componentName := HASCompName + "17" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - ContainerImage: "an-image", - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - // Make sure the component resource has been updated properly - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message).Should(ContainSubstring("successfully created")) - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Reason).Should(Equal("OK")) - - //If replica is unset upon creation, then it should be nil - Expect(createdHasComp.Spec.Replicas).Should(BeNil()) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return strings.Contains(createdHasApp.Status.Devfile, "containerImage/backend") - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - Expect(createdHasApp.Status.Devfile).Should(ContainSubstring("containerImage/backend")) - - // Trigger a new reconcile that is not related to the replica - createdHasComp.Spec.ContainerImage = "Newimage" - Expect(k8sClient.Update(ctx, createdHasComp)).Should(Succeed()) - - updatedHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, updatedHasComp) - return len(updatedHasComp.Status.Conditions) > 1 && updatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Type == "Updated" && updatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Status == metav1.ConditionTrue - }, timeout, interval).Should(BeTrue()) - - Expect(updatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Status).Should(Equal(metav1.ConditionTrue)) - //replica should remain nil - Expect(createdHasComp.Spec.Replicas).Should(BeNil()) - - //Update replica - updatedHasComp.Spec.Replicas = &oneReplica - Expect(k8sClient.Update(ctx, updatedHasComp)).Should(Succeed()) - newUpdatedHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, newUpdatedHasComp) - return len(newUpdatedHasComp.Status.Conditions) > 1 && newUpdatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Type == "Updated" && newUpdatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Status == metav1.ConditionTrue - }, timeout, interval).Should(BeTrue()) - - Expect(newUpdatedHasComp.Status.Conditions[len(newUpdatedHasComp.Status.Conditions)-1].Status).Should(Equal(metav1.ConditionTrue)) - //replica should not be nil and should have a value - Expect(newUpdatedHasComp.Spec.Replicas).Should(Not(BeNil())) - Expect(*newUpdatedHasComp.Spec.Replicas).Should(Equal(oneReplica)) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with Dockerfile URL set", func() { - It("Should create successfully and update the Application", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "18" - componentName := HASCompName + "18" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - Context: "context", - DockerfileURL: "http://dockerfile.uri", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the component resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - hasCompDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - dockerfileComponents, err := hasCompDevfile.GetComponents(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(dockerfileComponents)).Should(Equal(2)) - - for _, component := range dockerfileComponents { - Expect(component.Name).Should(BeElementOf([]string{"dockerfile-build", "kubernetes-deploy"})) - if component.Image != nil && component.Image.Dockerfile != nil { - Expect(component.Image.Dockerfile.Uri).Should(Equal(hasComp.Spec.Source.GitSource.DockerfileURL)) - Expect(component.Image.Dockerfile.BuildContext).Should(Equal("./")) - } else if component.Kubernetes != nil { - Expect(component.Kubernetes.Inlined).Should(ContainSubstring("Deployment")) - } - } - - // Check the HAS Application devfile - hasAppDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasApp.Status.Devfile)}) - Expect(err).Should(Not(HaveOccurred())) - - hasProjects, err := hasAppDevfile.GetProjects(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(hasProjects)).ShouldNot(Equal(0)) - - nameMatched := false - repoLinkMatched := false - for _, project := range hasProjects { - if project.Name == ComponentName { - nameMatched = true - } - if project.Git != nil && project.Git.GitLikeProjectSource.Remotes["origin"] == SampleRepoLink { - repoLinkMatched = true - } - } - Expect(nameMatched).Should(Equal(true)) - Expect(repoLinkMatched).Should(Equal(true)) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with Dockerfile URL set for repo with devfile URL", func() { - It("Should create successfully and override local Dockerfile URL references in the Devfile", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "20" - componentName := HASCompName + "20" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/node-express-hello-no-devfile", - DevfileURL: "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml", - DockerfileURL: "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/Dockerfile", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the component resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - hasCompDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - Expect(err).Should(Not(HaveOccurred())) - - devfileComponents, err := hasCompDevfile.GetComponents(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(devfileComponents)).Should(Equal(3)) - - for _, component := range devfileComponents { - Expect(component.Name).Should(BeElementOf([]string{"image-build", "kubernetes-deploy", "runtime"})) - if component.Image != nil && component.Image.Dockerfile != nil { - Expect(component.Image.Dockerfile.Uri).Should(Equal(hasComp.Spec.Source.GitSource.DockerfileURL)) - } - } - - // Check the HAS Application devfile - hasAppDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasApp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - hasProjects, err := hasAppDevfile.GetProjects(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(hasProjects)).ShouldNot(Equal(0)) - - nameMatched := false - repoLinkMatched := false - for _, project := range hasProjects { - if project.Name == ComponentName { - nameMatched = true - } - if project.Git != nil && project.Git.GitLikeProjectSource.Remotes["origin"] == "https://github.com/devfile-resources/node-express-hello-no-devfile" { - repoLinkMatched = true - } - } - Expect(nameMatched).Should(Equal(true)) - Expect(repoLinkMatched).Should(Equal(true)) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Private Component with basic field set", func() { - It("Should create successfully and update the Application", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "24" - componentName := HASCompName + "24" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - StringData: map[string]string{ - "password": "valid-token", // token tied to mock implementation in devfile/library. See https://github.com/devfile/library/blob/main/pkg/util/mock.go#L250 - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Secret: componentName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/devfile-sample-python-basic-private", // It doesn't matter if we are using pub/pvt repo here. We are mock testing the token, "valid-token" returns a mock devfile. See https://github.com/devfile/library/blob/main/pkg/util/mock.go#L250 - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - _, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - Expect(err).Should(Not(HaveOccurred())) - - // Check the HAS Application devfile - hasAppDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasApp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - hasProjects, err := hasAppDevfile.GetProjects(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(hasProjects)).ShouldNot(Equal(0)) - - nameMatched := false - repoLinkMatched := false - for _, project := range hasProjects { - if project.Name == ComponentName { - nameMatched = true - } - if project.Git != nil && project.Git.GitLikeProjectSource.Remotes["origin"] == "https://github.com/devfile-resources/devfile-sample-python-basic-private" { - repoLinkMatched = true - } - } - Expect(nameMatched).Should(Equal(true)) - Expect(repoLinkMatched).Should(Equal(true)) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Private Component with basic field set and a private parent uri", func() { - It("Should create successfully and update the Application", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "26" - componentName := HASCompName + "26" - - originalPort := 1111 - updatedPort := 2222 - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - StringData: map[string]string{ - "password": "parent-devfile", // notsecret - see mock implementation in devfile/library https://github.com/devfile/library/blob/main/pkg/util/mock.go - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Secret: componentName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/devfile-sample-python-basic-private", // It doesn't matter if we are using pub/pvt repo here. We are mock testing the token, "parent-devfile" returns a mock devfile and mock parent. See https://github.com/devfile/library/blob/main/pkg/util/mock.go - }, - }, - }, - TargetPort: originalPort, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 (Created) on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(createdHasComp.Status.Devfile).Should(Not(Equal(""))) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component devfile - _, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasComp.Status.Devfile)}) - Expect(err).Should(Not(HaveOccurred())) - - // Check the HAS Application devfile - hasAppDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(createdHasApp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - hasProjects, err := hasAppDevfile.GetProjects(common.DevfileOptions{}) - Expect(err).Should(Not(HaveOccurred())) - Expect(len(hasProjects)).ShouldNot(Equal(0)) - - nameMatched := false - repoLinkMatched := false - for _, project := range hasProjects { - if project.Name == ComponentName { - nameMatched = true - } - if project.Git != nil && project.Git.GitLikeProjectSource.Remotes["origin"] == "https://github.com/devfile-resources/devfile-sample-python-basic-private" { - repoLinkMatched = true - } - } - Expect(nameMatched).Should(Equal(true)) - Expect(repoLinkMatched).Should(Equal(true)) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Update Component - createdHasComp.Spec.TargetPort = updatedPort - - Expect(k8sClient.Update(ctx, createdHasComp)).Should(Succeed()) - - updatedHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, updatedHasComp) - return updatedHasComp.Status.Conditions[len(updatedHasComp.Status.Conditions)-1].Type == "Updated" - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Component - Expect(updatedHasComp.Status.Devfile).Should(Not(Equal(""))) - - // Check the Component updated devfile - hasCompUpdatedDevfile, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: []byte(updatedHasComp.Status.Devfile)}) - - Expect(err).Should(Not(HaveOccurred())) - - checklist := updateChecklist{ - port: updatedPort, - } - - verifyHASComponentUpdates(hasCompUpdatedDevfile, checklist, nil) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create private Component for an Application with basic field set", func() { - It("Should create SPI FCR resource and persist it even though the associated private Component is in an error state", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - ctx := context.Background() - - applicationName := HASAppName + "27" - componentName := HASCompName + "27" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - StringData: map[string]string{ - "password": "invalid-token", // token tied to mock implementation in devfile/library. See https://github.com/devfile/library/blob/main/pkg/util/mock.go#L250 - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasCompPrivate := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Secret: componentName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "http://github.com/dummy/create-spi-fcr-return-devfile", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasCompPrivate)).Should(Succeed()) - - // Look up the has app resource that was created. - hasCompPrivateLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasPrivateComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompPrivateLookupKey, createdHasPrivateComp) - return len(createdHasPrivateComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasPrivateComp.Status.Devfile).Should(Equal("")) - Expect(createdHasPrivateComp.Status.Conditions[len(createdHasPrivateComp.Status.Conditions)-1].Reason).Should(Equal("Error")) - Expect(strings.ToLower(createdHasPrivateComp.Status.Conditions[len(createdHasPrivateComp.Status.Conditions)-1].Message)).Should(ContainSubstring("error getting devfile")) - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) == beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) > beforeCreateFailedReqs).To(BeTrue()) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - createdHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, createdHasApp) - return len(createdHasApp.Status.Conditions) > 0 && strings.Contains(createdHasApp.Status.Devfile, ComponentName) - }, timeout, interval).Should(BeTrue()) - - // Make sure the devfile model was properly set in Application - Expect(createdHasApp.Status.Devfile).Should(Not(Equal(""))) - - // check for the SPI FCR that got created for private component, its a mock test client, so the SPI FCR does not get processed besides getting created. - createdSPIFCR := &spiapi.SPIFileContentRequest{} - spiFCRQueryLookupKey := types.NamespacedName{Name: "spi-fcr-" + componentName + "0", Namespace: HASAppNamespace} - Eventually(func() bool { - k8sClient.Get(context.Background(), spiFCRQueryLookupKey, createdSPIFCR) - return createdSPIFCR.Spec.RepoUrl != "" - }, timeout, interval).Should(BeTrue()) - - // Delete the specified private HASComp resource - deleteHASCompCR(hasCompPrivateLookupKey) - - // Ensure the SPIFCR that is associate with the private component has owner reference - // Kube client created with a test environment config does not clean up Kube resources - // with owner referneces. - createdSPIFCR = &spiapi.SPIFileContentRequest{} - Eventually(func() bool { - k8sClient.Get(context.Background(), spiFCRQueryLookupKey, createdSPIFCR) - ownerRefs := createdSPIFCR.GetOwnerReferences() - if len(ownerRefs) == 1 { - if ownerRefs[0].Name == componentName && ownerRefs[0].Kind == "Component" { - return true - } - } - return false - }, timeout, interval).Should(BeTrue()) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create Component with basic field set including devfileURL", func() { - It("Should error out on a devfile that has incompatible data and mark it as an user error on the metrics", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "28" - componentName := HASCompName + "28" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://raw.githubusercontent.com/maysunfaisal/devfile-sample-go-basic-placeholder/main/devfile.yaml", - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasComp.Status.Devfile).Should(Equal("")) - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Reason).Should(Equal("Error")) - Expect(strings.ToLower(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message)).Should(ContainSubstring("error unmarshaling")) - - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Create component having git source from gitlab", func() { - It("Should not increase the component failure metrics", func() { - beforeCreateTotalReqs := testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.GetComponentCreationFailed()) - - ctx := context.Background() - - applicationName := HASAppName + "30" - componentName := HASCompName + "30" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleGitlabRepoLink, - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - createdHasComp := &appstudiov1alpha1.Component{} - Eventually(func() bool { - k8sClient.Get(ctx, hasCompLookupKey, createdHasComp) - return len(createdHasComp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - // Make sure the err was set - Expect(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Reason).Should(Equal("Error")) - Expect(strings.ToLower(createdHasComp.Status.Conditions[len(createdHasComp.Status.Conditions)-1].Message)).Should(ContainSubstring("component create failed: unable to")) - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - Expect(testutil.ToFloat64(metrics.GetComponentCreationTotalReqs()) > beforeCreateTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationSucceeded()) > beforeCreateSucceedReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.GetComponentCreationFailed()) == beforeCreateFailedReqs).To(BeTrue()) - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) - - Context("Component CR with finalizer", func() { - It("Should delete successfully", func() { - ctx := context.Background() - - applicationName := HASAppName + "31" - componentName := HASCompName + "31" - - createAndFetchSimpleApp(applicationName, HASAppNamespace, DisplayName, Description) - - hasComp := &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: HASAppNamespace, - Finalizers: []string{compFinalizerName}, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: ComponentName, - Application: applicationName, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: SampleGitlabRepoLink, - }, - }, - }, - }, - } - Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) - - hasCompLookupKey := types.NamespacedName{Name: componentName, Namespace: HASAppNamespace} - hasAppLookupKey := types.NamespacedName{Name: applicationName, Namespace: HASAppNamespace} - - // Delete the specified HASComp resource - deleteHASCompCR(hasCompLookupKey) - - // Delete the specified HASApp resource - deleteHASAppCR(hasAppLookupKey) - }) - }) -}) - -type updateChecklist struct { - route string - port int - replica int - env []corev1.EnvVar - resources corev1.ResourceRequirements -} - -// verifyHASComponentUpdates verifies if the devfile data has been properly updated with the Component CR values -func verifyHASComponentUpdates(devfile data.DevfileData, checklist updateChecklist, goPkgTest *testing.T) { - // container component should be updated with the necessary hasComp properties - components, err := devfile.GetComponents(common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: v1alpha2.KubernetesComponentType, - }, - }) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - } else if err != nil { - goPkgTest.Error(err) - } - - requests := checklist.resources.Requests - limits := checklist.resources.Limits - - for _, component := range components { - componentAttributes := component.Attributes - var err error - - // Check the route - if checklist.route != "" { - route := componentAttributes.Get(devfilePkg.RouteKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(route).Should(Equal(checklist.route)) - } else if err != nil { - goPkgTest.Error(err) - } else if route != checklist.route { - goPkgTest.Errorf("expected: %v, got: %v", checklist.route, route) - } - } - - // Check the replica - if checklist.replica != 0 { - replicas := componentAttributes.Get(devfilePkg.ReplicaKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(replicas).Should(Equal(float64(checklist.replica))) - } else if err != nil { - goPkgTest.Error(err) - } else if int(replicas.(float64)) != checklist.replica { - goPkgTest.Errorf("expected: %v, got: %v", checklist.replica, replicas) - } - } - - // Check the storage limit - if _, ok := limits[corev1.ResourceStorage]; ok { - storageLimitChecklist := limits[corev1.ResourceStorage] - storageLimit := componentAttributes.Get(devfilePkg.StorageLimitKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(storageLimit).Should(Equal(storageLimitChecklist.String())) - } else if err != nil { - goPkgTest.Error(err) - } else if storageLimit.(string) != storageLimitChecklist.String() { - goPkgTest.Errorf("expected: %v, got: %v", storageLimitChecklist.String(), storageLimit) - } - } - - // Check the storage request - if _, ok := requests[corev1.ResourceStorage]; ok { - storageRequestChecklist := requests[corev1.ResourceStorage] - storageRequest := componentAttributes.Get(devfilePkg.StorageRequestKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(storageRequest).Should(Equal(storageRequestChecklist.String())) - } else if err != nil { - goPkgTest.Error(err) - } else if storageRequest.(string) != storageRequestChecklist.String() { - goPkgTest.Errorf("expected: %v, got: %v", storageRequestChecklist.String(), storageRequest) - } - } - - // Check the memory limit - if _, ok := limits[corev1.ResourceMemory]; ok { - memoryLimitChecklist := limits[corev1.ResourceMemory] - memoryLimit := componentAttributes.Get(devfilePkg.MemoryLimitKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(memoryLimit.(string)).Should(Equal(memoryLimitChecklist.String())) - } else if err != nil { - goPkgTest.Error(err) - } else if memoryLimit.(string) != memoryLimitChecklist.String() { - goPkgTest.Errorf("expected: %v, got: %v", memoryLimitChecklist.String(), memoryLimit) - } - } - - // Check the memory request - if _, ok := requests[corev1.ResourceMemory]; ok { - memoryRequestChecklist := requests[corev1.ResourceMemory] - memoryRequest := componentAttributes.Get(devfilePkg.MemoryRequestKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(memoryRequest).Should(Equal(memoryRequestChecklist.String())) - } else if err != nil { - goPkgTest.Error(err) - } else if memoryRequest.(string) != memoryRequestChecklist.String() { - goPkgTest.Errorf("expected: %v, got: %v", memoryRequestChecklist.String(), memoryRequest) - } - } - - // Check the cpu limit - if _, ok := limits[corev1.ResourceCPU]; ok { - cpuLimitChecklist := limits[corev1.ResourceCPU] - cpuLimit := componentAttributes.Get(devfilePkg.CpuLimitKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(cpuLimit).Should(Equal(cpuLimitChecklist.String())) - } else if err != nil { - goPkgTest.Error(err) - } else if cpuLimit.(string) != cpuLimitChecklist.String() { - goPkgTest.Errorf("expected: %v, got: %v", cpuLimitChecklist.String(), cpuLimit) - } - } - - // Check the cpu request - if _, ok := requests[corev1.ResourceCPU]; ok { - cpuRequestChecklist := requests[corev1.ResourceCPU] - cpuRequest := componentAttributes.Get(devfilePkg.CpuRequestKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(cpuRequest).Should(Equal(cpuRequestChecklist.String())) - } else if err != nil { - goPkgTest.Error(err) - } else if cpuRequest.(string) != cpuRequestChecklist.String() { - goPkgTest.Errorf("expected: %v, got: %v", cpuRequestChecklist.String(), cpuRequest) - } - } - - // Check for container port - if checklist.port != 0 { - containerPort := componentAttributes.Get(devfilePkg.ContainerImagePortKey, &err) - if goPkgTest == nil { - Expect(err).Should(Not(HaveOccurred())) - Expect(containerPort).Should(Equal(float64(checklist.port))) - } else if err != nil { - goPkgTest.Error(err) - } else if int(containerPort.(float64)) != checklist.port { - goPkgTest.Errorf("expected: %v, got: %v", checklist.port, containerPort) - } - } - // Check for env - for _, checklistEnv := range checklist.env { - isMatched := false - var containerENVs []corev1.EnvVar - err := componentAttributes.GetInto(devfilePkg.ContainerENVKey, &containerENVs) - for _, containerEnv := range containerENVs { - if containerEnv.Name == checklistEnv.Name && containerEnv.Value == checklistEnv.Value { - isMatched = true - } - } - if goPkgTest == nil { - Expect(isMatched).Should(Equal(true)) - } else if err != nil { - goPkgTest.Error(err) - } else if !isMatched { - goPkgTest.Errorf("expected: %v, got: %v", true, isMatched) - } - } - } -} - -// deleteHASCompCR deletes the specified hasComp resource and verifies it was properly deleted -func deleteHASCompCR(hasCompLookupKey types.NamespacedName) { - // Delete - Eventually(func() error { - f := &appstudiov1alpha1.Component{} - k8sClient.Get(context.Background(), hasCompLookupKey, f) - return k8sClient.Delete(context.Background(), f) - }, timeout, interval).Should(Succeed()) - - // Wait for delete to finish - Eventually(func() error { - f := &appstudiov1alpha1.Component{} - return k8sClient.Get(context.Background(), hasCompLookupKey, f) - }, timeout, interval).ShouldNot(Succeed()) -} - -// Simple function to create, retrieve from k8s, and return a simple Application CR -func createAndFetchSimpleApp(name string, namespace string, display string, description string) *appstudiov1alpha1.Application { - ctx := context.Background() - - hasApp := &appstudiov1alpha1.Application{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Application", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ApplicationSpec{ - DisplayName: display, - Description: description, - }, - } - - Expect(k8sClient.Create(ctx, hasApp)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasAppLookupKey := types.NamespacedName{Name: name, Namespace: namespace} - fetchedHasApp := &appstudiov1alpha1.Application{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasAppLookupKey, fetchedHasApp) - return len(fetchedHasApp.Status.Conditions) > 0 - }, timeout, interval).Should(BeTrue()) - - return fetchedHasApp -} diff --git a/controllers/componentdetectionquery_controller.go b/controllers/componentdetectionquery_controller.go deleted file mode 100644 index 6d381463f..000000000 --- a/controllers/componentdetectionquery_controller.go +++ /dev/null @@ -1,546 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "encoding/json" - "fmt" - "path" - "reflect" - "strings" - "time" - - "github.com/go-logr/logr" - "github.com/hashicorp/go-multierror" - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - "github.com/prometheus/client_golang/prometheus" - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "github.com/redhat-appstudio/application-service/pkg/github" - logutil "github.com/redhat-appstudio/application-service/pkg/log" - "github.com/redhat-appstudio/application-service/pkg/metrics" - "github.com/redhat-appstudio/application-service/pkg/util" - "github.com/spf13/afero" - "golang.org/x/exp/maps" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -// ComponentDetectionQueryReconciler reconciles a ComponentDetectionQuery object -type ComponentDetectionQueryReconciler struct { - client.Client - Scheme *runtime.Scheme - Log logr.Logger - GitHubTokenClient github.GitHubToken - DevfileRegistryURL string - AppFS afero.Afero - RunKubernetesJob bool - Config *rest.Config - CdqAnalysisImage string - CDQUtil cdqanalysis.CDQUtil -} - -const cdqName = "ComponentDetectionQuery" - -// CDQReconcileTimeout is the default timeout, 5 mins, for the context of cdq reconcile -const CDQReconcileTimeout = 5 * time.Minute - -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=componentdetectionqueries,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=componentdetectionqueries/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=appstudio.redhat.com,resources=componentdetectionqueries/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;delete -//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the ComponentDetectionQuery object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile -func (r *ComponentDetectionQueryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := ctrl.LoggerFrom(ctx) - - // set 5 mins timeout for cdq reconcile - ctx, cancel := context.WithTimeout(ctx, CDQReconcileTimeout) - defer cancel() - - // Fetch the ComponentDetectionQuery instance - var componentDetectionQuery appstudiov1alpha1.ComponentDetectionQuery - err := r.Get(ctx, req.NamespacedName, &componentDetectionQuery) - if err != nil { - if errors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - return ctrl.Result{}, nil - } - // Error reading the object - requeue the request. - return ctrl.Result{}, err - } - - // If there are no conditions attached to the CDQ, the resource was just created - if len(componentDetectionQuery.Status.Conditions) == 0 { - // Start the ComponentDetectionQuery, and update its status condition accordingly - log.Info(fmt.Sprintf("Checking to see if a component can be detected %v", req.NamespacedName)) - r.SetDetectingConditionAndUpdateCR(ctx, req, &componentDetectionQuery) - - // Create a copy of the CDQ, to use as the base when setting the CDQ status via mergepatch later - copiedCDQ := componentDetectionQuery.DeepCopy() - - var gitToken string - if componentDetectionQuery.Spec.Secret != "" { - gitSecret := corev1.Secret{} - namespacedName := types.NamespacedName{ - Name: componentDetectionQuery.Spec.Secret, - Namespace: componentDetectionQuery.Namespace, - } - err = r.Client.Get(ctx, namespacedName, &gitSecret) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to retrieve Git secret %v, exiting reconcile loop %v", componentDetectionQuery.Spec.Secret, req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - gitToken = string(gitSecret.Data["password"]) - } - - // Create a Go-GitHub client for checking the default branch - ghClient, err := r.GitHubTokenClient.GetNewGitHubClient(gitToken) - if err != nil { - log.Error(err, "Unable to create Go-GitHub client due to error") - return reconcile.Result{}, err - } - - // Add the Go-GitHub client name to the context - ctx = context.WithValue(ctx, github.GHClientKey, ghClient.TokenName) - - source := componentDetectionQuery.Spec.GitSource - var devfilePath string - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - componentPortsMap := make(map[string][]int) - context := source.Context - - if context == "" { - context = "./" - } - // remove leading and trailing spaces of the repo URL - source.URL = strings.TrimSpace(source.URL) - sourceURL := source.URL - // If the repository URL ends in a forward slash, remove it to avoid issues with default branch lookup - if string(sourceURL[len(sourceURL)-1]) == "/" { - sourceURL = sourceURL[0 : len(sourceURL)-1] - } - - // there is no need to perform a GET on the url as we are going to clone soon, - // this also saves us the headache of using token here for private repos. - err = util.ValidateEndpoint(sourceURL) - if err != nil { - log.Error(err, fmt.Sprintf("Failed to validate the source URL %v... %v", source.URL, req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - - cdqInfo := &cdqanalysis.CDQInfo{ - DevfileRegistryURL: r.DevfileRegistryURL, - GitURL: cdqanalysis.GitURL{RepoURL: source.URL, Revision: source.Revision, Token: gitToken}, - } - - if source.DevfileURL == "" { - log.Info(fmt.Sprintf("Attempting to read a devfile from the URL %s... %v", source.URL, req.NamespacedName)) - metrics.ImportGitRepoTotalReqs.Inc() - - var devfilesMapReturned map[string][]byte - var devfilesURLMapReturned, dockerfileContextMapReturned map[string]string - var componentPortsMapReturned map[string][]int - revision := source.Revision - - // with annotation runCDQAnalysisLocal = true, would allow the CDQ controller to run the cdq-analysis go modoule - // it is being used for CDQ controller tests to test both k8s job and go module - if r.RunKubernetesJob && !(componentDetectionQuery.Annotations["runCDQAnalysisLocal"] == "true") { - // perfume cdq job that requires repo cloning and azlier analysis - clientset, err := kubernetes.NewForConfig(r.Config) - if err != nil { - log.Error(err, fmt.Sprintf("Error creating clientset with config... %v", req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - jobName := req.Name + "-job" - var backOffLimit int32 = 0 - jobSpec := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: jobName, - Namespace: req.Namespace, - }, - Spec: batchv1.JobSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - ServiceAccountName: "application-service-controller-manager", - Containers: []corev1.Container{ - { - Name: jobName, - Image: r.CdqAnalysisImage, - ImagePullPolicy: corev1.PullAlways, - Env: []corev1.EnvVar{ - { - Name: "NAME", - Value: req.Name, - }, - { - Name: "NAMESPACE", - Value: req.Namespace, - }, - { - Name: "GITHUB_TOKEN", - Value: gitToken, - }, - { - Name: "CONTEXT_PATH", - Value: context, - }, - { - Name: "REVISION", - Value: revision, - }, - { - Name: "URL", - Value: source.URL, - }, - { - Name: "DEVFILE_REGISTRY_URL", - Value: r.DevfileRegistryURL, - }, - { - Name: "CREATE_K8S_Job", - Value: "true", - }, - }, - }, - }, - RestartPolicy: corev1.RestartPolicyNever, - }, - }, - BackoffLimit: &backOffLimit, - }, - } - err = r.Client.Create(ctx, jobSpec, &client.CreateOptions{}) - if err != nil { - log.Error(err, fmt.Sprintf("Error creating cdq analysis job %s... %v", jobName, req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } else { - //print job details - log.Info(fmt.Sprintf("Successfully created cdq analysis job %v, waiting for config map to be created... %v", jobName, req.NamespacedName)) - } - - cm, err := waitForConfigMap(clientset, ctx, req.Name, req.Namespace) - if err != nil || cm == nil { - if cm == nil { - err = fmt.Errorf("failed to wait for configmap creation, configmap is nil") - } - log.Error(err, fmt.Sprintf("Error waiting for configmap creation ...%v", req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - cleanupK8sResources(log, clientset, ctx, fmt.Sprintf("%s-job", req.Name), req.Name, req.Namespace) - return ctrl.Result{}, nil - } - var errMapReturned map[string]string - var unmarshalErr error - err = json.Unmarshal(cm.BinaryData["devfilesMap"], &devfilesMapReturned) - if err != nil { - unmarshalErr = multierror.Append(unmarshalErr, fmt.Errorf("unmarshal devfilesMap: %v", err)) - } - err = json.Unmarshal(cm.BinaryData["dockerfileContextMap"], &dockerfileContextMapReturned) - if err != nil { - unmarshalErr = multierror.Append(unmarshalErr, fmt.Errorf("unmarshal dockerfileContextMap: %v", err)) - } - err = json.Unmarshal(cm.BinaryData["devfilesURLMap"], &devfilesURLMapReturned) - if err != nil { - unmarshalErr = multierror.Append(unmarshalErr, fmt.Errorf("unmarshal devfilesURLMap: %v", err)) - } - err = json.Unmarshal(cm.BinaryData["componentPortsMap"], &componentPortsMapReturned) - if err != nil { - unmarshalErr = multierror.Append(unmarshalErr, fmt.Errorf("unmarshal componentPortsMap: %v", err)) - } - err = json.Unmarshal(cm.BinaryData["revision"], &revision) - if err != nil { - unmarshalErr = multierror.Append(unmarshalErr, fmt.Errorf("unmarshal revision: %v", err)) - } - err = json.Unmarshal(cm.BinaryData["errorMap"], &errMapReturned) - if err != nil { - unmarshalErr = multierror.Append(unmarshalErr, fmt.Errorf("unmarshal errorMap: %v", err)) - } - cleanupK8sResources(log, clientset, ctx, fmt.Sprintf("%s-job", req.Name), req.Name, req.Namespace) - - if unmarshalErr != nil { - log.Error(unmarshalErr, fmt.Sprintf("Failed to unmarshal the returned result from CDQ configmap... %v", req.NamespacedName)) - } - - if errMapReturned != nil && !reflect.DeepEqual(errMapReturned, map[string]string{}) { - var retErr error - // only 1 index in the error map - for key, value := range errMapReturned { - if key == "NoDevfileFound" { - metrics.ImportGitRepoSucceeded.Inc() - retErr = &cdqanalysis.NoDevfileFound{Err: fmt.Errorf(value)} - } else if key == "NoDockerfileFound" { - metrics.ImportGitRepoSucceeded.Inc() - retErr = &cdqanalysis.NoDockerfileFound{Err: fmt.Errorf(value)} - } else if key == "RepoNotFound" { - metrics.ImportGitRepoSucceeded.Inc() - retErr = &cdqanalysis.RepoNotFound{Err: fmt.Errorf(value)} - } else if key == "InvalidDevfile" { - metrics.ImportGitRepoSucceeded.Inc() - retErr = &cdqanalysis.InvalidDevfile{Err: fmt.Errorf(value)} - } else if key == "InvalidURL" { - metrics.ImportGitRepoSucceeded.Inc() - retErr = &cdqanalysis.InvalidURL{Err: fmt.Errorf(value)} - } else if key == "AuthenticationFailed" { - metrics.ImportGitRepoSucceeded.Inc() - retErr = &cdqanalysis.AuthenticationFailed{Err: fmt.Errorf(value)} - } else { - // Increment the git import failure metric - metrics.ImportGitRepoFailed.Inc() - retErr = &cdqanalysis.InternalError{Err: fmt.Errorf(value)} - } - } - log.Error(retErr, fmt.Sprintf("Unable to analyze the repo via kubernetes job... %v", req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, retErr) - return ctrl.Result{}, nil - } - - } else { - k8sInfoClient := cdqanalysis.K8sInfoClient{ - Log: log, - CreateK8sJob: false, - } - - devfilesMapReturned, devfilesURLMapReturned, dockerfileContextMapReturned, componentPortsMapReturned, revision, err = cdqanalysis.CloneAndAnalyze(k8sInfoClient, req.Namespace, req.Name, context, cdqInfo, r.CDQUtil) - if err != nil { - switch err.(type) { - case *cdqanalysis.NoDevfileFound: - metrics.ImportGitRepoSucceeded.Inc() - log.Error(err, fmt.Sprintf("NoDevfileFound error running cdq analysis... %v", req.NamespacedName)) - case *cdqanalysis.NoDockerfileFound: - metrics.ImportGitRepoSucceeded.Inc() - log.Error(err, fmt.Sprintf("NoDockerfileFound error running cdq analysis... %v", req.NamespacedName)) - case *cdqanalysis.RepoNotFound: - metrics.ImportGitRepoSucceeded.Inc() - log.Error(err, fmt.Sprintf("RepoNotFound error running cdq analysis... %v", req.NamespacedName)) - case *cdqanalysis.InvalidDevfile: - metrics.ImportGitRepoSucceeded.Inc() - log.Error(err, fmt.Sprintf("InvalidDevfile error running cdq analysis... %v", req.NamespacedName)) - case *cdqanalysis.InvalidURL: - metrics.ImportGitRepoSucceeded.Inc() - log.Error(err, fmt.Sprintf("InvalidURL error running cdq analysis... %v", req.NamespacedName)) - case *cdqanalysis.AuthenticationFailed: - metrics.ImportGitRepoSucceeded.Inc() - log.Error(err, fmt.Sprintf("AuthenticationFailed error running cdq analysis... %v", req.NamespacedName)) - default: - // Increment the git import failure metric only on non user failure - metrics.ImportGitRepoFailed.Inc() - log.Error(err, fmt.Sprintf("Internal error running cdq analysis... %v", req.NamespacedName)) - } - - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - } - metrics.ImportGitRepoSucceeded.Inc() - maps.Copy(devfilesMap, devfilesMapReturned) - maps.Copy(dockerfileContextMap, dockerfileContextMapReturned) - maps.Copy(devfilesURLMap, devfilesURLMapReturned) - maps.Copy(componentPortsMap, componentPortsMapReturned) - devfilePath, _ = cdqanalysis.GetDevfileAndDockerFilePaths(*cdqInfo) - componentDetectionQuery.Spec.GitSource.Revision = revision - - } else { - log.Info(fmt.Sprintf("devfile was explicitly specified at %s %v", source.DevfileURL, req.NamespacedName)) - - // For scenarios where a devfile is passed in, we still need to use the GH API for branch detection as we do not clone. - if source.Revision == "" { - log.Info(fmt.Sprintf("Look for default branch of repo %s... %v", source.URL, req.NamespacedName)) - metricsLabel := prometheus.Labels{"controller": cdqName, "tokenName": ghClient.TokenName, "operation": "GetDefaultBranchFromURL"} - metrics.ControllerGitRequest.With(metricsLabel).Inc() - source.Revision, err = ghClient.GetDefaultBranchFromURL(sourceURL, ctx) - metrics.HandleRateLimitMetrics(err, metricsLabel) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to get default branch of Github Repo %v, try to fall back to main branch... %v", source.URL, req.NamespacedName)) - metricsLabel := prometheus.Labels{"controller": cdqName, "tokenName": ghClient.TokenName, "operation": "GetBranchFromURL"} - metrics.ControllerGitRequest.With(metricsLabel).Inc() - _, err := ghClient.GetBranchFromURL(sourceURL, ctx, "main") - if err != nil { - metrics.HandleRateLimitMetrics(err, metricsLabel) - log.Error(err, fmt.Sprintf("Unable to get main branch of Github Repo %v ... %v", source.URL, req.NamespacedName)) - retErr := fmt.Errorf("unable to get default branch of Github Repo %v, try to fall back to main branch, failed to get main branch... %v", source.URL, req.NamespacedName) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, retErr) - return ctrl.Result{}, nil - } else { - source.Revision = "main" - } - } - } - - // set in the CDQ spec - componentDetectionQuery.Spec.GitSource.Revision = source.Revision - - shouldIgnoreDevfile, devfileBytes, err := r.CDQUtil.ValidateDevfile(log, source.DevfileURL, gitToken) - if err != nil { - // if a direct devfileURL is provided and errors out, we dont do an alizer detection - log.Error(err, fmt.Sprintf("Unable to GET %s, exiting reconcile loop %v", source.DevfileURL, req.NamespacedName)) - err := fmt.Errorf("unable to GET from %s", source.DevfileURL) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - if shouldIgnoreDevfile { - // if a direct devfileURL is provided and errors out, we dont do an alizer detection - log.Error(err, fmt.Sprintf("the provided devfileURL %s does not contain a valid outerloop definition, exiting reconcile loop %v", source.DevfileURL, req.NamespacedName)) - err := fmt.Errorf("the provided devfileURL %s does not contain a valid outerloop definition", source.DevfileURL) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - devfilesMap[context] = devfileBytes - devfilesURLMap[context] = source.DevfileURL - } - - for context := range devfilesMap { - if _, ok := devfilesURLMap[context]; !ok { - updatedLink, err := cdqanalysis.UpdateGitLink(source.URL, source.Revision, path.Join(context, devfilePath)) - if err != nil { - log.Error(err, fmt.Sprintf( - "Unable to update the devfile link %v", req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - devfilesURLMap[context] = updatedLink - } - } - // only update the componentStub when a component has been detected - if len(devfilesMap) != 0 || len(devfilesURLMap) != 0 || len(dockerfileContextMap) != 0 { - err = r.updateComponentStub(req, ctx, &componentDetectionQuery, devfilesMap, devfilesURLMap, dockerfileContextMap, componentPortsMap) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to update the component stub %v", req.NamespacedName)) - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, err) - return ctrl.Result{}, nil - } - } - - r.SetCompleteConditionAndUpdateCR(ctx, req, &componentDetectionQuery, copiedCDQ, nil) - } else { - // CDQ resource has been requeued after it originally ran - // Delete the resource as it's no longer needed and can be cleaned up - log.Info(fmt.Sprintf("Deleting finished ComponentDetectionQuery resource %v", req.NamespacedName)) - if err = r.Delete(ctx, &componentDetectionQuery); err != nil { - // Delete failed. Log the error but don't bother modifying the resource's status - logutil.LogAPIResourceChangeEvent(log, componentDetectionQuery.Name, "ComponentDetectionQuery", logutil.ResourceDelete, err) - } - } - - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *ComponentDetectionQueryReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { - log := ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ComponentDetectionQuery") - - return ctrl.NewControllerManagedBy(mgr). - For(&appstudiov1alpha1.ComponentDetectionQuery{}, - builder.WithPredicates(predicate.GenerationChangedPredicate{})).WithEventFilter(predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - log := log.WithValues("namespace", e.Object.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.Object.GetName(), "ComponentDetectionQuery", logutil.ResourceCreate, nil) - return true - }, - UpdateFunc: func(e event.UpdateEvent) bool { - log := log.WithValues("namespace", e.ObjectNew.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.ObjectNew.GetName(), "ComponentDetectionQuery", logutil.ResourceUpdate, nil) - return true - }, - DeleteFunc: func(e event.DeleteEvent) bool { - log := log.WithValues("namespace", e.Object.GetNamespace()) - logutil.LogAPIResourceChangeEvent(log, e.Object.GetName(), "ComponentDetectionQuery", logutil.ResourceDelete, nil) - return false - }, - }). - Complete(r) -} - -func waitForConfigMap(clientset *kubernetes.Clientset, ctx context.Context, name, namespace string) (*corev1.ConfigMap, error) { - // 5 mins timeout - timeout := int64(300) - opts := metav1.ListOptions{ - TypeMeta: metav1.TypeMeta{}, - FieldSelector: fmt.Sprintf("metadata.name=%s", name), - TimeoutSeconds: &timeout, - } - watcher, err := clientset.CoreV1().ConfigMaps(namespace).Watch(ctx, opts) - if err != nil { - return nil, err - } - defer watcher.Stop() - - for { - select { - case event := <-watcher.ResultChan(): - configMap := event.Object.(*corev1.ConfigMap) - return configMap, nil - - case <-ctx.Done(): - return nil, fmt.Errorf("context done while waiting for configmap creation, the context might be cancelled or exceeded") - } - } -} - -func cleanupK8sResources(log logr.Logger, clientset *kubernetes.Clientset, ctx context.Context, jobName, configMapName, namespace string) { - log.Info(fmt.Sprintf("Attempting to cleanup k8s resources for cdq analysis... %s", namespace)) - log.Info(fmt.Sprintf("Deleting job %s... %s", jobName, namespace)) - - jobsClient := clientset.BatchV1().Jobs(namespace) - - pp := metav1.DeletePropagationBackground - - err := jobsClient.Delete(ctx, jobName, metav1.DeleteOptions{PropagationPolicy: &pp}) - - if err != nil { - log.Error(err, fmt.Sprintf("Failed to delete job %s... %s", jobName, namespace)) - } else { - log.Info(fmt.Sprintf("Successfully deleted job %s... %s", jobName, namespace)) - } - - log.Info(fmt.Sprintf("Deleting config map %s... %s", configMapName, namespace)) - configMapClient := clientset.CoreV1().ConfigMaps(namespace) - err = configMapClient.Delete(ctx, configMapName, metav1.DeleteOptions{PropagationPolicy: &pp}) - if err != nil { - log.Error(err, fmt.Sprintf("Failed to delete config map %s... %s", configMapName, namespace)) - } else { - log.Info(fmt.Sprintf("Successfully deleted config map %s... %s", configMapName, namespace)) - } -} diff --git a/controllers/componentdetectionquery_controller_conditions.go b/controllers/componentdetectionquery_controller_conditions.go deleted file mode 100644 index 8fe992d6d..000000000 --- a/controllers/componentdetectionquery_controller_conditions.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2022-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - logutil "github.com/redhat-appstudio/application-service/pkg/log" -) - -func (r *ComponentDetectionQueryReconciler) SetDetectingConditionAndUpdateCR(ctx context.Context, req ctrl.Request, componentDetectionQuery *appstudiov1alpha1.ComponentDetectionQuery) { - log := ctrl.LoggerFrom(ctx) - - patch := client.MergeFrom(componentDetectionQuery.DeepCopy()) - - meta.SetStatusCondition(&componentDetectionQuery.Status.Conditions, metav1.Condition{ - Type: "Processing", - Status: metav1.ConditionTrue, - Reason: "Success", - Message: "ComponentDetectionQuery is processing", - }) - - err := r.Client.Status().Patch(ctx, componentDetectionQuery, patch) - if err != nil { - log.Error(err, "Unable to update ComponentDetectionQuery") - } -} - -func (r *ComponentDetectionQueryReconciler) SetCompleteConditionAndUpdateCR(ctx context.Context, req ctrl.Request, componentDetectionQuery *appstudiov1alpha1.ComponentDetectionQuery, originalCDQ *appstudiov1alpha1.ComponentDetectionQuery, completeError error) { - log := ctrl.LoggerFrom(ctx) - - patch := client.MergeFrom(originalCDQ.DeepCopy()) - - if completeError == nil { - message := "ComponentDetectionQuery has successfully finished" - if len(componentDetectionQuery.Status.ComponentDetected) == 0 { - message = "ComponentDetectionQuery has successfully finished, no components detected" - } - meta.SetStatusCondition(&componentDetectionQuery.Status.Conditions, metav1.Condition{ - Type: "Completed", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: message, - }) - logutil.LogAPIResourceChangeEvent(log, componentDetectionQuery.Name, "ComponentDetectionQuery", logutil.ResourceComplete, nil) - } else { - meta.SetStatusCondition(&componentDetectionQuery.Status.Conditions, metav1.Condition{ - Type: "Completed", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("ComponentDetectionQuery failed: %v", completeError), - }) - logutil.LogAPIResourceChangeEvent(log, componentDetectionQuery.Name, "ComponentDetectionQuery", logutil.ResourceComplete, completeError) - } - err := r.Client.Status().Patch(ctx, componentDetectionQuery, patch) - if err != nil { - // Error attempting to update the CDQ status. Since some CDQ status fields have specific validation rules (specifically the detected components), a bug could cause - // an invalid field to be present in the status. _If_ the status update fails, still attempt to update only the status conditions - log.Error(err, "Unable to update ComponentDetectionQuery. Will attempt to update only the status condition") - - copiedCDQ := originalCDQ.DeepCopy() - meta.SetStatusCondition(&copiedCDQ.Status.Conditions, metav1.Condition{ - Type: "Completed", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("ComponentDetectionQuery failed: %v", completeError), - }) - err := r.Client.Status().Patch(ctx, componentDetectionQuery, patch) - if err != nil { - log.Error(err, "Unable to update ComponentDetectionQuery status conditions") - } - - } -} diff --git a/controllers/componentdetectionquery_controller_conditions_test.go b/controllers/componentdetectionquery_controller_conditions_test.go deleted file mode 100644 index fc47e09e0..000000000 --- a/controllers/componentdetectionquery_controller_conditions_test.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -Copyright 2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "fmt" - "reflect" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" -) - -func TestSetCompleteConditionAndUpdateCR(t *testing.T) { - // Set up a fake Kubernetes client and Component reconciler - scheme := runtime.NewScheme() - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(appstudiov1alpha1.AddToScheme(scheme)) - fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - r := &ComponentDetectionQueryReconciler{ - Log: ctrl.Log.WithName("controllers").WithName("Component"), - Client: fakeClient, - } - - originalCDQ := appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cdq", - Namespace: "test-namespace", - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/fakeorg/fakerepo", - }, - }, - } - r.Client.Create(context.Background(), &originalCDQ) - - cdqWithComponentDetected := originalCDQ - cdqWithComponentDetected.Status = appstudiov1alpha1.ComponentDetectionQueryStatus{ - ComponentDetected: appstudiov1alpha1.ComponentDetectionMap{ - "component1": appstudiov1alpha1.ComponentDetectionDescription{ - ComponentStub: appstudiov1alpha1.ComponentSpec{ - ComponentName: "component1", - }, - }, - }, - } - - request := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "test-namespace", - Name: "test-cdq", - }, - } - - tests := []struct { - name string - originalCDQ appstudiov1alpha1.ComponentDetectionQuery - updateCDQ appstudiov1alpha1.ComponentDetectionQuery - err error - wantCondition metav1.Condition - }{ - { - name: "Simple CDQ, no error", - originalCDQ: cdqWithComponentDetected, - updateCDQ: cdqWithComponentDetected, - wantCondition: metav1.Condition{ - Type: "Completed", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "ComponentDetectionQuery has successfully finished", - }, - }, - { - name: "Simple CDQ, no component detected, no error", - originalCDQ: originalCDQ, - updateCDQ: originalCDQ, - wantCondition: metav1.Condition{ - Type: "Completed", - Status: metav1.ConditionTrue, - Reason: "OK", - Message: "ComponentDetectionQuery has successfully finished, no components detected", - }, - }, - { - name: "Simple CDQ, with error", - originalCDQ: originalCDQ, - updateCDQ: originalCDQ, - err: fmt.Errorf("some error"), - wantCondition: metav1.Condition{ - Type: "Completed", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("ComponentDetectionQuery failed: %v", fmt.Errorf("some error")), - }, - }, - { - name: "CDQ with invalid status fields", - originalCDQ: originalCDQ, - updateCDQ: appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-cdq", - Namespace: "test-namespace", - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/fakeorg/fakerepo", - }, - }, - Status: appstudiov1alpha1.ComponentDetectionQueryStatus{ - ComponentDetected: appstudiov1alpha1.ComponentDetectionMap{ - "-ohu7": appstudiov1alpha1.ComponentDetectionDescription{ - ComponentStub: appstudiov1alpha1.ComponentSpec{ - ComponentName: "-ohu7", - }, - }, - }, - }, - }, - err: fmt.Errorf("some error"), - wantCondition: metav1.Condition{ - Type: "Completed", - Status: metav1.ConditionFalse, - Reason: "Error", - Message: fmt.Sprintf("ComponentDetectionQuery failed: %v", fmt.Errorf("some error")), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r.SetCompleteConditionAndUpdateCR(context.Background(), request, &tt.updateCDQ, &tt.originalCDQ, tt.err) - - // Now get the resource and verify its status condition - getCDQ := appstudiov1alpha1.ComponentDetectionQuery{} - err := r.Client.Get(context.Background(), types.NamespacedName{Namespace: "test-namespace", Name: "test-cdq"}, &getCDQ) - if err != nil { - t.Errorf("TestSetCompleteConditionAndUpdateCR(): Unexpected error: %v", err) - } - - if len(getCDQ.Status.Conditions) != 1 { - t.Errorf("TestSetCompleteConditionAndUpdateCR(): Unexpected error, length of %v was %v, not 1", getCDQ.Status.Conditions, len(getCDQ.Status.Conditions)) - } - tt.wantCondition.LastTransitionTime = getCDQ.Status.Conditions[0].LastTransitionTime - tt.wantCondition.ObservedGeneration = getCDQ.Status.Conditions[0].ObservedGeneration - if !reflect.DeepEqual(getCDQ.Status.Conditions[0], tt.wantCondition) { - t.Errorf("TestSetCompleteConditionAndUpdateCR(): expected %v, got %v", tt.wantCondition, getCDQ.Status.Conditions[0]) - } - }) - } -} diff --git a/controllers/componentdetectionquery_controller_test.go b/controllers/componentdetectionquery_controller_test.go deleted file mode 100644 index 61837d107..000000000 --- a/controllers/componentdetectionquery_controller_test.go +++ /dev/null @@ -1,3357 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/redhat-appstudio/application-service/pkg/metrics" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - //+kubebuilder:scaffold:imports -) - -var _ = Describe("Component Detection Query controller", func() { - - // Define utility constants for object names and testing timeouts/durations and intervals. - const ( - HASAppName = "test-application" - HASCompName = "test-component" - HASCompDetQuery = "test-componentdetectionquery" - HASNamespace = "default" - DisplayName = "petclinic" - Description = "Simple petclinic app" - ComponentName = "devfile-sample-java-springboot-basic" - SampleRepoLink = "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" - ) - - prometheus.MustRegister(metrics.ImportGitRepoTotalReqs, metrics.ImportGitRepoFailed, metrics.ImportGitRepoSucceeded) - - // test CDQ module - Context("Create Component Detection Query with URL set", func() { - It("Should successfully detect a devfile", func() { - ctx := context.Background() - beforeImportGitRepoTotalReqs := testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) - beforeImportGitRepoFailed := testutil.ToFloat64(metrics.ImportGitRepoFailed) - beforeImportGitRepoSucceeded := testutil.ToFloat64(metrics.ImportGitRepoSucceeded) - - queryName := HASCompDetQuery + "1" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - Revision: "main", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Revision).Should(ContainSubstring("main")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - Expect(testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) > beforeImportGitRepoTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoSucceeded) > beforeImportGitRepoSucceeded).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoFailed) == beforeImportGitRepoFailed).To(BeTrue()) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with devfileURL set", func() { - It("Should successfully get a devfile", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "2" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.ComponentName).Should(ContainSubstring(ComponentName)) - Expect(devfileDesc.ComponentStub.ComponentName).Should(Equal(ComponentName)) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with devfileURL and isMultiComponent set", func() { - It("Should use the devfileURL without err", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "3" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none", - DevfileURL: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the status is successful - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("multi-components-none")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with multi comp repo with no devfiles", func() { - It("Should successfully get the correct devfiles and Dockerfiles", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "4" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(2)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(Or(ContainSubstring("java-springboot"), ContainSubstring("python"))) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(BeElementOf([]string{"devfile-sample-java-springboot-basic", "devfile-sample-python-basic"})) - if strings.Contains(devfileName, "java-springboot") { - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile")) - } else if strings.Contains(devfileName, "python") { - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile")) - } - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with a non component devfile repo", func() { - It("Should complete with no error if no devfile or Dockerfile detected", func() { - ctx := context.Background() - beforeImportGitRepoTotalReqs := testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) - beforeImportGitRepoSucceeded := testutil.ToFloat64(metrics.ImportGitRepoSucceeded) - beforeImportGitRepoFailed := testutil.ToFloat64(metrics.ImportGitRepoFailed) - queryName := HASCompDetQuery + "5" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/octocat/Hello-World", - Revision: "master", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the cdq complete with success status - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - Expect(testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) > beforeImportGitRepoTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoSucceeded) > beforeImportGitRepoSucceeded).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoFailed) == beforeImportGitRepoFailed).To(BeTrue()) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with multi component repo that has no devfile", func() { - It("Should match a devfile with alizer if it can be a component", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "6" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(2)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(Or(ContainSubstring("java-springboot"), ContainSubstring("python"))) - Expect(devfileDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(BeElementOf([]string{"devfile-sample-java-springboot-basic", "devfile-sample-python-basic"})) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).ShouldNot(BeEmpty()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with repo that has no devfile", func() { - It("Should match a devfile with alizer if it can be a component", func() { - ctx := context.Background() - - queryName := "python-src-none" + HASCompDetQuery + "7" // this name is tied to mock fn in detect_mock.go - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/python-src-none", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect([]string{devfileName}).Should(ContainElement(ContainSubstring("python-src-none"))) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(BeElementOf([]string{"./"})) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with Devfile URL that does not exist", func() { - It("Should err out", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "8" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://registry.devfile.io/devfiles/fake", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery failed: unable to GET")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with invalid Git URL", func() { - It("Should err out", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "9" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/redhat-appstudio-appdata/!@#$%U%I$F DFDN##", - Revision: "main", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("parse \"https://github.com/redhat-appstudio-appdata/!@#$%U%I$F DFDN##\": invalid URL escape \"%U%\"")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with private multicomponent Github repo", func() { - It("Should err out due to authentication required error", func() { - ctx := context.Background() - beforeImportGitRepoTotalReqs := testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) - beforeImportGitRepoFailed := testutil.ToFloat64(metrics.ImportGitRepoFailed) - beforeImportGitRepoSucceeded := testutil.ToFloat64(metrics.ImportGitRepoSucceeded) - queryName := HASCompDetQuery + "10" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-private", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("authentication failed")) - - Expect(testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) > beforeImportGitRepoTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoSucceeded) > beforeImportGitRepoSucceeded).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoFailed) == beforeImportGitRepoFailed).To(BeTrue()) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - // Private repo tests - Context("Create Component Detection Query with private git repo + invalid token", func() { - It("Should err out due to invalid token", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "11" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - StringData: map[string]string{ - "password": "fake-token", - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-private", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // index is 1 because of CDQ status condition Processing - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("authentication failed")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with private git repo + valid mock token", func() { - It("Should successfully detect the component from the repo", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "12" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - StringData: map[string]string{ - "password": "valid-mock-token", // string is tied to mock clone func in cdq-analysis/pkg/componentdetectionquery_mock.go - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, // using an actual public repo here for testing a valid token case, see cdq-analysis/pkg/componentdetectionquery_mock.go for more information - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Revision).Should(ContainSubstring("main")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with private git repo with devfileURL set + valid mock token", func() { - It("Should err out", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "13" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - StringData: map[string]string{ - "password": "valid-mock-token", // string is tied to mock clone func in cdq-analysis/pkg/componentdetectionquery_mock.go - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - DevfileURL: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml", - URL: SampleRepoLink, // using an actual public repo here for testing a valid token case, see cdq-analysis/pkg/componentdetectionquery_mock.go for more information - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Revision).Should(ContainSubstring("main")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with private git repo and DevfileURL set + invalid token", func() { - It("Should err out due to invalid token", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "14" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - StringData: map[string]string{ - "password": "fake-token", - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - devfileURL := "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - DevfileURL: devfileURL, - URL: SampleRepoLink, - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // index is 1 because of CDQ status condition Processing - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("unable to GET from %s", devfileURL)) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with no secret", func() { - It("Should error out since specified secret does not exist", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "15" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/test-repo/testrepo", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - // Make sure the a devfile is detected - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring(fmt.Sprintf("ComponentDetectionQuery failed: Secret %q not found", queryName))) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with URL set to repo with invalid devfile", func() { - It("Should detect a devfile but return an error", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "16" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/test-bad-devfile", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure that the proper error condition is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("cannot unmarshal string into Go value of type map[string]interface")) - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with springboot repo that has devfile", func() { - It("Should return a correct devfile when repo URL has leading and trailing spaces", func() { - ctx := context.Background() - - queryName := "springboot" + HASCompDetQuery + "17" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: " https://github.com/devfile-samples/devfile-sample-java-springboot-basic ", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right status is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure a devfile is matched - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - for _, componentDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(componentDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - Expect(componentDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with URL set", func() { - It("Should err out cloning on a fake sample", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "18" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/fake-sample.git", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(0)) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery failed")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("A Requeued ComponentDetectionQuery", func() { - It("Should delete itself", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "19" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/fake-sample", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure no component was detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(0)) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery failed")) - - // Trigger a requeue by updating the resource - createdHasCompDetectionQuery.Spec.GitSource.URL = SampleRepoLink - Expect(k8sClient.Update(ctx, createdHasCompDetectionQuery)).Should(Succeed()) - - // Validate that the resource has been deleted - hasCompDetQueryLookupKey = types.NamespacedName{Name: queryName, Namespace: HASNamespace} - deletedCompDetQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - err := k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, deletedCompDetQuery) - return err != nil - }, timeout, interval).Should(BeTrue()) - }) - }) - - Context("Create Component Detection Query with multi component repo that has no devfile or Dockerfile", func() { - It("Should attempt to match a devfile or Dockerfile for every component", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "20" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-dockerfile", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the right status is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the devfiles are detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(5)) // mocked, not accurate. check unit test for accurate detection that uses the alizer client instead of the mock client. - for _, componentDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(componentDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - - Context("Create Component Detection Query with a Dockerfile repo", func() { - It("Should return successfully", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "python-src-docker" + "21" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/python-src-docker", - Revision: "testbranch", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the right status is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the devfiles are detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - for _, componentDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(componentDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - Expect(componentDesc.ComponentStub.Source.GitSource.DockerfileURL).ShouldNot(BeEmpty()) - Expect(componentDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("Dockerfile")) - Expect(componentDesc.ComponentStub.Source.GitSource.Revision).Should(Equal("testbranch")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with context provided", func() { - It("Should successfully get the devfiles", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "22" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none", - Context: "devfile-sample-java-springboot-basic", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("java-springboot")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("devfile-sample-java-springboot-basic")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with context set to \"./\"", func() { - It("Should successfully detect a devfile", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "23" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - Context: "./", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with context set to a deeper level folder", func() { - It("Should successfully detect a devfile", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "24" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-deep", - Context: "python/devfile-sample-python-basic", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("python-basic")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("python/devfile-sample-python-basic")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with Devfile URL that has no kubernetes definition", func() { - It("Should err out", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "25" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - DevfileURL: "https://raw.githubusercontent.com/devfile-resources/multi-components-with-no-kubecomps/main/devfile-sample-java-springboot-basic/.devfile/.devfile.yaml", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("does not contain a valid outerloop definition")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with repo that has devfile but no Dockerfile", func() { - It("Should successfully detect a devfile and match the proper Dockerfile for it", func() { - ctx := context.Background() - - queryName := "nodejs-no-dockerfile" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/node-express-hello-devfile-no-dockerfile", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("node")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/Dockerfile")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query for spring boot repository with multiple components detected, one at root", func() { - It("Should only return one component, a spring boot devfile", func() { - ctx := context.Background() - - queryName := "spring-boot-root-component" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/todo-spring-boot/", - Revision: "main", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Revision).Should(ContainSubstring("main")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query for nodejs repository with detectable port", func() { - It("Should only return one component, with target port set", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "devfile-sample-nodejs-basic" + "26" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/single-component-port-detected", - Revision: "main", - Context: "nodejs", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("nodejs")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("nodejs")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Revision).Should(ContainSubstring("main")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.TargetPort).Should(Equal(8080)) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query for Dockerfile component", func() { - It("Should only return one component, with target port set", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "dockerfile-node-sample" + "26" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/node-sample-dockerfile", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("node-sample")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("Dockerfile")) - Expect(devfileDesc.ComponentStub.TargetPort).Should(Equal(5005)) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with Dockerfile under other common locations", func() { - It("Should return Dockerfile under common sub folder", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "27" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-component-dockerfile-deep", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the right status is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the right branch is set - for _, component := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(component.ComponentStub.Source.GitSource.Revision).Should(Equal("main")) - } - - // Make sure the components are detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(3)) // mocked, not accurate. check unit test for accurate detection that uses the alizer client instead of the mock client. - for _, componentDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(componentDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with multicomponent repo with local Dockerfiles", func() { - It("Should return successfully and properly set the DockerfileURL", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "quality-dashboard" + "28" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/quality-dashboard", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(2)) - - for dockerFileName, dockerFileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(dockerFileName).Should(Or(ContainSubstring("backend-quality-dashboard"), ContainSubstring("frontend-quality-dashboard"))) - Expect(dockerFileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("Dockerfile")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with multi comp repo with no devfiles and context path set", func() { - It("Should successfully get the correct devfiles and Dockerfiles", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "multicontext" + "29" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none-path", - Context: "context", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(2)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(Or(ContainSubstring("java-springboot"), ContainSubstring("python"))) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(BeElementOf([]string{"context/devfile-sample-java-springboot-basic", "context/devfile-sample-python-basic"})) - if strings.Contains(devfileName, "java-springboot") { - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile")) - } else if strings.Contains(devfileName, "python") { - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile")) - } - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with repo that cannot find and dockerfile or devfile match", func() { - It("Should successfully return the CDQ", func() { - ctx := context.Background() - - queryName := "repo-no-dockerfile-or-devfile" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/empty", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(0)) - - // Make sure the right status is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished, no components detected")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query for invalid outerloop devfile but valid Dockerfile component", func() { - It("Should return CDQ success status", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "no-outerloop-with-dockerfile" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/no-outerloop-python", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("python")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("Dockerfile")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Create Component Detection Query with public git repo + invalid mock token", func() { - It("Should successfully detect the component from the repo", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "30" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - StringData: map[string]string{ - "password": "invalid-mock-token", - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - Annotations: map[string]string{ - "runCDQAnalysisLocal": "true", - }, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, // using an actual public repo here for testing a invalid token case - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // index is 1 because of CDQ status condition Processing - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("failed to retrieve https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - // test CDQ job - springDevfileContext := ` -schemaVersion: 2.2.0 -metadata: - name: java-springboot - version: 1.2.1 - projectType: springboot - provider: Red Hat - language: Java -` - - pythonDevfileContext := ` -schemaVersion: 2.2.0 -metadata: - name: python - version: 1.0.1 - projectType: Python - provider: Red Hat - language: Python -` - - nodeJSDevfileContext := ` -schemaVersion: 2.2.0 -metadata: - name: nodejs - version: 2.1.1 - projectType: Node.js - provider: Red Hat - language: JavaScript -` - - Context("Run CDQ Job - Create Component Detection Query with URL set", func() { - It("Should successfully detect a devfile", func() { - ctx := context.Background() - beforeImportGitRepoTotalReqs := testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) - beforeImportGitRepoSucceeded := testutil.ToFloat64(metrics.ImportGitRepoSucceeded) - beforeImportGitRepoFailed := testutil.ToFloat64(metrics.ImportGitRepoFailed) - - queryName := HASCompDetQuery + "-job1" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, - Revision: "main", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - devfilesURLMap["./"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml" - dockerfileContextMap["./"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile" - devfilesMap["./"] = []byte(springDevfileContext) - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Revision).Should(ContainSubstring("main")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - - Expect(testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) > beforeImportGitRepoTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoSucceeded) > beforeImportGitRepoSucceeded).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoFailed) == beforeImportGitRepoFailed).To(BeTrue()) - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with multi comp repo with no devfiles", func() { - It("Should successfully get the correct devfiles and dockerfiles", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job2" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - - devfilesURLMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml" - devfilesURLMap["devfile-sample-python-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml" - dockerfileContextMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile" - dockerfileContextMap["devfile-sample-python-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile" - devfilesMap["devfile-sample-java-springboot-basic"] = []byte(springDevfileContext) - devfilesMap["devfile-sample-python-basic"] = []byte(pythonDevfileContext) - - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(2)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(Or(ContainSubstring("java-springboot"), ContainSubstring("python"))) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(BeElementOf([]string{"devfile-sample-java-springboot-basic", "devfile-sample-python-basic"})) - if strings.Contains(devfileName, "java-springboot") { - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile")) - } else if strings.Contains(devfileName, "python") { - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile")) - } - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with a non component devfile repo", func() { - It("Should complete without any devfile detected", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job3" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/octocat/Hello-World", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with multi component repo that has no devfile", func() { - It("Should match a devfile with alizer if it can be a component", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job4" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - - devfilesURLMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml" - devfilesURLMap["devfile-sample-python-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/devfile.yaml" - dockerfileContextMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile" - dockerfileContextMap["devfile-sample-python-basic"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile" - devfilesMap["devfile-sample-java-springboot-basic"] = []byte(springDevfileContext) - devfilesMap["devfile-sample-python-basic"] = []byte(pythonDevfileContext) - - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(2)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(Or(ContainSubstring("java-springboot"), ContainSubstring("python"))) - Expect(devfileDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(BeElementOf([]string{"devfile-sample-java-springboot-basic", "devfile-sample-python-basic"})) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).ShouldNot(BeEmpty()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with repo that has no devfile", func() { - It("Should match a devfile with alizer if it can be a component", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job5" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/devfile-sample-java-springboot-basic-no-devfile", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - - devfilesURLMap["./"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml" - dockerfileContextMap["./"] = "https://raw.githubusercontent.com/devfile-resources/devfile-sample-java-springboot-basic-no-devfile/main/docker/Dockerfile" - devfilesMap["./"] = []byte(springDevfileContext) - - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect([]string{devfileName}).Should(ContainElement(ContainSubstring("devfile-sample-java-springboot-basic-no-devfile"))) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(BeElementOf([]string{"./"})) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-resources/devfile-sample-java-springboot-basic-no-devfile/main/docker/Dockerfile")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with private multicomponent Github repo", func() { - It("Should err out due to authentication required error", func() { - ctx := context.Background() - - beforeImportGitRepoTotalReqs := testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) - beforeImportGitRepoSucceeded := testutil.ToFloat64(metrics.ImportGitRepoSucceeded) - beforeImportGitRepoFailed := testutil.ToFloat64(metrics.ImportGitRepoFailed) - - queryName := HASCompDetQuery + "-job6" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/private-repo-test", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - errorMap := make(map[string]string) - errorMap["InternalError"] = "some internal system error" - - errorMapbytes, _ := json.Marshal(errorMap) - configMapBinaryData["errorMap"] = errorMapbytes - - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 0 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("internal error: some internal system error")) - - Expect(testutil.ToFloat64(metrics.ImportGitRepoTotalReqs) > beforeImportGitRepoTotalReqs).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoSucceeded) == beforeImportGitRepoSucceeded).To(BeTrue()) - Expect(testutil.ToFloat64(metrics.ImportGitRepoFailed) > beforeImportGitRepoFailed).To(BeTrue()) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - // Private repo tests - Context("Run CDQ Job - Create Component Detection Query with private git repo + invalid token", func() { - It("Should err out due to invalid token", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job7" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - StringData: map[string]string{ - "password": "fake-token", - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/private-repo-test", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - errorMap := make(map[string]string) - errorMap["InternalError"] = "failed to clone the repo" - - errorMapbytes, _ := json.Marshal(errorMap) - configMapBinaryData["errorMap"] = errorMapbytes - - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 0 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // index is 1 because of CDQ status condition Processing - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery failed: internal error: failed to clone the repo")) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with private git repo + valid mock token", func() { - It("Should successfully detect the component from the repo", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job8" - - // Create a git secret - tokenSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - StringData: map[string]string{ - "password": "valid-mock-token", - }, - } - - Expect(k8sClient.Create(ctx, tokenSecret)).Should(Succeed()) - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - URL: SampleRepoLink, // using an actual public repo here for testing a valid token case - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - devfilesURLMap["./"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml" - dockerfileContextMap["./"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile" - devfilesMap["./"] = []byte(springDevfileContext) - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["revision"] = []byte("main") - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("spring")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/devfile.yaml")) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with no secret", func() { - It("Should error out since specified secret does not exist", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job10" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - Secret: queryName, - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/test-repo/testrepo", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - errorMap := make(map[string]string) - errorMap["InternalError"] = fmt.Sprintf("Secret %q not found", queryName) - - errorMapbytes, _ := json.Marshal(errorMap) - configMapBinaryData["errorMap"] = errorMapbytes - - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 0 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring(fmt.Sprintf("ComponentDetectionQuery failed: Secret %q not found", queryName))) - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with URL set to repo with invalid devfile", func() { - It("Should detect a devfile but return an error", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job11" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/test-bad-devfile", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - errorMap := make(map[string]string) - errorMap["InternalError"] = "failed to decode devfile json: json: cannot unmarshal string into Go value of type map[string]" - - errorMapbytes, _ := json.Marshal(errorMap) - configMapBinaryData["errorMap"] = errorMapbytes - - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 0 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure that the proper error condition is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Status).Should(Equal(metav1.ConditionFalse)) - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("internal error: failed to decode devfile json: json: cannot unmarshal string into Go value of type map[string]")) - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - A Requeued ComponentDetectionQuery", func() { - It("Should delete itself", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job12" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/fake-sample", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - errorMap := make(map[string]string) - errorMap["AuthenticationFailed"] = "authentication failed" - - errorMapbytes, _ := json.Marshal(errorMap) - configMapBinaryData["errorMap"] = errorMapbytes - - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 0 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure no component was detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(0)) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("authentication failed")) - - // Trigger a requeue by updating the resource - createdHasCompDetectionQuery.Spec.GitSource.URL = SampleRepoLink - Expect(k8sClient.Update(ctx, createdHasCompDetectionQuery)).Should(Succeed()) - - // Validate that the resource has been deleted - hasCompDetQueryLookupKey = types.NamespacedName{Name: queryName, Namespace: HASNamespace} - deletedCompDetQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - deletedCDQJob := &batchv1.Job{} - deletedConfigMap := &corev1.ConfigMap{} - Eventually(func() bool { - err := k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, deletedCompDetQuery) - return err != nil - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - err := k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, deletedCDQJob) - return err != nil - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - err := k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, deletedConfigMap) - return err != nil - }, timeout, interval).Should(BeTrue()) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with multi component repo that has no devfile or dockerfile", func() { - It("Should attempt to match a devfile or dockerfile for every component", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job13" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-dockerfile", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - - devfilesURLMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/devfile.yaml" - dockerfileContextMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/docker/Dockerfile" - devfilesMap["devfile-sample-java-springboot-basic"] = []byte(springDevfileContext) - - devfilesURLMap["devfile-sample-nodejs-basic"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-nodejs-basic/devfile.yaml" - dockerfileContextMap["devfile-sample-nodejs-basic"] = "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/Dockerfile" - devfilesMap["devfile-sample-nodejs-basic"] = []byte(nodeJSDevfileContext) - - devfilesURLMap["devfile-sample-python-basic"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-python-basic/devfile.yaml" - dockerfileContextMap["devfile-sample-python-basic"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-python-basic/Dockerfile" - devfilesMap["devfile-sample-python-basic"] = []byte(pythonDevfileContext) - - devfilesURLMap["python-src-none"] = "https://registry.devfile.io/devfiles/python-basic" - dockerfileContextMap["python-src-none"] = "https://raw.githubusercontent.com/devfile-samples/devfile-sample-python-basic/main/docker/Dockerfile" - devfilesMap["python-src-none"] = []byte(pythonDevfileContext) - - dockerfileContextMap["python-src-docker"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/python-src-docker/Dockerfile" - - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the right status is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the devfiles are detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(5)) // mocked, not accurate. check unit test for accurate detection that uses the alizer client instead of the mock client. - for _, componentDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(componentDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - - Context("Run CDQ Job - Create Component Detection Query with a dockerfile repo", func() { - It("Should return successfully", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job14" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/python-src-docker", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - dockerfileContextMap := make(map[string]string) - - dockerfileContextMap["./"] = "https://raw.githubusercontent.com/devfile-resources/python-src-docker/main/Dockerfile" - - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 0 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout20s, interval).Should(BeTrue()) - - // Make sure the right status is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the devfiles are detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - for _, componentDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(componentDesc.ComponentStub.Source.GitSource).ShouldNot(BeNil()) - Expect(componentDesc.ComponentStub.Source.GitSource.DockerfileURL).ShouldNot(BeEmpty()) - Expect(componentDesc.ComponentStub.Source.GitSource.DockerfileURL).Should(Equal("https://raw.githubusercontent.com/devfile-resources/python-src-docker/main/Dockerfile")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query with context provided", func() { - It("Should successfully get the devfiles", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job15" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/multi-components-none", - Context: "devfile-sample-java-springboot-basic", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - - devfilesURLMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/devfile.yaml" - dockerfileContextMap["devfile-sample-java-springboot-basic"] = "https://raw.githubusercontent.com/devfile-resources/multi-components-dockerfile/main/devfile-sample-java-springboot-basic/docker/Dockerfile" - devfilesMap["devfile-sample-java-springboot-basic"] = []byte(springDevfileContext) - - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("java-springboot")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("devfile-sample-java-springboot-basic")) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - }) - }) - - Context("Run CDQ Job - Create Component Detection Query for nodejs repository with detectable port", func() { - It("Should only return one component, with target port set", func() { - ctx := context.Background() - - queryName := HASCompDetQuery + "-job16" - - hasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "ComponentDetectionQuery", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-resources/single-component-port-detected", - Revision: "main", - Context: "nodejs", - }, - }, - } - - Expect(k8sClient.Create(ctx, hasCompDetectionQuery)).Should(Succeed()) - - configMapBinaryData := make(map[string][]byte) - devfilesMap := make(map[string][]byte) - devfilesURLMap := make(map[string]string) - dockerfileContextMap := make(map[string]string) - componentPortsMap := make(map[string][]int) - - devfilesURLMap["./"] = "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml" - dockerfileContextMap["./"] = "https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/docker/Dockerfile" - devfilesMap["./"] = []byte(nodeJSDevfileContext) - componentPortsMap["./"] = []int{8080} - - devfilesMapbytes, _ := json.Marshal(devfilesMap) - devfilesURLMapbytes, _ := json.Marshal(devfilesURLMap) - dockerfileContextMapbytes, _ := json.Marshal(dockerfileContextMap) - componentPortsMapbytes, _ := json.Marshal(componentPortsMap) - - configMapBinaryData["devfilesMap"] = devfilesMapbytes - configMapBinaryData["devfilesURLMap"] = devfilesURLMapbytes - configMapBinaryData["dockerfileContextMap"] = dockerfileContextMapbytes - configMapBinaryData["componentPortsMap"] = componentPortsMapbytes - cdqConfigMap := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: queryName, - Namespace: HASNamespace, - }, - BinaryData: configMapBinaryData, - } - Expect(k8sClient.Create(ctx, &cdqConfigMap)).Should(Succeed()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - hasCompDetQueryLookupKey := types.NamespacedName{Name: queryName, Namespace: HASNamespace} - createdHasCompDetectionQuery := &appstudiov1alpha1.ComponentDetectionQuery{} - createdJob := &batchv1.Job{} - createdConfigMap := &corev1.ConfigMap{} - // The job won't be actually completed, as the container image won't be pulled - // check for the object to ensure the job has been created - Eventually(func() bool { - k8sClient.Get(context.Background(), types.NamespacedName{Name: queryName + "-job", Namespace: HASNamespace}, createdJob) - return createdJob != nil - }, timeout, interval).Should(BeTrue()) - - // Look up the has app resource that was created. - // num(conditions) may still be < 1 on the first try, so retry until at least _some_ condition is set - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdConfigMap) - return len(createdConfigMap.BinaryData) > 1 - }, timeout, interval).Should(BeTrue()) - Eventually(func() bool { - k8sClient.Get(context.Background(), hasCompDetQueryLookupKey, createdHasCompDetectionQuery) - return len(createdHasCompDetectionQuery.Status.Conditions) > 1 - }, timeout, interval).Should(BeTrue()) - - // Make sure the right err is set - Expect(createdHasCompDetectionQuery.Status.Conditions[1].Message).Should(ContainSubstring("ComponentDetectionQuery has successfully finished")) - - // Make sure the a devfile is detected - Expect(len(createdHasCompDetectionQuery.Status.ComponentDetected)).Should(Equal(1)) - - for devfileName, devfileDesc := range createdHasCompDetectionQuery.Status.ComponentDetected { - Expect(devfileName).Should(ContainSubstring("single-component-port-detected")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Context).Should(ContainSubstring("./")) - Expect(devfileDesc.ComponentStub.Source.GitSource.Revision).Should(ContainSubstring("main")) - Expect(devfileDesc.ComponentStub.Source.GitSource.DevfileURL).Should(Equal("https://raw.githubusercontent.com/nodeshift-starters/devfile-sample/main/devfile.yaml")) - Expect(devfileDesc.ComponentStub.TargetPort).Should(Equal(8080)) - Expect(devfileDesc.DevfileFound).Should(BeTrue()) - } - - // Delete the specified Detection Query resource - deleteCompDetQueryCR(hasCompDetQueryLookupKey) - - }) - }) - }) - - }) - -}) - -// deleteCompDetQueryCR deletes the specified Comp Detection Query resource and verifies it was properly deleted -func deleteCompDetQueryCR(hasCompDetectionQueryLookupKey types.NamespacedName) { - // Delete - Eventually(func() error { - f := &appstudiov1alpha1.ComponentDetectionQuery{} - k8sClient.Get(context.Background(), hasCompDetectionQueryLookupKey, f) - return k8sClient.Delete(context.Background(), f) - }, timeout, interval).Should(Succeed()) - - // Wait for delete to finish - Eventually(func() error { - f := &appstudiov1alpha1.ComponentDetectionQuery{} - return k8sClient.Get(context.Background(), hasCompDetectionQueryLookupKey, f) - }, timeout, interval).ShouldNot(Succeed()) -} diff --git a/controllers/errors.go b/controllers/errors.go deleted file mode 100644 index 387cfcd6b..000000000 --- a/controllers/errors.go +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright 2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -import ( - "fmt" - - "github.com/redhat-developer/gitops-generator/pkg/util" -) - -type NotSupported struct { - err error -} - -func (e *NotSupported) Error() string { - return util.SanitizeErrorMessage(fmt.Errorf("not supported error: %v", e.err)).Error() -} diff --git a/controllers/mapper.go b/controllers/mapper.go deleted file mode 100644 index 5cd385973..000000000 --- a/controllers/mapper.go +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -import ( - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -// MapComponentToApplication returns an event handler that will convert events on a Component CR to events on its parent Application -func MapComponentToApplication() func(object client.Object) []reconcile.Request { - return func(obj client.Object) []reconcile.Request { - component := obj.(*appstudiov1alpha1.Component) - - if component != nil && component.Spec.Application != "" { - return []reconcile.Request{ - { - NamespacedName: types.NamespacedName{ - Namespace: component.Namespace, - Name: component.Spec.Application, - }, - }, - } - } - // the obj was not in the namespace or it did not have the required Application. - return []reconcile.Request{} - } -} diff --git a/controllers/mapper_test.go b/controllers/mapper_test.go deleted file mode 100644 index 90c82c1fe..000000000 --- a/controllers/mapper_test.go +++ /dev/null @@ -1,139 +0,0 @@ -// -// Copyright 2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -import ( - "context" - "testing" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -func TestMapApplicationToComponent(t *testing.T) { - - const ( - HASAppName = "test-app" - HASCompName = "test-comp" - Namespace = "default" - DisplayName = "an application" - ComponentName = "backend" - SampleRepoLink = "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" - ) - - applicationName := HASAppName + "1" - componentName := HASCompName + "1" - componentName2 := HASCompName + "2" - - componentOne := appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Namespace: Namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: componentName, - Application: applicationName, - }, - } - componentTwo := appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName2, - Namespace: Namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: componentName2, - }, - } - - //fakeClient := NewFakeClient(t, componentOne, applicationOne) - - t.Run("should return component's parent application", func(t *testing.T) { - // when - requests := MapComponentToApplication()(&componentOne) - - // then - require.Len(t, requests, 1) // binding4 is not returned because binding4 does not have a label matching the staging env - assert.Contains(t, requests, newRequest(applicationName)) - }) - - t.Run("should return no Application requests when Component app name is nil", func(t *testing.T) { - // when - requests := MapComponentToApplication()(&componentTwo) - - // then - require.Empty(t, requests) - }) -} - -func newRequest(name string) reconcile.Request { - return reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: name, - }, - } -} - -// NewFakeClient creates a fake K8s client with ability to override specific List function -// Adapted from https://github.com/codeready-toolchain/toolchain-common/blob/master/pkg/test/client.go#L19 -// fake client pkg by default does not allow mocking or injecting errors, -// see https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/client/fake/doc.go#L31 -func NewFakeClient(t *testing.T, initObjs ...runtime.Object) *FakeClient { - s := scheme.Scheme - err := appstudiov1alpha1.AddToScheme(s) - require.NoError(t, err) - cl := fake.NewClientBuilder(). - WithScheme(s). - WithRuntimeObjects(initObjs...). - Build() - return &FakeClient{Client: cl} -} - -type FakeClient struct { - client.Client - MockList func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error - MockGet func(ctx context.Context, key types.NamespacedName, obj client.Object, opts ...client.GetOption) error -} - -func (c *FakeClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - if c.MockList != nil { - return c.MockList(ctx, list, opts...) - } - return c.Client.List(ctx, list, opts...) -} - -func (c *FakeClient) Get(ctx context.Context, key types.NamespacedName, obj client.Object, opts ...client.GetOption) error { - if c.MockGet != nil { - return c.MockGet(ctx, key, obj) - } - return c.Client.Get(ctx, key, obj) -} diff --git a/controllers/start_test_env.go b/controllers/start_test_env.go deleted file mode 100644 index 1f860199a..000000000 --- a/controllers/start_test_env.go +++ /dev/null @@ -1,146 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -import ( - "context" - "go/build" - "os" - "path/filepath" - - spiapi "github.com/redhat-appstudio/service-provider-integration-operator/api/v1beta1" - - ginkgo "github.com/onsi/ginkgo" - gomega "github.com/onsi/gomega" - "k8s.io/client-go/kubernetes/scheme" - - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - ctrl "sigs.k8s.io/controller-runtime" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - routev1 "github.com/openshift/api/route/v1" - - github "github.com/redhat-appstudio/application-service/pkg/github" - "github.com/redhat-appstudio/application-service/pkg/spi" - "github.com/redhat-appstudio/application-service/pkg/util/ioutils" - - devfileParserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util" -) - -var ( - k8sClient client.Client // You'll be using this client in your tests. - testEnv *envtest.Environment - ctx context.Context - cancel context.CancelFunc -) - -func SetupTestEnv() (client.Client, *envtest.Environment, context.Context, context.CancelFunc) { - logf.SetLogger(zap.New(zap.WriteTo(ginkgo.GinkgoWriter), zap.UseDevMode(true))) - - ctx, cancel = context.WithCancel(context.TODO()) - applicationAPIDepVersion := "v0.0.0-20231016183051-2dde965fce17" - spiAPIDepVersion := "v0.2023.22-0.20230713080056-eae17aa8c172" - - ginkgo.By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{ - filepath.Join("..", "hack", "routecrd"), - filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "redhat-appstudio", "application-api@"+applicationAPIDepVersion, "manifests"), - filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "redhat-appstudio", "service-provider-integration-operator@"+spiAPIDepVersion, "config", "crd", "bases"), - }, - ErrorIfCRDPathMissing: true, - } - - cfg, err := testEnv.Start() - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - gomega.Expect(cfg).NotTo(gomega.BeNil()) - - err = appstudiov1alpha1.AddToScheme(scheme.Scheme) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - err = routev1.AddToScheme(scheme.Scheme) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - err = spiapi.AddToScheme(scheme.Scheme) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - gomega.Expect(k8sClient).NotTo(gomega.BeNil()) - - k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: scheme.Scheme, - }) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - mockGhTokenClient := github.MockGitHubTokenClient{} - mockDevfileUtilsClient := devfileParserUtil.NewMockDevfileUtilsClient() - - // Retrieve the option to specify a cdq-analysis image - cdqAnalysisImage := os.Getenv("CDQ_ANALYSIS_IMAGE") - if cdqAnalysisImage == "" { - cdqAnalysisImage = "quay.io/redhat-appstudio/cdq-analysis:next" - } - - // To Do: Set up reconcilers for the other controllers - err = (&ApplicationReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("Application"), - }).SetupWithManager(ctx, k8sManager) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - err = (&ComponentReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("Component"), - AppFS: ioutils.NewMemoryFilesystem(), - SPIClient: spi.MockSPIClient{ - K8sClient: k8sClient, - }, - GitHubTokenClient: mockGhTokenClient, - DevfileUtilsClient: &mockDevfileUtilsClient, - }).SetupWithManager(ctx, k8sManager) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - err = (&ComponentDetectionQueryReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("ComponentDetectionQuery"), - GitHubTokenClient: mockGhTokenClient, - DevfileRegistryURL: cdqanalysis.DevfileStageRegistryEndpoint, // Use the staging devfile registry for tests - AppFS: ioutils.NewMemoryFilesystem(), - Config: cfg, - RunKubernetesJob: true, - CdqAnalysisImage: cdqAnalysisImage, - CDQUtil: cdqanalysis.NewCDQUtilMockClient(), - }).SetupWithManager(ctx, k8sManager) - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - - go func() { - defer ginkgo.GinkgoRecover() - err = k8sManager.Start(ctx) - gomega.Expect(err).ToNot(gomega.HaveOccurred(), "failed to run manager") - }() - return k8sClient, testEnv, ctx, cancel -} diff --git a/controllers/suite_test.go b/controllers/suite_test.go deleted file mode 100644 index 44e94bf29..000000000 --- a/controllers/suite_test.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - //+kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -func TestAPIs(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, - "Controller Suite") -} - -var _ = BeforeSuite(func() { - SetupTestEnv() -}, 60) - -var _ = AfterSuite(func() { - cancel() - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/controllers/update.go b/controllers/update.go deleted file mode 100644 index 686464c0d..000000000 --- a/controllers/update.go +++ /dev/null @@ -1,689 +0,0 @@ -/* -Copyright 2021-2023 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controllers - -import ( - "context" - "fmt" - "regexp" - "strings" - "unicode" - - "github.com/devfile/library/v2/pkg/devfile/parser" - - "github.com/brianvoe/gofakeit/v6" - devfileAPIV1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/api/v2/pkg/attributes" - data "github.com/devfile/library/v2/pkg/devfile/parser/data" - "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - "github.com/go-logr/logr" - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - devfile "github.com/redhat-appstudio/application-service/pkg/devfile" - "github.com/redhat-appstudio/application-service/pkg/metrics" - "github.com/redhat-appstudio/application-service/pkg/util" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -func (r *ComponentReconciler) updateComponentDevfileModel(req ctrl.Request, hasCompDevfileData data.DevfileData, component appstudiov1alpha1.Component) error { - log := r.Log.WithValues("controllerKind", "Component").WithValues("name", req.NamespacedName.Name).WithValues("namespace", req.NamespacedName.Namespace) - - // If DockerfileURL is set and the devfile contains references to a Dockerfile then update the devfile - source := component.Spec.Source - var err error - if source.GitSource != nil && source.GitSource.DockerfileURL != "" { - hasCompDevfileData, err = devfile.UpdateLocalDockerfileURItoAbsolute(hasCompDevfileData, source.GitSource.DockerfileURL) - if err != nil { - return fmt.Errorf("unable to convert local Dockerfile URIs to absolute in Component devfile %v", req.NamespacedName) - } - } - - kubernetesComponents, err := hasCompDevfileData.GetComponents(common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: devfileAPIV1.KubernetesComponentType, - }, - }) - if err != nil { - return err - } - - for _, kubernetesComponent := range kubernetesComponents { - compUpdateRequired := false - // Update for Replica - currentReplica := 1 // default value is 1 - keyFound := true - - if len(kubernetesComponent.Attributes) == 0 { - kubernetesComponent.Attributes = attributes.Attributes{} - keyFound = false - } else { - var err error - currentReplica = int(kubernetesComponent.Attributes.GetNumber(devfile.ReplicaKey, &err)) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return &devfile.DevfileAttributeParse{Key: devfile.ReplicaKey, Err: err} - } else { - keyFound = false - currentReplica = 1 //if an error is raised, it'll set currentReplica to 0 so we need to reset back to the default - } - } - } - - numReplicas := 1 //default value - if component.Spec.Replicas != nil { - numReplicas = util.GetIntValue(component.Spec.Replicas) - // Component.Spec.Replicas will override any other settings. - // We will write the attribute if it doesn't exist for the initial creation case when comp.spec.replica is 1 - if currentReplica != numReplicas || !keyFound { - log.Info(fmt.Sprintf("setting devfile component %s attribute %s to %v", kubernetesComponent.Name, devfile.ReplicaKey, numReplicas)) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutInteger(devfile.ReplicaKey, numReplicas) - compUpdateRequired = true - } - } else { - //check to see if we have an inlined deployment - isDeployReplicaSet := false - inlined := kubernetesComponent.Kubernetes.Inlined - if inlined != "" { - log.Info(fmt.Sprintf("reading the kubernetes inline from component %s", component.Name)) - src := parser.YamlSrc{ - Data: []byte(inlined), - } - - values, err := parser.ReadKubernetesYaml(src, nil, nil) - if err != nil { - return err - } - - resources, err := parser.ParseKubernetesYaml(values) - if err != nil { - return err - } - - if len(resources.Deployments) > 0 { - replica := resources.Deployments[0].Spec.Replicas - if replica != nil { - isDeployReplicaSet = true - //remove the deployment/replicas attribute which can be left behind if we go from a set value to an unset value - if kubernetesComponent.Attributes.Exists(devfile.ReplicaKey) { - var err error - num := int(kubernetesComponent.Attributes.GetNumber(devfile.ReplicaKey, &err)) - - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return &devfile.DevfileAttributeParse{Key: devfile.ReplicaKey, Err: err} - } else { - log.Info(fmt.Sprintf("deleting %s attribute with value %v", devfile.ReplicaKey, num)) - delete(kubernetesComponent.Attributes, devfile.ReplicaKey) - } - } - - } - } - } - } - - //set the default if replicas is unset in the component and deployment spec. - if !isDeployReplicaSet { - log.Info(fmt.Sprintf("setting devfile component %s attribute component.Spec.Replicas to %v", kubernetesComponent.Name, 1)) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutInteger(devfile.ReplicaKey, 1) - compUpdateRequired = true - } - } - - // Update for Port - var err error - currentPort := int(kubernetesComponent.Attributes.GetNumber(devfile.ContainerImagePortKey, &err)) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return &devfile.DevfileAttributeParse{Key: devfile.ContainerImagePortKey, Err: err} - } - } - if currentPort != component.Spec.TargetPort { - log.Info(fmt.Sprintf("setting devfile component %s attribute component.Spec.TargetPort %v", kubernetesComponent.Name, component.Spec.TargetPort)) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutInteger(devfile.ContainerImagePortKey, component.Spec.TargetPort) - compUpdateRequired = true - } - - // Update for Route - if component.Spec.Route != "" { - log.Info(fmt.Sprintf("setting devfile component %s attribute component.Spec.Route %s", kubernetesComponent.Name, component.Spec.Route)) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutString(devfile.RouteKey, component.Spec.Route) - compUpdateRequired = true - } - - // Update for Env - currentENV := []corev1.EnvVar{} - err = kubernetesComponent.Attributes.GetInto(devfile.ContainerENVKey, ¤tENV) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return &devfile.DevfileAttributeParse{Key: devfile.ContainerENVKey, Err: err} - } - } - for _, env := range component.Spec.Env { - if env.ValueFrom != nil { - return &NotSupported{err: fmt.Errorf("env.ValueFrom is not supported at the moment, use env.value")} - } - - name := env.Name - value := env.Value - isPresent := false - - for i, devfileEnv := range currentENV { - if devfileEnv.Name == name { - isPresent = true - log.Info(fmt.Sprintf("setting devfileComponent %s env %s", kubernetesComponent.Name, devfileEnv.Name)) - devfileEnv.Value = value - currentENV[i] = devfileEnv - } - } - - if !isPresent { - log.Info(fmt.Sprintf("appending to devfile component %s env %s", kubernetesComponent.Name, name)) - currentENV = append(currentENV, env) - } - var err error - kubernetesComponent.Attributes = kubernetesComponent.Attributes.FromMap(map[string]interface{}{devfile.ContainerENVKey: currentENV}, &err) - if err != nil { - return &devfile.DevfileAttributeParse{Key: devfile.ContainerENVKey, Err: err} - } - compUpdateRequired = true - } - - // Update for limits - limits := component.Spec.Resources.Limits - if len(limits) > 0 { - // CPU Limit - resourceCPULimit := limits[corev1.ResourceCPU] - if resourceCPULimit.String() != "" { - log.Info(fmt.Sprintf("setting devfile component %s attribute cpu limit to %s", kubernetesComponent.Name, resourceCPULimit.String())) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutString(devfile.CpuLimitKey, resourceCPULimit.String()) - compUpdateRequired = true - } - - // Memory Limit - resourceMemoryLimit := limits[corev1.ResourceMemory] - if resourceMemoryLimit.String() != "" { - log.Info(fmt.Sprintf("setting devfile component %s attribute memory limit to %s", kubernetesComponent.Name, resourceMemoryLimit.String())) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutString(devfile.MemoryLimitKey, resourceMemoryLimit.String()) - compUpdateRequired = true - } - - // Storage Limit - resourceStorageLimit := limits[corev1.ResourceStorage] - if resourceStorageLimit.String() != "" { - log.Info(fmt.Sprintf("setting devfile component %s attribute storage limit to %s", kubernetesComponent.Name, resourceStorageLimit.String())) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutString(devfile.StorageLimitKey, resourceStorageLimit.String()) - compUpdateRequired = true - } - } - - // Update for requests - requests := component.Spec.Resources.Requests - if len(requests) > 0 { - // CPU Request - resourceCPURequest := requests[corev1.ResourceCPU] - if len(kubernetesComponent.Attributes) == 0 { - kubernetesComponent.Attributes = attributes.Attributes{} - } - if resourceCPURequest.String() != "" { - log.Info(fmt.Sprintf("updating devfile component %s attribute cpu request to %s", kubernetesComponent.Name, resourceCPURequest.String())) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutString(devfile.CpuRequestKey, resourceCPURequest.String()) - compUpdateRequired = true - } - - // Memory Request - resourceMemoryRequest := requests[corev1.ResourceMemory] - if resourceMemoryRequest.String() != "" { - log.Info(fmt.Sprintf("updating devfile component %s attribute memory request to %s", kubernetesComponent.Name, resourceMemoryRequest.String())) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutString(devfile.MemoryRequestKey, resourceMemoryRequest.String()) - compUpdateRequired = true - } - - // Storage Request - resourceStorageRequest := requests[corev1.ResourceStorage] - if resourceStorageRequest.String() != "" { - log.Info(fmt.Sprintf("updating devfile component %s attribute storage request to %s", kubernetesComponent.Name, resourceStorageRequest.String())) - kubernetesComponent.Attributes = kubernetesComponent.Attributes.PutString(devfile.StorageRequestKey, resourceStorageRequest.String()) - compUpdateRequired = true - } - } - - if compUpdateRequired { - // Update the devfileComponent once it has been updated with the Component data - log.Info(fmt.Sprintf("updating devfile component name %s ...", kubernetesComponent.Name)) - err := hasCompDevfileData.UpdateComponent(kubernetesComponent) - if err != nil { - return err - } - } - } - - return nil -} - -// addComponentsToApplicationDevfileModel updates the Application's devfile model to include all of the -func (r *ApplicationReconciler) addComponentsToApplicationDevfileModel(devSpec *devfileAPIV1.DevWorkspaceTemplateSpec, components []appstudiov1alpha1.Component) error { - - for _, component := range components { - if component.Spec.Source.GitSource != nil { - newProject := devfileAPIV1.Project{ - Name: component.Spec.ComponentName, - ProjectSource: devfileAPIV1.ProjectSource{ - Git: &devfileAPIV1.GitProjectSource{ - GitLikeProjectSource: devfileAPIV1.GitLikeProjectSource{ - Remotes: map[string]string{ - "origin": component.Spec.Source.GitSource.URL, - }, - }, - }, - }, - } - projects := devSpec.Projects - for _, project := range projects { - if project.Name == newProject.Name { - return fmt.Errorf("application already has a component with name %s", newProject.Name) - } - } - devSpec.Projects = append(devSpec.Projects, newProject) - } else if component.Spec.ContainerImage != "" { - var err error - - // Add the image as a top level attribute - devfileAttributes := devSpec.Attributes - if devfileAttributes == nil { - devfileAttributes = attributes.Attributes{} - devSpec.Attributes = devfileAttributes - } - imageAttrString := fmt.Sprintf("containerImage/%s", component.Spec.ComponentName) - componentImage := devfileAttributes.GetString(imageAttrString, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - if componentImage != "" { - return fmt.Errorf("application already has a component with name %s", component.Name) - } - devSpec.Attributes = devfileAttributes.PutString(imageAttrString, component.Spec.ContainerImage) - - } else { - return fmt.Errorf("component source is nil") - } - - } - - return nil -} - -// getAndAddComponentApplicationsToModel retrieves the list of components that belong to the application CR and adds them to the application's devfile model -func (r *ApplicationReconciler) getAndAddComponentApplicationsToModel(log logr.Logger, req reconcile.Request, applicationName string, devSpec *devfileAPIV1.DevWorkspaceTemplateSpec) error { - - // Find all components owned by the application - var components []appstudiov1alpha1.Component - var componentList appstudiov1alpha1.ComponentList - var err error - err = r.Client.List(ctx, &componentList, &client.ListOptions{ - Namespace: req.NamespacedName.Namespace, - }) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to list Components for %v", req.NamespacedName)) - metrics.ApplicationCreationFailed.Inc() - return err - } - - for _, component := range componentList.Items { - if component.Spec.Application == applicationName { - components = append(components, component) - } - } - - // Add the components to the Devfile model - err = r.addComponentsToApplicationDevfileModel(devSpec, components) - if err != nil { - // User error - so increment the "success" metric - since we're tracking only system errors - metrics.ApplicationCreationSucceeded.Inc() - log.Error(err, fmt.Sprintf("Error adding components to devfile for Application %v", req.NamespacedName)) - return err - } - - return nil -} - -func (r *ComponentDetectionQueryReconciler) updateComponentStub(req ctrl.Request, ctx context.Context, componentDetectionQuery *appstudiov1alpha1.ComponentDetectionQuery, devfilesMap map[string][]byte, devfilesURLMap map[string]string, dockerfileContextMap map[string]string, componentPortsMap map[string][]int) error { - - if componentDetectionQuery == nil { - return fmt.Errorf("componentDetectionQuery is nil") - } - - log := r.Log.WithValues("controllerKind", "ComponentDetectionQuery").WithValues("name", req.NamespacedName.Name).WithValues("namespace", req.NamespacedName.Namespace) - - if len(componentDetectionQuery.Status.ComponentDetected) == 0 { - componentDetectionQuery.Status.ComponentDetected = make(appstudiov1alpha1.ComponentDetectionMap) - } - - log.Info(fmt.Sprintf("Devfiles detected: %v", len(devfilesMap))) - - for context, devfileBytes := range devfilesMap { - log.Info(fmt.Sprintf("Currently reading the devfile for context %v", context)) - // Parse the Component Devfile - compDevfileData, err := cdqanalysis.ParseDevfileWithParserArgs(&parser.ParserArgs{Data: devfileBytes}) - if err != nil { - return err - } - - devfileMetadata := compDevfileData.GetMetadata() - devfileKubernetesComponents, err := compDevfileData.GetComponents(common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: devfileAPIV1.KubernetesComponentType, - }, - }) - if err != nil { - return err - } - - gitSource := &appstudiov1alpha1.GitSource{ - Context: context, - URL: componentDetectionQuery.Spec.GitSource.URL, - Revision: componentDetectionQuery.Spec.GitSource.Revision, - DevfileURL: devfilesURLMap[context], - DockerfileURL: dockerfileContextMap[context], - } - componentName := r.getComponentName(log, ctx, req.Namespace, gitSource, componentDetectionQuery.Spec.GenerateComponentName) - - componentStub := appstudiov1alpha1.ComponentSpec{ - ComponentName: componentName, - Application: "insert-application-name", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: gitSource, - }, - }, - } - - if len(componentPortsMap[context]) != 0 { - componentStub.TargetPort = componentPortsMap[context][0] - } - - // Since a devfile can have N container components, we only try to populate the stub with the first Kubernetes component - if len(devfileKubernetesComponents) != 0 { - kubernetesComponentAttribute := devfileKubernetesComponents[0].Attributes - - // Devfile Env - err := kubernetesComponentAttribute.GetInto(devfile.ContainerENVKey, &componentStub.Env) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - - // Devfile Port - if componentStub.TargetPort == 0 { - componentStub.TargetPort = int(kubernetesComponentAttribute.GetNumber(devfile.ContainerImagePortKey, &err)) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - } - - // Devfile Route - componentStub.Route = kubernetesComponentAttribute.GetString(devfile.RouteKey, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - - // Devfile Replica - numReplicas := int(kubernetesComponentAttribute.GetNumber(devfile.ReplicaKey, &err)) - componentStub.Replicas = &numReplicas - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - - // Devfile Limits - if len(componentStub.Resources.Limits) == 0 { - componentStub.Resources.Limits = make(corev1.ResourceList) - } - limits := componentStub.Resources.Limits - - // CPU Limit - cpuLimitString := kubernetesComponentAttribute.GetString(devfile.CpuLimitKey, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - if cpuLimitString != "" { - cpuLimit, err := resource.ParseQuantity(cpuLimitString) - if err != nil { - return err - } - limits[corev1.ResourceCPU] = cpuLimit - } - - // Memory Limit - memoryLimitString := kubernetesComponentAttribute.GetString(devfile.MemoryLimitKey, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - if memoryLimitString != "" { - memoryLimit, err := resource.ParseQuantity(memoryLimitString) - if err != nil { - return err - } - limits[corev1.ResourceMemory] = memoryLimit - } - - // Storage Limit - storageLimitString := kubernetesComponentAttribute.GetString(devfile.StorageLimitKey, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - if storageLimitString != "" { - storageLimit, err := resource.ParseQuantity(storageLimitString) - if err != nil { - return err - } - limits[corev1.ResourceStorage] = storageLimit - } - - // Devfile Request - if len(componentStub.Resources.Requests) == 0 { - componentStub.Resources.Requests = make(corev1.ResourceList) - } - requests := componentStub.Resources.Requests - - // CPU Request - cpuRequestString := kubernetesComponentAttribute.GetString(devfile.CpuRequestKey, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - if cpuRequestString != "" { - cpuRequest, err := resource.ParseQuantity(cpuRequestString) - if err != nil { - return err - } - requests[corev1.ResourceCPU] = cpuRequest - } - - // Memory Request - memoryRequestString := kubernetesComponentAttribute.GetString(devfile.MemoryRequestKey, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - if memoryRequestString != "" { - memoryRequest, err := resource.ParseQuantity(memoryRequestString) - if err != nil { - return err - } - requests[corev1.ResourceMemory] = memoryRequest - } - - // Storage Request - storageRequestString := kubernetesComponentAttribute.GetString(devfile.StorageRequestKey, &err) - if err != nil { - if _, ok := err.(*attributes.KeyNotFoundError); !ok { - return err - } - } - if storageRequestString != "" { - storageRequest, err := resource.ParseQuantity(storageRequestString) - if err != nil { - return err - } - requests[corev1.ResourceStorage] = storageRequest - } - } - - componentDetectionQuery.Status.ComponentDetected[componentName] = appstudiov1alpha1.ComponentDetectionDescription{ - DevfileFound: len(devfilesURLMap[context]) != 0, // if we did not find a devfile URL map for the given context, it means a devfile was not found in the context - Language: devfileMetadata.Language, - ProjectType: devfileMetadata.ProjectType, - ComponentStub: componentStub, - } - - // Once the Dockerfile has been processed, remove it - delete(dockerfileContextMap, context) - } - - log.Info(fmt.Sprintf("Dockerfiles detected: %v", len(dockerfileContextMap))) - - // process the dockefileMap that does not have an associated devfile with it - for context, link := range dockerfileContextMap { - log.Info(fmt.Sprintf("Currently reading the Dockerfile for context %v", context)) - - gitSource := &appstudiov1alpha1.GitSource{ - Context: context, - URL: componentDetectionQuery.Spec.GitSource.URL, - Revision: componentDetectionQuery.Spec.GitSource.Revision, - DockerfileURL: link, - } - componentName := r.getComponentName(log, ctx, req.Namespace, gitSource, componentDetectionQuery.Spec.GenerateComponentName) - - detectComp := appstudiov1alpha1.ComponentDetectionDescription{ - DevfileFound: false, // always false since there is only a Dockerfile present for these contexts - Language: "Dockerfile", - ProjectType: "Dockerfile", - ComponentStub: appstudiov1alpha1.ComponentSpec{ - ComponentName: componentName, - Application: "insert-application-name", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: gitSource, - }, - }, - }, - } - - if len(componentPortsMap[context]) != 0 { - detectComp.ComponentStub.TargetPort = componentPortsMap[context][0] - } - - componentDetectionQuery.Status.ComponentDetected[componentName] = detectComp - - } - - return nil -} - -func (r *ComponentDetectionQueryReconciler) getComponentName(log logr.Logger, ctx context.Context, namespace string, gitSource *appstudiov1alpha1.GitSource, generateComponentName bool) string { - var componentName string - repoUrl := gitSource.URL - - generateCompName := false - if generateComponentName { - generateCompName = generateComponentName - } - - if len(repoUrl) != 0 { - // If the repository URL ends in a forward slash, remove it to avoid issues with parsing the repository name - if string(repoUrl[len(repoUrl)-1]) == "/" { - repoUrl = repoUrl[0 : len(repoUrl)-1] - } - lastElement := repoUrl[strings.LastIndex(repoUrl, "/")+1:] - repoName := strings.Split(lastElement, ".git")[0] - componentName = repoName - context := gitSource.Context - if context != "" && context != "./" && context != "." { - componentName = fmt.Sprintf("%s-%s", context, repoName) - } - } - - // Return a sanitized version of the component name - // If len(componentName) is 0, then it will also handle generating a random name for it. - componentName = sanitizeComponentName(componentName) - if generateCompName { - componentName = fmt.Sprintf("%s-%s", componentName, util.GetRandomString(4, true)) - } else { - compNamespacedName := types.NamespacedName{ - Namespace: namespace, - Name: componentName, - } - // Fetch the Component instance - var tempComp appstudiov1alpha1.Component - err := r.Get(ctx, compNamespacedName, &tempComp) - if err == nil || !errors.IsNotFound(err) { - log.Info(fmt.Sprintf("the component %v already exist, appending random chars at the end...", compNamespacedName)) - componentName = fmt.Sprintf("%s-%s", componentName, util.GetRandomString(4, true)) - } - } - - return componentName -} - -// sanitizeComponentName sanitizes component name with the following requirements: -// - Contain at most 63 characters -// - Contain only lowercase alphanumeric characters or ‘-’ -// - Start with an alphabet character -// - End with an alphanumeric character -// - Must not contain all numeric values -func sanitizeComponentName(name string) string { - exclusive := regexp.MustCompile(`[^a-zA-Z0-9-]`) - // filter out invalid characters - name = exclusive.ReplaceAllString(name, "") - // Fallback: A proper Component name should never be an empty string, but in case it is, generate a random name for it. - if name == "" { - name = gofakeit.Noun() - } - if unicode.IsDigit(rune(name[0])) { - // starts with numeric values, prefix a character - name = fmt.Sprintf("comp-%s", name) - } - name = strings.ToLower(name) - if len(name) > 58 { - name = name[0:58] - } - - return name -} diff --git a/controllers/update_test.go b/controllers/update_test.go deleted file mode 100644 index 51eacf3a6..000000000 --- a/controllers/update_test.go +++ /dev/null @@ -1,2342 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controllers - -import ( - "context" - "strings" - "testing" - - devfileAPIV1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/api/v2/pkg/attributes" - "github.com/devfile/api/v2/pkg/devfile" - v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" - "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - "github.com/prometheus/client_golang/prometheus/testutil" - devfilePkg "github.com/redhat-appstudio/application-service/pkg/devfile" - "github.com/redhat-appstudio/application-service/pkg/metrics" - "github.com/stretchr/testify/assert" - "go.uber.org/zap/zapcore" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/yaml" -) - -var ( - oneReplica = 1 - zeroReplica = 0 - threeReplicas = 3 -) - -var k8sInlined = ` - apiVersion: apps/v1 - kind: Deployment - metadata: - creationTimestamp: null - labels: - maysun: test - name: deploy-sample - spec: - replicas: 3 - selector: {} - strategy: {} - template: - metadata: - creationTimestamp: null - labels: - app.kubernetes.io/instance: component-sample - spec: - containers: - - env: - - name: FOO - value: foo1 - - name: BARBAR - value: bar1 - image: quay.io/redhat-appstudio/user-workload:application-service-system-component-sample - imagePullPolicy: Always - livenessProbe: - httpGet: - path: / - port: 1111 - initialDelaySeconds: 10 - periodSeconds: 10 - name: container-image - ports: - - containerPort: 1111 - readinessProbe: - initialDelaySeconds: 10 - periodSeconds: 10 - tcpSocket: - port: 1111 - resources: - limits: - cpu: "2" - memory: 500Mi - requests: - cpu: 700m - memory: 400Mi - status: {}` - -func TestUpdateApplicationDevfileModel(t *testing.T) { - tests := []struct { - name string - projects []devfileAPIV1.Project - attributes attributes.Attributes - containerImage string - components []appstudiov1alpha1.Component - wantErr bool - }{ - { - name: "Project already present", - projects: []devfileAPIV1.Project{ - { - Name: "duplicate", - }, - }, - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "duplicate", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{}, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Project added successfully", - projects: []devfileAPIV1.Project{ - { - Name: "present", - }, - }, - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "new", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - }, - }, - }, - }, - { - name: "Git source in Component is nil", - projects: []devfileAPIV1.Project{ - { - Name: "present", - }, - }, - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "new", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: nil, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Devfile Projects list is nil", - projects: nil, - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "new", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: nil, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Container image added successfully", - attributes: attributes.Attributes{}.PutString("containerImage/otherComponent", "other-image"), - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "new", - ContainerImage: "an-image", - }, - }, - }, - }, - { - name: "Container image already exists", - attributes: attributes.Attributes{}.PutString("containerImage/new", "an-image"), - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "new", - ContainerImage: "an-image", - }, - }, - }, - wantErr: true, - }, - { - name: "Container image already exists, but invalid entry", - attributes: attributes.Attributes{}.Put("containerImage/new", make(chan error), nil), - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "new", - ContainerImage: "an-image", - }, - }, - }, - wantErr: true, - }, - { - name: "Multiple Projects added successfully", - projects: nil, - components: []appstudiov1alpha1.Component{ - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "compname", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - }, - }, - { - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "compnametwo", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "urltwo", - }, - }, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - devfileData := &v2.DevfileV2{ - Devfile: devfileAPIV1.Devfile{ - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Attributes: tt.attributes, - Projects: tt.projects, - }, - }, - }, - } - r := ApplicationReconciler{} - err := r.addComponentsToApplicationDevfileModel(&devfileData.DevWorkspaceTemplateSpec, tt.components) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if err == nil { - for _, component := range tt.components { - if component.Spec.Source.GitSource != nil { - projects, err := devfileData.GetProjects(common.DevfileOptions{}) - if err != nil { - t.Errorf("got unexpected error: %v", err) - } - matched := false - for _, project := range projects { - projectGitSrc := project.ProjectSource.Git - if project.Name == component.Spec.ComponentName && projectGitSrc != nil && projectGitSrc.Remotes["origin"] == component.Spec.Source.GitSource.URL { - matched = true - } - } - - if !matched { - t.Errorf("unable to find devfile with project: %s", component.Spec.ComponentName) - } - - } else { - devfileAttr, err := devfileData.GetAttributes() - if err != nil { - t.Errorf("got unexpected error: %v", err) - } - if devfileAttr == nil { - t.Errorf("devfile attributes should not be nil") - } - containerImage := devfileAttr.GetString("containerImage/new", &err) - if err != nil { - t.Errorf("got unexpected error: %v", err) - } - if containerImage != component.Spec.ContainerImage { - t.Errorf("unable to find component with container iamge: %s", component.Spec.ContainerImage) - } - } - } - - } - }) - } -} - -func TestGetAndAddComponentApplicationsToModel(t *testing.T) { - applicationName := "my-app" - namespace := "default" - component1 := "component-one" - component2 := "component-two" - - tests := []struct { - name string - projects []devfileAPIV1.Project - containerImage string - attributes attributes.Attributes - applicationName string - componentOne *appstudiov1alpha1.Component - componentTwo *appstudiov1alpha1.Component - wantErr bool - }{ - { - name: "Project already present", - projects: []devfileAPIV1.Project{ - { - Name: "duplicate", - }, - }, - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "duplicate", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{}, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Project added successfully", - projects: []devfileAPIV1.Project{ - { - Name: "present", - }, - }, - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "new", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - }, - }, - }, - { - name: "Git source in Component is nil", - projects: []devfileAPIV1.Project{ - { - Name: "present", - }, - }, - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "new", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: nil, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Devfile Projects list is nil", - projects: nil, - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "new", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: nil, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Container image added successfully", - attributes: attributes.Attributes{}.PutString("containerImage/otherComponent", "other-image"), - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "new", - ContainerImage: "an-image", - }, - }, - }, - { - name: "Container image already exists", - attributes: attributes.Attributes{}.PutString("containerImage/new", "an-image"), - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "new", - ContainerImage: "an-image", - }, - }, - wantErr: true, - }, - { - name: "Container image already exists, but invalid entry", - attributes: attributes.Attributes{}.Put("containerImage/new", make(chan error), nil), - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "new", - ContainerImage: "an-image", - }, - }, - wantErr: true, - }, - { - name: "Multiple Projects added successfully", - projects: nil, - applicationName: applicationName, - componentOne: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component1, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "compname", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - }, - }, - - componentTwo: &appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - Kind: "Component", - APIVersion: "appstudio.redhat.com", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component2, - Namespace: namespace, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Application: applicationName, - ComponentName: "compnametwo", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "urltwo", - }, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var fakeClient *FakeClient - if tt.componentOne != nil && tt.componentTwo != nil { - fakeClient = NewFakeClient(t, tt.componentOne, tt.componentTwo) - } else if tt.componentOne != nil { - fakeClient = NewFakeClient(t, tt.componentOne) - } else if tt.componentTwo != nil { - fakeClient = NewFakeClient(t, tt.componentTwo) - } - - beforeCreateSucceedReqs := testutil.ToFloat64(metrics.ApplicationCreationSucceeded) - beforeCreateFailedReqs := testutil.ToFloat64(metrics.ApplicationCreationFailed) - - devSpec := devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Attributes: tt.attributes, - Projects: tt.projects, - }, - } - - log := zap.New(zap.UseFlagOptions(&zap.Options{ - Development: true, - TimeEncoder: zapcore.ISO8601TimeEncoder, - })) - r := ApplicationReconciler{Client: fakeClient} - req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: "default", Name: tt.applicationName}} - err := r.getAndAddComponentApplicationsToModel(log, req, tt.applicationName, &devSpec) - if (err != nil) != tt.wantErr { - t.Errorf("TestGetAndAddComponentApplicationsToModel() unexpected error: %v", err) - } - - if err != nil && tt.name == "Container image already exists" { - // This is a user error scenario, so expect the "creation success" metric to be incremented (as only system errors are counted for failure) - if testutil.ToFloat64(metrics.ApplicationCreationSucceeded) <= beforeCreateSucceedReqs { - t.Errorf("TestGetAndAddComponentApplicationsToModel() expected metric 'ApplicationCreationSucceeded' to be incremented") - } - if testutil.ToFloat64(metrics.ApplicationCreationFailed) != beforeCreateFailedReqs { - t.Errorf("TestGetAndAddComponentApplicationsToModel() expected metric 'ApplicationCreationFailed' to be unchanged") - } - } - - components := []appstudiov1alpha1.Component{} - if tt.componentOne != nil { - components = append(components, *tt.componentOne) - } - if tt.componentTwo != nil { - components = append(components, *tt.componentTwo) - } - if err == nil && !tt.wantErr { - for _, component := range components { - if component.Spec.Source.GitSource != nil { - projects := devSpec.Projects - matched := false - for _, project := range projects { - projectGitSrc := project.ProjectSource.Git - if project.Name == component.Spec.ComponentName && projectGitSrc != nil && projectGitSrc.Remotes["origin"] == component.Spec.Source.GitSource.URL { - matched = true - } - } - - if !matched { - t.Errorf("unable to find devfile with project: %s", component.Spec.ComponentName) - } - - } else { - devfileAttr := devSpec.Attributes - if devfileAttr == nil { - t.Errorf("devfile attributes should not be nil") - } - containerImage := devfileAttr.GetString("containerImage/new", &err) - if err != nil { - t.Errorf("got unexpected error: %v", err) - } - if containerImage != component.Spec.ContainerImage { - t.Errorf("unable to find component with container iamge: %s", component.Spec.ContainerImage) - } - } - } - - } - }) - } -} - -func TestUpdateComponentDevfileModel(t *testing.T) { - - storage1GiResource, err := resource.ParseQuantity("1Gi") - if err != nil { - t.Error(err) - } - core500mResource, err := resource.ParseQuantity("500m") - if err != nil { - t.Error(err) - } - - originalResources := corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: core500mResource, - corev1.ResourceMemory: storage1GiResource, - corev1.ResourceStorage: storage1GiResource, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: core500mResource, - corev1.ResourceMemory: storage1GiResource, - corev1.ResourceStorage: storage1GiResource, - }, - } - - envAttributes := attributes.Attributes{}.FromMap(map[string]interface{}{devfilePkg.ContainerENVKey: []corev1.EnvVar{{Name: "FOO", Value: "foo"}}}, &err) - emptyAttributes := attributes.Attributes{} - if err != nil { - t.Error(err) - } - - env := []corev1.EnvVar{ - { - Name: "FOO", - Value: "foo1", - }, - { - Name: "BAR", - Value: "bar1", - }, - } - - tests := []struct { - name string - components []devfileAPIV1.Component - component appstudiov1alpha1.Component - updateExpected bool - wantErr bool - wantReplica *int - }{ - { - name: "No kubernetes component", - components: []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Container: &devfileAPIV1.ContainerComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - }, - }, - }, - { - name: "one kubernetes component", - components: []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 1001), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - Replicas: &oneReplica, - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &oneReplica, - }, - { - // This is the default test case where replicas is unset everywhere - name: "replica test: no attributes, comp.Spec.Replicas is nil, and no deployment spec. Replica should be 1", - components: []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &oneReplica, - }, - { - name: "replica test: comp.Spec.Replicas is 3, no deployment spec. Replicas should be 3", - components: []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Replicas: &threeReplicas, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &threeReplicas, - }, - { - // Initial creation case, where the attribute does not exist so an update is expected - name: "replica test: comp.Spec.Replicas is 1, no deployment spec. Replicas should be 1", - components: []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Replicas: &oneReplica, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &oneReplica, - }, - { - // similar to the initial creation case above, this time attribute list is present but key not found - name: "replica test: comp.Spec.Replicas is 1, no deployment spec. Replicas should be 1", - components: []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: emptyAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 1001), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - - Replicas: &oneReplica, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &oneReplica, - }, - { - // component.Spec.Replicas is explicitly set as 0, so it should override the deployment replica - name: "replica test: comp.Spec.Replicas is 0, Deployment spec is 3. Replicas should be 0", - components: []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{ - K8sLikeComponent: devfileAPIV1.K8sLikeComponent{ - K8sLikeComponentLocation: devfileAPIV1.K8sLikeComponentLocation{ - Inlined: k8sInlined, - }, - }, - }, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Replicas: &zeroReplica, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &zeroReplica, - }, - { - // No update to the component model because deployment replica will be used - name: "replica test: comp.Spec.Replicas is nil, deployment spec replica is 3. No update", - components: []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{ - K8sLikeComponent: devfileAPIV1.K8sLikeComponent{ - K8sLikeComponentLocation: devfileAPIV1.K8sLikeComponentLocation{ - Inlined: k8sInlined, - }, - }, - }, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: false, - }, - { - // no update to the component model because replica value has not changed - name: "replica test: comp.Spec.Replicas is 1, attribute deploy/replicas: 1. Replicas should be 1, no update", - components: []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ReplicaKey, 1), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Replicas: &oneReplica, - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: false, - }, - { - name: "two kubernetes components", - components: []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 1001), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - { - Name: "component2", - Attributes: envAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 3333).PutString(devfilePkg.MemoryLimitKey, "2Gi"), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - }, - Route: "route1", - Replicas: &oneReplica, - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &oneReplica, - }, - { - name: "Component with envFrom component - should error out as it's not supported right now", - components: []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes, - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "component1", - Env: []corev1.EnvVar{ - { - Name: "FOO", - Value: "foo", - }, - { - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: "test", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Component with invalid component type - should error out", - components: []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{}, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "component1", - Env: []corev1.EnvVar{ - { - Name: "FOO", - Value: "foo", - }, - { - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: "test", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "image component with local Dockerfile uri updated to component's absolute DockerfileURL", - components: []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 1001), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - }, - { - Name: "component2", - Attributes: envAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 3333).PutString(devfilePkg.MemoryLimitKey, "2Gi"), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Image: &devfileAPIV1.ImageComponent{ - - Image: devfileAPIV1.Image{ - ImageUnion: devfileAPIV1.ImageUnion{ - Dockerfile: &devfileAPIV1.DockerfileImage{ - DockerfileSrc: devfileAPIV1.DockerfileSrc{ - Uri: "./dockerfile", - }, - }, - }, - }, - }, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - DockerfileURL: "https://website.com/dockerfiles/dockerfile", - }, - }, - }, - Route: "route1", - Replicas: &oneReplica, - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - updateExpected: true, - wantReplica: &oneReplica, - }, - { - name: "devfile with invalid components, error out when trying to update devfile's Dockerfile uri", - components: []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 1001), - ComponentUnion: devfileAPIV1.ComponentUnion{ - ComponentType: "bad-component", - }, - }, - { - Name: "component2", - Attributes: envAttributes.PutInteger(devfilePkg.ContainerImagePortKey, 3333).PutString(devfilePkg.MemoryLimitKey, "2Gi"), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Image: &devfileAPIV1.ImageComponent{ - - Image: devfileAPIV1.Image{ - ImageUnion: devfileAPIV1.ImageUnion{ - Dockerfile: &devfileAPIV1.DockerfileImage{ - DockerfileSrc: devfileAPIV1.DockerfileSrc{ - Uri: "./dockerfile", - }, - }, - }, - }, - }, - }, - }, - }, - component: appstudiov1alpha1.Component{ - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: "componentName", - Application: "applicationName", - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: "url", - DockerfileURL: "https://website.com/dockerfiles/dockerfile", - }, - }, - }, - Route: "route1", - Replicas: &oneReplica, - TargetPort: 1111, - Env: env, - Resources: originalResources, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - devfileData := &v2.DevfileV2{ - Devfile: devfileAPIV1.Devfile{ - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: tt.components, - }, - }, - }, - } - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{ - Development: true, - }))) - r := ComponentReconciler{ - Log: ctrl.Log.WithName("TestUpdateComponentDevfileModel"), - } - err := r.updateComponentDevfileModel(ctrl.Request{}, devfileData, tt.component) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if err == nil { - if tt.updateExpected { - // it has been updated - checklist := updateChecklist{ - route: tt.component.Spec.Route, - replica: *tt.wantReplica, - port: tt.component.Spec.TargetPort, - env: tt.component.Spec.Env, - resources: tt.component.Spec.Resources, - } - - verifyHASComponentUpdates(devfileData, checklist, t) - } - } - }) - } -} - -func TestUpdateComponentStub(t *testing.T) { - var err error - envAttributes := attributes.Attributes{}.FromMap(map[string]interface{}{devfilePkg.ContainerENVKey: []corev1.EnvVar{{Name: "name1", Value: "value1"}}}, &err) - if err != nil { - t.Error(err) - } - ctx := context.TODO() - fakeClientNoErr := NewFakeClient(t) - fakeClientNoErr.MockGet = func(ctx context.Context, key types.NamespacedName, obj client.Object, opts ...client.GetOption) error { - return nil - } - - fakeClientWithErr := NewFakeClient(t) - fakeClientWithErr.MockGet = func(ctx context.Context, key types.NamespacedName, obj client.Object, opts ...client.GetOption) error { - return errors.NewNotFound(schema.GroupResource{}, "not found") - } - - componentsValid := []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ReplicaKey, 1).PutString(devfilePkg.RouteKey, "route1").PutInteger( - devfilePkg.ContainerImagePortKey, 1001).PutString(devfilePkg.CpuLimitKey, "2").PutString(devfilePkg.CpuRequestKey, "700m").PutString( - devfilePkg.MemoryLimitKey, "500Mi").PutString(devfilePkg.MemoryRequestKey, "400Mi").PutString( - devfilePkg.StorageLimitKey, "400Mi").PutString(devfilePkg.StorageRequestKey, "200Mi"), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{ - K8sLikeComponent: devfileAPIV1.K8sLikeComponent{ - K8sLikeComponentLocation: devfileAPIV1.K8sLikeComponentLocation{ - Uri: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/deploy.yaml", - }, - }, - }, - }, - }, - { - Name: "component2", - Attributes: attributes.Attributes{}.PutInteger(devfilePkg.ContainerImagePortKey, 1003), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{ - K8sLikeComponent: devfileAPIV1.K8sLikeComponent{ - K8sLikeComponentLocation: devfileAPIV1.K8sLikeComponentLocation{ - Uri: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/deploy.yaml", - }, - }, - }, - }, - }, - } - - componentsInvalidDeployYamlErr := []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ReplicaKey, 1).PutString(devfilePkg.RouteKey, "route1").PutInteger( - devfilePkg.ContainerImagePortKey, 1001).PutString(devfilePkg.CpuLimitKey, "2").PutString(devfilePkg.CpuRequestKey, "700m").PutString( - devfilePkg.MemoryLimitKey, "500Mi").PutString(devfilePkg.MemoryRequestKey, "400Mi").PutString( - devfilePkg.StorageLimitKey, "400Mi").PutString(devfilePkg.StorageRequestKey, "200Mi"), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{ - K8sLikeComponent: devfileAPIV1.K8sLikeComponent{ - K8sLikeComponentLocation: devfileAPIV1.K8sLikeComponentLocation{ - Uri: "testLocation/deploy.yaml", - }, - }, - }, - }, - }, - } - - componentsValidWithPort := []devfileAPIV1.Component{ - { - Name: "component1", - Attributes: envAttributes.PutInteger(devfilePkg.ReplicaKey, 1).PutString(devfilePkg.RouteKey, "route1").PutInteger( - devfilePkg.ContainerImagePortKey, 8080).PutString(devfilePkg.CpuLimitKey, "2").PutString(devfilePkg.CpuRequestKey, "700m").PutString( - devfilePkg.MemoryLimitKey, "500Mi").PutString(devfilePkg.MemoryRequestKey, "400Mi").PutString( - devfilePkg.StorageLimitKey, "400Mi").PutString(devfilePkg.StorageRequestKey, "200Mi"), - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{ - K8sLikeComponent: devfileAPIV1.K8sLikeComponent{ - K8sLikeComponentLocation: devfileAPIV1.K8sLikeComponentLocation{ - Uri: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/deploy.yaml", - }, - }, - }, - }, - }, - } - - componentsReplicaErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutBoolean(devfilePkg.ReplicaKey, true), - }, - } - - componentsContainerPortErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutBoolean(devfilePkg.ContainerImagePortKey, true), - }, - } - - componentsRouteErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.Put(devfilePkg.RouteKey, []string{"a", "b"}, &err), - }, - } - if err != nil { - t.Errorf("unexpected err: %+v", err) - return - } - - componentsStorageLimitErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.Put(devfilePkg.StorageLimitKey, []string{"a", "b"}, &err), - }, - } - if err != nil { - t.Errorf("unexpected err: %+v", err) - return - } - - componentsStorageRequestErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.Put(devfilePkg.StorageRequestKey, []string{"a", "b"}, &err), - }, - } - if err != nil { - t.Errorf("unexpected err: %+v", err) - return - } - - componentsCpuLimitErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.Put(devfilePkg.CpuLimitKey, []string{"a", "b"}, &err), - }, - } - if err != nil { - t.Errorf("unexpected err: %+v", err) - return - } - - componentsCpuRequestErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.Put(devfilePkg.CpuRequestKey, []string{"a", "b"}, &err), - }, - } - if err != nil { - t.Errorf("unexpected err: %+v", err) - return - } - - componentsMemoryLimitErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.Put(devfilePkg.MemoryLimitKey, []string{"a", "b"}, &err), - }, - } - if err != nil { - t.Errorf("unexpected err: %+v", err) - return - } - - componentsMemoryRequestErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.Put(devfilePkg.MemoryRequestKey, []string{"a", "b"}, &err), - }, - } - if err != nil { - t.Errorf("unexpected err: %+v", err) - return - } - - componentsCpuLimitParseErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutString(devfilePkg.CpuLimitKey, "xyz"), - }, - } - - componentsMemoryLimitParseErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutString(devfilePkg.MemoryLimitKey, "xyz"), - }, - } - - componentsStorageLimitParseErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutString(devfilePkg.StorageLimitKey, "xyz"), - }, - } - - componentsCpuRequestParseErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutString(devfilePkg.CpuRequestKey, "xyz"), - }, - } - - componentsMemoryRequestParseErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutString(devfilePkg.MemoryRequestKey, "xyz"), - }, - } - - componentsStorageRequestParseErr := []devfileAPIV1.Component{ - { - Name: "component1", - ComponentUnion: devfileAPIV1.ComponentUnion{ - Kubernetes: &devfileAPIV1.KubernetesComponent{}, - }, - Attributes: attributes.Attributes{}.PutString(devfilePkg.StorageRequestKey, "xyz"), - }, - } - - tests := []struct { - name string - devfilesDataMap map[string]*v2.DevfileV2 - devfilesURLMap map[string]string - dockerfileURLMap map[string]string - componentPortsMap map[string][]int - isNil bool - testNoDup bool - wantErr bool - }{ - { - name: "Kubernetes Components present", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsValid, - }, - }, - }, - }, - }, - }, - { - name: "Detected ports present and with component exist", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsValidWithPort, - }, - }, - }, - }, - }, - testNoDup: true, - componentPortsMap: map[string][]int{ - "./": {8080}, - }, - }, - { - name: "Kubernetes Components present with a devfile URL", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsValid, - }, - }, - }, - }, - }, - devfilesURLMap: map[string]string{ - "./": "http://somelink", - }, - }, - { - name: "Kubernetes Components present with a devfile & Dockerfile URL", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsValid, - }, - }, - }, - }, - }, - devfilesURLMap: map[string]string{ - "./": "http://somelink", - }, - dockerfileURLMap: map[string]string{ - "./": "http://someotherlink", - }, - }, - { - name: "Dockerfile URL only", - dockerfileURLMap: map[string]string{ - "./": "http://someotherlink", - }, - }, - { - name: "Dockerfile URL with ports", - dockerfileURLMap: map[string]string{ - "./": "Dockerfile", - }, - componentPortsMap: map[string][]int{ - "./": {8080}, - }, - }, - { - name: "No Kubernetes Components present", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{}, - }, - }, - }, - }, - }, - { - name: "Check err condition", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{}, - }, - }, - }, - }, - isNil: true, - wantErr: true, - }, - { - name: "Check err for replica as non integer", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsReplicaErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for container port as non integer", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsContainerPortErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for route as non string", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsRouteErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for storage limit as non string", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsStorageLimitErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for storage request as non string", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsStorageRequestErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for cpu limit as non string", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsCpuLimitErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for cpu request as non string", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsCpuRequestErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for memory limit as non string", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsMemoryLimitErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for memory request as non string", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsMemoryRequestErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for cpu limit parse err", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsCpuLimitParseErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for memory limit parse err", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsMemoryLimitParseErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for storage limit parse err", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsStorageLimitParseErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for cpu request parse err", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsCpuRequestParseErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for memory request parse err", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsMemoryRequestParseErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for storage request parse err", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsStorageRequestParseErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Check err for invalid deploy yaml uri error", - devfilesDataMap: map[string]*v2.DevfileV2{ - "./": { - Devfile: devfileAPIV1.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: "2.2.0", - Metadata: devfile.DevfileMetadata{ - Name: "test-devfile", - Language: "language", - ProjectType: "project", - }, - }, - DevWorkspaceTemplateSpec: devfileAPIV1.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: devfileAPIV1.DevWorkspaceTemplateSpecContent{ - Components: componentsInvalidDeployYamlErr, - }, - }, - }, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - componentDetectionQuery := appstudiov1alpha1.ComponentDetectionQuery{ - Spec: appstudiov1alpha1.ComponentDetectionQuerySpec{ - GitSource: appstudiov1alpha1.GitSource{ - URL: "url", - }, - }, - } - devfilesMap := make(map[string][]byte) - - for context, devfileData := range tt.devfilesDataMap { - yamlData, err := yaml.Marshal(devfileData) - if err != nil { - t.Errorf("unexpected error %v", err) - } - devfilesMap[context] = yamlData - } - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{ - Development: true, - }))) - - r := ComponentDetectionQueryReconciler{ - Log: ctrl.Log.WithName("TestUpdateComponentStub"), - } - if tt.testNoDup { - r.Client = fakeClientWithErr - } else { - r.Client = fakeClientNoErr - } - var err error - if tt.isNil { - err = r.updateComponentStub(ctrl.Request{}, ctx, nil, devfilesMap, nil, nil, nil) - } else { - err = r.updateComponentStub(ctrl.Request{}, ctx, &componentDetectionQuery, devfilesMap, tt.devfilesURLMap, tt.dockerfileURLMap, tt.componentPortsMap) - } - - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if err == nil { - for compName, hasCompDetection := range componentDetectionQuery.Status.ComponentDetected { - if tt.testNoDup { - assert.Equal(t, "url", compName, "The component name should match the expected name") - } else { - assert.NotEqual(t, "url", compName, "The component name should not exactly match the expected name") - } - // Application Name - assert.Equal(t, hasCompDetection.ComponentStub.Application, "insert-application-name", "The application name should match the generic name") - - if len(tt.devfilesDataMap) != 0 { - // Language - assert.Equal(t, hasCompDetection.Language, tt.devfilesDataMap[hasCompDetection.ComponentStub.Source.GitSource.Context].Metadata.Language, "The language should be the same") - - // Project Type - assert.Equal(t, hasCompDetection.ProjectType, tt.devfilesDataMap[hasCompDetection.ComponentStub.Source.GitSource.Context].Metadata.ProjectType, "The project type should be the same") - - // Devfile Found - assert.Equal(t, hasCompDetection.DevfileFound, len(tt.devfilesURLMap[hasCompDetection.ComponentStub.Source.GitSource.Context]) != 0, "The devfile found did not match expected") - - // Component Name - assert.Contains(t, hasCompDetection.ComponentStub.ComponentName, "url", "The component name did not match the expected") - - // Devfile URL - if len(tt.devfilesURLMap) > 0 { - assert.NotNil(t, hasCompDetection.ComponentStub.Source.GitSource, "The git source cannot be nil for this test") - assert.Equal(t, hasCompDetection.ComponentStub.Source.GitSource.URL, "url", "The URL should match") - assert.Equal(t, hasCompDetection.ComponentStub.Source.GitSource.DevfileURL, tt.devfilesURLMap[hasCompDetection.ComponentStub.Source.GitSource.Context], "The devfile URL should match") - } - - // Dockerfile URL - if len(tt.dockerfileURLMap) > 0 { - assert.NotNil(t, hasCompDetection.ComponentStub.Source.GitSource, "The git source cannot be nil for this test") - assert.Equal(t, hasCompDetection.ComponentStub.Source.GitSource.URL, "url", "The URL should match") - assert.Equal(t, hasCompDetection.ComponentStub.Source.GitSource.DockerfileURL, tt.dockerfileURLMap[hasCompDetection.ComponentStub.Source.GitSource.Context], "The Dockerfile URL should match") - } - - for _, devfileComponent := range tt.devfilesDataMap[hasCompDetection.ComponentStub.Source.GitSource.Context].Components { - if devfileComponent.Kubernetes != nil { - componentAttributes := devfileComponent.Attributes - var containerENVs []corev1.EnvVar - err := componentAttributes.GetInto(devfilePkg.ContainerENVKey, &containerENVs) - assert.Nil(t, err, "err should be nil") - for _, devfileEnv := range containerENVs { - matched := false - for _, compEnv := range hasCompDetection.ComponentStub.Env { - if devfileEnv.Name == compEnv.Name && devfileEnv.Value == compEnv.Value { - matched = true - } - } - assert.True(t, matched, "env %s:%s should match", devfileEnv.Name, devfileEnv.Value) - } - - limits := hasCompDetection.ComponentStub.Resources.Limits - if len(limits) > 0 { - resourceCPULimit := limits[corev1.ResourceCPU] - assert.Equal(t, resourceCPULimit.String(), devfileComponent.Attributes.GetString(devfilePkg.CpuLimitKey, &err), "The cpu limit should be the same") - assert.Nil(t, err, "err should be nil") - - resourceMemoryLimit := limits[corev1.ResourceMemory] - assert.Equal(t, resourceMemoryLimit.String(), devfileComponent.Attributes.GetString(devfilePkg.MemoryLimitKey, &err), "The memory limit should be the same") - assert.Nil(t, err, "err should be nil") - - resourceStorageLimit := limits[corev1.ResourceStorage] - assert.Equal(t, resourceStorageLimit.String(), devfileComponent.Attributes.GetString(devfilePkg.StorageLimitKey, &err), "The storage limit should be the same") - assert.Nil(t, err, "err should be nil") - } - - requests := hasCompDetection.ComponentStub.Resources.Requests - if len(requests) > 0 { - resourceCPURequest := requests[corev1.ResourceCPU] - assert.Equal(t, resourceCPURequest.String(), devfileComponent.Attributes.GetString(devfilePkg.CpuRequestKey, &err), "The cpu request should be the same") - assert.Nil(t, err, "err should be nil") - - resourceMemoryRequest := requests[corev1.ResourceMemory] - assert.Equal(t, resourceMemoryRequest.String(), devfileComponent.Attributes.GetString(devfilePkg.MemoryRequestKey, &err), "The memory request should be the same") - assert.Nil(t, err, "err should be nil") - - resourceStorageRequest := requests[corev1.ResourceStorage] - assert.Equal(t, resourceStorageRequest.String(), devfileComponent.Attributes.GetString(devfilePkg.StorageRequestKey, &err), "The storage request should be the same") - assert.Nil(t, err, "err should be nil") - } - - assert.Equal(t, *hasCompDetection.ComponentStub.Replicas, int(devfileComponent.Attributes.GetNumber(devfilePkg.ReplicaKey, &err)), "The replicas should be the same") - assert.Nil(t, err, "err should be nil") - - assert.Equal(t, hasCompDetection.ComponentStub.TargetPort, int(devfileComponent.Attributes.GetNumber(devfilePkg.ContainerImagePortKey, &err)), "The target port should be the same") - assert.Nil(t, err, "err should be nil") - - assert.Equal(t, hasCompDetection.ComponentStub.Route, devfileComponent.Attributes.GetString(devfilePkg.RouteKey, &err), "The route should be the same") - assert.Nil(t, err, "err should be nil") - - break // dont check for the second Kubernetes component - } - } - } - - if len(tt.dockerfileURLMap) != 0 { - // Language - assert.Equal(t, hasCompDetection.Language, "Dockerfile", "The language should be the same") - - // Project Type - assert.Equal(t, hasCompDetection.ProjectType, "Dockerfile", "The project type should be the same") - - // Devfile Found - assert.Equal(t, hasCompDetection.DevfileFound, false, "The devfile found did not match expected") - - // Component Name - assert.Contains(t, hasCompDetection.ComponentStub.ComponentName, "url", "The component name did not match the expected") - - // Dockerfile URL - if len(tt.dockerfileURLMap) > 0 { - assert.NotNil(t, hasCompDetection.ComponentStub.Source.GitSource, "The git source cannot be nil for this test") - assert.Equal(t, hasCompDetection.ComponentStub.Source.GitSource.URL, "url", "The URL should match") - assert.Equal(t, hasCompDetection.ComponentStub.Source.GitSource.DockerfileURL, tt.dockerfileURLMap[hasCompDetection.ComponentStub.Source.GitSource.Context], "The Dockerfile URL should match") - } - } - } - } - }) - } -} - -func TestGetComponentName(t *testing.T) { - // Repos used in tests are most likely dummy repos - ctx := context.TODO() - fakeClientNoErr := NewFakeClient(t) - fakeClientNoErr.MockGet = func(ctx context.Context, key types.NamespacedName, obj client.Object, opts ...client.GetOption) error { - return nil - } - - fakeClientWithErr := NewFakeClient(t) - fakeClientWithErr.MockGet = func(ctx context.Context, key types.NamespacedName, obj client.Object, opts ...client.GetOption) error { - return errors.NewNotFound(schema.GroupResource{}, "not found") - } - - r := ComponentDetectionQueryReconciler{ - Log: ctrl.Log.WithName("TestGetComponentName"), - } - - tests := []struct { - name string - gitSource *appstudiov1alpha1.GitSource - testNoDup bool - expectedName string - generateComponentName bool - }{ - { - name: "valid repo name", - gitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/devfile-sample-go-basic", - }, - testNoDup: true, - generateComponentName: false, - expectedName: "devfile-sample-go-basic", - }, - { - name: "long repo name with special chars", - gitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/123-testdevfilego--ImportRepository--withaverylongreporitoryname-test-validation-and-generation", - }, - testNoDup: true, - generateComponentName: false, - expectedName: "comp-123-testdevfilego--importrepository--withaverylongrep", - }, - { - name: "numeric repo name", - gitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/123454678.git", - }, - expectedName: "comp-123454678", - }, - { - name: "valid repo name with context", - gitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/devfile-multi-component", - Context: "nodejs", - }, - expectedName: "nodejs-devfile-multi-component", - generateComponentName: true, - }, - { - name: "repo URL with forward slash at the end", - gitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/devfile-multi-component/", - }, - expectedName: "devfile-multi-component", - generateComponentName: true, - }, - { - name: "repo URL with forward slash and context", - gitSource: &appstudiov1alpha1.GitSource{ - URL: "https://github.com/devfile-samples/devfile-multi-component/", - Context: "nodejs", - }, - expectedName: "nodejs-devfile-multi-component", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.testNoDup { - r.Client = fakeClientWithErr - } else { - r.Client = fakeClientNoErr - } - - gotComponentName := r.getComponentName(r.Log, ctx, "default", tt.gitSource, tt.generateComponentName) - if !tt.generateComponentName && tt.testNoDup { - assert.Equal(t, tt.expectedName, gotComponentName, "the component name should equal to repo name") - } else { - assert.Contains(t, gotComponentName, tt.expectedName, "the component name should contains the expected name") - assert.NotEqual(t, tt.expectedName, gotComponentName, "the component name should not equal to repo name") - } - - }) - } - -} - -func TestSanitizeComponentName(t *testing.T) { - - tests := []struct { - name string - componentName string - want string - }{ - { - name: "simple component name", - componentName: "devfile-sample-go-basic", - want: "devfile-sample-go-basic", - }, - { - name: "simple component name, all numbers", - componentName: "123412341234", - want: "comp-123412341234", - }, - { - name: "simple component name, start with a number", - componentName: "123-testcomp", - want: "comp-123-testcomp", - }, - { - name: "Empty string, should have a name generated for it", - componentName: "", - }, - { - name: "component name with uppercase", - componentName: "devfile-SAMPLE-gO-BASIC", - want: "devfile-sample-go-basic", - }, - { - name: "component name with greater than 58 characters", - componentName: "devfile-sample-go-basic-devfile-sample-go-basic-devfile-sample", - want: "devfile-sample-go-basic-devfile-sample-go-basic-devfile-sa", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sanitizedName := sanitizeComponentName(tt.componentName) - - if tt.componentName == "" { - if sanitizedName == "" { - t.Errorf("TestSanitizeComponentName(): expected generated name for empty component name, got %v", sanitizedName) - } - } else { - if !strings.Contains(sanitizedName, tt.want) { - t.Errorf("TestSanitizeComponentName(): want %v, got %v", tt.want, sanitizedName) - } - } - - }) - } - -} diff --git a/entrypoint.sh b/entrypoint.sh index 91aa0d5d4..8fc514db8 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,6 +1,5 @@ #!/bin/sh set -eux -echo "running manager under tini" -exec tini -- /manager $* +exec /manager $* diff --git a/gitops/generate_build.go b/gitops/generate_build.go deleted file mode 100644 index 56a325ff2..000000000 --- a/gitops/generate_build.go +++ /dev/null @@ -1,178 +0,0 @@ -/* -Copyright 2021 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gitops - -import ( - "fmt" - "net/url" - "regexp" - "strings" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - pacv1alpha1 "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1" - gitopsprepare "github.com/redhat-appstudio/application-service/gitops/prepare" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - PaCAnnotation = "pipelinesascode" - GitProviderAnnotationName = "git-provider" - GitProviderAnnotationURL = "git-provider-url" - PipelinesAsCodeWebhooksSecretName = "pipelines-as-code-webhooks-secret" - PipelinesAsCode_githubAppIdKey = "github-application-id" - PipelinesAsCode_githubPrivateKey = "github-private-key" -) - -func getBuildCommonLabelsForComponent(component *appstudiov1alpha1.Component) map[string]string { - labels := map[string]string{ - "pipelines.appstudio.openshift.io/type": "build", - "build.appstudio.openshift.io/build": "true", - "build.appstudio.openshift.io/type": "build", - "build.appstudio.openshift.io/version": "0.1", - "appstudio.openshift.io/component": component.Name, - "appstudio.openshift.io/application": component.Spec.Application, - } - return labels -} - -// GeneratePACRepository creates configuration of Pipelines as Code repository object. -func GeneratePACRepository(component appstudiov1alpha1.Component, config map[string][]byte) (*pacv1alpha1.Repository, error) { - gitProvider, err := GetGitProvider(component) - if err != nil { - return nil, err - } - - isAppUsed := IsPaCApplicationConfigured(gitProvider, config) - - var gitProviderConfig *pacv1alpha1.GitProvider = nil - if !isAppUsed { - // Webhook is used - gitProviderConfig = &pacv1alpha1.GitProvider{ - Secret: &pacv1alpha1.Secret{ - Name: gitopsprepare.PipelinesAsCodeSecretName, - Key: GetProviderTokenKey(gitProvider), - }, - WebhookSecret: &pacv1alpha1.Secret{ - Name: PipelinesAsCodeWebhooksSecretName, - Key: GetWebhookSecretKeyForComponent(component), - }, - } - - if gitProvider == "gitlab" { - gitProviderConfig.URL = "https://gitlab.com" - } - } - - if url, ok := component.Annotations[GitProviderAnnotationURL]; ok { - if gitProviderConfig == nil { - gitProviderConfig = &pacv1alpha1.GitProvider{} - } - gitProviderConfig.URL = url - } - - repository := &pacv1alpha1.Repository{ - TypeMeta: metav1.TypeMeta{ - Kind: "Repository", - APIVersion: "pipelinesascode.tekton.dev/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: component.Name, - Namespace: component.Namespace, - Annotations: getBuildCommonLabelsForComponent(&component), - }, - Spec: pacv1alpha1.RepositorySpec{ - URL: strings.TrimSuffix(strings.TrimSuffix(component.Spec.Source.GitSource.URL, ".git"), "/"), - GitProvider: gitProviderConfig, - }, - } - - return repository, nil -} - -// GetProviderTokenKey returns key (field name) of the given provider access token in the Pipelines as Code k8s secret -func GetProviderTokenKey(gitProvider string) string { - return gitProvider + ".token" -} - -func GetWebhookSecretKeyForComponent(component appstudiov1alpha1.Component) string { - gitRepoUrl := strings.TrimSuffix(component.Spec.Source.GitSource.URL, ".git") - - notAllowedCharRegex, _ := regexp.Compile("[^-._a-zA-Z0-9]{1}") - return notAllowedCharRegex.ReplaceAllString(gitRepoUrl, "_") -} - -// GetGitProvider returns git provider name based on the repository url, e.g. github, gitlab, etc or git-privider annotation -func GetGitProvider(component appstudiov1alpha1.Component) (string, error) { - allowedGitProviders := map[string]bool{"github": true, "gitlab": true, "bitbucket": true} - gitProvider := "" - - sourceUrl := component.Spec.Source.GitSource.URL - - if strings.HasPrefix(sourceUrl, "git@") { - // git@github.com:redhat-appstudio/application-service.git - sourceUrl = strings.TrimPrefix(sourceUrl, "git@") - host := strings.Split(sourceUrl, ":")[0] - gitProvider = strings.Split(host, ".")[0] - } else { - // https://github.com/redhat-appstudio/application-service - u, err := url.Parse(sourceUrl) - if err != nil { - return "", err - } - uParts := strings.Split(u.Hostname(), ".") - if len(uParts) == 1 { - gitProvider = uParts[0] - } else { - gitProvider = uParts[len(uParts)-2] - } - } - - var err error - if !allowedGitProviders[gitProvider] { - // Self-hosted git provider, check for git-provider annotation on the component - gitProviderAnnotationValue := component.GetAnnotations()[GitProviderAnnotationName] - if gitProviderAnnotationValue != "" { - if allowedGitProviders[gitProviderAnnotationValue] { - gitProvider = gitProviderAnnotationValue - } else { - err = fmt.Errorf("unsupported \"%s\" annotation value: %s", GitProviderAnnotationName, gitProviderAnnotationValue) - } - } else { - err = fmt.Errorf("self-hosted git provider is not specified via \"%s\" annotation in the component", GitProviderAnnotationName) - } - } - - return gitProvider, err -} - -// IsPaCApplicationConfigured checks if Pipelines as Code credentials configured for given provider. -// Application is preffered over webhook if possible. -func IsPaCApplicationConfigured(gitProvider string, config map[string][]byte) bool { - isAppUsed := false - - switch gitProvider { - case "github": - if len(config[PipelinesAsCode_githubAppIdKey]) != 0 || len(config[PipelinesAsCode_githubPrivateKey]) != 0 { - isAppUsed = true - } - default: - // Application is not supported - isAppUsed = false - } - - return isAppUsed -} diff --git a/gitops/generate_build_test.go b/gitops/generate_build_test.go deleted file mode 100644 index 7e785b7e8..000000000 --- a/gitops/generate_build_test.go +++ /dev/null @@ -1,458 +0,0 @@ -/* -Copyright 2021 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package gitops - -import ( - "reflect" - "strings" - "testing" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - pacv1alpha1 "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1" - gitopsprepare "github.com/redhat-appstudio/application-service/gitops/prepare" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestGeneratePACRepository(t *testing.T) { - getComponent := func(repoUrl string, annotations map[string]string) appstudiov1alpha1.Component { - return appstudiov1alpha1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcomponent", - Namespace: "workspace-name", - Annotations: annotations, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: repoUrl, - }, - }, - }, - }, - } - } - - tests := []struct { - name string - repoUrl string - componentAnnotations map[string]string - pacConfig map[string][]byte - expectedGitProviderConfig *pacv1alpha1.GitProvider - }{ - { - name: "should create PaC repository for Github application", - repoUrl: "https://github.com/user/test-component-repository", - pacConfig: map[string][]byte{ - PipelinesAsCode_githubAppIdKey: []byte("12345"), - PipelinesAsCode_githubPrivateKey: []byte("private-key"), - }, - expectedGitProviderConfig: nil, - }, - { - name: "should create PaC repository for Github application even if Github webhook configured", - repoUrl: "https://github.com/user/test-component-repository", - pacConfig: map[string][]byte{ - PipelinesAsCode_githubAppIdKey: []byte("12345"), - PipelinesAsCode_githubPrivateKey: []byte("private-key"), - "github.token": []byte("ghp_token"), - }, - expectedGitProviderConfig: nil, - }, - { - name: "should create PaC repository for Github webhook", - repoUrl: "https://github.com/user/test-component-repository", - pacConfig: map[string][]byte{ - "github.token": []byte("ghp_token"), - "gitlab.token": []byte("glpat-token"), - }, - expectedGitProviderConfig: &pacv1alpha1.GitProvider{ - Secret: &pacv1alpha1.Secret{ - Name: gitopsprepare.PipelinesAsCodeSecretName, - Key: "github.token", - }, - WebhookSecret: &pacv1alpha1.Secret{ - Name: PipelinesAsCodeWebhooksSecretName, - Key: GetWebhookSecretKeyForComponent(getComponent("https://github.com/user/test-component-repository", nil)), - }, - }, - }, - { - name: "should create PaC repository for GitLab webhook", - repoUrl: "https://gitlab.com/user/test-component-repository/", - pacConfig: map[string][]byte{ - "github.token": []byte("ghp_token"), - "gitlab.token": []byte("glpat-token"), - }, - expectedGitProviderConfig: &pacv1alpha1.GitProvider{ - Secret: &pacv1alpha1.Secret{ - Name: gitopsprepare.PipelinesAsCodeSecretName, - Key: "gitlab.token", - }, - WebhookSecret: &pacv1alpha1.Secret{ - Name: PipelinesAsCodeWebhooksSecretName, - Key: GetWebhookSecretKeyForComponent(getComponent("https://gitlab.com/user/test-component-repository/", nil)), - }, - URL: "https://gitlab.com", - }, - }, - { - name: "should create PaC repository for GitLab webhook even if GitHub application configured", - repoUrl: "https://gitlab.com/user/test-component-repository.git", - pacConfig: map[string][]byte{ - PipelinesAsCode_githubAppIdKey: []byte("12345"), - PipelinesAsCode_githubPrivateKey: []byte("private-key"), - "gitlab.token": []byte("glpat-token"), - }, - expectedGitProviderConfig: &pacv1alpha1.GitProvider{ - Secret: &pacv1alpha1.Secret{ - Name: gitopsprepare.PipelinesAsCodeSecretName, - Key: "gitlab.token", - }, - WebhookSecret: &pacv1alpha1.Secret{ - Name: PipelinesAsCodeWebhooksSecretName, - Key: GetWebhookSecretKeyForComponent(getComponent("https://gitlab.com/user/test-component-repository", nil)), - }, - URL: "https://gitlab.com", - }, - }, - { - name: "should create PaC repository for self-hosted GitLab webhook", - repoUrl: "https://gitlab.self-hosted.com/user/test-component-repository/", - componentAnnotations: map[string]string{ - GitProviderAnnotationName: "gitlab", - GitProviderAnnotationURL: "https://gitlab.self-hosted.com", - }, - pacConfig: map[string][]byte{ - "github.token": []byte("ghp_token"), - "gitlab.token": []byte("glpat-token"), - }, - expectedGitProviderConfig: &pacv1alpha1.GitProvider{ - Secret: &pacv1alpha1.Secret{ - Name: gitopsprepare.PipelinesAsCodeSecretName, - Key: "gitlab.token", - }, - WebhookSecret: &pacv1alpha1.Secret{ - Name: PipelinesAsCodeWebhooksSecretName, - Key: GetWebhookSecretKeyForComponent(getComponent("https://gitlab.self-hosted.com/user/test-component-repository/", nil)), - }, - URL: "https://gitlab.self-hosted.com", - }, - }, - { - name: "should create PaC repository for Github application on self-hosted Github", - repoUrl: "https://github.self-hosted.com/user/test-component-repository", - componentAnnotations: map[string]string{ - GitProviderAnnotationName: "github", - GitProviderAnnotationURL: "https://github.self-hosted.com", - }, - pacConfig: map[string][]byte{ - PipelinesAsCode_githubAppIdKey: []byte("12345"), - PipelinesAsCode_githubPrivateKey: []byte("private-key"), - }, - expectedGitProviderConfig: &pacv1alpha1.GitProvider{ - URL: "https://github.self-hosted.com", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - component := getComponent(tt.repoUrl, tt.componentAnnotations) - - pacRepo, err := GeneratePACRepository(component, tt.pacConfig) - - if err != nil { - t.Errorf("Failed to generate PaC repository object. Cause: %v", err) - } - - if pacRepo.Name != component.Name { - t.Errorf("Generated PaC repository must have the same name as corresponding component") - } - if pacRepo.Namespace != component.Namespace { - t.Errorf("Generated PaC repository must have the same namespace as corresponding component") - } - if len(pacRepo.Annotations) == 0 { - t.Errorf("Generated PaC repository must have annotations") - } - if pacRepo.Annotations["appstudio.openshift.io/component"] != component.Name { - t.Errorf("Generated PaC repository must have component annotation") - } - expectedRepo := strings.TrimSuffix(strings.TrimSuffix(tt.repoUrl, ".git"), "/") - if pacRepo.Spec.URL != expectedRepo { - t.Errorf("Wrong git repository URL in PaC repository: %s, want %s", pacRepo.Spec.URL, expectedRepo) - } - if !reflect.DeepEqual(pacRepo.Spec.GitProvider, tt.expectedGitProviderConfig) { - t.Errorf("Wrong git provider config in PaC repository: %#v, want %#v", pacRepo.Spec.GitProvider, tt.expectedGitProviderConfig) - } - }) - } -} - -func TestGetProviderTokenKey(t *testing.T) { - tests := []struct { - name string - provider string - want string - }{ - { - name: "check github key", - provider: "github", - want: "github.token", - }, - { - name: "check gitlab key", - provider: "gitlab", - want: "gitlab.token", - }, - { - name: "check bitbucket key", - provider: "bitbucket", - want: "bitbucket.token", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetProviderTokenKey(tt.provider); got != tt.want { - t.Errorf("Wrong git provider access token key: %s, want %s", got, tt.want) - } - }) - } -} - -func TestGetWebhookSecretKeyForComponent(t *testing.T) { - getComponent := func(repoUrl string) appstudiov1alpha1.Component { - return appstudiov1alpha1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcomponent", - Namespace: "workspace-name", - }, - Spec: appstudiov1alpha1.ComponentSpec{ - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: repoUrl, - }, - }, - }, - }, - } - } - - tests := []struct { - name string - component appstudiov1alpha1.Component - want string - }{ - { - name: "should return key for the url", - component: getComponent("https://github.com/user/test-component-repository"), - want: "https___github.com_user_test-component-repository", - }, - { - name: "should ignore .git suffix", - component: getComponent("https://github.com/user/test-component-repository.git"), - want: "https___github.com_user_test-component-repository", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetWebhookSecretKeyForComponent(tt.component); got != tt.want { - t.Errorf("Expected '%s', but got '%s'", tt.want, got) - } - }) - } -} - -func TestGetGitProvider(t *testing.T) { - getComponent := func(repoUrl, annotationValue string) appstudiov1alpha1.Component { - componentMeta := metav1.ObjectMeta{ - Name: "testcomponent", - Namespace: "workspace-name", - } - if annotationValue != "" { - componentMeta.Annotations = map[string]string{ - GitProviderAnnotationName: annotationValue, - } - } - - component := appstudiov1alpha1.Component{ - ObjectMeta: componentMeta, - Spec: appstudiov1alpha1.ComponentSpec{ - Source: appstudiov1alpha1.ComponentSource{ - ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ - GitSource: &appstudiov1alpha1.GitSource{ - URL: repoUrl, - }, - }, - }, - }, - } - return component - } - - tests := []struct { - name string - componentRepoUrl string - componentGitProviderAnnotation string - want string - expectError bool - }{ - { - name: "should detect github provider via http url", - componentRepoUrl: "https://github.com/user/test-component-repository", - want: "github", - }, - { - name: "should detect github provider via git url", - componentRepoUrl: "git@github.com:user/test-component-repository", - want: "github", - }, - { - name: "should detect gitlab provider via http url", - componentRepoUrl: "https://gitlab.com/user/test-component-repository", - want: "gitlab", - }, - { - name: "should detect gitlab provider via git url", - componentRepoUrl: "git@gitlab.com:user/test-component-repository", - want: "gitlab", - }, - { - name: "should detect bitbucket provider via http url", - componentRepoUrl: "https://bitbucket.org/user/test-component-repository", - want: "bitbucket", - }, - { - name: "should detect bitbucket provider via git url", - componentRepoUrl: "git@bitbucket.org:user/test-component-repository", - want: "bitbucket", - }, - { - name: "should detect github provider via annotation", - componentRepoUrl: "https://mydomain.com/user/test-component-repository", - componentGitProviderAnnotation: "github", - want: "github", - }, - { - name: "should detect gitlab provider via annotation", - componentRepoUrl: "https://mydomain.com/user/test-component-repository", - componentGitProviderAnnotation: "gitlab", - want: "gitlab", - }, - { - name: "should detect bitbucket provider via annotation", - componentRepoUrl: "https://mydomain.com/user/test-component-repository", - componentGitProviderAnnotation: "bitbucket", - want: "bitbucket", - }, - { - name: "should fail to detect git provider for self-hosted instance if annotation is not set", - componentRepoUrl: "https://mydomain.com/user/test-component-repository", - expectError: true, - }, - { - name: "should fail to detect git provider for self-hosted instance if annotation is set to invalid value", - componentRepoUrl: "https://mydomain.com/user/test-component-repository", - componentGitProviderAnnotation: "mylab", - expectError: true, - }, - { - name: "should fail to detect git provider component repository URL is invalid", - componentRepoUrl: "12345", - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - component := getComponent(tt.componentRepoUrl, tt.componentGitProviderAnnotation) - got, err := GetGitProvider(component) - if tt.expectError { - if err == nil { - t.Errorf("Detecting git provider for component with '%s' url and '%s' annotation value should fail", tt.componentRepoUrl, tt.componentGitProviderAnnotation) - } - } else { - if got != tt.want { - t.Errorf("Expected git provider is: %s, but got %s", tt.want, got) - } - } - }) - } -} - -func TestIsPaCApplicationConfigured(t *testing.T) { - tests := []struct { - name string - gitProvider string - config map[string][]byte - want bool - }{ - { - name: "should detect github application configured", - gitProvider: "github", - config: map[string][]byte{ - PipelinesAsCode_githubAppIdKey: []byte("12345"), - PipelinesAsCode_githubPrivateKey: []byte("private-key"), - }, - want: true, - }, - { - name: "should prefer github application if both github application and webhook configured", - gitProvider: "github", - config: map[string][]byte{ - PipelinesAsCode_githubAppIdKey: []byte("12345"), - PipelinesAsCode_githubPrivateKey: []byte("private-key"), - "github.token": []byte("ghp_token"), - }, - want: true, - }, - { - name: "should not detect application if it is not configured", - gitProvider: "github", - config: map[string][]byte{ - "github.token": []byte("ghp_token"), - }, - want: false, - }, - { - name: "should not detect application if configuration empty", - gitProvider: "github", - config: map[string][]byte{}, - want: false, - }, - { - name: "should not detect GitHub application if gilab webhook configured", - gitProvider: "gitlab", - config: map[string][]byte{ - PipelinesAsCode_githubAppIdKey: []byte("12345"), - PipelinesAsCode_githubPrivateKey: []byte("private-key"), - "gitlab.token": []byte("glpat-token"), - }, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := IsPaCApplicationConfigured(tt.gitProvider, tt.config); got != tt.want { - t.Errorf("want %t, but got %t", tt.want, got) - } - }) - } -} diff --git a/gitops/prepare/prepare.go b/gitops/prepare/prepare.go deleted file mode 100644 index e6dc6daef..000000000 --- a/gitops/prepare/prepare.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2021 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package prepare - -import ( - "context" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - // Pipelines as Code global configuration secret name - PipelinesAsCodeSecretName = "pipelines-as-code-secret" - // Pipelines as Code global configuration secret namespace - buildServiceNamespaceName = "build-service" -) - -// Holds data that needs to be queried from the cluster in order for the gitops generation function to work -// This struct is left here so more data can be added as needed -type GitopsConfig struct { - // Contains data from Pipelies as Code configuration k8s secret - PipelinesAsCodeCredentials map[string][]byte -} - -func PrepareGitopsConfig(ctx context.Context, cli client.Client, component appstudiov1alpha1.Component) GitopsConfig { - data := GitopsConfig{} - - data.PipelinesAsCodeCredentials = getPipelinesAsCodeConfigurationSecretData(ctx, cli, component) - - return data -} - -func getPipelinesAsCodeConfigurationSecretData(ctx context.Context, cli client.Client, component appstudiov1alpha1.Component) map[string][]byte { - pacSecret := &corev1.Secret{} - err := cli.Get(ctx, types.NamespacedName{Name: PipelinesAsCodeSecretName, Namespace: buildServiceNamespaceName}, pacSecret) - if err != nil { - return make(map[string][]byte) - } - return pacSecret.Data -} diff --git a/gitops/prepare/prepare_test.go b/gitops/prepare/prepare_test.go deleted file mode 100644 index 9ddad4fea..000000000 --- a/gitops/prepare/prepare_test.go +++ /dev/null @@ -1,135 +0,0 @@ -/* -Copyright 2021 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package prepare - -import ( - "context" - "reflect" - "testing" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - crclient "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestPrepareGitopsConfig(t *testing.T) { - - component := appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "myName", - Namespace: "myNamespace", - }, - } - tests := []struct { - name string - pacSecret corev1.Secret - want GitopsConfig - }{ - { - name: "should resolve the build bundle in case a configmap exists in the component's namespace", - pacSecret: corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: PipelinesAsCodeSecretName, - Namespace: buildServiceNamespaceName, - }, - Data: map[string][]byte{ - "github.token": []byte("ghp_token"), - }, - }, - want: GitopsConfig{ - PipelinesAsCodeCredentials: map[string][]byte{ - "github.token": []byte("ghp_token"), - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client := fake.NewClientBuilder().WithRuntimeObjects(&tt.pacSecret).Build() - if got := PrepareGitopsConfig(context.TODO(), client, component); !reflect.DeepEqual(got, tt.want) { - t.Errorf("PrepareGitopsConfig() = %v, want %v", got, tt.want) - } - }) - } - -} - -func TestGetPipelinesAsCodeConfigurationSecretData(t *testing.T) { - ctx := context.TODO() - - component := appstudiov1alpha1.Component{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "appstudio.redhat.com/v1alpha1", - Kind: "Component", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "myName", - Namespace: "myNamespace", - }, - } - - tests := []struct { - name string - data *corev1.Secret - want map[string][]byte - }{ - { - name: "secret exists", - data: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: buildServiceNamespaceName, - Name: PipelinesAsCodeSecretName, - }, - Data: map[string][]byte{ - "github.token": []byte("ghp_token"), - }, - }, - want: map[string][]byte{ - "github.token": []byte("ghp_token"), - }, - }, - { - name: "secret does not exist", - want: map[string][]byte{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var client crclient.WithWatch - client = fake.NewClientBuilder().Build() - if tt.data != nil { - client = fake.NewClientBuilder().WithRuntimeObjects(tt.data).Build() - } - - if got := getPipelinesAsCodeConfigurationSecretData(ctx, client, component); !reflect.DeepEqual(got, tt.want) { - t.Errorf("got %v, want %v", got, tt.want) - } - }) - } - -} diff --git a/go.mod b/go.mod index e859a319d..911fe5714 100644 --- a/go.mod +++ b/go.mod @@ -3,196 +3,84 @@ module github.com/redhat-appstudio/application-service go 1.19 require ( - github.com/brianvoe/gofakeit/v6 v6.9.0 - github.com/devfile/api/v2 v2.2.2 - github.com/devfile/library/v2 v2.2.2 github.com/go-logr/logr v1.4.1 - github.com/gofri/go-github-ratelimit v1.0.3-0.20230428184158-a500e14de53f - github.com/google/go-github/v59 v59.0.0 - github.com/hashicorp/go-multierror v1.1.1 github.com/konflux-ci/application-api v0.0.0-20240527211352-be061932d497 github.com/konflux-ci/operator-toolkit v0.0.0-20240402130556-ef6dcbeca69d - github.com/migueleliasweb/go-github-mock v0.0.23 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.27.10 - github.com/openshift-pipelines/pipelines-as-code v0.0.0-20220622161720-2a6007e17200 github.com/openshift/api v0.0.0-20220912161038-458ad9ca9ca5 - github.com/pact-foundation/pact-go/v2 v2.0.0-beta.23 - github.com/prometheus/client_golang v1.17.0 - github.com/redhat-appstudio/application-service/cdq-analysis v0.0.0 - github.com/redhat-appstudio/service-provider-integration-operator v0.2023.22-0.20230713080056-eae17aa8c172 - github.com/redhat-developer/gitops-generator v0.0.0-20231030195824-c48f5bf6bf21 - github.com/spf13/afero v1.11.0 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/oauth2 v0.16.0 k8s.io/api v0.26.10 k8s.io/apimachinery v0.27.7 k8s.io/client-go v0.26.10 sigs.k8s.io/controller-runtime v0.14.7 - sigs.k8s.io/yaml v1.3.0 ) require ( - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.23.3 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect - cloud.google.com/go/storage v1.36.0 // indirect - dario.cat/mergo v1.0.0 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.12.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect - github.com/aws/aws-sdk-go v1.44.298 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/containerd/containerd v1.7.13 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/devfile/alizer v1.4.0 // indirect - github.com/devfile/registry-support/index/generator v0.0.0-20240311135803-6215550f93d4 // indirect - github.com/devfile/registry-support/registry-library v0.0.0-20240404132339-d10fc19d3acc // indirect - github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v25.0.3+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v25.0.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.0 // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-metrics v0.0.1 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect - github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.14.1 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.11.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // 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/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.1 // indirect - github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/logutils v1.0.0 // indirect github.com/imdario/mergo v0.3.16 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.13.1 // indirect - github.com/moby/locker v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/redhat-appstudio/remote-secret v0.0.0-20230713072146-a6094c712436 // indirect - github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect - github.com/sergi/go-diff v1.3.1 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect - github.com/spf13/cobra v1.8.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/ulikunitz/xz v0.5.11 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.22.0 // indirect - golang.org/x/sync v0.6.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.19.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect - google.golang.org/api v0.155.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect - google.golang.org/grpc v1.62.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.26.10 // indirect k8s.io/component-base v0.26.10 // indirect - k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/pod-security-admission v0.26.10 // indirect k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect - knative.dev/pkg v0.0.0-20221011175852-714b7630a836 // indirect - oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) replace github.com/antlr/antlr4 => github.com/antlr/antlr4 v0.0.0-20211106181442-e4c1a74c66bd replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.11.0 -replace github.com/redhat-appstudio/application-service/cdq-analysis => ./cdq-analysis - replace github.com/Microsoft/hcsshim => github.com/Microsoft/hcsshim v0.12.2 diff --git a/go.sum b/go.sum index b5e14d82b..265e22d8d 100644 --- a/go.sum +++ b/go.sum @@ -1,270 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= -cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8= -cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.12.2 h1:AcXy+yfRvrx20g9v7qYaJv5Rh+8GaHOS6b8G6Wx/nKs= -github.com/Microsoft/hcsshim v0.12.2/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -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/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= -github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 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/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/brianvoe/gofakeit/v6 v6.9.0 h1:UCGhPCKLiqBc910TKS7LcOGf74NozftibFCbGIS6GZQ= -github.com/brianvoe/gofakeit/v6 v6.9.0/go.mod h1:palrJUk4Fyw38zIFB/uBZqsgzW5VsNllhHKKwAebzew= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -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/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= -github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= -github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= -github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU= github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -274,93 +22,31 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/devfile/alizer v1.4.0 h1:m1vifzRqeLVWmArwZvnUvZ1HXKCfcdkXbI0xPwuyjtk= -github.com/devfile/alizer v1.4.0/go.mod h1:5kjRnm61+eUU5AQaowgdvVif4oNTWvHqyymoZFga//s= -github.com/devfile/api/v2 v2.2.2 h1:DXRCPWFlZhTIE38Of2jzTRjQHadfbxBC8GS+m+EjoCU= -github.com/devfile/api/v2 v2.2.2/go.mod h1:qp8jcw12y1JdCsxjK/7LJ7uWaJOxcY1s2LUk5PhbkbM= -github.com/devfile/library/v2 v2.2.2 h1:iLtFQ16aYMcB+vUx7NKtKPiTEursxwueu6/qOailubA= -github.com/devfile/library/v2 v2.2.2/go.mod h1:LHgAu9VApI++hE+cr6CWrkj1OlzHOJeKbraqC5PxSec= -github.com/devfile/registry-support/index/generator v0.0.0-20240311135803-6215550f93d4 h1:t09mGdy31tC2YBp6kVD7x8m0jq1CyBUSYMUvrF0iaWw= -github.com/devfile/registry-support/index/generator v0.0.0-20240311135803-6215550f93d4/go.mod h1:3Ngbmm12LW03tAEHpDNymM7zryd5H1Xo3ZAGlBpecf8= -github.com/devfile/registry-support/registry-library v0.0.0-20240404132339-d10fc19d3acc h1:TuOJ37ZnFJtHh55Yi9spX/+sNxYBjDgH2bmQNIyRMeE= -github.com/devfile/registry-support/registry-library v0.0.0-20240404132339-d10fc19d3acc/go.mod h1:2RRLQaOYuzh8n59euz6Bu60hFoX/LLVM9uzFOFDyOZM= -github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= -github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= -github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= -github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= -github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -375,38 +61,16 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -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/gofri/go-github-ratelimit v1.0.3-0.20230428184158-a500e14de53f h1:Kzf4F+3yVpgqLvgp8IM4BwHwCiPA5wghoHhCdR8Fcpw= -github.com/gofri/go-github-ratelimit v1.0.3-0.20230428184158-a500e14de53f/go.mod h1:OnCi5gV+hAG/LMR7llGhU7yHt44se9sYgKPnafoL7RY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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= @@ -414,152 +78,47 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W 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.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -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 v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 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.4.1/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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -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/go-github/v59 v59.0.0 h1:7h6bgpF5as0YQLLkEiVqpgtJqjimMYhBkD4jT5aN3VA= -github.com/google/go-github/v59 v59.0.0/go.mod h1:rJU4R0rQHFVFDOkqGWxfLNo6vEk4dv40oDjhV/gH6wM= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.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/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/pprof v0.0.0-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= -github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= -github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 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.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konflux-ci/application-api v0.0.0-20240527211352-be061932d497 h1:Xv063qqQY8tPNYvx4+hTtWw4DPDAVa4A1SJibvPBsS4= github.com/konflux-ci/application-api v0.0.0-20240527211352-be061932d497/go.mod h1:948Z+a1IbfRT0RtoHzWWSN9YEucSbMJTHaMhz7dVICc= github.com/konflux-ci/operator-toolkit v0.0.0-20240402130556-ef6dcbeca69d h1:z7j3mglNoXvIrw5Vz/Ul+izoITRaqYURPIWrFoEyHgI= github.com/konflux-ci/operator-toolkit v0.0.0-20240402130556-ef6dcbeca69d/go.mod h1:AcChx7FjpYSIkDvQgaUKyauuF0PXm3ivB5MqZSC9Eis= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -568,49 +127,24 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/migueleliasweb/go-github-mock v0.0.23 h1:GOi9oX/+Seu9JQ19V8bPDLqDI7M9iEOjo3g8v1k6L2c= -github.com/migueleliasweb/go-github-mock v0.0.23/go.mod h1:NsT8FGbkvIZQtDu38+295sZEX8snaUiiQgsGxi6GUxk= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/buildkit v0.13.1 h1:L8afOFhPq2RPJJSr/VyzbufwID7jquZVB7oFHbPRcPE= -github.com/moby/buildkit v0.13.1/go.mod h1:aNmNQKLBFYAOFuzQjR3VA27/FijlvtBD1pjNwTSN37k= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= 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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -628,86 +162,35 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/openshift-pipelines/pipelines-as-code v0.0.0-20220622161720-2a6007e17200 h1:zu9Z85uKHVMEg/nAd0J9xNLUnu6C3mtRf5l7xJiTilM= -github.com/openshift-pipelines/pipelines-as-code v0.0.0-20220622161720-2a6007e17200/go.mod h1:C2xZcwv55w1W74LjkEN7NRp2z0UOj0/VXMi8ZuNBPGI= github.com/openshift/api v0.0.0-20220912161038-458ad9ca9ca5 h1:M42Vl3QcuzhX4IgRNIQU+ACdd8/ZTya3GvGWwh4Gl6M= github.com/openshift/api v0.0.0-20220912161038-458ad9ca9ca5/go.mod h1:LEnw1IVscIxyDnltE3Wi7bQb/QzIM8BfPNKoGA1Qlxw= github.com/openshift/build-machinery-go v0.0.0-20211213093930-7e33a7eb4ce3/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= -github.com/pact-foundation/pact-go/v2 v2.0.0-beta.23 h1:C+VVrGscTLUFfqm8yr2rvoeS5+zQkjT50oO4qBO1j5s= -github.com/pact-foundation/pact-go/v2 v2.0.0-beta.23/go.mod h1:y8gF0j+X2sKd2wyyXeZn0AW0JtHeki+1Rmpu8hl0ytw= -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/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/redhat-appstudio/remote-secret v0.0.0-20230713072146-a6094c712436 h1:6XYknDslXDSVpNqEKnI+gKj3Uu4afK2S+IIH3KogX1E= -github.com/redhat-appstudio/remote-secret v0.0.0-20230713072146-a6094c712436/go.mod h1:QlcxNg0q63QFGr9ceI3dH7a7vO6gGh+2TJrbTiMf9DE= -github.com/redhat-appstudio/service-provider-integration-operator v0.2023.22-0.20230713080056-eae17aa8c172 h1:Zibq2RBTzcoDgZcTfM5k2o1qrKuix3YPtYQ/NuP039Y= -github.com/redhat-appstudio/service-provider-integration-operator v0.2023.22-0.20230713080056-eae17aa8c172/go.mod h1:FGHbzLSIpXZKFVKKQr1sSrgN3+wBpJw0wTTjTRNNm+k= -github.com/redhat-developer/gitops-generator v0.0.0-20231030195824-c48f5bf6bf21 h1:qEcliyKbhx9bElsJXiO1yLf3hwdwmrz5z+aZRD10QRk= -github.com/redhat-developer/gitops-generator v0.0.0-20231030195824-c48f5bf6bf21/go.mod h1:2L3ozspkvZiHEvzEV5rKRnSRhU+Ox+7UT1Tz+/sTldk= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= -github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -716,52 +199,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -771,552 +214,120 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 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/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -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/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.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-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 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= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= -google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= @@ -1326,34 +337,25 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1364,14 +366,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I= k8s.io/api v0.26.10 h1:skTnrDR0r8dg4MMLf6YZIzugxNM0BjFsWKPkNc5kOvk= k8s.io/api v0.26.10/go.mod h1:ou/H3yviqrHtP/DSPVTfsc7qNfmU06OhajytJfYXkXw= @@ -1387,8 +383,6 @@ k8s.io/component-base v0.26.10 h1:vl3Gfe5aC09mNxfnQtTng7u3rnBVrShOK3MAkqEleb0= k8s.io/component-base v0.26.10/go.mod h1:/IDdENUHG5uGxqcofZajovYXE9KSPzJ4yQbkYQt7oN0= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= @@ -1397,19 +391,10 @@ k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/pod-security-admission v0.26.10 h1:D2MF9JbMRu3pB7Onx26DHm6MHJRh3s6ZK0UKoRRD2to= -k8s.io/pod-security-admission v0.26.10/go.mod h1:AurbRHBkqh8GSj+nDgsY0NLefkiGCmZJbzMJXQZpte8= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -knative.dev/pkg v0.0.0-20221011175852-714b7630a836 h1:0N7Zo/O+xeUUebJPm9keBaGclrUoEbljr3J1MsqtaIM= -knative.dev/pkg v0.0.0-20221011175852-714b7630a836/go.mod h1:DMTRDJ5WRxf/DrlOPzohzfhSuJggscLZ8EavOq9O/x8= -oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= -oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= diff --git a/main.go b/main.go index 235b7a3f6..d6a0fc3ef 100644 --- a/main.go +++ b/main.go @@ -19,21 +19,14 @@ package main import ( "crypto/tls" "flag" - "fmt" "log" "net/http" "os" - "path/filepath" - "strconv" - - spiapi "github.com/redhat-appstudio/service-provider-integration-operator/api/v1beta1" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. "go.uber.org/zap/zapcore" _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" "github.com/konflux-ci/operator-toolkit/webhook" "k8s.io/apimachinery/pkg/runtime" @@ -46,13 +39,7 @@ import ( routev1 "github.com/openshift/api/route/v1" appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "github.com/redhat-appstudio/application-service/controllers" - "github.com/redhat-appstudio/application-service/controllers/webhooks" - "github.com/redhat-appstudio/application-service/pkg/availability" - "github.com/redhat-appstudio/application-service/pkg/github" - "github.com/redhat-appstudio/application-service/pkg/spi" - "github.com/redhat-appstudio/application-service/pkg/util/ioutils" + "github.com/redhat-appstudio/application-service/webhooks" // Enable pprof for profiling /* #nosec G108 -- debug code */ @@ -70,8 +57,6 @@ func init() { utilruntime.Must(appstudiov1alpha1.AddToScheme(scheme)) - utilruntime.Must(spiapi.AddToScheme(scheme)) - //+kubebuilder:scaffold:scheme } @@ -128,92 +113,6 @@ func main() { os.Exit(1) } - // Retrieve the option to specify a custom devfile registry - devfileRegistryURL := os.Getenv("DEVFILE_REGISTRY_URL") - if devfileRegistryURL == "" { - devfileRegistryURL = cdqanalysis.DevfileRegistryEndpoint - } - - // Retrieve the option to specify a cdq-analysis image - cdqAnalysisImage := os.Getenv("CDQ_ANALYSIS_IMAGE") - if cdqAnalysisImage == "" { - cdqAnalysisImage = cdqanalysis.CDQAnalysisImage - } - - // Retrieve the option to run cdq analysis with a k8s job - var RunKubernetesJob bool - runK8SJobCDQStr := os.Getenv("RUN_K8S_JOB_CDQ") - if runK8SJobCDQStr == "" { - RunKubernetesJob = false - } else { - RunKubernetesJob, err = strconv.ParseBool(runK8SJobCDQStr) - if err != nil { - setupLog.Info(fmt.Sprintf("unable to parse bool value from ENV $RUN_K8S_JOB_CDQ: %v, run go module instead ", err)) - RunKubernetesJob = false - } - } - - // Parse any passed in tokens and set up a client for handling the github tokens - err = github.ParseGitHubTokens() - if err != nil { - setupLog.Error(err, "unable to set up github tokens") - os.Exit(1) - } - ghTokenClient := github.GitHubTokenClient{} - setupLog.Info(fmt.Sprintf("There are %v token(s) available", len(github.Clients))) - - if err = (&controllers.ApplicationReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("Application"), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Application") - os.Exit(1) - } - if err = (&controllers.ComponentReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("Component"), - AppFS: ioutils.NewFilesystem(), - GitHubTokenClient: ghTokenClient, - SPIClient: spi.SPIClient{ - K8sClient: mgr.GetClient(), - }, - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Component") - os.Exit(1) - } - config, err := rest.InClusterConfig() - if err != nil { - // Couldn't find an InClusterConfig, may be running outside of Kube, so try to find a local kube config file - var kubeconfig string - if os.Getenv("KUBECONFIG") != "" { - kubeconfig = os.Getenv("KUBECONFIG") - } else { - kubeconfig = filepath.Join(os.Getenv("HOME"), ".kube", "config") - } - config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - setupLog.Error(err, "Unable to retrieve Kubernetes InClusterConfig") - os.Exit(1) - } - } - if err = (&controllers.ComponentDetectionQueryReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("ComponentDetectionQuery"), - GitHubTokenClient: ghTokenClient, - DevfileRegistryURL: devfileRegistryURL, - AppFS: ioutils.NewFilesystem(), - CdqAnalysisImage: cdqAnalysisImage, - RunKubernetesJob: RunKubernetesJob, - Config: config, - CDQUtil: cdqanalysis.NewCDQUtilClient(), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "ComponentDetectionQuery") - os.Exit(1) - } - if os.Getenv("ENABLE_WEBHOOKS") != "false" { setupLog.Info("setting up webhooks") setUpWebhooks(mgr) @@ -230,12 +129,6 @@ func main() { os.Exit(1) } - availabilityChecker := &availability.AvailabilityWatchdog{GitHubTokenClient: ghTokenClient} - if err := mgr.Add(availabilityChecker); err != nil { - setupLog.Error(err, "unable to set up availability checks") - os.Exit(1) - } - setupLog.Info("starting manager") if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") diff --git a/pkg/availability/git.go b/pkg/availability/git.go deleted file mode 100644 index 60b747011..000000000 --- a/pkg/availability/git.go +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package availability - -import ( - "context" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/redhat-appstudio/application-service/pkg/github" - "github.com/redhat-appstudio/application-service/pkg/metrics" - ctrl "sigs.k8s.io/controller-runtime" -) - -// Inspired from https://github.com/konflux-ci/remote-secret/blob/main/pkg/availability/storage_watchdog.go - -type AvailabilityWatchdog struct { - GitHubTokenClient github.GitHubToken -} - -func (r *AvailabilityWatchdog) Start(ctx context.Context) error { - // Check every 20 minutes - ticker := time.NewTicker(20 * time.Minute) - go func() { - for { - // make call immediately to avoid waiting for the first tick - r.checkAvailability(ctx) - select { - case <-ticker.C: - continue - case <-ctx.Done(): - ticker.Stop() - return - } - } - }() - return nil -} - -func (r *AvailabilityWatchdog) checkAvailability(ctx context.Context) { - log := ctrl.LoggerFrom(ctx) - checkGitLabel := prometheus.Labels{"check": "github"} - - ghClient, err := r.GitHubTokenClient.GetNewGitHubClient("") - if err != nil { - log.Error(err, "Unable to create Go-GitHub client due to error, marking HAS availability as down") - metrics.HASAvailabilityGauge.With(checkGitLabel).Set(0) - } - - isGitAvailable, err := ghClient.GetGitStatus(ctx) - if !isGitAvailable { - log.Error(err, "Unable to create Go-GitHub client due to error, marking HAS availability as down") - metrics.HASAvailabilityGauge.With(checkGitLabel).Set(0) - } else { - log.Info("HAS is marked as available, Git check has passed...") - metrics.HASAvailabilityGauge.With(checkGitLabel).Set(1) - } -} diff --git a/pkg/availability/git_test.go b/pkg/availability/git_test.go deleted file mode 100644 index 204627dd3..000000000 --- a/pkg/availability/git_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package availability - -import ( - "context" - "testing" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/redhat-appstudio/application-service/pkg/github" - "github.com/redhat-appstudio/application-service/pkg/metrics" - "github.com/stretchr/testify/assert" -) - -func TestCheckAvailability(t *testing.T) { - - t.Run("check availability", func(t *testing.T) { - checkGitLabel := prometheus.Labels{"check": "github"} - r := AvailabilityWatchdog{ - GitHubTokenClient: github.MockGitHubTokenClient{}, - } - - r.checkAvailability(context.TODO()) - - assert.Equal(t, 1.0, testutil.ToFloat64(metrics.HASAvailabilityGauge.With(checkGitLabel))) - }) -} diff --git a/pkg/devfile/constants.go b/pkg/devfile/constants.go deleted file mode 100644 index 6d14327f5..000000000 --- a/pkg/devfile/constants.go +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package devfile - -const ( - // RouteKey is the key to reference route - RouteKey = "deployment/route" - - // ReplicaKey is the key to reference replica - ReplicaKey = "deployment/replicas" - - // StorageLimitKey is the key to reference storage limit - StorageLimitKey = "deployment/storageLimit" - - // StorageRequestKey is the key to reference storage request - StorageRequestKey = "deployment/storageRequest" - - // CpuLimitKey is the key to reference cpu limit - CpuLimitKey = "deployment/cpuLimit" - - // CpuRequestKey is the key to reference cpu request - CpuRequestKey = "deployment/cpuRequest" - - // MemoryLimitKey is the key to reference memory limit - MemoryLimitKey = "deployment/memoryLimit" - - // MemoryRequestKey is the key to reference memory request - MemoryRequestKey = "deployment/memoryRequest" - - // ContainerImagePortKey is the key to reference container image port - ContainerImagePortKey = "deployment/container-port" - - // ContainerENVKey is the key to reference container environment variables - ContainerENVKey = "deployment/containerENV" -) diff --git a/pkg/devfile/devfile.go b/pkg/devfile/devfile.go deleted file mode 100644 index 4ec699bb0..000000000 --- a/pkg/devfile/devfile.go +++ /dev/null @@ -1,371 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package devfile - -import ( - "strconv" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/api/v2/pkg/devfile" - "github.com/devfile/library/v2/pkg/devfile/generator" - data "github.com/devfile/library/v2/pkg/devfile/parser/data" - "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - - routev1 "github.com/openshift/api/route/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/yaml" -) - -// GetIngressFromEndpoint gets an ingress resource from the devfile endpoint information -func GetIngressFromEndpoint(name, serviceName, port, path string, secure bool, annotations map[string]string, hostname string) (networkingv1.Ingress, error) { - - if path == "" { - path = "/" - } - - implementationSpecific := networkingv1.PathTypeImplementationSpecific - - portNumber, err := strconv.ParseInt(port, 10, 32) - if err != nil { - return networkingv1.Ingress{}, err - } - - ingress := networkingv1.Ingress{ - TypeMeta: generator.GetTypeMeta("Ingress", "networking.k8s.io/v1"), - ObjectMeta: generator.GetObjectMeta(name, "", nil, annotations), - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - Host: hostname, - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: path, - PathType: &implementationSpecific, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: serviceName, - Port: networkingv1.ServiceBackendPort{ - Number: int32(portNumber), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - return ingress, nil -} - -// GetRouteFromEndpoint gets the route resource -func GetRouteFromEndpoint(name, serviceName, port, path string, secure bool, annotations map[string]string) routev1.Route { - - if path == "" { - path = "/" - } - - routeParams := generator.RouteParams{ - ObjectMeta: generator.GetObjectMeta(name, "", nil, nil), - TypeMeta: generator.GetTypeMeta("Route", "route.openshift.io/v1"), - RouteSpecParams: generator.RouteSpecParams{ - ServiceName: serviceName, - PortNumber: intstr.FromString(port), - Path: path, - Secure: secure, - }, - } - - return *generator.GetRoute(v1alpha2.Endpoint{Annotations: annotations}, routeParams) -} - -// ConvertApplicationToDevfile takes in a given Application CR and converts it to -// a devfile object -func ConvertApplicationToDevfile(hasApp appstudiov1alpha1.Application) (data.DevfileData, error) { - devfileVersion := string(data.APISchemaVersion220) - devfileData, err := data.NewDevfileData(devfileVersion) - if err != nil { - return nil, err - } - - devfileData.SetSchemaVersion(devfileVersion) - - devfileData.SetMetadata(devfile.DevfileMetadata{ - Name: hasApp.Spec.DisplayName, - Description: hasApp.Spec.Description, - }) - - return devfileData, nil -} - -func ConvertImageComponentToDevfile(comp appstudiov1alpha1.Component) (data.DevfileData, error) { - devfileVersion := string(data.APISchemaVersion220) - devfileData, err := data.NewDevfileData(devfileVersion) - if err != nil { - return nil, err - } - - devfileData.SetSchemaVersion(devfileVersion) - devfileData.SetMetadata(devfile.DevfileMetadata{ - Name: comp.Spec.ComponentName, - }) - - deploymentTemplate := GenerateDeploymentTemplate(comp.Name, comp.Spec.Application, comp.Spec.ContainerImage) - deploymentTemplateBytes, err := yaml.Marshal(deploymentTemplate) - if err != nil { - return nil, err - } - - // Generate a stub container component for the devfile - components := []v1alpha2.Component{ - { - Name: "kubernetes-deploy", - ComponentUnion: v1alpha2.ComponentUnion{ - Kubernetes: &v1alpha2.KubernetesComponent{ - K8sLikeComponent: v1alpha2.K8sLikeComponent{ - K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{ - Inlined: string(deploymentTemplateBytes), - }, - }, - }, - }, - }, - } - - err = devfileData.AddComponents(components) - if err != nil { - return nil, err - } - - return devfileData, nil -} - -// CreateDevfileForDockerfileBuild creates a devfile with the Dockerfile uri and build context -func CreateDevfileForDockerfileBuild(dockerfileUri, buildContext, name, application string) (data.DevfileData, error) { - devfileVersion := string(data.APISchemaVersion220) - devfileData, err := data.NewDevfileData(devfileVersion) - if err != nil { - return nil, err - } - - devfileData.SetSchemaVersion(devfileVersion) - - devfileData.SetMetadata(devfile.DevfileMetadata{ - Name: "dockerfile-component", - Description: "Basic Devfile for a Dockerfile Component", - }) - - deploymentTemplate := GenerateDeploymentTemplate(name, application, "") - deploymentTemplateBytes, err := yaml.Marshal(deploymentTemplate) - if err != nil { - return nil, err - } - - components := []v1alpha2.Component{ - { - Name: "dockerfile-build", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: dockerfileUri, - }, - Dockerfile: v1alpha2.Dockerfile{ - BuildContext: buildContext, - }, - }, - }, - }, - }, - }, - }, - { - Name: "kubernetes-deploy", - ComponentUnion: v1alpha2.ComponentUnion{ - Kubernetes: &v1alpha2.KubernetesComponent{ - K8sLikeComponent: v1alpha2.K8sLikeComponent{ - K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{ - Inlined: string(deploymentTemplateBytes), - }, - }, - }, - }, - }, - } - err = devfileData.AddComponents(components) - if err != nil { - return nil, err - } - - commands := []v1alpha2.Command{ - { - Id: "build-image", - CommandUnion: v1alpha2.CommandUnion{ - Apply: &v1alpha2.ApplyCommand{ - Component: "dockerfile-build", - }, - }, - }, - } - err = devfileData.AddCommands(commands) - if err != nil { - return nil, err - } - - return devfileData, nil -} - -// GenerateDeploymentTemplate generates a deployment template with the information passed -func GenerateDeploymentTemplate(name, application, image string) appsv1.Deployment { - - k8sLabels := generateK8sLabels(name, application) - matchLabels := getMatchLabel(name) - - containerImage := "image" - if image != "" { - containerImage = image - } - - deployment := appsv1.Deployment{ - TypeMeta: v1.TypeMeta{ - Kind: "Deployment", - APIVersion: "apps/v1", - }, - ObjectMeta: v1.ObjectMeta{ - Name: name, - Labels: k8sLabels, - }, - Spec: appsv1.DeploymentSpec{ - Selector: &v1.LabelSelector{ - MatchLabels: matchLabels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: v1.ObjectMeta{ - Labels: matchLabels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "container-image", - Image: containerImage, - ImagePullPolicy: corev1.PullAlways, - }, - }, - }, - }, - }, - } - - return deployment - -} - -func generateK8sLabels(name, application string) map[string]string { - return map[string]string{ - "app.kubernetes.io/name": name, - "app.kubernetes.io/instance": name, - "app.kubernetes.io/part-of": application, - "app.kubernetes.io/managed-by": "kustomize", - "app.kubernetes.io/created-by": "application-service", - } -} - -// GetMatchLabel returns the label selector that will be used to tie deployments, services, and pods together -// For cleanliness, using just one unique label from the generateK8sLabels function -func getMatchLabel(name string) map[string]string { - return map[string]string{ - "app.kubernetes.io/instance": name, - } -} - -// FindAndDownloadDevfile downloads devfile from the various possible devfile locations in dir and returns the contents and its context -func FindAndDownloadDevfile(dir, token string) ([]byte, string, error) { - var devfileBytes []byte - var err error - - for _, path := range cdqanalysis.ValidDevfileLocations { - devfilePath := dir + "/" + path - devfileBytes, err = DownloadFile(devfilePath, token) - if err == nil { - // if we get a 200, return - return devfileBytes, path, err - } - } - - return nil, "", &cdqanalysis.NoDevfileFound{Location: dir} -} - -// FindAndDownloadDockerfile downloads Dockerfile from the various possible Dockerfile, or Containerfile locations in dir and returns the contents and its context -func FindAndDownloadDockerfile(dir, token string) ([]byte, string, error) { - var dockerfileBytes []byte - var err error - // Containerfile is an alternate name for Dockerfile - - for _, path := range cdqanalysis.ValidDockerfileLocations { - dockerfilePath := dir + "/" + path - dockerfileBytes, err = DownloadFile(dockerfilePath, token) - if err == nil { - // if we get a 200, return - return dockerfileBytes, path, err - } - } - - return nil, "", &cdqanalysis.NoDockerfileFound{Location: dir} -} - -// DownloadFile downloads the specified file -func DownloadFile(file, token string) ([]byte, error) { - return cdqanalysis.CurlEndpoint(file, token) -} - -// UpdateLocalDockerfileURItoAbsolute takes in a Devfile, and a DockefileURL, and returns back a Devfile with any local URIs to the Dockerfile updates to be absolute -func UpdateLocalDockerfileURItoAbsolute(devfile data.DevfileData, dockerfileURL string) (data.DevfileData, error) { - devfileComponents, err := devfile.GetComponents(common.DevfileOptions{ComponentOptions: common.ComponentOptions{ - ComponentType: v1alpha2.ImageComponentType, - }}) - if err != nil { - return nil, err - } - - for _, comp := range devfileComponents { - if comp.Image != nil && comp.Image.Dockerfile != nil { - comp.Image.Dockerfile.Uri = dockerfileURL - - // Update the component in the devfile - err = devfile.UpdateComponent(comp) - if err != nil { - return nil, err - } - } - } - - return devfile, err -} diff --git a/pkg/devfile/devfile_test.go b/pkg/devfile/devfile_test.go deleted file mode 100644 index 6a08c3e12..000000000 --- a/pkg/devfile/devfile_test.go +++ /dev/null @@ -1,790 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package devfile - -import ( - "reflect" - "testing" - - "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" - "github.com/devfile/api/v2/pkg/devfile" - data "github.com/devfile/library/v2/pkg/devfile/parser/data" - v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2" - "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common" - appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/yaml" - - routev1 "github.com/openshift/api/route/v1" - networkingv1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestConvertApplicationToDevfile(t *testing.T) { - tests := []struct { - name string - hasApp appstudiov1alpha1.Application - wantDevfile *v2.DevfileV2 - }{ - { - name: "Simple HASApp CR", - hasApp: appstudiov1alpha1.Application{ - Spec: appstudiov1alpha1.ApplicationSpec{ - DisplayName: "Petclinic", - }, - }, - wantDevfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "Petclinic", - }, - }, - }, - }, - }, - { - name: "HASApp CR with branch and context fields set", - hasApp: appstudiov1alpha1.Application{ - Spec: appstudiov1alpha1.ApplicationSpec{ - DisplayName: "Petclinic", - }, - }, - wantDevfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "Petclinic", - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Convert the hasApp resource to a devfile - convertedDevfile, err := ConvertApplicationToDevfile(tt.hasApp) - if err != nil { - t.Errorf("TestConvertApplicationToDevfile() unexpected error: %v", err) - } else if !reflect.DeepEqual(convertedDevfile, tt.wantDevfile) { - t.Errorf("TestConvertApplicationToDevfile() error: expected %v got %v", tt.wantDevfile, convertedDevfile) - } - }) - } -} - -func TestConvertImageComponentToDevfile(t *testing.T) { - - compName := "component" - applicationName := "application" - image := "image" - - deploymentTemplate := GenerateDeploymentTemplate(compName, applicationName, image) - deploymentTemplateBytes, err := yaml.Marshal(deploymentTemplate) - if err != nil { - t.Errorf("TestConvertImageComponentToDevfile() unexpected error: %v", err) - return - } - - tests := []struct { - name string - comp appstudiov1alpha1.Component - wantDevfile *v2.DevfileV2 - }{ - { - name: "Simple Component CR", - comp: appstudiov1alpha1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: compName, - }, - Spec: appstudiov1alpha1.ComponentSpec{ - ComponentName: compName, - ContainerImage: image, - Application: applicationName, - }, - }, - wantDevfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: compName, - }, - }, - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - Name: "kubernetes-deploy", - ComponentUnion: v1alpha2.ComponentUnion{ - Kubernetes: &v1alpha2.KubernetesComponent{ - K8sLikeComponent: v1alpha2.K8sLikeComponent{ - K8sLikeComponentLocation: v1alpha2.K8sLikeComponentLocation{ - Inlined: string(deploymentTemplateBytes), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Convert the hasApp resource to a devfile - convertedDevfile, err := ConvertImageComponentToDevfile(tt.comp) - if err != nil { - t.Errorf("TestConvertImageComponentToDevfile() unexpected error: %v", err) - } else if !reflect.DeepEqual(convertedDevfile, tt.wantDevfile) { - t.Errorf("TestConvertImageComponentToDevfile() error: expected %v got %v", tt.wantDevfile, convertedDevfile) - } - }) - } -} - -func TestFindAndDownloadDevfile(t *testing.T) { - tests := []struct { - name string - url string - wantDevfileContext string - wantErr bool - }{ - { - name: "Curl devfile.yaml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case1", - wantDevfileContext: "devfile.yaml", - }, - { - name: "Curl .devfile.yaml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case2", - wantDevfileContext: ".devfile.yaml", - }, - { - name: "Curl devfile.yml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case3", - wantDevfileContext: "devfile.yml", - }, - { - name: "Curl .devfile.yml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case4", - wantDevfileContext: ".devfile.yml", - }, - { - name: "Curl .devfile/devfile.yaml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case5", - wantDevfileContext: ".devfile/devfile.yaml", - }, - { - name: "Curl .devfile/.devfile.yaml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case6", - wantDevfileContext: ".devfile/.devfile.yaml", - }, - { - name: "Curl .devfile/devfile.yml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case7", - wantDevfileContext: ".devfile/devfile.yml", - }, - { - name: "Curl .devfile/.devfile.yml", - url: "https://raw.githubusercontent.com/devfile-resources/devfile-priority/main/case8", - wantDevfileContext: ".devfile/.devfile.yml", - }, - { - name: "Cannot curl for a devfile", - url: "https://github.com/octocat/Hello-World", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - contents, devfileContext, err := FindAndDownloadDevfile(tt.url, "") - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if err == nil && contents == nil { - t.Errorf("unable to read body") - } else if err == nil && (devfileContext != tt.wantDevfileContext) { - t.Errorf("devfile context did not match, got %v, wanted %v", devfileContext, tt.wantDevfileContext) - } - }) - } -} - -func TestFindAndDownloadDockerfile(t *testing.T) { - tests := []struct { - name string - url string - wantDockerfileContext string - wantErr bool - }{ - { - name: "Curl Dockerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case1", - wantDockerfileContext: "Dockerfile", - }, - { - name: "Curl docker/Dockerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case2", - wantDockerfileContext: "docker/Dockerfile", - }, - { - name: "Curl .docker/Dockerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case3", - wantDockerfileContext: ".docker/Dockerfile", - }, - { - name: "Curl build/Dockerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case4", - wantDockerfileContext: "build/Dockerfile", - }, - { - name: "Curl Containerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case5", - wantDockerfileContext: "Containerfile", - }, - { - name: "Curl docker/Containerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case6", - wantDockerfileContext: "docker/Containerfile", - }, - { - name: "Curl .docker/Containerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case7", - wantDockerfileContext: ".docker/Containerfile", - }, - { - name: "Curl build/Containerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case8", - wantDockerfileContext: "build/Containerfile", - }, - { - name: "Curl dockerfile", - url: "https://raw.githubusercontent.com/devfile-resources/dockerfile-priority/main/case9", - wantDockerfileContext: "dockerfile", - }, - { - name: "Cannot curl for a Dockerfile or a Containerfile", - url: "https://github.com/octocat/Hello-World", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - contents, dockerfileContext, err := FindAndDownloadDockerfile(tt.url, "") - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else if err == nil && contents == nil { - t.Errorf("unable to read body") - } else if err == nil && (dockerfileContext != tt.wantDockerfileContext) { - t.Errorf("Dockerfile context did not match, got %v, wanted %v", dockerfileContext, tt.wantDockerfileContext) - } - }) - } -} - -func TestCreateDevfileForDockerfileBuild(t *testing.T) { - tests := []struct { - name string - uri string - context string - wantErr bool - }{ - { - name: "Set Dockerfile Uri and Context", - uri: "dockerfile/uri", - context: "context", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotDevfile, err := CreateDevfileForDockerfileBuild(tt.uri, tt.context, "", "") - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error %v", err) - } else { - // Devfile Metadata - metadata := gotDevfile.GetMetadata() - assert.Equal(t, "dockerfile-component", metadata.Name, "Devfile metadata name should be equal") - assert.Equal(t, "Basic Devfile for a Dockerfile Component", metadata.Description, "Devfile metadata description should be equal") - - // Kubernetes Component - if kubernetesComponents, err := gotDevfile.GetComponents(common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: v1alpha2.KubernetesComponentType, - }, - }); err != nil { - t.Errorf("unexpected error %v", err) - } else if len(kubernetesComponents) != 1 { - t.Error("expected 1 Kubernetes component") - } else { - assert.Equal(t, "kubernetes-deploy", kubernetesComponents[0].Name, "component name should be equal") - assert.Contains(t, kubernetesComponents[0].Kubernetes.Inlined, "Deployment", "the inlined content should contain deployment") - } - - // Image Component - if imageComponents, err := gotDevfile.GetComponents(common.DevfileOptions{ - ComponentOptions: common.ComponentOptions{ - ComponentType: v1alpha2.ImageComponentType, - }, - }); err != nil { - t.Errorf("unexpected error %v", err) - return - } else if len(imageComponents) != 1 { - t.Error("expected 1 image component") - } else { - assert.Equal(t, "dockerfile-build", imageComponents[0].Name, "component name should be equal") - assert.NotNil(t, imageComponents[0].Image, "Image component should not be nil") - assert.NotNil(t, imageComponents[0].Image.Dockerfile, "Dockerfile Image component should not be nil") - assert.Equal(t, tt.uri, imageComponents[0].Image.Dockerfile.DockerfileSrc.Uri, "Dockerfile uri should be equal") - assert.Equal(t, tt.context, imageComponents[0].Image.Dockerfile.Dockerfile.BuildContext, "Dockerfile context should be equal") - } - - // Apply Command - if applyCommands, err := gotDevfile.GetCommands(common.DevfileOptions{ - CommandOptions: common.CommandOptions{ - CommandType: v1alpha2.ApplyCommandType, - }, - }); err != nil { - t.Errorf("unexpected error %v", err) - return - } else if len(applyCommands) != 1 { - t.Error("expected 1 apply command") - } else { - assert.Equal(t, "build-image", applyCommands[0].Id, "command id should be equal") - assert.NotNil(t, applyCommands[0].Apply, "Apply command should not be nil") - assert.Equal(t, "dockerfile-build", applyCommands[0].Apply.Component, "command component reference should be equal") - } - } - }) - } -} - -func TestGetRouteFromEndpoint(t *testing.T) { - - var ( - name = "route1" - serviceName = "service1" - path = "" - port = "1234" - secure = true - annotations = map[string]string{ - "key1": "value1", - } - ) - t.Run(name, func(t *testing.T) { - actualRoute := GetRouteFromEndpoint(name, serviceName, port, path, secure, annotations) - assert.Equal(t, "Route", actualRoute.Kind, "Kind did not match") - assert.Equal(t, "route.openshift.io/v1", actualRoute.APIVersion, "APIVersion did not match") - assert.Equal(t, name, actualRoute.Name, "Route name did not match") - assert.Equal(t, "/", actualRoute.Spec.Path, "Route path did not match") - assert.NotNil(t, actualRoute.Spec.Port, "Route Port should not be nil") - assert.Equal(t, intstr.FromString(port), actualRoute.Spec.Port.TargetPort, "Route port did not match") - assert.NotNil(t, actualRoute.Spec.TLS, "Route TLS should not be nil") - assert.Equal(t, routev1.TLSTerminationEdge, actualRoute.Spec.TLS.Termination, "Route port did not match") - actualRouteAnnotations := actualRoute.GetAnnotations() - assert.NotEmpty(t, actualRouteAnnotations, "Route annotations should not be empty") - assert.Equal(t, "value1", actualRouteAnnotations["key1"], "Route annotation did not match") - }) -} - -func TestGenerateDeploymentTemplate(t *testing.T) { - - var ( - name = "deploy1" - application = "application1" - image = "image1" - ) - t.Run(name, func(t *testing.T) { - actualDeployment := GenerateDeploymentTemplate(name, application, image) - assert.Equal(t, "Deployment", actualDeployment.Kind, "Kind did not match") - assert.Equal(t, name, actualDeployment.Name, "Name did not match") - assert.Equal(t, generateK8sLabels(name, application), actualDeployment.Labels, "Labels did not match") - assert.NotNil(t, actualDeployment.Spec.Selector, "Selector can not be nil") - assert.Equal(t, getMatchLabel(name), actualDeployment.Spec.Selector.MatchLabels, "Match Labels did not match") - assert.Equal(t, getMatchLabel(name), actualDeployment.Spec.Template.Labels, "Match Labels did not match") - assert.Equal(t, 1, len(actualDeployment.Spec.Template.Spec.Containers), "Should have only 1 container") - assert.Equal(t, image, actualDeployment.Spec.Template.Spec.Containers[0].Image, "Container Image did not match") - }) -} - -func TestUpdateLocalDockerfileURItoAbsolute(t *testing.T) { - tests := []struct { - name string - devfile *v2.DevfileV2 - dockerfileURL string - wantDevfile *v2.DevfileV2 - wantErr bool - }{ - { - name: "devfile.yaml with local Dockerfile URI references", - devfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "SomeDevfile", - }, - }, - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - Name: "image-build", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageName: "component-image", - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "./Dockerfile", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - dockerfileURL: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - wantDevfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "SomeDevfile", - }, - }, - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - Name: "image-build", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageName: "component-image", - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "devfile.yaml with local Dockerfile URI reference, and multiple other components", - devfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "SomeDevfile", - }, - }, - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - Name: "other-components", - ComponentUnion: v1alpha2.ComponentUnion{ - Container: &v1alpha2.ContainerComponent{ - BaseComponent: v1alpha2.BaseComponent{}, - }, - }, - }, - { - Name: "image-build", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageName: "component-image", - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "./Dockerfile", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - dockerfileURL: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - wantDevfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "SomeDevfile", - }, - }, - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - Name: "other-components", - ComponentUnion: v1alpha2.ComponentUnion{ - Container: &v1alpha2.ContainerComponent{ - BaseComponent: v1alpha2.BaseComponent{}, - }, - }, - }, - { - Name: "image-build", - ComponentUnion: v1alpha2.ComponentUnion{ - Image: &v1alpha2.ImageComponent{ - Image: v1alpha2.Image{ - ImageName: "component-image", - ImageUnion: v1alpha2.ImageUnion{ - Dockerfile: &v1alpha2.DockerfileImage{ - DockerfileSrc: v1alpha2.DockerfileSrc{ - Uri: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "devfile.yaml with no local Dockerfile URI reference", - devfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "SomeDevfile", - }, - }, - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - Name: "other-components", - ComponentUnion: v1alpha2.ComponentUnion{ - Container: &v1alpha2.ContainerComponent{ - BaseComponent: v1alpha2.BaseComponent{}, - }, - }, - }, - { - Name: "another-component", - ComponentUnion: v1alpha2.ComponentUnion{ - Container: &v1alpha2.ContainerComponent{ - BaseComponent: v1alpha2.BaseComponent{}, - }, - }, - }, - }, - }, - }, - }, - }, - dockerfileURL: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - wantDevfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevfileHeader: devfile.DevfileHeader{ - SchemaVersion: string(data.APISchemaVersion220), - Metadata: devfile.DevfileMetadata{ - Name: "SomeDevfile", - }, - }, - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - Name: "other-components", - ComponentUnion: v1alpha2.ComponentUnion{ - Container: &v1alpha2.ContainerComponent{ - BaseComponent: v1alpha2.BaseComponent{}, - }, - }, - }, - { - Name: "another-component", - ComponentUnion: v1alpha2.ComponentUnion{ - Container: &v1alpha2.ContainerComponent{ - BaseComponent: v1alpha2.BaseComponent{}, - }, - }, - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "devfile.yaml with invalid components, should return err", - devfile: &v2.DevfileV2{ - Devfile: v1alpha2.Devfile{ - DevWorkspaceTemplateSpec: v1alpha2.DevWorkspaceTemplateSpec{ - DevWorkspaceTemplateSpecContent: v1alpha2.DevWorkspaceTemplateSpecContent{ - Components: []v1alpha2.Component{ - { - ComponentUnion: v1alpha2.ComponentUnion{ - ComponentType: "bad-component", - }, - }, - }, - }, - }, - }, - }, - dockerfileURL: "https://raw.githubusercontent.com/devfile-samples/devfile-sample-java-springboot-basic/main/docker/Dockerfile", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - devfile, err := UpdateLocalDockerfileURItoAbsolute(tt.devfile, tt.dockerfileURL) - if (err != nil) != tt.wantErr { - t.Errorf("TestUpdateLocalDockerfileURItoAbsolute() unexpected error: %v", err) - } - - if !tt.wantErr && !reflect.DeepEqual(devfile, tt.wantDevfile) { - t.Errorf("devfile content did not match, got %v, wanted %v", devfile, tt.wantDevfile) - } - }) - } -} - -func TestGetIngressFromEndpoint(t *testing.T) { - - componentName := "test-component" - - implementationSpecific := networkingv1.PathTypeImplementationSpecific - - tests := []struct { - name string - ingressName string - serviceName string - port string - path string - hostname string - annotations map[string]string - wantErr bool - wantIngress networkingv1.Ingress - }{ - { - name: "Get simple ingress", - ingressName: componentName, - serviceName: componentName, - port: "5000", - path: "", - hostname: componentName + ".example.com", - annotations: map[string]string{ - "test": "yes", - }, - wantIngress: networkingv1.Ingress{ - TypeMeta: metav1.TypeMeta{ - Kind: "Ingress", - APIVersion: "networking.k8s.io/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: componentName, - Annotations: map[string]string{ - "test": "yes", - }, - }, - Spec: networkingv1.IngressSpec{ - Rules: []networkingv1.IngressRule{ - { - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: &implementationSpecific, - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: componentName, - Port: networkingv1.ServiceBackendPort{ - Number: 5000, - }, - }, - }, - }, - }, - }, - }, - Host: componentName + ".example.com", - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - generatedIngress, err := GetIngressFromEndpoint(tt.ingressName, tt.serviceName, tt.port, tt.path, false, tt.annotations, tt.hostname) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } else if !reflect.DeepEqual(tt.wantIngress, generatedIngress) { - t.Errorf("Expected: %+v, \nGot: %+v", tt.wantIngress, generatedIngress) - } - }) - } -} diff --git a/pkg/devfile/errors.go b/pkg/devfile/errors.go deleted file mode 100644 index 59bb915e3..000000000 --- a/pkg/devfile/errors.go +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright 2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package devfile - -import "fmt" - -// NoFileFound returns an error if no file was found -type NoFileFound struct { - Location string - Err error -} - -func (e *NoFileFound) Error() string { - errMsg := fmt.Sprintf("unable to find file in the specified location %s", e.Location) - if e.Err != nil { - errMsg = fmt.Sprintf("%s due to %v", errMsg, e.Err) - } - return errMsg -} - -// MissingOuterloop returns an error if no Kubernetes Component was found in a Devfile -type MissingOuterloop struct { -} - -func (e *MissingOuterloop) Error() string { - return "the devfile has no kubernetes components defined, missing outerloop definition" -} - -// IncompatibleDevfile returns an error if the Devfile being read is incompatible due to user error -type IncompatibleDevfile struct { - Err error -} - -func (e *IncompatibleDevfile) Error() string { - return fmt.Sprintf("devfile is incompatible: %v", e.Err) -} - -// DevfileAttributeParse returns an error if was an issue parsing the attribute key -type DevfileAttributeParse struct { - Key string - Err error -} - -func (e *DevfileAttributeParse) Error() string { - errMsg := fmt.Sprintf("error parsing key %s: %v", e.Key, e.Err) - - return errMsg -} diff --git a/pkg/devfile/errors_test.go b/pkg/devfile/errors_test.go deleted file mode 100644 index dc760a160..000000000 --- a/pkg/devfile/errors_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright 2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package devfile - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNoFileFoundErr(t *testing.T) { - - tests := []struct { - name string - args NoFileFound - wantErrString string - }{ - { - name: "No file Found at location", - args: NoFileFound{ - Location: "/path", - }, - wantErrString: "unable to find file in the specified location /path", - }, - { - name: "No file Found at location due to an err", - args: NoFileFound{ - Location: "/path", - Err: fmt.Errorf("a dummy err"), - }, - wantErrString: "unable to find file in the specified location /path due to a dummy err", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - errString := tt.args.Error() - assert.Equal(t, tt.wantErrString, errString, "the err string should be equal") - }) - } -} diff --git a/pkg/devfile/utils.go b/pkg/devfile/utils.go deleted file mode 100644 index cbeedfe56..000000000 --- a/pkg/devfile/utils.go +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package devfile - -import ( - "fmt" - "regexp" -) - -// GetIngressHostName gets the ingress host name from the component name, namepsace and ingress domain -func GetIngressHostName(componentName, namespace, ingressDomain string) (string, error) { - - regexString := `[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*` - ingressHostRegex := regexp.MustCompile(regexString) - - host := fmt.Sprintf("%s-%s.%s", componentName, namespace, ingressDomain) - - if !ingressHostRegex.MatchString(host) { - return "", fmt.Errorf("hostname %s should match regex %s", host, regexString) - } - - return host, nil -} diff --git a/pkg/devfile/utils_test.go b/pkg/devfile/utils_test.go deleted file mode 100644 index ce1c7e5c2..000000000 --- a/pkg/devfile/utils_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package devfile - -import ( - "reflect" - "testing" -) - -func TestGetIngressHostName(t *testing.T) { - - tests := []struct { - name string - componentName string - namespace string - ingressDomain string - wantHostName string - wantErr bool - }{ - { - name: "all string present", - componentName: "my-component", - namespace: "test", - ingressDomain: "domain.example.com", - wantHostName: "my-component-test.domain.example.com", - }, - { - name: "Capitalized component name should be ok", - componentName: "my-Component", - namespace: "test", - ingressDomain: "domain.example.com", - wantHostName: "my-Component-test.domain.example.com", - }, - { - name: "invalid char in string", - componentName: "&", - namespace: "$", - ingressDomain: "$", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - gotHostName, err := GetIngressHostName(tt.componentName, tt.namespace, tt.ingressDomain) - if !tt.wantErr && err != nil { - t.Errorf("Unexpected err: %+v", err) - } else if tt.wantErr && err == nil { - t.Errorf("Expected error but got nil") - } else if !reflect.DeepEqual(tt.wantHostName, gotHostName) { - t.Errorf("Expected: %+v, \nGot: %+v", tt.wantHostName, gotHostName) - } - }) - } -} diff --git a/pkg/github/errors.go b/pkg/github/errors.go deleted file mode 100644 index 3d37549e4..000000000 --- a/pkg/github/errors.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package github - -// GitHubUserErr is a user related error for the GitHub related operations -type GitHubUserErr struct { - Err string -} - -func (e *GitHubUserErr) Error() string { - return e.Err -} - -// GitHubSystemErr is a system related error for the GitHub related operations -type GitHubSystemErr struct { - Err string -} - -func (e *GitHubSystemErr) Error() string { - return e.Err -} diff --git a/pkg/github/github.go b/pkg/github/github.go deleted file mode 100644 index 960de02c2..000000000 --- a/pkg/github/github.go +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package github - -import ( - "context" - "fmt" - "net/url" - "strings" - "sync" - - "github.com/google/go-github/v59/github" - "github.com/redhat-appstudio/application-service/pkg/metrics" -) - -const AppStudioAppDataOrg = "redhat-appstudio-appdata" - -// GitHubClient represents a Go-GitHub client, along with the name of the GitHub token that was used to initialize it -type GitHubClient struct { - TokenName string - Token string - Client *github.Client - SecondaryRateLimit SecondaryRateLimit - PrimaryRateLimited bool // flag to denote if the token has been near primary rate limited -} - -type SecondaryRateLimit struct { - isLimitReached bool - mu sync.Mutex -} - -type ContextKey string - -const ( - GHClientKey ContextKey = "ghClient" -) - -// ServerError is used to identify gitops repo creation failures caused by server errors -type ServerError struct { - err error -} - -func (e *ServerError) Error() string { - return fmt.Errorf("failed to create gitops repo due to error: %v", e.err).Error() -} - -func (g *GitHubClient) GenerateNewRepository(ctx context.Context, orgName string, repoName string, description string) (string, error) { - isPrivate := false - appStudioAppDataURL := "https://github.com/" + orgName + "/" - metrics.GitOpsRepoCreationTotalReqs.Inc() - r := &github.Repository{Name: &repoName, Private: &isPrivate, Description: &description} - _, resp, err := g.Client.Repositories.Create(ctx, orgName, r) - - if resp != nil && 500 <= resp.StatusCode && resp.StatusCode <= 599 { - // return custom error - if err != nil { - metrics.GitOpsRepoCreationFailed.Inc() - return "", &ServerError{err: err} - } - } - - if err != nil { - return "", err - } - repoURL := appStudioAppDataURL + repoName - metrics.GitOpsRepoCreationSucceeded.Inc() - return repoURL, nil -} - -// GetRepoNameFromURL returns the repository name from the Git repo URL -func GetRepoNameFromURL(repoURL string, orgName string) (string, error) { - parts := strings.Split(repoURL, orgName+"/") - if len(parts) < 2 { - return "", fmt.Errorf("error: unable to parse Git repository URL: %v", repoURL) - } - return parts[1], nil -} - -// GetRepoAndOrgFromURL returns both the github org and repository name from a given github URL -// Format must be of the form: <github-domain>/owner/repository(.git) -// If .git is appended to the end, it will be removed from the returned repo name -func GetRepoAndOrgFromURL(repoURL string) (string, string, error) { - parsedURL, err := url.Parse(repoURL) - if err != nil { - return "", "", fmt.Errorf("error: invalid URL: %v", repoURL) - } - - // The URL Path should contain the org and repo name in the form: orgname/reponame. - parts := strings.Split(parsedURL.Path, "/") - if len(parts) != 3 { - return "", "", fmt.Errorf("error: unable to parse Git repository URL: %v", repoURL) - } - orgName := parts[1] - if orgName == "" { - return "", "", fmt.Errorf("error: unable to retrieve organization name from URL: %v", repoURL) - } - repoName := strings.Split(parts[2], ".git")[0] - if repoName == "" { - return "", "", fmt.Errorf("error: unable to retrieve repository name from URL: %v", repoURL) - } - return repoName, orgName, nil -} - -// GetGitStatus returns the status of the Git API with a simple noop call -func (g *GitHubClient) GetGitStatus(ctx context.Context) (bool, error) { - quote, response, err := g.Client.Meta.Zen(ctx) - if err == nil && response != nil && response.StatusCode >= 200 && response.StatusCode <= 299 && quote != "" { - return true, nil - } - return false, err -} - -// GetDefaultBranchFromURL returns the default branch of a given repoURL -func (g *GitHubClient) GetDefaultBranchFromURL(repoURL string, ctx context.Context) (string, error) { - repoName, orgName, err := GetRepoAndOrgFromURL(repoURL) - if err != nil { - return "", err - } - - repo, _, err := g.Client.Repositories.Get(ctx, orgName, repoName) - if err != nil || repo == nil { - return "", fmt.Errorf("failed to get repo %s under %s, error: %v", repoName, orgName, err) - } - - return *repo.DefaultBranch, nil -} - -// GetBranchFromURL returns the requested branch of a given repoURL -func (g *GitHubClient) GetBranchFromURL(repoURL string, ctx context.Context, branchName string) (*github.Branch, error) { - repoName, orgName, err := GetRepoAndOrgFromURL(repoURL) - if err != nil { - return nil, &GitHubUserErr{Err: err.Error()} - } - - branch, _, err := g.Client.Repositories.GetBranch(ctx, orgName, repoName, branchName, 1) - if err != nil || branch == nil { - return nil, &GitHubSystemErr{Err: fmt.Sprintf("failed to get branch %s from repo %s under %s, error: %v", branchName, repoName, orgName, err)} - } - - return branch, nil -} - -// GetLatestCommitSHAFromRepository gets the latest Commit SHA from the repository -func (g *GitHubClient) GetLatestCommitSHAFromRepository(ctx context.Context, repoName string, orgName string, branch string) (string, error) { - commitSHA, _, err := g.Client.Repositories.GetCommitSHA1(ctx, orgName, repoName, branch, "") - if err != nil { - return "", err - } - return commitSHA, nil -} - -// Delete Repository takes in the given repository URL and attempts to delete it -func (g *GitHubClient) DeleteRepository(ctx context.Context, orgName string, repoName string) error { - // Retrieve just the repository name from the URL - _, err := g.Client.Repositories.Delete(ctx, orgName, repoName) - if err != nil { - return err - } - return nil -} diff --git a/pkg/github/github_test.go b/pkg/github/github_test.go deleted file mode 100644 index 523f6709b..000000000 --- a/pkg/github/github_test.go +++ /dev/null @@ -1,522 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package github - -import ( - "context" - "strings" - "testing" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/redhat-appstudio/application-service/pkg/metrics" - "github.com/stretchr/testify/assert" -) - -func TestGenerateNewRepository(t *testing.T) { - - ctx := context.WithValue(context.Background(), GHClientKey, "mock") - ctxNoClient := context.Background() - ctxClientDoesNotExist := context.WithValue(context.Background(), GHClientKey, "does-not-exist") - - prometheus.MustRegister(metrics.GitOpsRepoCreationTotalReqs, metrics.GitOpsRepoCreationSucceeded, metrics.GitOpsRepoCreationFailed) - tests := []struct { - name string - ctx context.Context - repoName string - orgName string - want string - wantErr bool - numReposCreated int //this represents the cumulative counts of metrics assuming the tests run in order - numReposCreationFailed int //this represents the cumulative counts of metrics assuming the tests run in order - }{ - { - name: "Simple repo name", - ctx: ctx, - repoName: "test-repo-1", - orgName: "redhat-appstudio-appdata", - want: "https://github.com/redhat-appstudio-appdata/test-repo-1", - wantErr: false, - numReposCreated: 1, - numReposCreationFailed: 0, - }, - { - name: "Repo creation fails due to server error", - ctx: ctx, - repoName: "test-server-error-response", - orgName: "redhat-appstudio-appdata", - want: "https://github.com/redhat-appstudio-appdata/test-error-response", - wantErr: true, - numReposCreated: 1, - numReposCreationFailed: 1, - }, - { - name: "Repo creation fails due to server error, failed metric count increases", - ctx: ctx, - repoName: "test-server-error-response-2", - orgName: "redhat-appstudio-appdata", - want: "https://github.com/redhat-appstudio-appdata/test-error-response-2", - wantErr: true, - numReposCreated: 1, - numReposCreationFailed: 2, - }, - { - name: "Repo creation fails due to user error, metric counts should not increase", - ctx: ctx, - repoName: "test-user-error-response", - orgName: "redhat-appstudio-appdata", - want: "https://github.com/redhat-appstudio-appdata/test-user-error-response", - wantErr: true, - numReposCreated: 1, - numReposCreationFailed: 2, - }, - { - name: "Repo creation fails due to secondary rate limit", - ctx: ctx, - repoName: "secondary-rate-limit", - orgName: "redhat-appstudio-appdata", - want: "https://github.com/redhat-appstudio-appdata/secondary-rate-limit", - wantErr: true, - numReposCreated: 1, - numReposCreationFailed: 2, - }, - { - name: "Secondary rate limit callback fails due to no token name passed in request, but should not panic", - ctx: ctxNoClient, - repoName: "secondary-rate-limit-callback-fail", - orgName: "redhat-appstudio-appdata", - want: "https://github.com/redhat-appstudio-appdata/secondary-rate-limit", - wantErr: true, - numReposCreated: 1, - numReposCreationFailed: 2, - }, - { - name: "Secondary rate limit callback fails due to an incorrect/non-existent token name, but should not panic", - ctx: ctxClientDoesNotExist, - repoName: "secondary-rate-limit-callback-fail", - orgName: "redhat-appstudio-appdata", - want: "https://github.com/redhat-appstudio-appdata/secondary-rate-limit", - wantErr: true, - numReposCreated: 1, - numReposCreationFailed: 2, - }, - } - - numTests := len(tests) - for _, tt := range tests { - mockedClient := GitHubClient{ - Client: GetMockedClient(), - TokenName: "mock", - } - if Clients == nil { - Clients = make(map[string]*GitHubClient) - } - Clients["mock"] = &mockedClient - - // Deliberately lock the secondary rate limit object until we need to test the related fields - Clients["mock"].SecondaryRateLimit.mu.Lock() - - t.Run(tt.name, func(t *testing.T) { - repoURL, err := mockedClient.GenerateNewRepository(tt.ctx, tt.orgName, tt.repoName, "") - - if err != nil && tt.wantErr { - if _, ok := err.(*ServerError); ok { - //validate error message - if !strings.Contains(err.Error(), "failed to create gitops repo due to error:") { - t.Errorf("TestGenerateNewRepository() unexpected server error message: %v", err) - } - //when there is a server error, we should collect the metric - assert.Equal(t, float64(tt.numReposCreated), testutil.ToFloat64(metrics.GitOpsRepoCreationSucceeded)) - assert.Equal(t, float64(tt.numReposCreationFailed), testutil.ToFloat64(metrics.GitOpsRepoCreationFailed)) - } else { - //If it's a user error, metric counts should not increase. The test cases should reflect that - assert.Equal(t, float64(tt.numReposCreated), testutil.ToFloat64(metrics.GitOpsRepoCreationSucceeded)) - assert.Equal(t, float64(tt.numReposCreationFailed), testutil.ToFloat64(metrics.GitOpsRepoCreationFailed)) - } - - } - if (err != nil) && !tt.wantErr { - t.Errorf("TestGenerateNewRepository() unexpected error value: %v", err) - - } - if !tt.wantErr && repoURL != tt.want { - t.Errorf("TestGenerateNewRepository() error: expected %v got %v", tt.want, repoURL) - } - - if tt.repoName == "secondary-rate-limit" { - Clients["mock"].SecondaryRateLimit.mu.Unlock() - time.Sleep(time.Second * 1) - if !Clients["mock"].SecondaryRateLimit.isLimitReached { - t.Errorf("TestGenerateNewRepository() error expected github client to be secondary rate limited") - } - time.Sleep(time.Second * 3) - if Clients["mock"].SecondaryRateLimit.isLimitReached { - t.Errorf("TestGenerateNewRepository() error expected github client to no longer be secondary rate limited") - } - } - //verify the number of successful repos created - assert.Equal(t, float64(tt.numReposCreated), testutil.ToFloat64(metrics.GitOpsRepoCreationSucceeded)) - assert.Equal(t, float64(tt.numReposCreationFailed), testutil.ToFloat64(metrics.GitOpsRepoCreationFailed)) - }) - } - - assert.Equal(t, float64(numTests), testutil.ToFloat64(metrics.GitOpsRepoCreationTotalReqs)) -} - -func TestDeleteRepository(t *testing.T) { - tests := []struct { - name string - repoName string - orgName string - wantErr bool - }{ - { - name: "Simple repo url", - repoName: "test-repo-1", - orgName: "redhat-appstudio-appdata", - wantErr: false, - }, - { - name: "Invalid repo name", - repoName: "https://github.com/invalid/url", - orgName: "redhat-appstudio-appdata", - wantErr: true, - }, - } - - for _, tt := range tests { - mockedClient := GitHubClient{ - Client: GetMockedClient(), - } - - t.Run(tt.name, func(t *testing.T) { - err := mockedClient.DeleteRepository(context.Background(), tt.orgName, tt.repoName) - - if tt.wantErr != (err != nil) { - t.Errorf("TestDeleteRepository() error: expected %v, got %v", err, tt.wantErr) - } - }) - } -} - -func TestGetRepoNameFromURL(t *testing.T) { - tests := []struct { - name string - repoURL string - orgName string - want string - wantErr bool - }{ - { - name: "Simple repo url", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-1", - orgName: "redhat-appstudio-appdata", - want: "test-repo-1", - wantErr: false, - }, - { - name: "Simple repo url, invalid org name", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-1", - orgName: "fakeorg", - want: "test-repo-1", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - repoName, err := GetRepoNameFromURL(tt.repoURL, tt.orgName) - - if tt.wantErr != (err != nil) { - t.Errorf("TestGetRepoNameFromURL() error: expected an error to be returned") - } - - if !tt.wantErr && (repoName != tt.want) { - t.Errorf("TestGetRepoNameFromURL() error: expected %v got %v", tt.want, repoName) - } - }) - } -} - -func TestGetRepoAndOrgFromURL(t *testing.T) { - tests := []struct { - name string - repoURL string - wantRepo string - wantOrg string - wantErr bool - wantErrString string - }{ - { - name: "Simple repo url", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-1", - wantRepo: "test-repo-1", - wantOrg: "redhat-appstudio-appdata", - wantErr: false, - }, - { - name: "Repo url with .git", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-1.git", - wantRepo: "test-repo-1", - wantOrg: "redhat-appstudio-appdata", - wantErr: false, - }, - { - name: "Repo url without scheme", - repoURL: "github.com/redhat-appstudio-appdata/test-repo-1", - wantRepo: "test-repo-1", - wantOrg: "redhat-appstudio-appdata", - wantErr: false, - }, - { - name: "Invalid repo url", - repoURL: "github.comasdfsdfsafd", - wantErr: true, - wantErrString: "error: unable to parse Git repository URL", - }, - { - name: "Invalid repo url, with partial path", - repoURL: "github.com/asdfsdfsafd", - wantErr: true, - wantErrString: "error: unable to parse Git repository URL", - }, - { - name: "Invalid repo url, with too many paths", - repoURL: "github.com/asdfsdfsafd/another/another/path", - wantErr: true, - wantErrString: "error: unable to parse Git repository URL", - }, - { - name: "Unparseable URL", - repoURL: "http://github.com/?org\nrepo", - wantErr: true, - wantErrString: "error: invalid URL", - }, - { - name: "Unparseable organization name", - repoURL: "https://github.com//test", - wantErr: true, - wantErrString: "error: unable to retrieve organization name from URL", - }, - { - name: "Unparseable repository name", - repoURL: "https://github.com/organization/", - wantErr: true, - wantErrString: "error: unable to retrieve repository name from URL", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - repoName, orgName, err := GetRepoAndOrgFromURL(tt.repoURL) - - if tt.wantErr != (err != nil) { - t.Errorf("TestGetRepoAndOrgFromURL() error: expected an error to be returned") - } - - if tt.wantErr { - errMsg := err.Error() - if !strings.Contains(errMsg, tt.wantErrString) { - t.Errorf("TestGetRepoAndOrgFromURL() error: expected error message %v got %v", tt.wantErrString, errMsg) - } - } - - if !tt.wantErr && (repoName != tt.wantRepo) { - t.Errorf("TestGetRepoAndOrgFromURL() error: expected %v got %v", tt.wantRepo, repoName) - } - - if !tt.wantErr && (orgName != tt.wantOrg) { - t.Errorf("TestGetRepoAndOrgFromURL() error: expected %v got %v", tt.wantOrg, orgName) - } - }) - } -} - -func TestGetGitStatus(t *testing.T) { - tests := []struct { - name string - repoName string - orgName string - wantAvailable bool - wantErr bool - }{ - { - name: "Simple repo name", - repoName: "test-repo-1", - orgName: "redhat-appstudio-appdata", - wantAvailable: true, - wantErr: false, - }, - } - - for _, tt := range tests { - mockedClient := GitHubClient{ - Client: GetMockedClient(), - } - - t.Run(tt.name, func(t *testing.T) { - isGitAvailable, err := mockedClient.GetGitStatus(context.Background()) - - if tt.wantErr != (err != nil) { - t.Errorf("TestGetGitStatus() unexpected error value: %v", err) - } - if !tt.wantErr && isGitAvailable != tt.wantAvailable { - t.Errorf("TestGetGitStatus() error: expected %v got %v", tt.wantAvailable, isGitAvailable) - } - }) - } -} - -func TestGetLatestCommitSHAFromRepository(t *testing.T) { - tests := []struct { - name string - repoName string - orgName string - want string - wantErr bool - }{ - { - name: "Simple repo name", - repoName: "test-repo-1", - orgName: "redhat-appstudio-appdata", - want: "ca82a6dff817ec66f44342007202690a93763949", - wantErr: false, - }, - { - name: "Simple repo name", - repoName: "test-error-response", - orgName: "some-org", - wantErr: true, - }, - } - - for _, tt := range tests { - mockedClient := GitHubClient{ - Client: GetMockedClient(), - } - - t.Run(tt.name, func(t *testing.T) { - commitSHA, err := mockedClient.GetLatestCommitSHAFromRepository(context.Background(), tt.orgName, tt.repoName, "main") - - if tt.wantErr != (err != nil) { - t.Errorf("TestGetLatestCommitSHAFromRepository() unexpected error value: %v", err) - } - if !tt.wantErr && commitSHA != tt.want { - t.Errorf("TestGetLatestCommitSHAFromRepository() error: expected %v got %v", tt.want, commitSHA) - } - }) - } -} - -func TestGetDefaultBranchFromURL(t *testing.T) { - tests := []struct { - name string - repoURL string - want string - wantErr bool - }{ - { - name: "repo with main as default branch", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-1", - want: "main", - wantErr: false, - }, - { - name: "repo with master as default branch", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-2.git", - want: "master", - wantErr: false, - }, - { - name: "Simple repo name", - repoURL: "https://github.com/some-org/test-error-response", - wantErr: true, - }, - { - name: "Unparseable URL", - repoURL: "http://github.com/?org\nrepo", - wantErr: true, - }, - } - - for _, tt := range tests { - mockedClient := GitHubClient{ - Client: GetMockedClient(), - } - - t.Run(tt.name, func(t *testing.T) { - defaultBranch, err := mockedClient.GetDefaultBranchFromURL(tt.repoURL, context.Background()) - - if tt.wantErr != (err != nil) { - t.Errorf("TestGetDefaultBranchFromURL() unexpected error value: %v", err) - } - if !tt.wantErr && defaultBranch != tt.want { - t.Errorf("TestGetDefaultBranchFromURL() error: expected %v got %v", tt.want, defaultBranch) - } - }) - } -} - -func TestGetBranchFromURL(t *testing.T) { - tests := []struct { - name string - repoURL string - branchName string - wantErr bool - }{ - { - name: "repo with main as default branch", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-1", - branchName: "main", - wantErr: false, - }, - { - name: "repo with master as default branch", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-2.git", - branchName: "master", - wantErr: false, - }, - { - name: "Simple repo name", - repoURL: "https://github.com/redhat-appstudio-appdata/test-repo-2.git", - branchName: "main", - wantErr: true, - }, - { - name: "Unparseable URL", - repoURL: "http://github.com/?org\nrepo", - wantErr: true, - }, - } - - for _, tt := range tests { - mockedClient := GitHubClient{ - Client: GetMockedClient(), - } - - t.Run(tt.name, func(t *testing.T) { - branch, err := mockedClient.GetBranchFromURL(tt.repoURL, context.Background(), tt.branchName) - - if tt.wantErr != (err != nil) { - t.Errorf("TestGetBranchFromURL() unexpected error value: %v, branch %v", err, branch) - } - if !tt.wantErr && *branch.Name != tt.branchName { - t.Errorf("TestGetBranchFromURL() error: expected %v got %v", tt.branchName, *branch.Name) - } - }) - } -} diff --git a/pkg/github/mock.go b/pkg/github/mock.go deleted file mode 100644 index 10b49196b..000000000 --- a/pkg/github/mock.go +++ /dev/null @@ -1,312 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package github - -import ( - "io" - "net/http" - "strings" - - "github.com/google/go-github/v59/github" - "github.com/migueleliasweb/go-github-mock/src/mock" -) - -// GetMockedClient returns a simple mocked go-github client -func GetMockedClient() *github.Client { - mockedHTTPClient := mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetUsersByUsername, - github.User{ - Name: github.String("testuser"), - }, - ), - mock.WithRequestMatch( - mock.GetUsersOrgsByUsername, - []github.Organization{ - { - Name: github.String("redhat-appstudio-appdata"), - }, - }, - ), - mock.WithRequestMatchHandler( - mock.PostOrgsReposByOrg, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - b, _ := io.ReadAll(req.Body) - reqBody := string(b) - // ToDo: Figure out a better way to dynamically mock errors - if strings.Contains(reqBody, "test-error-response") || strings.Contains(reqBody, "test-server-error-response") || strings.Contains(reqBody, "test-server-error-response-2") { - WriteError(w, - http.StatusInternalServerError, - "github went belly up or something", - ) - } else if strings.Contains(reqBody, "test-user-error-response") { - WriteError(w, - http.StatusUnauthorized, - "user is unauthorized", - ) - } else if strings.Contains(reqBody, "secondary-rate-limit") { - w.Header().Add("Retry-After", "3") - WriteError(w, - http.StatusForbidden, - "secondary rate limit", - ) - } else { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Repository{ - Name: github.String("test-repo-1"), - })) - } - }), - ), - mock.WithRequestMatchHandler( - mock.DeleteReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Repository{ - Name: github.String("test-repo-1"), - })) - }), - ), - mock.WithRequestMatchHandler( - mock.GetRateLimit, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - /* #nosec G104 -- test code */ - response := new(struct { - Resources *github.RateLimits `json:"resources"` - }) - response.Resources = &github.RateLimits{ - Core: &github.Rate{ - Limit: 5000, - Remaining: 100, - }, - Search: &github.Rate{ - Limit: 30, - Remaining: 15, - }, - } - w.Write(mock.MustMarshal(response)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if strings.Contains(req.RequestURI, "test-error-response") { - mock.WriteError(w, - http.StatusInternalServerError, - "github went belly up or something", - ) - } else { - /* #nosec G104 -- test code */ - w.Write([]byte("ca82a6dff817ec66f44342007202690a93763949")) - } - }), - ), - mock.WithRequestMatchHandler( - mock.GetZen, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - /* #nosec G104 -- test code */ - w.Write([]byte("peace")) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if strings.Contains(req.RequestURI, "test-error-response") { - mock.WriteError(w, - http.StatusInternalServerError, - "github went belly up or something", - ) - } else if strings.Contains(req.RequestURI, "multi-component-dockerfile-deep") { - w.Write(mock.MustMarshal(github.Repository{ - Name: github.String("multi-component-dockerfile-deep"), - DefaultBranch: github.String("main"), - })) - } else if strings.Contains(req.RequestURI, "create-spi-fcr") { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Repository{ - Name: github.String("create-spi-fcr"), - DefaultBranch: github.String("main"), - })) - } else if strings.Contains(req.RequestURI, "test-repo-2") { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Repository{ - Name: github.String("test-repo-2"), - DefaultBranch: github.String("master"), - })) - } else { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Repository{ - Name: github.String("test-repo-1"), - DefaultBranch: github.String("main"), - })) - } - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposBranchesByOwnerByRepoByBranch, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if strings.Contains(req.RequestURI, "test-repo-2") && strings.Contains(req.RequestURI, "master") { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Branch{ - Name: github.String("master"), - })) - } else if strings.Contains(req.RequestURI, "test-repo-1") && strings.Contains(req.RequestURI, "main") { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Branch{ - Name: github.String("main"), - })) - } else if strings.Contains(req.RequestURI, "multi-component-dockerfile-deep") && strings.Contains(req.RequestURI, "main") { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Branch{ - Name: github.String("main"), - })) - } else if strings.Contains(req.RequestURI, "create-spi-fcr") && strings.Contains(req.RequestURI, "main") { - /* #nosec G104 -- test code */ - w.Write(mock.MustMarshal(github.Branch{ - Name: github.String("main"), - })) - } else { - mock.WriteError(w, - http.StatusInternalServerError, - "github went belly up or something", - ) - } - }), - ), - ) - - cl, _ := createGitHubClientFromToken(&mockedHTTPClient.Transport, "", "mock") - return cl.Client - -} - -func GetMockedPrimaryRateLimitedClient() *github.Client { - mockedHTTPClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetRateLimit, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - /* #nosec G104 -- test code */ - response := new(struct { - Resources *github.RateLimits `json:"resources"` - }) - response.Resources = &github.RateLimits{ - Core: &github.Rate{ - Limit: 5000, - Remaining: 0, - }, - Search: &github.Rate{ - Limit: 30, - Remaining: 0, - }, - } - w.Write(mock.MustMarshal(response)) - }), - ), - ) - - cl, _ := createGitHubClientFromToken(&mockedHTTPClient.Transport, "", "mock") - return cl.Client - -} - -func GetMockedResetPrimaryRateLimitedClient() *github.Client { - mockedHTTPClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetRateLimit, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - /* #nosec G104 -- test code */ - response := new(struct { - Resources *github.RateLimits `json:"resources"` - }) - response.Resources = &github.RateLimits{ - Core: &github.Rate{ - Limit: 5000, - Remaining: 4999, - }, - Search: &github.Rate{ - Limit: 30, - Remaining: 29, - }, - } - w.Write(mock.MustMarshal(response)) - }), - ), - ) - - cl, _ := createGitHubClientFromToken(&mockedHTTPClient.Transport, "", "mock") - return cl.Client -} - -func GetMockedSecondaryRateLimitedClient() *github.Client { - mockedHTTPClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetRateLimit, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - /* #nosec G104 -- test code */ - response := new(struct { - Resources *github.RateLimits `json:"resources"` - }) - response.Resources = &github.RateLimits{ - Core: &github.Rate{ - Limit: 5000, - Remaining: 100, - }, - Search: &github.Rate{ - Limit: 30, - Remaining: 15, - }, - } - w.Write(mock.MustMarshal(response)) - }), - ), - mock.WithRequestMatchHandler( - mock.PostOrgsReposByOrg, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Add("Retry-After", "3") - WriteError(w, - http.StatusForbidden, - "secondary rate limit", - ) - }), - ), - ) - - cl, _ := createGitHubClientFromToken(&mockedHTTPClient.Transport, "", "mock") - return cl.Client - -} - -// WriteError - based on the mock implementation to handle writing back a response -// workaround until PR https://github.com/migueleliasweb/go-github-mock/pull/41 is merged -func WriteError( - w http.ResponseWriter, - httpStatus int, - msg string, - errors ...github.Error, -) { - w.WriteHeader(httpStatus) - - w.Write(mock.MustMarshal(mockGitHubErrorResponse{ - Message: msg, - Errors: errors, - })) - -} - -type mockGitHubErrorResponse struct { - Message string `json:"message"` // error message - Errors []github.Error `json:"errors"` // more detail on individual errors -} diff --git a/pkg/github/token.go b/pkg/github/token.go deleted file mode 100644 index 8299cdcbe..000000000 --- a/pkg/github/token.go +++ /dev/null @@ -1,250 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package github - -import ( - "context" - "fmt" - "math/rand" - "net/http" - "os" - "strings" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/redhat-appstudio/application-service/pkg/metrics" - - "github.com/gofri/go-github-ratelimit/github_ratelimit" - "github.com/google/go-github/v59/github" - "golang.org/x/oauth2" - - ctrl "sigs.k8s.io/controller-runtime" -) - -type GitHubToken interface { - GetNewGitHubClient(token string) (*GitHubClient, error) -} - -type GitHubTokenClient struct { -} - -// Tokens is mapping of token names to GitHub tokens -var Clients map[string]*GitHubClient - -// ParseGitHubTokens parses all of the possible GitHub tokens available to HAS and makes them available within the "github" package -// This function should *only* be called once: at operator startup. -func ParseGitHubTokens() error { - githubToken := os.Getenv("GITHUB_AUTH_TOKEN") - githubTokenList := os.Getenv("GITHUB_TOKEN_LIST") - if githubToken == "" && githubTokenList == "" { - return fmt.Errorf("no GitHub tokens were provided. Either GITHUB_TOKEN_LIST or GITHUB_AUTH_TOKEN (legacy) must be set") - } - - Clients = make(map[string]*GitHubClient) - if githubToken != "" { - // The old token format, stored in 'GITHUB_AUTH_TOKEN', didn't require a key/'name' for the token - // So use the key 'GITHUB_AUTH_TOKEN' for it - ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken}) - tc := oauth2.NewClient(context.Background(), ts) - token, err := createGitHubClientFromToken(&tc.Transport, githubToken, "GITHUB_AUTH_TOKEN") - if err != nil { - return err - } - Clients["GITHUB_AUTH_TOKEN"] = token - } - - // Parse any tokens passed in through the 'GITHUB_TOKEN_LIST' environment variable - // e.g. GITHUB_TOKEN_LIST=token1:ghp_faketoken,token2:ghp_anothertoken - //emptyGitHubClient := GitHubClient{} - if githubTokenList != "" { - // Each token key-value pair is separated by a comma, so split the string based on commas and loop over each key-value pair - tokenKeyValuePairs := strings.Split(githubTokenList, ",") - for _, tokenKeyValuePair := range tokenKeyValuePairs { - // Each token key-value pair is separated by a colon, so split the key-value pair and - // If the key-value pair doesn't split cleanly (i.e. only two strings returned), return an error - // If the key has already been added, return an error - splitTokenKeyValuePair := strings.Split(tokenKeyValuePair, ":") - if len(splitTokenKeyValuePair) != 2 { - return fmt.Errorf("unable to parse github token from key-value pair. Please ensure the GitHub secret is formatted correctly according to the documentation") - } - tokenKey := splitTokenKeyValuePair[0] - tokenValue := splitTokenKeyValuePair[1] - - if Clients[tokenKey] != nil { - return fmt.Errorf("a token with the key '%s' already exists. Each token must have a unique key", tokenKey) - } - - ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tokenValue}) - tc := oauth2.NewClient(context.Background(), ts) - token, err := createGitHubClientFromToken(&tc.Transport, tokenValue, tokenKey) - if err != nil { - return err - } - Clients[tokenKey] = token - } - } - - return nil -} - -// getRandomClient randomly retrieves a token from all of the tokens available to HAS -// It returns the token and the name/key of the token -func getRandomClient(clientPool map[string]*GitHubClient) (*GitHubClient, error) { - if len(clientPool) == 0 { - return nil, fmt.Errorf("no GitHub tokens available") - } - /* #nosec G404 -- not used for cryptographic purposes*/ - index := rand.Intn(len(clientPool)) - - i := 0 - var ghClient *GitHubClient - for _, v := range clientPool { - if i == index { - ghClient = v - } - i++ - } - - // Check the Primary rate limit - rl, _, err := ghClient.Client.RateLimit.Get(context.Background()) - if err != nil { - return nil, err - } - - prlTokenMetric := metrics.TokenPoolGauge.With(prometheus.Labels{"rateLimited": "primary", "tokenName": ghClient.TokenName}) - if rl != nil && (rl.Core != nil && rl.Core.Remaining < 10) || (rl.Search != nil && rl.Search.Remaining < 2) { - newClientPool := make(map[string]*GitHubClient) - for k, v := range clientPool { - if k != ghClient.TokenName { - newClientPool[k] = v - } - } - - // if token has not been previously rate limited, increment the metric - if !ghClient.PrimaryRateLimited { - prlTokenMetric.Inc() - ghClient.PrimaryRateLimited = true - } - - return getRandomClient(newClientPool) - } - - // if token has been previously rate limited, then decrement the counter and reset the boolean - if ghClient.PrimaryRateLimited { - prlTokenMetric.Dec() - ghClient.PrimaryRateLimited = false - } - - // Check the secondary rate limit - var isSecondaryRl bool - ghClient.SecondaryRateLimit.mu.Lock() - isSecondaryRl = ghClient.SecondaryRateLimit.isLimitReached - ghClient.SecondaryRateLimit.mu.Unlock() - - if isSecondaryRl { - newClientPool := make(map[string]*GitHubClient) - for k, v := range clientPool { - if k != ghClient.TokenName { - newClientPool[k] = v - } - } - - return getRandomClient(newClientPool) - } - return ghClient, nil -} - -// GetNewGitHubClient returns a Go-GitHub client -// If a token is passed in (non-empty string) it will use that token for the GitHub client -// If no token is passed in (empty string), a token will be randomly selected by HAS. -// It returns the GitHub client, and (if a token was randomly selected) the name of the token used for the client -// If an error is encountered retrieving the token, or initializing the client, an error is returned -func (g GitHubTokenClient) GetNewGitHubClient(token string) (*GitHubClient, error) { - var ghToken string - if token != "" { - ghToken = token - ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: ghToken}) - tc := oauth2.NewClient(context.Background(), ts) - ghClient, err := createGitHubClientFromToken(&tc.Transport, ghToken, "") - if err != nil { - return nil, err - } - return ghClient, nil - } else { - ghClient, err := getRandomClient(Clients) - if err != nil { - return nil, err - } - return ghClient, nil - } -} - -func createGitHubClientFromToken(roundTripper *http.RoundTripper, ghToken string, ghTokenName string) (*GitHubClient, error) { - rateLimiter, err := github_ratelimit.NewRateLimitWaiterClient(*roundTripper, github_ratelimit.WithSingleSleepLimit(0, rateLimitCallBackfunc)) - - if err != nil { - return nil, err - } - client := github.NewClient(rateLimiter) - githubClient := GitHubClient{ - TokenName: ghTokenName, - Token: ghToken, - Client: client, - } - - return &githubClient, nil -} - -func rateLimitCallBackfunc(cbContext *github_ratelimit.CallbackContext) { - // Retrieve the request's context and get the client name from it - // Use the client name to lookup the client pointer - req := *cbContext.Request - reqCtx := req.Context() - log := ctrl.LoggerFrom(reqCtx) - ghClientNameObj := reqCtx.Value(GHClientKey) - if ghClientNameObj == nil { - // The GitHub Client should never be nil - it must always be set before we access the GH API - // But if it is nil, returning prematurely is preferable to panicking - log.Error(fmt.Errorf("a Go-GitHub client name was not set in GitHub API request, cannot execute secondary rate limit callback"), "") - return - } - ghClientName := ghClientNameObj.(string) - ghClient := Clients[ghClientName] - if ghClient == nil { - // Likewise, the GitHub client should never be nil, it's directly set from the calling Go-GitHub client - // But if it is nil, returning prematurely is preferable to panicking. - log.Error(fmt.Errorf("a Go-GitHub client with the name %v as set in the GitHub API request does not exist, cannot execute secondary rate limit callback", ghClientName), "") - return - } - - // Start a goroutine that marks the given client as rate limited and sleeps for 'TotalSleepTime' - go func(client *GitHubClient) { - srlTokenMetric := metrics.TokenPoolGauge.With(prometheus.Labels{"rateLimited": "secondary", "tokenName": client.TokenName}) - - client.SecondaryRateLimit.mu.Lock() - client.SecondaryRateLimit.isLimitReached = true - srlTokenMetric.Inc() - client.SecondaryRateLimit.mu.Unlock() - - // Sleep until the rate limit is over - time.Sleep(time.Until(*cbContext.SleepUntil)) - client.SecondaryRateLimit.mu.Lock() - client.SecondaryRateLimit.isLimitReached = false - srlTokenMetric.Dec() - client.SecondaryRateLimit.mu.Unlock() - }(ghClient) - -} diff --git a/pkg/github/token_mock.go b/pkg/github/token_mock.go deleted file mode 100644 index 240b06351..000000000 --- a/pkg/github/token_mock.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package github - -type MockGitHubTokenClient struct { -} - -type MockPrimaryRateLimitGitHubTokenClient struct { -} - -type MockResetPrimaryRateLimitGitHubTokenClient struct { -} - -// GetNewGitHubClient returns a mocked Go-GitHub client. No actual tokens are passed in or used when this function is called -func (g MockGitHubTokenClient) GetNewGitHubClient(token string) (*GitHubClient, error) { - fakeClients := make(map[string]*GitHubClient) - fakeClients["fake1"] = &GitHubClient{ - TokenName: "fake1", - Token: token, - Client: GetMockedClient(), - } - fakeClients["fake2"] = &GitHubClient{ - TokenName: "fake2", - Token: "faketoken2", - Client: GetMockedClient(), - } - fakeClients["fake3"] = &GitHubClient{ - TokenName: "fake3", - Token: "faketoken3", - Client: GetMockedClient(), - } - - return getRandomClient(fakeClients) -} - -func (g MockPrimaryRateLimitGitHubTokenClient) GetNewGitHubClient(token string) (*GitHubClient, error) { - fakeClients := make(map[string]*GitHubClient) - fakeClients["fake1"] = &GitHubClient{ - TokenName: "fake1", - Token: token, - Client: GetMockedPrimaryRateLimitedClient(), - } - - return getRandomClient(fakeClients) -} - -func (g MockResetPrimaryRateLimitGitHubTokenClient) GetNewGitHubClient(token string) (*GitHubClient, error) { - fakeClients := make(map[string]*GitHubClient) - fakeClients["fake_reset"] = &GitHubClient{ - TokenName: "fake_reset", - Token: token, - Client: GetMockedResetPrimaryRateLimitedClient(), - } - - return getRandomClient(fakeClients) -} diff --git a/pkg/github/token_test.go b/pkg/github/token_test.go deleted file mode 100644 index 9c8064c86..000000000 --- a/pkg/github/token_test.go +++ /dev/null @@ -1,325 +0,0 @@ -// -// Copyright 2021-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package github - -import ( - "context" - "os" - "reflect" - "testing" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/testutil" - "github.com/redhat-appstudio/application-service/pkg/metrics" - "github.com/stretchr/testify/assert" -) - -func TestParseGitHubTokens(t *testing.T) { - tests := []struct { - name string - githubTokenEnv string - githubTokenListEnv string - want map[string]*GitHubClient - wantErr bool - }{ - { - name: "No tokens set", - wantErr: true, - }, - { - name: "Only one token, stored in GITHUB_AUTH_TOKEN", - githubTokenEnv: "some_token", - want: map[string]*GitHubClient{ - "GITHUB_AUTH_TOKEN": { - TokenName: "GITHUB_AUTH_TOKEN", - Token: "some_token", - }, - }, - }, - { - name: "Only one token, stored in GITHUB_TOKEN_LIST", - githubTokenListEnv: "token1:list_token", - want: map[string]*GitHubClient{ - "token1": { - TokenName: "token1", - Token: "list_token", - }, - }, - }, - { - name: "Two tokens, one each stored in GITHUB_AUTH_TOKEN and GITHUB_TOKEN_LIST", - githubTokenEnv: "some_token", - githubTokenListEnv: "token1:list_token", - want: map[string]*GitHubClient{ - "GITHUB_AUTH_TOKEN": { - TokenName: "GITHUB_AUTH_TOKEN", - Token: "some_token", - }, - "token1": { - TokenName: "token1", - Token: "list_token", - }, - }, - }, - { - name: "Multiple tokens", - githubTokenEnv: "some_token", - githubTokenListEnv: "token1:list_token,token2:another_token,token3:third_token", - want: map[string]*GitHubClient{ - "GITHUB_AUTH_TOKEN": { - TokenName: "GITHUB_AUTH_TOKEN", - Token: "some_token", - }, - "token1": { - TokenName: "token1", - Token: "list_token", - }, - "token2": { - TokenName: "token2", - Token: "another_token", - }, - "token3": { - TokenName: "token3", - Token: "third_token", - }, - }, - }, - { - name: "Error parsing tokens", - githubTokenEnv: "some_token", - githubTokenListEnv: "token1:list_token,token2:another_token,token3", - wantErr: true, - }, - { - name: "Error parsing tokens - invalid key separator", - githubTokenEnv: "some_token", - githubTokenListEnv: "token1:list_token,token2:another_token:", - wantErr: true, - }, - { - name: "Error parsing tokens - duplicate keys", - githubTokenEnv: "some_token", - githubTokenListEnv: "token1:list_token,token1:another_token", - wantErr: true, - }, - { - name: "Error parsing tokens - duplicate keys", - githubTokenEnv: "some_token", - githubTokenListEnv: "token1:list_token,token1:another_token", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - os.Unsetenv("GITHUB_AUTH_TOKEN") - os.Unsetenv("GITHUB_TOKEN_LIST") - if tt.githubTokenEnv != "" { - os.Setenv("GITHUB_AUTH_TOKEN", tt.githubTokenEnv) - } - if tt.githubTokenListEnv != "" { - os.Setenv("GITHUB_TOKEN_LIST", tt.githubTokenListEnv) - } - - err := ParseGitHubTokens() - if tt.wantErr != (err != nil) { - t.Errorf("TestParseGitHubTokens() error: unexpected error value %v", err) - } - if !tt.wantErr { - for k, v := range Clients { - client := v.Client - tt.want[k].Client = client - } - if !reflect.DeepEqual(Clients, tt.want) { - t.Errorf("TestParseGitHubTokens() error: expected %v got %v", tt.want, Clients) - } - } - - }) - } -} - -func TestGetNewGitHubClient(t *testing.T) { - //ghTokenClient := GitHubTokenClient{} - - //fakeToken := "ghp_faketoken" - - tests := []struct { - name string - client GitHubToken - githubTokenEnv string - githubTokenListEnv string - passedInToken string - wantErr bool - }{ - { - name: "Mock client", - client: MockGitHubTokenClient{}, - wantErr: false, - }, - { - name: "Passed in token", - client: GitHubTokenClient{}, - passedInToken: "fake-token", - wantErr: false, - }, - { - name: "Empty token passed in - should error out because rate limited", - client: MockPrimaryRateLimitGitHubTokenClient{}, - githubTokenEnv: " ", // Use an empty token here instead of a fake token string, since we need to make a request to GH RateLimit API - wantErr: true, - }, - { - name: "Invalid token passed in", - client: GitHubTokenClient{}, - githubTokenEnv: "sdfldskjfdklsn", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - Clients = nil - os.Unsetenv("GITHUB_AUTH_TOKEN") - os.Unsetenv("GITHUB_TOKEN_LIST") - if tt.githubTokenEnv != "" { - os.Setenv("GITHUB_AUTH_TOKEN", tt.githubTokenEnv) - } - if tt.githubTokenListEnv != "" { - os.Setenv("GITHUB_TOKEN_LIST", tt.githubTokenListEnv) - } - - _ = ParseGitHubTokens() - ghClient, err := tt.client.GetNewGitHubClient(tt.passedInToken) - if tt.wantErr != (err != nil) { - t.Errorf("TestGetNewGitHubClient() error: unexpected error value %v", err) - } - if tt.name != "Mock client" && tt.name != "Passed in token" && !tt.wantErr { - if ghClient == nil { - t.Errorf("TestGetNewGitHubClient() error: did not expect GitHub Client to be nil") - } - if ghClient != nil && Clients[ghClient.TokenName] == nil { - t.Errorf("TestGetNewGitHubClient() error: expected token value %v with key %v", Clients[ghClient.TokenName], ghClient.TokenName) - } - } - - }) - } -} - -func TestGetRandomClient(t *testing.T) { - ctx := context.WithValue(context.Background(), GHClientKey, "mock") - tests := []struct { - name string - client GitHubToken - clientPool map[string]*GitHubClient - githubTokenEnv string - githubTokenListEnv string - passedInToken string - wantErr bool - wantNumPRLTokens int //expected number of primary rate limited tokens - wantNumSRLTokens int //expected number of secondary rate limited tokens - }{ - { - name: "Empty client pool - should return an error", - clientPool: make(map[string]*GitHubClient), - wantErr: true, - wantNumPRLTokens: 0, - wantNumSRLTokens: 0, - }, - { - name: "primary-rate-limit - increment counter", - clientPool: map[string]*GitHubClient{ - "fake_reset": { - TokenName: "fake_reset", - Token: "fake_reset", - Client: GetMockedPrimaryRateLimitedClient(), - }, - }, - wantErr: true, - passedInToken: "fake_reset", - wantNumPRLTokens: 1, - wantNumSRLTokens: 0, - }, - { - name: "primary-rate-limit - decrement counter after primary rate limit reset", - clientPool: map[string]*GitHubClient{ - "fake_reset": { - TokenName: "fake_reset", - Token: "fake_reset", - Client: GetMockedResetPrimaryRateLimitedClient(), - PrimaryRateLimited: true, - }, - }, - wantErr: false, - passedInToken: "fake_reset", - wantNumPRLTokens: 0, - wantNumSRLTokens: 0, - }, - { - name: "secondary-rate-limit", - clientPool: map[string]*GitHubClient{ - "mock": { - TokenName: "mock", - Token: "fake", - Client: GetMockedSecondaryRateLimitedClient(), - }, - }, - wantErr: true, - passedInToken: "mock", - wantNumPRLTokens: 0, - wantNumSRLTokens: 1, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - client, err := getRandomClient(tt.clientPool) - if tt.wantErr != (err != nil) && tt.name != "secondary-rate-limit" { - t.Errorf("TestGetRandomClient() error: unexpected error value %v", err) - } - if tt.name == "secondary-rate-limit" { - Clients = tt.clientPool - // Deliberately lock the secondary rate limit object until we need to test the related fields - client.SecondaryRateLimit.mu.Lock() - _, err := client.GenerateNewRepository(ctx, "test-org", "test-repo", "test description") - if err == nil { - t.Error("TestGetRandomClient() error: expected err not to be nil") - } - - client.SecondaryRateLimit.mu.Unlock() - time.Sleep(time.Second * 1) - - //verify SRL metric has been incremented - assert.Equal(t, float64(tt.wantNumSRLTokens), testutil.ToFloat64(metrics.TokenPoolGauge.With(prometheus.Labels{"rateLimited": "secondary", "tokenName": tt.passedInToken}))) - _, err = getRandomClient(tt.clientPool) - if err == nil { - t.Error("TestGetRandomClient() error: unexpected err not to be nil") - } - - //verify SRL metric has been decremented - time.Sleep(time.Second * 2) - assert.Equal(t, float64(tt.wantNumSRLTokens-1), testutil.ToFloat64(metrics.TokenPoolGauge.With(prometheus.Labels{"rateLimited": "secondary", "tokenName": tt.passedInToken}))) - - } else { - assert.Equal(t, float64(tt.wantNumPRLTokens), testutil.ToFloat64(metrics.TokenPoolGauge.With(prometheus.Labels{"rateLimited": "primary", "tokenName": tt.passedInToken}))) - assert.Equal(t, float64(tt.wantNumSRLTokens), testutil.ToFloat64(metrics.TokenPoolGauge.With(prometheus.Labels{"rateLimited": "secondary", "tokenName": tt.passedInToken}))) - } - }) - } -} diff --git a/pkg/log/log.go b/pkg/log/log.go deleted file mode 100644 index aa8e93e50..000000000 --- a/pkg/log/log.go +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright 2022-2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package log - -import ( - "fmt" - - "github.com/go-logr/logr" -) - -type ResourceChangeType string - -const ( - ResourceCreate ResourceChangeType = "Create" - ResourceUpdate ResourceChangeType = "Update" - ResourceDelete ResourceChangeType = "Delete" - // complete is only for CDQ condition change upon completion - ResourceComplete ResourceChangeType = "Complete" -) - -func LogAPIResourceChangeEvent(log logr.Logger, resourceName string, resourceKind string, resourceChangeType ResourceChangeType, err error) { - log = log.WithValues("audit", "true") - - if resourceKind == "" { - log.Error(nil, "resourceKind passed to LogAPIResourceChangeEvent was empty") - return - } - log = log.WithValues("name", resourceName).WithValues("controllerKind", resourceKind).WithValues("action", resourceChangeType) - if err != nil { - log.Info(fmt.Sprintf("API Resource change event failed: %s, error: %v", string(resourceChangeType), err)) - } else { - log.Info(fmt.Sprintf("API Resource changed: %s", string(resourceChangeType))) - } -} diff --git a/pkg/metrics/application.go b/pkg/metrics/application.go deleted file mode 100644 index 79a02e003..000000000 --- a/pkg/metrics/application.go +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import "github.com/prometheus/client_golang/prometheus" - -var ( - ApplicationDeletionTotalReqs = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_application_deletion_total", - Help: "Number of application deletion requests processed", - }, - ) - ApplicationDeletionFailed = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_application_failed_deletion_total", - Help: "Number of failed application deletion requests", - }, - ) - - ApplicationDeletionSucceeded = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_application_successful_deletion_total", - Help: "Number of successful application deletion requests", - }, - ) - - ApplicationCreationTotalReqs = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_application_creation_total", - Help: "Number of application creation requests processed", - }, - ) - ApplicationCreationFailed = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_application_failed_creation_total", - Help: "Number of failed application creation requests", - }, - ) - - ApplicationCreationSucceeded = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_application_successful_creation_total", - Help: "Number of successful application creation requests", - }, - ) -) diff --git a/pkg/metrics/component.go b/pkg/metrics/component.go deleted file mode 100644 index 9fb932858..000000000 --- a/pkg/metrics/component.go +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "strings" - - "github.com/prometheus/client_golang/prometheus" -) - -var ( - componentCreationTotalReqs = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_component_creation_total", - Help: "Number of component creation requests processed", - }, - ) - componentCreationFailed = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_component_failed_creation_total", - Help: "Number of failed component creation requests", - }, - ) - - componentCreationSucceeded = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_component_successful_creation_total", - Help: "Number of successful component creation requests", - }, - ) - - ComponentDeletionTotalReqs = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_component_deletion_total", - Help: "Number of component deletion requests processed", - }, - ) - ComponentDeletionFailed = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_component_failed_deletion_total", - Help: "Number of failed component deletion requests", - }, - ) - - ComponentDeletionSucceeded = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_component_successful_deletion_total", - Help: "Number of successful component deletion requests", - }, - ) -) - -// IncrementComponentCreationFailed increments the component creation failed metric. -// Pass in the new error to update the metric, otherwise it will be ignored. -func IncrementComponentCreationFailed(oldError, newError string) { - if newError != "" && (oldError == "" || !strings.Contains(oldError, newError)) { - // pair the componentCreationTotalReqs counter with componentCreationFailed because - // we dont want a situation where we increment componentCreationTotalReqs in the - // beginning of a reconcile, and we skip the componentCreationFailed metric because - // the errors are the same. Otherwise we will have a situation where neither the success - // nor the fail metric is increasing but the total request count is increasing. - componentCreationTotalReqs.Inc() - componentCreationFailed.Inc() - } -} - -func GetComponentCreationFailed() prometheus.Counter { - return componentCreationFailed -} - -func GetComponentDeletionFailed() prometheus.Counter { - return ComponentDeletionFailed -} - -// IncrementComponentCreationSucceeded increments the component creation succeeded metric. -func IncrementComponentCreationSucceeded(oldError, newError string) { - if oldError == "" || newError == "" || !strings.Contains(oldError, newError) { - // pair the componentCreationTotalReqs counter with componentCreationSucceeded because - // we dont want a situation where we increment componentCreationTotalReqs in the - // beginning of a reconcile, and we skip the componentCreationSucceeded metric because - // the errors are the same. Otherwise we will have a situation where neither the success - // nor the fail metric is increasing but the total request count is increasing. - componentCreationTotalReqs.Inc() - componentCreationSucceeded.Inc() - } -} - -func GetComponentCreationSucceeded() prometheus.Counter { - return componentCreationSucceeded -} - -func GetComponentDeletionSucceeded() prometheus.Counter { - return ComponentDeletionSucceeded -} - -func GetComponentCreationTotalReqs() prometheus.Counter { - return componentCreationTotalReqs -} - -func GetComponentDeletionTotalReqs() prometheus.Counter { - return ComponentDeletionTotalReqs -} diff --git a/pkg/metrics/component_test.go b/pkg/metrics/component_test.go deleted file mode 100644 index e03c2c9c4..000000000 --- a/pkg/metrics/component_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "testing" - - "github.com/prometheus/client_golang/prometheus/testutil" -) - -func TestComponentMetricsIncrement(t *testing.T) { - - tests := []struct { - name string - oldErr string - newErr string - expectSuccessIncremented bool - expectFailureIncremented bool - }{ - { - name: "no errors", - oldErr: "", - newErr: "", - expectSuccessIncremented: true, - expectFailureIncremented: false, - }, - { - name: "no old error, new error", - oldErr: "", - newErr: "error", - expectSuccessIncremented: true, - expectFailureIncremented: true, - }, - { - name: "old error, no new error", - oldErr: "error", - newErr: "", - expectSuccessIncremented: true, - expectFailureIncremented: false, - }, - { - name: "old error, new error - same error", - oldErr: "error", - newErr: "error", - expectSuccessIncremented: false, - expectFailureIncremented: false, - }, - { - name: "old error, new error - different error", - oldErr: "error", - newErr: "error 2", - expectSuccessIncremented: true, - expectFailureIncremented: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - beforeCreateSuccess := testutil.ToFloat64(componentCreationSucceeded) - beforeCreateFailed := testutil.ToFloat64(componentCreationFailed) - - IncrementComponentCreationSucceeded(tt.oldErr, tt.newErr) - - if tt.expectSuccessIncremented && testutil.ToFloat64(componentCreationSucceeded) <= beforeCreateSuccess { - t.Errorf("TestComponentMetricsIncrement error: expected component create success metrics to be incremented but was not incremented") - } else if !tt.expectSuccessIncremented && testutil.ToFloat64(componentCreationSucceeded) > beforeCreateSuccess { - t.Errorf("TestComponentMetricsIncrement error: expected component create success metrics not to be incremented but it was incremented") - } - - IncrementComponentCreationFailed(tt.oldErr, tt.newErr) - - if tt.expectFailureIncremented && testutil.ToFloat64(componentCreationFailed) <= beforeCreateFailed { - t.Errorf("TestComponentMetricsIncrement error: expected component create failed metrics to be incremented but was not incremented") - } else if !tt.expectFailureIncremented && testutil.ToFloat64(componentCreationFailed) > beforeCreateFailed { - t.Errorf("TestComponentMetricsIncrement error: expected component create failed metrics not to be incremented but it was incremented") - } - - }) - } -} diff --git a/pkg/metrics/git.go b/pkg/metrics/git.go deleted file mode 100644 index 92e0bfee9..000000000 --- a/pkg/metrics/git.go +++ /dev/null @@ -1,84 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import "github.com/prometheus/client_golang/prometheus" - -var ( - ControllerGitRequest = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: "controller_git_request", - Help: "Number of git operation requests. Not an SLI metric", - }, - []string{"controller", "tokenName", "operation"}, - ) - - SecondaryRateLimitCounter = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: "secondary_rate_limit_total", - Help: "Number of times the secondary rate limit has been reached. Not an SLI metric", - }, - []string{"controller", "tokenName", "operation"}, - ) - - PrimaryRateLimitCounter = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: "primary_rate_limit_total", - Help: "Number of times the primary rate limit has been reached. Not an SLI metric", - }, - []string{"controller", "tokenName", "operation"}, - ) - - TokenPoolGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "token_pool_gauge", - Help: "Gauge counter to track whether a token has been primary/secondary rate limited", - }, - - //rateLimited - can have the value of "primary" or "secondary" - //tokenName - the name of the token being rate limited - []string{"rateLimited", "tokenName"}, - ) - - ImportGitRepoTotalReqs = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_import_git_repo_total", - Help: "Number of import from git repository requests processed", - }, - ) - ImportGitRepoFailed = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_failed_importc_git_repo_total", - Help: "Number of failed import from git repository requests", - }, - ) - - ImportGitRepoSucceeded = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_successful_import_git_repo_total", - Help: "Number of successful import from git repository requests", - }, - ) - - HASAvailabilityGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "has_availability", - Help: "Availability of the Konflux application service component", - }, - // check - can have the values of "github" or whatever availability is being checked - []string{"check"}, - ) -) diff --git a/pkg/metrics/gitops.go b/pkg/metrics/gitops.go deleted file mode 100644 index 3c6b5e729..000000000 --- a/pkg/metrics/gitops.go +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright 2024 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import "github.com/prometheus/client_golang/prometheus" - -var ( - GitOpsRepoCreationTotalReqs = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_gitops_repo_creation_total", - Help: "Number of gitops creation requests processed", - }, - ) - GitOpsRepoCreationFailed = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_gitops_failed_repo_creation_total", - Help: "Number of failed gitops creation requests", - }, - ) - - GitOpsRepoCreationSucceeded = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "has_gitops_successful_repo_creation_total", - Help: "Number of successful gitops creation requests", - }, - ) -) diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go deleted file mode 100644 index fb116b699..000000000 --- a/pkg/metrics/metrics.go +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright 2023 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - gh "github.com/google/go-github/v59/github" - "github.com/prometheus/client_golang/prometheus" - "sigs.k8s.io/controller-runtime/pkg/metrics" -) - -func init() { - // Register custom metrics with the global prometheus registry - metrics.Registry.MustRegister(GitOpsRepoCreationTotalReqs, GitOpsRepoCreationFailed, GitOpsRepoCreationSucceeded, ControllerGitRequest, - SecondaryRateLimitCounter, PrimaryRateLimitCounter, TokenPoolGauge, HASAvailabilityGauge, - ApplicationDeletionTotalReqs, ApplicationDeletionSucceeded, ApplicationDeletionFailed, - ApplicationCreationSucceeded, ApplicationCreationFailed, ApplicationCreationTotalReqs, - componentCreationTotalReqs, componentCreationSucceeded, componentCreationFailed, - ComponentDeletionTotalReqs, ComponentDeletionSucceeded, ComponentDeletionFailed, - ImportGitRepoTotalReqs, ImportGitRepoFailed, ImportGitRepoSucceeded) -} - -// HandleRateLimitMetrics checks the error type to verify a primary or secondary rate limit has been encountered -func HandleRateLimitMetrics(err error, labels prometheus.Labels) { - if _, ok := err.(*gh.RateLimitError); ok { - PrimaryRateLimitCounter.With(labels).Inc() - } else if _, ok := err.(*gh.AbuseRateLimitError); ok { - SecondaryRateLimitCounter.With(labels).Inc() - } -} diff --git a/pkg/spi/spi.go b/pkg/spi/spi.go deleted file mode 100644 index 8038a24b1..000000000 --- a/pkg/spi/spi.go +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright 2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package spi - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "io" - "path/filepath" - "strconv" - - "github.com/konflux-ci/application-api/api/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/types" - - ctrl "sigs.k8s.io/controller-runtime" - - spiapi "github.com/redhat-appstudio/service-provider-integration-operator/api/v1beta1" - "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" - - cdqanalysis "github.com/redhat-appstudio/application-service/cdq-analysis/pkg" - "github.com/redhat-appstudio/application-service/pkg/devfile" -) - -type SPI interface { - GetFileContents(ctx context.Context, name string, component v1alpha1.Component, repoUrl string, filepath string, ref string) (io.ReadCloser, error) -} - -const ( - SPIFCR_waiting_for_delivered_phase = "File content request status has not been delivered" - SPIFCR_prefix = "spi-fcr-" -) - -type SPIClient struct { - K8sClient client.Client -} - -// SPIFileContentRequestError returns an internal error -type SPIFileContentRequestError struct { - Message string -} - -func (e *SPIFileContentRequestError) Error() string { - return "SPIFileContentRequest failed " + e.Message -} - -var ValidDevfileLocations = cdqanalysis.ValidDevfileLocations - -// GetFileContents is a wrapper call to scm file retriever's GetFileContents() -func (s SPIClient) GetFileContents(ctx context.Context, name string, component v1alpha1.Component, repoUrl string, filepath string, ref string) (io.ReadCloser, error) { - log := ctrl.LoggerFrom(ctx) - spiFCRLookupKey := types.NamespacedName{Name: SPIFCR_prefix + name, Namespace: component.Namespace} //We'll use a unique component name and filepath to construct the name - spiFCR := &spiapi.SPIFileContentRequest{} - log.Info("Looking up SPIFileContentRequest CR", "name", spiFCRLookupKey.Name, "namespace", spiFCRLookupKey.Namespace) - err := s.K8sClient.Get(ctx, spiFCRLookupKey, spiFCR) - if err != nil { - if errors.IsNotFound(err) { - spiFCR.Name = spiFCRLookupKey.Name - spiFCR.Namespace = spiFCRLookupKey.Namespace - spiFCR.Spec.RepoUrl = repoUrl - spiFCR.Spec.FilePath = filepath - spiFCR.Spec.Ref = ref - //add an owner reference - ownerReference := metav1.OwnerReference{ - APIVersion: component.APIVersion, - Kind: component.Kind, - Name: component.Name, - UID: component.UID, - } - spiFCR.SetOwnerReferences(append(spiFCR.GetOwnerReferences(), ownerReference)) - - log.Info("SPIFileContentRequest CR not found, creating a new request", "name", spiFCR.Name, "namespace", spiFCR.Namespace) - err = s.K8sClient.Create(ctx, spiFCR) - if err != nil { - log.Error(err, "Unable to create an SPIFileContentRequest CR", "name", spiFCR.Name, "namespace", spiFCR.Namespace) - return nil, &SPIFileContentRequestError{fmt.Sprintf("Failed to create an SPIFileContentRequest CR: %s", err.Error())} - } - } - } - - return getFileContentFromSPIFCR(*spiFCR, log) -} - -func getFileContentFromSPIFCR(fcr spiapi.SPIFileContentRequest, log logr.Logger) (io.ReadCloser, error) { - - if fcr.Status.Phase == spiapi.SPIFileContentRequestPhaseDelivered { - fcrName := fcr.Name - //get contents and decode them - fileContent := fcr.Status.Content - decodedFileContent, err := base64.StdEncoding.DecodeString(fileContent) - if err != nil { - return nil, &SPIFileContentRequestError{fmt.Sprintf("Failed to decode file content %s ", err)} - } - - log.Info("SPIFileContentRequest: Decoded file contents successfully ", "name", fcrName, "namespace", fcr.Namespace) - return io.NopCloser(bytes.NewBuffer(decodedFileContent)), nil - } else { - log.Info("SPI Phase delivered not reached") - return nil, &SPIFileContentRequestError{SPIFCR_waiting_for_delivered_phase} - } - -} - -func DownloadDevfileUsingSPI(s SPI, ctx context.Context, component v1alpha1.Component, repoURL string, ref string, path string) ([]byte, string, error) { - for i, filename := range ValidDevfileLocations { - devfileBytes, err := DownloadFileUsingSPI(s, ctx, component.Name+strconv.Itoa(i), component, repoURL, ref, filepath.Join("/", path, filename)) //pass in unique name so SPIFileContentRequest can be created for each valid devfile location type - if err == nil { - return devfileBytes, filename, nil - } else { - if _, ok := err.(*devfile.NoFileFound); !ok { - return nil, filename, err - } - } - } - return nil, "", &cdqanalysis.NoDevfileFound{Location: repoURL} -} - -func DownloadFileUsingSPI(s SPI, ctx context.Context, fcrName string, component v1alpha1.Component, repoURL string, ref string, filepath string) ([]byte, error) { - // Call out to SPI via SPIFileContentRequests - r, err := s.GetFileContents(ctx, fcrName, component, repoURL, filepath, ref) - if err == nil { - fileBytes, err := io.ReadAll(r) - if err != nil { - return nil, err - } - return fileBytes, nil - } - - return nil, &devfile.NoFileFound{Location: repoURL, Err: err} -} - -func DownloadDevfileandDockerfileUsingSPI(s SPI, ctx context.Context, name string, component v1alpha1.Component, repoURL string, ref string, path string) ([]byte, []byte, string, error) { - - devfileBytes, filename, err := DownloadDevfileUsingSPI(s, ctx, component, repoURL, ref, path) - if err != nil { - if _, ok := err.(*cdqanalysis.NoDevfileFound); !ok { - return nil, nil, filename, err - } - } - - dockerfileBytes, err := DownloadFileUsingSPI(s, ctx, name, component, repoURL, ref, filepath.Join("/", path, "Dockerfile")) - if err != nil { - if _, ok := err.(*devfile.NoFileFound); !ok { - return nil, nil, "", err - } - } - - return devfileBytes, dockerfileBytes, filename, nil -} diff --git a/pkg/spi/spi_mock.go b/pkg/spi/spi_mock.go deleted file mode 100644 index be85466da..000000000 --- a/pkg/spi/spi_mock.go +++ /dev/null @@ -1,201 +0,0 @@ -// -// Copyright 2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package spi - -import ( - "context" - "fmt" - "io" - "strings" - - "github.com/konflux-ci/application-api/api/v1alpha1" - spiapi "github.com/redhat-appstudio/service-provider-integration-operator/api/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/stretchr/testify/mock" -) - -type mockReadCloser struct { - mock.Mock -} - -func (m *mockReadCloser) Read(p []byte) (n int, err error) { - args := m.Called(p) - return args.Int(0), args.Error(1) -} - -func (m *mockReadCloser) Close() error { - args := m.Called() - return args.Error(0) -} - -type MockSPIClient struct { - K8sClient client.Client -} - -var mockDevfile = ` -schemaVersion: 2.2.0 -metadata: - displayName: Go Runtime - icon: https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg - language: go - name: go - projectType: go - tags: - - Go - version: 1.0.0 -starterProjects: - - name: go-starter - git: - checkoutFrom: - revision: main - remotes: - origin: https://github.com/devfile-samples/devfile-stack-go.git -components: - - container: - endpoints: - - name: http - targetPort: 8080 - image: golang:latest - memoryLimit: 1024Mi - mountSources: true - sourceMapping: /project - name: runtime - - name: image-build - image: - imageName: go-image:latest - dockerfile: - uri: docker/Dockerfile - buildContext: . - rootRequired: false - - name: kubernetes-deploy - kubernetes: - inlined: |- - apiVersion: apps/v1 - kind: Deployment - metadata: - creationTimestamp: null - labels: - maysun: test - name: deploy-sample - endpoints: - - name: http-8081 - targetPort: 8081 - path: / -commands: - - exec: - commandLine: GOCACHE=/project/.cache go build main.go - component: runtime - group: - isDefault: true - kind: build - workingDir: /project - id: build - - exec: - commandLine: ./main - component: runtime - group: - isDefault: true - kind: run - workingDir: /project - id: run - - id: build-image - apply: - component: image-build - - id: deployk8s - apply: - component: kubernetes-deploy - - id: deploy - composite: - commands: - - build-image - - deployk8s - group: - kind: deploy - isDefault: true -` - -var mockDockerfile = ` -FROM python:slim - -WORKDIR /projects - -RUN python3 -m venv venv -RUN . venv/bin/activate - -# optimize image caching -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY . . - -EXPOSE 8081 -CMD [ "waitress-serve", "--port=8081", "app:app"] -` - -// GetFileContents mocks the GetFileContents function from SPI -// If "repoURL" parameter contains "test-error-response", then an error value will be returned, -// otherwise we return a mock devfile that can be read. -func (s MockSPIClient) GetFileContents(ctx context.Context, name string, component v1alpha1.Component, repoURL string, filepath string, ref string) (io.ReadCloser, error) { - if strings.Contains(repoURL, "test-error-response") { - return nil, fmt.Errorf("file not found") - } else if strings.Contains(repoURL, "test-parse-error") || (strings.Contains(repoURL, "test-error-dockerfile-response")) { - mockReadCloser := mockReadCloser{} - mockReadCloser.On("Read", mock.AnythingOfType("[]uint8")).Return(0, fmt.Errorf("error reading")) - mockReadCloser.On("Close").Return(fmt.Errorf("error closing")) - return &mockReadCloser, nil - } else if strings.Contains(repoURL, "create-spi-fcr") { - log := ctrl.LoggerFrom(ctx) - spiFCRLookupKey := types.NamespacedName{Name: SPIFCR_prefix + name, Namespace: component.Namespace} - spiFCR := &spiapi.SPIFileContentRequest{} - spiFCR.Name = spiFCRLookupKey.Name - spiFCR.Namespace = spiFCRLookupKey.Namespace - spiFCR.Spec.RepoUrl = repoURL - spiFCR.Spec.FilePath = filepath - spiFCR.Spec.Ref = ref - //add an owner reference - ownerReference := metav1.OwnerReference{ - APIVersion: component.APIVersion, - Kind: component.Kind, - Name: component.Name, - UID: component.UID, - } - spiFCR.SetOwnerReferences(append(spiFCR.GetOwnerReferences(), ownerReference)) - err := s.K8sClient.Create(ctx, spiFCR) - if err != nil { - return nil, &SPIFileContentRequestError{fmt.Sprintf("Failed to create an SPIFileContentRequest CR: %s", err.Error())} - } - - if strings.Contains(repoURL, "create-spi-fcr-return-devfile") { - stringReader := strings.NewReader(mockDevfile) - stringReadCloser := io.NopCloser(stringReader) - return stringReadCloser, nil - } - - return getFileContentFromSPIFCR(*spiFCR, log) - } else if strings.Contains(filepath, "Dockerfile") { - stringReader := strings.NewReader(mockDockerfile) - stringReadCloser := io.NopCloser(stringReader) - return stringReadCloser, nil - } else { - stringReader := strings.NewReader(mockDevfile) - stringReadCloser := io.NopCloser(stringReader) - return stringReadCloser, nil - } -} diff --git a/pkg/spi/spi_test.go b/pkg/spi/spi_test.go deleted file mode 100644 index ede42a179..000000000 --- a/pkg/spi/spi_test.go +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright 2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package spi - -import ( - "context" - "testing" - - "github.com/konflux-ci/application-api/api/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const testNamespace = "test-namespace" - -// TestDownloadDevfileFromSPI uses the Mock SPI client to test the DownloadDevfileFromSPI function -// Since SPI does not support running outside of Kube, we cannot unit test the non-mock SPI client at this moment -func TestDownloadDevfileFromSPI(t *testing.T) { - var mock MockSPIClient - - tests := []struct { - comp v1alpha1.Component - name string - repoUrl string - path string - want string - wantErr bool - }{ - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Successfully retrieve devfile, no context/path set", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-private-repo", - want: mockDevfile, - }, - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Successfully retrieve devfile, context/path set", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-private-repo", - path: "/test", - want: mockDevfile, - }, - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Unable to retrieve devfile", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-error-response", - wantErr: true, - }, - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Error reading devfile", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-parse-error", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Convert the hasApp resource to a devfile - devfileBytes, _, err := DownloadDevfileUsingSPI(mock, context.Background(), tt.comp, tt.repoUrl, "main", tt.path) - if (err != nil) != tt.wantErr { - t.Errorf("unexpected error return value: %v", err) - } - - devfileBytesString := string(devfileBytes) - if devfileBytesString != tt.want { - t.Errorf("error: expected %v, got %v", tt.want, devfileBytesString) - } - }) - } -} - -func TestDownloadDevfileandDockerfileUsingSPI(t *testing.T) { - var mock MockSPIClient - - tests := []struct { - comp v1alpha1.Component - name string - repoUrl string - path string - wantDevfile string - wantDockerfile string - wantErr bool - }{ - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Successfully retrieve devfile, no context/path set", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-private-repo", - wantDevfile: mockDevfile, - wantDockerfile: mockDockerfile, - }, - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Successfully retrieve devfile, context/path set", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-private-repo", - path: "/test", - wantDevfile: mockDevfile, - wantDockerfile: mockDockerfile, - }, - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Error reading devfile", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-parse-error", - wantErr: true, - }, - { - comp: v1alpha1.Component{ObjectMeta: v1.ObjectMeta{Name: "Error reading dockerfile", Namespace: testNamespace}}, - repoUrl: "https://github.com/testrepo/test-error-dockerfile-response", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - devfileBytes, dockerfileBytes, _, err := DownloadDevfileandDockerfileUsingSPI(mock, context.Background(), tt.name, tt.comp, tt.repoUrl, "main", tt.path) - if (err != nil) != tt.wantErr { - t.Errorf("unexpected error return value: %v", err) - return - } - - devfileBytesString := string(devfileBytes) - if devfileBytesString != tt.wantDevfile { - t.Errorf("devfile error: expected %v, got %v", tt.wantDevfile, devfileBytesString) - } - - dockerfileBytesString := string(dockerfileBytes) - if dockerfileBytesString != tt.wantDockerfile { - t.Errorf("Dockerfile error: expected %v, got %v", tt.wantDockerfile, dockerfileBytesString) - } - }) - } -} diff --git a/pkg/util/ioutils/ioutils.go b/pkg/util/ioutils/ioutils.go deleted file mode 100644 index 6d6757881..000000000 --- a/pkg/util/ioutils/ioutils.go +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright 2021-2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Originally from https://github.com/redhat-developer/kam/blob/master/pkg/pipelines/ioutils/file_utils.go - -package ioutils - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/go-logr/logr" - "github.com/spf13/afero" -) - -// NewFilesystem returns a local filesystem based afero FS implementation. -func NewFilesystem() afero.Afero { - return afero.Afero{Fs: afero.NewOsFs()} -} - -// NewMemoryFilesystem returns an in-memory afero FS implementation. -func NewMemoryFilesystem() afero.Afero { - return afero.Afero{Fs: afero.NewMemMapFs()} -} - -// NewReadOnlyFs returns a read-only file system -func NewReadOnlyFs() afero.Afero { - return afero.Afero{Fs: afero.NewReadOnlyFs(afero.NewOsFs())} -} - -// IsExisting returns bool whether path exists -func IsExisting(fs afero.Fs, path string) (bool, error) { - fileInfo, err := fs.Stat(path) - if err != nil { - return false, err - } - if fileInfo.IsDir() { - return true, fmt.Errorf("%q: Dir already exists at %s", filepath.Base(path), path) - } - return true, fmt.Errorf("%q: File already exists at %s", filepath.Base(path), path) -} - -// CreateTempPath creates a temp path with the prefix using the Afero FS -func CreateTempPath(prefix string, appFs afero.Afero) (string, error) { - return appFs.TempDir(os.TempDir(), prefix) -} - -// RemoveFolderAndLogError removes the specified folder. If the delete fails, no error is returned, but an error is logged -// Used in cases where we're cleaning up after encountering an error, but want to return the original error instead. -func RemoveFolderAndLogError(log logr.Logger, fs afero.Fs, path string) { - if path != "" { - err := os.RemoveAll(path) - if err != nil { - log.Error(err, fmt.Sprintf("Unable to delete folder %s", path)) - } - } -} diff --git a/pkg/util/ioutils/ioutils_test.go b/pkg/util/ioutils/ioutils_test.go deleted file mode 100644 index 917431580..000000000 --- a/pkg/util/ioutils/ioutils_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// -// Copyright 2021-2022 Red Hat, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ioutils - -import ( - "os" - "strings" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "go.uber.org/zap/zapcore" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -func TestIsExisting(t *testing.T) { - fs := NewFilesystem() - inmemoryFs := NewMemoryFilesystem() - readOnlyFs := NewReadOnlyFs() - dirName := "/tmp/test-dir" - fileName := "/tmp/test-file" - secondFile := "/tmp/test-two" - - // Make sure at least one file and one dir exists in each file system for testing - fs.Create(fileName) - // fs.Mkdir(dirName, 0755) - inmemoryFs.Create(fileName) - inmemoryFs.Mkdir(dirName, 0755) - - tests := []struct { - name string - path string - want bool - wantErrString string - fs afero.Afero - }{ - { - name: "Simple file does not exist, inmemory fs", - path: secondFile, - want: false, - wantErrString: "open /tmp/test-two: file does not exist", - fs: inmemoryFs, - }, - { - name: "File exists, inmemory fs", - path: fileName, - want: true, - wantErrString: "\"test-file\": File already exists at /tmp/test-file", - fs: inmemoryFs, - }, - { - name: "Dir already exists, inmemory fs", - path: dirName, - want: true, - wantErrString: "\"test-dir\": Dir already exists at /tmp/test-dir", - fs: inmemoryFs, - }, - { - name: "File does not exist, regular fs", - path: secondFile, - want: false, - wantErrString: "stat /tmp/test-two: no such file or directory", - fs: fs, - }, - { - name: "Dir already exists, readonly fs", - path: "/", - want: true, - wantErrString: "\"/\": Dir already exists at /", - fs: readOnlyFs, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - exists, err := IsExisting(tt.fs, tt.path) - if tt.wantErrString != "" { - if err == nil { - t.Errorf("TestIsExisting() expected error: %v, got: %v", tt.wantErrString, nil) - } else if err.Error() != tt.wantErrString { - t.Errorf("TestIsExisting() expected error: %v, got: %v", tt.wantErrString, err.Error()) - } - } else if tt.wantErrString == "" && err != nil { - t.Errorf("TestIsExisting() unexpected error: %v, got: %v", tt.wantErrString, err) - } - - if exists != tt.want { - t.Errorf("TestIsExisting() expected: %v, got: %v", tt.want, exists) - } - - }) - } -} - -func TestCreateTempPath(t *testing.T) { - fs := NewFilesystem() - inmemoryFs := NewMemoryFilesystem() - readOnlyFs := NewReadOnlyFs() - - tests := []struct { - name string - fs afero.Afero - wantErr bool - }{ - { - name: "inmemory fs", - fs: inmemoryFs, - }, - { - name: "read only fs", - fs: readOnlyFs, - wantErr: true, - }, - { - name: "local fs", - fs: fs, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - path, err := CreateTempPath("TestCreateTempPath", tt.fs) - if tt.wantErr && (err == nil) { - t.Error("wanted error but got nil") - } else if !tt.wantErr && err != nil { - t.Errorf("got unexpected error: %v", err) - } else if err == nil { - if !strings.Contains(path, os.TempDir()) { - t.Errorf("TestCreateTempPath error: the temp path should be in the OS temp dir") - } - - if !strings.Contains(path, "TestCreateTempPath") { - t.Errorf("TestCreateTempPath error: the temp path should contain the prefix") - } - - if isExist, err := IsExisting(tt.fs, path); isExist { - assert.NoError(t, tt.fs.RemoveAll(path), "unable to delete the temp path") - } else if err != nil { - t.Errorf("TestCreateTempPath unexpected error: %v", err) - } - } - }) - } -} - -func TestRemoveFolderAndLogError(t *testing.T) { - fs := NewFilesystem() - inmemoryFs := NewMemoryFilesystem() - readOnlyFs := NewReadOnlyFs() - - tests := []struct { - name string - fs afero.Afero - path string - }{ - { - name: "inmemory fs", - fs: inmemoryFs, - }, - { - name: "read only fs", - fs: readOnlyFs, - path: "/somepath", - }, - { - name: "local fs", - fs: fs, - }, - { - name: "empty path", - fs: fs, - path: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.path == "" && tt.name != "empty path" { - tt.path, _ = CreateTempPath("TestCreateTempPath", tt.fs) - } - log := zap.New(zap.UseFlagOptions(&zap.Options{ - Development: true, - TimeEncoder: zapcore.ISO8601TimeEncoder, - })) - RemoveFolderAndLogError(log, tt.fs, tt.path) - }) - } -} diff --git a/pkg/util/util.go b/pkg/util/util.go index b028495b0..74f55148b 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -15,67 +15,6 @@ package util -import ( - "fmt" - "math/rand" - "net/url" - "regexp" - "strings" -) - -var RevisionHistoryLimit = int32(0) - -// GetIntValue returns the value of an int pointer, with the default of 0 if nil -func GetIntValue(intPtr *int) int { - if intPtr != nil { - return *intPtr - } - - return 0 -} - -// ValidateEndpoint validates if the endpoint url can be parsed and if it has a host and a scheme -func ValidateEndpoint(endpoint string) error { - u, err := url.Parse(endpoint) - if err != nil { - return fmt.Errorf("failed to parse the url: %v, err: %v", endpoint, err) - } - - if len(u.Host) == 0 || len(u.Scheme) == 0 { - return fmt.Errorf("url %v is invalid", endpoint) - } - - return nil -} - -// CheckWithRegex checks if a name matches the pattern. -// If a pattern fails to compile, it returns false -func CheckWithRegex(pattern, name string) bool { - reg, err := regexp.Compile(pattern) - if err != nil { - return false - } - - return reg.MatchString(name) -} - -const schemaBytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - -// GetRandomString returns a random string which is n characters long. -// If lower is set to true a lower case string is returned. -func GetRandomString(n int, lower bool) string { - b := make([]byte, n) - for i := range b { - /* #nosec G404 -- not used for cryptographic purposes*/ - b[i] = schemaBytes[rand.Intn(len(schemaBytes)-1)] - } - randomString := string(b) - if lower { - randomString = strings.ToLower(randomString) - } - return randomString -} - // StrInList returns true if the given string is present in strList func StrInList(str string, strList []string) bool { for _, val := range strList { @@ -95,17 +34,3 @@ func RemoveStrFromList(str string, strList []string) []string { } return strList } - -// ValidateGithubURL checks if the given url includes github in hostname -// In case of invalid url (not able to parse / not github) returns an error. -func ValidateGithubURL(URL string) error { - parsedURL, err := url.Parse(URL) - if err != nil { - return err - } - - if strings.Contains(parsedURL.Host, "github") { - return nil - } - return fmt.Errorf("source git url %v is not from github", URL) -} diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index b9090f93c..72d9a4ce1 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -16,147 +16,11 @@ package util import ( - "strings" "testing" "github.com/stretchr/testify/assert" ) -func TestValidateEndpoint(t *testing.T) { - parseFail := "failed to parse the url" - - tests := []struct { - name string - url string - wantErr *string - }{ - { - name: "Valid Endpoint", - url: "https://google.ca", - }, - { - name: "Valid private repo", - url: "https://github.com/devfile-resources/multi-components-private", - }, - { - name: "Invalid URL failed to be parsed", - url: "\000x", - wantErr: &parseFail, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateEndpoint(tt.url) - if tt.wantErr != nil && (err == nil) { - t.Error("wanted error but got nil") - return - } else if tt.wantErr == nil && err != nil { - t.Errorf("got unexpected error %v", err) - return - } - if tt.wantErr != nil { - assert.Regexp(t, *tt.wantErr, err.Error(), "TestValidateEndpoint: Error message does not match") - } - }) - } -} - -func TestCheckWithRegex(t *testing.T) { - tests := []struct { - name string - test string - pattern string - wantMatch bool - }{ - { - name: "matching string", - test: "hi-00-HI", - pattern: "^[a-z]([-a-z0-9]*[a-z0-9])?", - wantMatch: true, - }, - { - name: "not a matching string", - test: "1-hi", - pattern: "^[a-z]([-a-z0-9]*[a-z0-9])?", - wantMatch: false, - }, - { - name: "bad pattern", - test: "hi-00-HI", - pattern: "(abc", - wantMatch: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotMatch := CheckWithRegex(tt.pattern, tt.test) - assert.Equal(t, tt.wantMatch, gotMatch, "the values should match") - }) - } -} - -func TestGetRandomString(t *testing.T) { - tests := []struct { - name string - length int - lower bool - }{ - { - name: "all lower case string", - length: 5, - lower: true, - }, - { - name: "contain upper case string", - length: 10, - lower: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotString := GetRandomString(tt.length, tt.lower) - assert.Equal(t, tt.length, len(gotString), "the values should match") - - if tt.lower == true { - assert.Equal(t, strings.ToLower(gotString), gotString, "the values should match") - } - - gotString2 := GetRandomString(tt.length, tt.lower) - assert.NotEqual(t, gotString, gotString2, "the two random string should not be the same") - }) - } -} - -func TestGetIntValue(t *testing.T) { - - value := 7 - - tests := []struct { - name string - replica *int - wantValue int - wantErr bool - }{ - { - name: "Unset value, expect default 0", - replica: nil, - wantValue: 0, - }, - { - name: "set value, expect set number", - replica: &value, - wantValue: 7, - }, - } - - for _, tt := range tests { - val := GetIntValue(tt.replica) - assert.True(t, val == tt.wantValue, "Expected int value %d got %d", tt.wantValue, val) - } -} - func TestStrInList(t *testing.T) { tests := []struct { name string @@ -223,36 +87,3 @@ func TestRemoveStrFromList(t *testing.T) { } } } - -func TestValidateGithubURL(t *testing.T) { - tests := []struct { - name string - sourceGitURL string - wantErr bool - }{ - { - name: "Valid github url", - sourceGitURL: "https://github.com/devfile-samples", - wantErr: false, - }, - { - name: "Invalid url", - sourceGitURL: "afgae devfile", - wantErr: true, - }, - { - name: "Not github url", - sourceGitURL: "https://gitlab.com/devfile-samples", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateGithubURL(tt.sourceGitURL) - if (err != nil) != tt.wantErr { - t.Errorf("TestValidateGithubURL() unexpected error: %v", err) - } - }) - } -} diff --git a/controllers/webhooks/application_webhook.go b/webhooks/application_webhook.go similarity index 100% rename from controllers/webhooks/application_webhook.go rename to webhooks/application_webhook.go diff --git a/controllers/webhooks/application_webhook_test.go b/webhooks/application_webhook_test.go similarity index 100% rename from controllers/webhooks/application_webhook_test.go rename to webhooks/application_webhook_test.go diff --git a/controllers/webhooks/application_webhook_unit_test.go b/webhooks/application_webhook_unit_test.go similarity index 100% rename from controllers/webhooks/application_webhook_unit_test.go rename to webhooks/application_webhook_unit_test.go diff --git a/controllers/webhooks/component_webhook.go b/webhooks/component_webhook.go similarity index 100% rename from controllers/webhooks/component_webhook.go rename to webhooks/component_webhook.go diff --git a/controllers/webhooks/component_webhook_test.go b/webhooks/component_webhook_test.go similarity index 100% rename from controllers/webhooks/component_webhook_test.go rename to webhooks/component_webhook_test.go diff --git a/controllers/webhooks/component_webhook_unit_test.go b/webhooks/component_webhook_unit_test.go similarity index 100% rename from controllers/webhooks/component_webhook_unit_test.go rename to webhooks/component_webhook_unit_test.go diff --git a/controllers/webhooks/webhook_suite_test.go b/webhooks/webhook_suite_test.go similarity index 98% rename from controllers/webhooks/webhook_suite_test.go rename to webhooks/webhook_suite_test.go index 565ba8f1c..c15c09780 100644 --- a/controllers/webhooks/webhook_suite_test.go +++ b/webhooks/webhook_suite_test.go @@ -67,7 +67,7 @@ var _ = BeforeSuite(func() { CRDDirectoryPaths: []string{filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "redhat-appstudio", "application-api@"+applicationAPIDepVersion, "manifests")}, ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + Paths: []string{filepath.Join("..", "config", "webhook")}, }, } diff --git a/controllers/webhooks/webhooks.go b/webhooks/webhooks.go similarity index 100% rename from controllers/webhooks/webhooks.go rename to webhooks/webhooks.go