diff --git a/.github/workflows/kyma-integration-k3d-agent-tests.yml b/.github/workflows/kyma-integration-k3d-agent-tests.yml new file mode 100644 index 00000000..5b10b4bd --- /dev/null +++ b/.github/workflows/kyma-integration-k3d-agent-tests.yml @@ -0,0 +1,20 @@ +name: run-cra-k3d +on: + push: + branches: [ main ] + pull_request_target: + branches: [ main ] + types: + - opened + - reopened + - synchronize + - ready_for_review +jobs: + run-cra-k3d: + uses: "./.github/workflows/reusable-k3d-agent-test.yml" + with: + k3d-version: v5.6.0 + secrets: + compass-host: ${{ secrets.COMPASS_HOST }} + compass-client-id: ${{ secrets.COMPASS_CLIENT_ID }} + compass-client-secret: ${{ secrets.COMPASS_CLIENT_SECRET }} diff --git a/.github/workflows/kyma-integration-k3d-app-gateway.yml b/.github/workflows/kyma-integration-k3d-app-gateway.yml new file mode 100644 index 00000000..bb9886fb --- /dev/null +++ b/.github/workflows/kyma-integration-k3d-app-gateway.yml @@ -0,0 +1,36 @@ +name: Run app-gateway integration tests on k3d +on: + push: + branches: [ main ] + pull_request: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: azure/setup-helm@v4.1.0 + id: install + - name: Checkout code + uses: actions/checkout@v3 + - name: Install k3d + env: + K3D_URL: https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh + DEFAULT_K3D_VERSION: v5.4.6 + run: curl --silent --fail $K3D_URL | TAG=$DEFAULT_K3D_VERSION bash + - name: Set up cache + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + /home/runner/work/application-connector-manager/application-connector-manager/bin + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Run unit tests + run: make -C tests/hack/ci k3d-gateway-tests + - name: Archive test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: app-gateway-test-results + path: application-gateway-test.log diff --git a/.github/workflows/kyma-integration-k3d-validator-tests.yml b/.github/workflows/kyma-integration-k3d-validator-tests.yml new file mode 100644 index 00000000..f0229e1c --- /dev/null +++ b/.github/workflows/kyma-integration-k3d-validator-tests.yml @@ -0,0 +1,39 @@ +name: Run app-con-validator integration tests on k3d +on: + push: + branches: [ main ] + pull_request: +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: azure/setup-helm@v4.1.0 + id: install + - name: Checkout code + uses: actions/checkout@v3 + - name: Install k3d + env: + K3D_URL: https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh + DEFAULT_K3D_VERSION: v5.4.6 + run: curl --silent --fail $K3D_URL | TAG=$DEFAULT_K3D_VERSION bash + - name: Insall yq + run: sudo add-apt-repository ppa:rmescandon/yq && sudo apt update && sudo apt install yq -y + - name: Set up cache + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + /home/runner/work/application-connector-manager/application-connector-manager/bin + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Run unit tests + run: make -C tests/hack/ci k3d-validator-tests + - name: Archive test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: application-connectivity-validator-test-results + path: application-connectivity-validator-test.log + diff --git a/.github/workflows/reusable-k3d-agent-test.yml b/.github/workflows/reusable-k3d-agent-test.yml new file mode 100644 index 00000000..5c75f44c --- /dev/null +++ b/.github/workflows/reusable-k3d-agent-test.yml @@ -0,0 +1,50 @@ +name: k3d CRA test +on: + workflow_call: + inputs: + k3d-version: + required: true + type: string + default: v5.6.0 + secrets: + compass-host: + required: true + compass-client-id: + required: true + compass-client-secret: + required: true +jobs: + test-cra: + runs-on: ubuntu-latest + steps: + - uses: azure/setup-helm@v4.1.0 + id: install-helm + - name: checkout + uses: actions/checkout@v3 + - name: install-k3d + env: + K3D_URL: https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh + DEFAULT_K3D_VERSION: ${{ inputs.k3d-version }} + run: curl --silent --fail $K3D_URL | TAG=$DEFAULT_K3D_VERSION bash + - name: setup-cache + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + /home/runner/work/application-connector-manager/application-connector-manager/bin + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: run-tests + env: + COMPASS_CLIENT_ID: ${{ secrets.compass-client-id }} + COMPASS_CLIENT_SECRET: ${{ secrets.compass-client-secret }} + COMPASS_HOST: ${{ secrets.compass-host }} + run: make -C tests/hack/ci k3d-agent-tests + - name: archive-logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: compass-runtime-agent-test-results + path: compass-runtime-agent-test.log diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index a5b2ce37..6f821bfe 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -11,7 +11,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 - - name: Set up cache uses: actions/cache@v3 with: @@ -22,12 +21,10 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - name: Set up go environment uses: actions/setup-go@v4 with: go-version: 1.21 - - name: Run unit tests run: make test diff --git a/.gitignore b/.gitignore index 71607907..64c0e9b0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ vendor/* # Output of the go coverage tool, specifically when used with LiteIDE *.out +application-gateway-test.log +compass-runtime-agent-test.log # Kubernetes Generated files - skip generated files, except for vendored files @@ -30,4 +32,4 @@ manifests/* mod bin -charts \ No newline at end of file +charts/**/* diff --git a/Dockerfile b/Dockerfile index c00b44fc..a45870e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.21.8 as builder +FROM golang:1.22.1 as builder WORKDIR /workspace diff --git a/go.mod b/go.mod index 7d34dcfc..44714d03 100644 --- a/go.mod +++ b/go.mod @@ -80,9 +80,10 @@ require ( ) replace ( - golang.org/x/crypto => golang.org/x/crypto v0.12.0 + golang.org/x/crypto => golang.org/x/crypto v0.17.0 golang.org/x/net => golang.org/x/net v0.17.0 golang.org/x/sys => golang.org/x/sys v0.11.0 golang.org/x/text => golang.org/x/text v0.12.0 golang.org/x/tools => golang.org/x/tools v0.12.0 + google.golang.org/protobuf => google.golang.org/protobuf v1.33.0 ) diff --git a/go.sum b/go.sum index b5809220..70259eef 100644 --- a/go.sum +++ b/go.sum @@ -117,7 +117,7 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -131,7 +131,6 @@ golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= @@ -150,8 +149,6 @@ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13 h1:U7+wNaVuSTaUqNvK2+osJ9ejEZxbjHHk8F2b6Hpx0AE= google.golang.org/genproto/googleapis/api v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:RdyHbowztCGQySiCvQPgWQWgWhGnouTdCflKoDBt32U= -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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/crypto/sha256/utils_test.go b/pkg/crypto/sha256/utils_test.go index caa42092..71a88b3b 100644 --- a/pkg/crypto/sha256/utils_test.go +++ b/pkg/crypto/sha256/utils_test.go @@ -54,13 +54,13 @@ func Test_calculateSHA256(t *testing.T) { u.SetGroupVersionKind(schema.GroupVersionKind{ Kind: "CustomResourceDefinition", Group: "apiextensions.k8s.io", - Version: "v1", + Version: "1.0.0", }) return u }(), }, - want: "9NtR-1kpz4ub0a8jS4YySJEGZKmPfvC5FLh5GNW5UlA=", + want: "YVinn88v80IHE3oiOSjwAPUG0OumBQ-dy1ypGbAounU=", }, } for _, tt := range tests { diff --git a/tests/.dockerignore b/tests/.dockerignore new file mode 100644 index 00000000..63055f97 --- /dev/null +++ b/tests/.dockerignore @@ -0,0 +1,2 @@ +deployments/ +README.md diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..9dc57812 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +resources/charts/gateway-test/certs/ \ No newline at end of file diff --git a/tests/Dockerfile.compass-runtime-agent b/tests/Dockerfile.compass-runtime-agent new file mode 100644 index 00000000..ecb50750 --- /dev/null +++ b/tests/Dockerfile.compass-runtime-agent @@ -0,0 +1,17 @@ +# image builder base on golang:1.22.1-alpine3.19 +FROM golang@sha256:0466223b8544fb7d4ff04748acc4d75a608234bf4e79563bff208d2060c0dd79 as builder + +WORKDIR /compass-test/ + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . + +RUN CGO_ENABLED=0 go test -v -c -o compass-test ./test/compass-runtime-agent/ + +FROM scratch + +COPY --from=builder /compass-test/compass-test / +ENTRYPOINT [ "/compass-test" ] +CMD ["-test.v", "-test.parallel", "1"] diff --git a/tests/Dockerfile.connectivity-validator b/tests/Dockerfile.connectivity-validator new file mode 100644 index 00000000..1fd553c8 --- /dev/null +++ b/tests/Dockerfile.connectivity-validator @@ -0,0 +1,17 @@ +# image builder base on golang:1.22.1-alpine3.19 +FROM golang@sha256:0466223b8544fb7d4ff04748acc4d75a608234bf4e79563bff208d2060c0dd79 as builder + +WORKDIR /validator-test/ + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . + +RUN CGO_ENABLED=0 go test -v -c -o validator-test ./test/application-connectivity-validator/ + +FROM scratch + +COPY --from=builder /validator-test/validator-test / +ENTRYPOINT [ "/validator-test" ] +CMD ["-test.v"] diff --git a/tests/Dockerfile.gateway b/tests/Dockerfile.gateway new file mode 100644 index 00000000..ab3046ad --- /dev/null +++ b/tests/Dockerfile.gateway @@ -0,0 +1,17 @@ +# image builder base on golang:1.22.1-alpine3.19 +FROM golang@sha256:0466223b8544fb7d4ff04748acc4d75a608234bf4e79563bff208d2060c0dd79 as builder + +WORKDIR /gateway-test/ + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . + +RUN CGO_ENABLED=0 go test -v -c -o gateway-test ./test/application-gateway/ + +FROM scratch + +COPY --from=builder /gateway-test/gateway-test / +ENTRYPOINT [ "/gateway-test" ] +CMD ["-test.v"] diff --git a/tests/Dockerfile.mockapp b/tests/Dockerfile.mockapp new file mode 100644 index 00000000..ca62d70c --- /dev/null +++ b/tests/Dockerfile.mockapp @@ -0,0 +1,17 @@ +# image builder base on golang:1.22.1-alpine3.19 +FROM golang@sha256:0466223b8544fb7d4ff04748acc4d75a608234bf4e79563bff208d2060c0dd79 as builder + +WORKDIR /mock-app/ + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . + + +RUN CGO_ENABLED=0 go build -v -o mock-app ./tools/external-api-mock-app + +FROM scratch +COPY --from=builder /mock-app/mock-app . +ENTRYPOINT [ "/mock-app" ] +CMD [] diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 00000000..92ed1713 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,38 @@ +GATEWAY_TEST_IMAGE = "$(DOCKER_PUSH_REPOSITORY)$(DOCKER_PUSH_DIRECTORY)/gateway-test:$(DOCKER_TAG)" +VALIDATOR_TEST_IMAGE = "$(DOCKER_PUSH_REPOSITORY)$(DOCKER_PUSH_DIRECTORY)/connectivity-validator-test:$(DOCKER_TAG)" +COMPASS_TEST_IMAGE = "$(DOCKER_PUSH_REPOSITORY)$(DOCKER_PUSH_DIRECTORY)/compass-runtime-agent-test:$(DOCKER_TAG)" +MOCK_APP_IMAGE = "$(DOCKER_PUSH_REPOSITORY)$(DOCKER_PUSH_DIRECTORY)/mock-app:$(DOCKER_TAG)" + +REGISTRY_PORT ?= 5001 + +publish-gateway-test: image-gateway-test + docker push $(GATEWAY_TEST_IMAGE) + +image-gateway-test: + docker build -t $(GATEWAY_TEST_IMAGE) -f Dockerfile.gateway . + +publish-mock-app: image-mock-app + docker push $(MOCK_APP_IMAGE) + +image-mock-app: + docker build -t $(MOCK_APP_IMAGE) -f Dockerfile.mockapp . + +publish-validator-test: image-validator-test + docker push $(VALIDATOR_TEST_IMAGE) + +image-validator-test: + docker build -t $(VALIDATOR_TEST_IMAGE) -f Dockerfile.connectivity-validator . + +publish-compass-runtime-agent-test: image-compass-runtime-agent-test + docker push $(COMPASS_TEST_IMAGE) + +.PHONY: image-compass-runtime-agent-test +image-compass-runtime-agent-test: + @echo "::group::image-compass-runtime-agent-test" + @docker build -t $(COMPASS_TEST_IMAGE) -f ${PWD}/tests/Dockerfile.compass-runtime-agent . + @echo "::endgroup::" + +.PHONY: compass-runtime-agent-test-image +compass-runtime-agent-test-image: \ + image-compass-runtime-agent-test \ + publish-compass-runtime-agent-test diff --git a/tests/Makefile.test-application-conn-validator b/tests/Makefile.test-application-conn-validator new file mode 100644 index 00000000..d0d4ba6d --- /dev/null +++ b/tests/Makefile.test-application-conn-validator @@ -0,0 +1,41 @@ +# -*- mode: Makefile -*- + +NAMESPACE ?= test +GOPATH ?= $(shell go env GOPATH) + +VALIDATOR_TEST_IMAGE = "$(DOCKER_PUSH_REPOSITORY)$(DOCKER_PUSH_DIRECTORY)/connectivity-validator-test:$(DOCKER_TAG)" +TEST_TIMEOUT = "3m" +MAKEFILE_NAME=Makefile.test-application-conn-validator + +test: test-validator + +.PHONY: patch-validator +patch-validator: + yq e -i '. |= (select(.kind == "Deployment" and .metadata.name == "central-application-connectivity-validator") | .spec.template.spec.containers[0].args[5] = "--eventingPublisherHost=echoserver.test.svc.cluster.local" | .spec.template.spec.containers[0].args[6] = "--eventingDestinationPath=/anything/rewrite" | .spec.template.metadata.annotations.["traffic.sidecar.istio.io/excludeInboundPorts"] += "8080")' ${PWD}/application-connector.yaml + +.PHONY: test-validator +test-validator: patch-validator create-resources + @echo "::group::test-validator" + ${PWD}/tests/scripts/fetch-test-logs.sh application-connectivity-validator-test ${PWD} + @echo "::endgroup::" + +.PHONY: create-resources +create-resources: + @echo "::group::create-test-namespace" + kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml \ + | kubectl apply -f - + kubectl label namespace $(NAMESPACE) istio-injection=enabled --overwrite + @echo "::endgroup::" + @echo "::group::create-resources::install-echoserver" + @helm template ${PWD}/tests/resources/charts/application-connectivity-validator-test/charts/echoserver \ + --set global.namespace=$(NAMESPACE) \ + | kubectl apply -f - + kubectl rollout status deployment echoserver -n test --timeout=90s + @echo "::endgroup::" + @echo "::group::create-resources::install-test" + @helm template ${PWD}/tests/resources/charts/application-connectivity-validator-test/charts/test \ + --set namespace=$(NAMESPACE) \ + --values ${PWD}/tests/resources/charts/application-connectivity-validator-test/values.yaml \ + | kubectl apply -f - + @echo "::endgroup::" + diff --git a/tests/Makefile.test-application-gateway b/tests/Makefile.test-application-gateway new file mode 100644 index 00000000..3153f810 --- /dev/null +++ b/tests/Makefile.test-application-gateway @@ -0,0 +1,78 @@ +# -*- mode: PWDmakefile -*- + +NAMESPACE ?= test +GOPATH ?= $(shell go env GOPATH) + +MOCK_SERVICE_NAME="mock-application" +APP_URL = "$(MOCK_SERVICE_NAME).$(NAMESPACE).svc.cluster.local" +TEST_TIMEOUT = "3m" +MAKEFILE_NAME=Makefile.test-application-gateway + +.PHONY: test +test: test-gateway + +.PHONY: clean +clean: clean-gateway-test + +.PHONY: test-gateway +test-gateway: disable-sidecar-for-mtls-test generate-certs create-resources + @echo "::group::test-gateway" + ${PWD}/tests/scripts/fetch-test-logs.sh application-gateway-test ${PWD} + @echo "::endgroup::" + +.PHONY: create-resources +create-resources: + @echo "::group::create-test-namespace" + kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + kubectl label namespace $(NAMESPACE) istio-injection=enabled --overwrite + @echo "::endgroup::" + @echo "::group::create-resources::install-mock-app" + helm template ${PWD}/tests/resources/charts/gateway-test/charts/mock-app \ + --set global.namespace=$(NAMESPACE) \ + --set mockServiceName=$(MOCK_SERVICE_NAME) \ + --values ${PWD}/tests/resources/charts/gateway-test/values.yaml \ + | kubectl apply -f - + kubectl rollout status deployment mock-application -n test --timeout=90s + @echo "::endgroup::" + @echo "::group::create-resources::install-test" + helm template ${PWD}/tests/resources/charts/gateway-test/charts/test \ + --set namespace=$(NAMESPACE) \ + --set mockServiceName=$(MOCK_SERVICE_NAME) \ + --values ${PWD}/tests/resources/charts/gateway-test/values.yaml \ + | kubectl apply -f - + @echo "::endgroup::" + +.PHONY: clean-gateway-test +clean-gateway-test: + @echo "::group::clean-gateway-test" + helm template ${PWD}/tests/resources/charts/gateway-test --set namespace=$(NAMESPACE) \ + | kubectl delete -f - + kubectl delete ns $(NAMESPACE) --ignore-not-found + @echo "::endgroup::" + +.PHONY: disable-sidecar-for-mtls-test +disable-sidecar-for-mtls-test: + @echo "::group::disable-sidecar-for-mtls-test" + kubectl -n kyma-system \ + patch deployment central-application-gateway \ + -p '{"spec":{"template":{"metadata":{"annotations":{"traffic.sidecar.istio.io/excludeOutboundPorts": "8090,8091"}}}}}' + kubectl rollout status deploy central-application-gateway -n kyma-system --timeout=1m + @echo "::endgroup::" + +.PHONY: enable-sidecar-after-mtls-test +enable-sidecar-after-mtls-test: + @echo "::group::enable-sidecar-for-mtls-test" + kubectl -n kyma-system \ + patch deployment central-application-gateway \ + --type=json \ + --patch '[{ "op": "remove", "path": "/spec/template/metadata/annotations/traffic.sidecar.istio.io~1excludeOutboundPorts"}]' + @echo "::endgroup::" + +.PHONY: generate-certs +generate-certs: + @echo "::group::generate-certs" + ${PWD}/tests/scripts/generate-self-signed-certs.sh $(APP_URL) ${PWD}/tests/resources/charts/gateway-test/charts/test/certs/positive + ${PWD}/tests/scripts/generate-self-signed-certs.sh $(APP_URL) ${PWD}/tests/resources/charts/gateway-test/charts/test/certs/negative + ${PWD}/tests/scripts/generate-self-signed-certs.sh test-other-ca ${PWD}/tests/resources/charts/gateway-test/charts/test/certs/invalid-ca + cp -p -R ${PWD}/tests/resources/charts/gateway-test/charts/test/certs ${PWD}/tests/resources/charts/gateway-test/charts/mock-app + @echo "::endgroup::" diff --git a/tests/Makefile.test-compass-runtime-agent b/tests/Makefile.test-compass-runtime-agent new file mode 100644 index 00000000..4f416273 --- /dev/null +++ b/tests/Makefile.test-compass-runtime-agent @@ -0,0 +1,55 @@ +# -*- mode: Makefile -*- + +NAMESPACE ?= test +GOPATH ?= $(shell go env GOPATH) +DIRECTOR_URL=https://compass-gateway-auth-oauth.$(COMPASS_HOST)/director/graphql +TOKENS_ENDPOINT=https://oauth2.$(COMPASS_HOST)/oauth2/token + +TEST_TIMEOUT = "4m" +CLUSTER_NAME ?= kyma +REGISTRY_PORT ?= 5001 +REGISTRY_NAME ?= ${CLUSTER_NAME}-registry + +.PHONY: test +test: test-compass-runtime-agent clean-compass-runtime-agent-test + +.PHONY: clean +clean: clean-compass-runtime-agent-test + +.PHONY: patch-compass-runtime-agent +patch-compass-runtime-agent: + yq e -i '. |= (select(.kind == "Deployment" and .metadata.name == "compass-runtime-agent") | .spec.template.spec.containers[0].env[1] = { "name": "APP_CONTROLLER_SYNC_PERIOD", "value": "15s" })' ${PWD}/application-connector.yaml + +.PHONY: test-compass-runtime-agent +test-compass-runtime-agent: install-compass-runtime-agent-test + @echo "::group::fetch-compass-runtime-agent-test-logs" + ${PWD}/tests/scripts/fetch-test-logs.sh compass-runtime-agent-test ${PWD} + @echo "::endgroup::" + +.PHONY: create-resources +create-resources: + @echo "::group::create-test-namespace" + kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + kubectl label namespace $(NAMESPACE) istio-injection=enabled --overwrite + @echo "::endgroup::" + +.PHONY: install-compass-runtime-agent-test +install-compass-runtime-agent-test: create-resources + @echo "::group::install-compass-runtime-agent-test" + @echo "::add-mask::$(TOKENS_ENDPOINT)" + @echo "::add-mask::$(DIRECTOR_URL)" + @echo "::add-mask::$(COMPASS_CLIENT_ID)" + @echo "::add-mask::$(COMPASS_CLIENT_SECRET)" + @helm template ${PWD}/tests/resources/charts/compass-runtime-agent-test \ + --set namespace=$(NAMESPACE) \ + --set compassCredentials.clientID=$(COMPASS_CLIENT_ID) \ + --set compassCredentials.clientSecret=$(COMPASS_CLIENT_SECRET) \ + --set compassCredentials.tokensEndpoint=$(TOKENS_ENDPOINT) \ + --set directorUrl=$(DIRECTOR_URL) \ + | kubectl apply -f - + @echo "::endgroup::" + +.PHONY: clean-compass-runtime-agent-test +clean-compass-runtime-agent-test: + helm template ${PWD}/tests/resources/charts/compass-runtime-agent-test | kubectl delete -f - + kubectl delete ns $(NAMESPACE) --ignore-not-found diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..861a55c3 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,6 @@ +# Component tests for Application Connector + +There are the following component tests for Application Connector: +- [Application Gateway](docs/application-gateway-tests.md) +- [Application Connectivity Validator](docs/application-connectivity-validator-tests.md) +- [Compass Runtime Agent](docs/compass-runtime-agent-tests.md) diff --git a/tests/docs/application-connectivity-validator-tests.md b/tests/docs/application-connectivity-validator-tests.md new file mode 100644 index 00000000..c293701f --- /dev/null +++ b/tests/docs/application-connectivity-validator-tests.md @@ -0,0 +1,86 @@ +# Application Connectivity Validator + +**Table of Contents** + +- [Application Connectivity Validator](#application-connectivity-validator) + - [Design and Architecture](#design-and-architecture) + - [Building](#building) + - [Running](#running) + - [Deploy a Kyma Cluster Locally](#deploy-a-kyma-cluster-locally) + - [Run the Tests](#run-the-tests) + - [Debugging](#debugging) + - [Running Without Cleanup](#running-without-cleanup) + +## Design and Architecture + +The tests consist of: +- [Test resources](../resources/charts/application-connectivity-validator-test/) used to perform the test +- [Test runner](../test/application-connectivity-validator/) with all the test cases + +The tests are executed as a Kubernetes Job in a Kyma cluster where the tested Application Connectivity Validator is installed. The test Job is deployed in the `test` namespace. + +![Connectivity Validator tests architecture](assets/connectivity-validator-tests-architecture.svg) + +> **NOTE:** Port `8080` must be excluded from redirection to Envoy, otherwise the Connectivity Validator test Pod cannot pass the `X-Forwarded-Client-Cert` header to Connectivity Validator. + +## Building + +Pipelines build the Application Connectivity Validator test using the **release** target from the `Makefile`. + +To build **and push** the Docker images of the tests, run: + +``` sh +./scripts/local-build.sh {DOCKER_TAG} {DOCKER_PUSH_REPOSITORY} +``` + +This will build the following images: +- `{DOCKER_PUSH_REPOSITORY}/connectivity-validator-test:{DOCKER_TAG}` + +## Running + +Tests can be run on any Kyma cluster with Application Connectivity Validator. + +Pipelines run the tests using the **test-validator** target from the `Makefile`. + +### Deploy a Kyma Cluster Locally + +1. Provision a local Kubernetes cluster with k3d: + ```sh + kyma provision k3d + ``` + +2. Install the minimal set of components required to run Application Connectivity Validator **for Kyma SKR (Compass mode)**: + + ```bash + kyma deploy --components-file ./resources/installation-config/mini-kyma-skr.yaml --value global.disableLegacyConnectivity=true + ``` + + >**TIP:** Read more about Kyma installation in the [official Kyma documentation](https://kyma-project.io/#/02-get-started/01-quick-install). + +### Run the Tests + +``` sh +make -f Makefile.test-application-conn-validator test-validator +``` + +By default, the tests clean up after themselves, removing all the previously created resources and the `test` namespace. + +> **CAUTION:** If the names of your existing resources are the same as the names used in the tests, running this command overrides or removes the existing resources. + +## Debugging + +### Running Without Cleanup + +To run the tests without removing all the created resources afterwards, run them in the debugging mode. + +1. To start the tests in the debugging mode, run: + + ``` shell + make -f Makefile.test-application-conn-validator test-validator-debug + ``` + +2. Once you've finished debugging, run: + + ``` shell + make -f Makefile.test-application-conn-validator clean-validator-test + ``` diff --git a/tests/docs/application-gateway-tests.md b/tests/docs/application-gateway-tests.md new file mode 100644 index 00000000..d8c08a37 --- /dev/null +++ b/tests/docs/application-gateway-tests.md @@ -0,0 +1,233 @@ +# Application Gateway + +**Table of Contents** + +- [Application Gateway](#application-gateway) + - [Design and Architecture](#design-and-architecture) + - [Mock Application](#mock-application) + - [Certificates](#certificates) + - [API Exposed on Port `8080`](#api-exposed-on-port-8080) + - [API Exposed on Port `8090`](#api-exposed-on-port-8090) + - [API Exposed on Port `8091`](#api-exposed-on-port-8091) + - [Building](#building) + - [Running](#running) + - [Deploy a Kyma Cluster Locally](#deploy-a-kyma-cluster-locally) + - [Run the Tests](#run-the-tests) + - [Debugging](#debugging) + - [Running Locally](#running-locally) + - [Running Without Cleanup](#running-without-cleanup) + +## Design and Architecture + +The tests consist of: +- [Application CRs](../resources/charts/gateway-test/charts/test/templates/applications/) describing the test cases +- [Secrets](../resources/charts/gateway-test/charts/test/templates/applications/credentials) referenced by the Application CRs +- [Test runners](../test/application-gateway/) with various checks for the subsets of cases, grouped by the Application CRs +- [Mock application](../tools/external-api-mock-app/) which simulates the remote endpoints + +Additionally, the following resources are created in the cluster: +- [Service Account](../resources/charts/gateway-test/charts/test/templates/service-account.yml) used by the tests to read the Application CRs +- [Secrets](../resources/charts/gateway-test/charts/test/templates/applications/credentials) used by the Mock application to configure mTLS servers + +The tests are executed as a Kubernetes Job in a Kyma cluster where the tested Application Gateway is installed. +The test Job and the mock application deployment are in the `test` namespace. + +![Application Gateway tests architecture](assets/app-gateway-tests-architecture.svg) + +## Mock Application + +Mock application exposes the following APIs: +- API on port `8080` implementing various authentication methods and returning the `OAuth` and `CSRF` tokens +- API on port `8090` implementing the `mTLS` authentication and returning the `OAuth` tokens +- API on port `8091` implementing the `mTLS` authentication and using an expired server certificate + +### Certificates + +To test mTLS-related authentication methods, you need: +- Server certificate, key, and the CA certificate for the mock application +- Client certificate and key stored in a Secret accessed by Application Gateway + +All certificates are generated using the **generate-certs** target from the `Makefile`. +The target is executed before the tests are run, and it invokes [`generate-self-signed-certs.sh`](../scripts/generate-self-signed-certs.sh), which creates the CA root, server, and client certificates and keys. + +> **NOTE:** Since self-signed certificates are used, Application CRs have the **skipVerify: true** property set to `true` to force Application Gateway to skip certificate verification. + +### API Exposed on Port `8080` + +To get tokens for the `OAuth` and `CSRF` protected endpoints, we have the following API: +![8080 token API](assets/api-tokens.png) + +To test authentication methods, we have the following API: +![8080 authorisation methods API](assets/api-auth-methods.png) + +The credentials used for authentication, such as `user` and `password`, are [hardcoded](../tools/external-api-mock-app/config.go). + +### API Exposed on Port `8090` + +To get tokens for the `OAuth` protected endpoints, we have the following API: +![8090 token API](assets/api-tokens-mtls.png) + +To test authentication methods, we have the following API: +![8090 authorisation methods API](assets/api-auth-methods-mtls.png) + +The credentials used for authentication, such as `clientID`, are [hardcoded](../tools/external-api-mock-app/config.go). +The server key, server certificate, and the CA root certificate for port `8090` are defined in [this Secret](../resources/charts/gateway-test/charts/mock-app/templates/credentials/mtls-cert-secret.yml). + +> **NOTE:** Port `8090` must be excluded from redirection to Envoy, otherwise Application Gateway cannot pass the client certificate to the mock application. + +### API Exposed on Port `8091` + +This API is identical to the one exposed on port `8090`. +The HTTPS server on port `8091` uses an expired server certificate. +The server key, server certificate, and the CA root certificate for port `8091` are defined in [this Secret](../resources/charts/gateway-test/charts/mock-app/templates/credentials/expired-mtls-cert-secret.yaml). + +> **NOTE:** Port `8091` must be excluded from redirection to Envoy, otherwise Application Gateway cannot pass the client certificate to the mock application. + +## Building + +Pipelines build the mock application and the Gateway test using the **release** target from the `Makefile`. + +To build **and push** the Docker images of the tests and the mock application, run: + +``` sh +./scripts/local-build.sh {DOCKER_TAG} {DOCKER_PUSH_REPOSITORY} +``` + +This will build the following images: +- `{DOCKER_PUSH_REPOSITORY}/gateway-test:{DOCKER_TAG}` +- `{DOCKER_PUSH_REPOSITORY}/mock-app:{DOCKER_TAG}` + +## Running + +Tests can be run on any Kyma cluster with Application Gateway. + +Pipelines run the tests using the **test-gateway** target from the `Makefile`. + +### Deploy a Kyma Cluster Locally + +1. Provision a local Kubernetes cluster with k3d: + ```sh + kyma provision k3d + ``` + +2. Install the minimal set of components required to run Application Gateway for either Kyma OS or SKR: + +
+
+ + Kyma OS (standalone mode) + + + ```sh + kyma deploy --components-file ./resources/installation-config/mini-kyma-os.yaml + ``` + +
+
+ + SKR (Compass mode) + + + ```bash + kyma deploy --components-file ./resources/installation-config/mini-kyma-skr.yaml + ``` + +
+
+ + >**TIP:** Read more about Kyma installation in the [official Kyma documentation](https://kyma-project.io/#/02-get-started/01-quick-install). + +### Run the Tests + +``` sh +make -f Makefile.test-application-gateway test-gateway +``` + +By default, the tests clean up after themselves, removing all the previously created resources and the `test` namespace. + +> **CAUTION:** If the names of your existing resources are the same as the names used in the tests, running this command overrides or removes the existing resources. + +## Debugging + +### Running Locally + +> **CAUTION:** Because of the way it accesses the Application CRs, the test Job must run **on a cluster**. +> Application Gateway and the mock application can both be run locally. + +To run the mock application locally, follow these steps: + +1. Change all the **targetUrl** values in the [Application CRs](../resources/charts/gateway-test/charts/test/templates/applications/) to reflect the new application URL. For example, `http://localhost:8081/v1/api/unsecure/ok`. +2. Change all the **centralGatewayUrl** values to reflect the new Application Gateway URL. For example, `http://localhost:8080/positive-authorisation/unsecure-always-ok`. +3. Deploy all the resources in the cluster. + > **NOTE:** You can omit the test Job and the Central Gateway, but it's easier to just let them fail. +4. Build the mock application: + +
+
+ + Docker + + + ```shell + export DOCKER_TAG="local" + export DOCKER_PUSH_REPOSITORY="{DOCKER_USERNAME}" + make image-mock-app + ``` + +
+
+ + Local + + + Change the hardcoded application port in [`config.go`](../tools/external-api-mock-app/config.go), and run: + ```shell + go build ./tools/external-api-mock-app/ + ``` +
+
+5. Run the mock application: + +
+
+ + Docker + + + ```shell + docker run -p 8180:8080 -p 8190:8090 -v "$PWD/resources/charts/gateway-test/charts/test/certs/positive:/etc/secret-volume:ro" -v "$PWD/resources/charts/gateway-test/charts/test/certs/negative:/etc/expired-server-cert-volume:ro" "$DOCKER_PUSH_REPOSITORY/mock-app:$DOCKER_TAG" + ``` + +
+
+ + Local + + + ```shell + ./external-api-mock-app + ``` + > **CAUTION:** For the certificates to work, you must copy them from `./k8s/gateway-test/certs` to `/etc/secret-volume`. + +
+
+6. Run [Application Gateway](https://github.com/kyma-project/kyma/tree/main/components/central-application-gateway) with the `-kubeConfig {PATH_TO_YOUR_KUBECONFIG_FILE}` parameter. + +You can now send requests to Application Gateway, and debug its behavior locally. + +### Running Without Cleanup + +To run the tests without removing all the created resources afterwards, run them in the debugging mode. + +1. To start the tests in the debugging mode, run: + + ``` shell + make disable-sidecar-for-mtls-test test-gateway-debug + ``` + +2. Once you've finished debugging, run: + + ``` shell + make clean-gateway-test enable-sidecar-after-mtls-test + ``` + diff --git a/tests/docs/assets/api-auth-methods-mtls.png b/tests/docs/assets/api-auth-methods-mtls.png new file mode 100644 index 00000000..743a1d4c Binary files /dev/null and b/tests/docs/assets/api-auth-methods-mtls.png differ diff --git a/tests/docs/assets/api-auth-methods.png b/tests/docs/assets/api-auth-methods.png new file mode 100644 index 00000000..29566ba0 Binary files /dev/null and b/tests/docs/assets/api-auth-methods.png differ diff --git a/tests/docs/assets/api-tokens-mtls.png b/tests/docs/assets/api-tokens-mtls.png new file mode 100644 index 00000000..61fe10fc Binary files /dev/null and b/tests/docs/assets/api-tokens-mtls.png differ diff --git a/tests/docs/assets/api-tokens.png b/tests/docs/assets/api-tokens.png new file mode 100644 index 00000000..386096e8 Binary files /dev/null and b/tests/docs/assets/api-tokens.png differ diff --git a/tests/docs/assets/app-gateway-tests-architecture.svg b/tests/docs/assets/app-gateway-tests-architecture.svg new file mode 100644 index 00000000..e148166a --- /dev/null +++ b/tests/docs/assets/app-gateway-tests-architecture.svg @@ -0,0 +1,4 @@ + + + +
Kyma cluster
Kyma cluster
4
4
kyma-system Namespace
kyma-system Namespace
Application Gateway
Application Gateway
test Namespace
test Namespace
Application CR
- service 1 (test case 1)
- service 2 (test case 2)
- ...                                
Application CR...
mock application (remote endpoints)
mock application (remo...
App Gateway test Pod
App Gateway test...
1
1
2
2
Text is not SVG - cannot display
\ No newline at end of file diff --git a/tests/docs/assets/compass-runtime-agent-tests-architecture.svg b/tests/docs/assets/compass-runtime-agent-tests-architecture.svg new file mode 100644 index 00000000..dc02a5e5 --- /dev/null +++ b/tests/docs/assets/compass-runtime-agent-tests-architecture.svg @@ -0,0 +1,4 @@ + + + +
Kyma cluster
Kyma cluster
4
4
kyma-system Namespace
kyma-system Namespace
Compass Runtime Agent
Compass Runtime Agent
test Namespace
test Namespace
Application CR
- service 1 (test case 1)
- service 2 (test case 2)
- ...                                
Application CR...
Compass Runtime Agent test Pod
Compass Runtime Agent test Pod
CompassConnection CR
CompassConnection CR
Compass Runtime Agent certificates Secret
Compass Runtime Agent certi...
1
1
3
3
4
4
5
5
2
2
6
6
CA root certificate Secret
CA root certificate Secret
7
7
4
4
Compass test environment
Compass test environment
Connector
Connector
Director
Director
Text is not SVG - cannot display
\ No newline at end of file diff --git a/tests/docs/assets/connectivity-validator-tests-architecture.svg b/tests/docs/assets/connectivity-validator-tests-architecture.svg new file mode 100644 index 00000000..8b852313 --- /dev/null +++ b/tests/docs/assets/connectivity-validator-tests-architecture.svg @@ -0,0 +1,4 @@ + + + +
Kyma cluster
Kyma cluster
4
4
kyma-system Namespace
kyma-system Namespace
Connectivity Validator
Connectivity Validator
test Namespace
test Namespace
mock service (echoservice)
mock service (echose...
Connectivity Validator test Pod
Connectivity Validat...
1
1
2
2
Text is not SVG - cannot display
\ No newline at end of file diff --git a/tests/docs/assets/mock-app-mtls-spec.yaml b/tests/docs/assets/mock-app-mtls-spec.yaml new file mode 100644 index 00000000..2852c73d --- /dev/null +++ b/tests/docs/assets/mock-app-mtls-spec.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.3 +info: + title: Mock Application for testing Application Gateway + description: |- + This is an API of Mock Application supporting Application Gateway Tests. + version: 1.0.11 +tags: + - name: OAuth tokens + description: Endpoints returning OAuth tokens + - name: CSRF + description: Endpoints protected by CSRF method + - name: No authentication + description: Endpoints not protected by any authentication method +paths: + /v1/api/mtls-oauth/token: + post: + tags: + - OAuth tokens + summary: Returns valid OAuth token + operationId: oauthToken + responses: + '200': + description: client_id and grant_type values correct + '401': + description: Bad client_id or grant_type value + + /v1/api/mtls/ok: + get: + tags: + - No authentication + summary: Returns status 200 OK if authorisation is successful + operationId: onBasicAuth + responses: + '200': + description: Authorisation successful + '401': + description: Client certificate is not valid + /v1/api/csrf-mtls/ok: + get: + tags: + - CSRF + summary: Returns status 200 OK if authorisation is successful + operationId: onCsrfOAuth + responses: + '200': + description: Authorisation successful + '401': + description: Client certificate is not valid + '403': + description: Username or password doesn't match or invalid CSRF token passed + diff --git a/tests/docs/assets/mock-app-spec.yaml b/tests/docs/assets/mock-app-spec.yaml new file mode 100644 index 00000000..53877cb2 --- /dev/null +++ b/tests/docs/assets/mock-app-spec.yaml @@ -0,0 +1,186 @@ +openapi: 3.0.3 +info: + title: Mock Application for testing Application Gateway + description: |- + This is an API of Mock Application supporting Application Gateway Tests. + version: 1.0.11 +tags: + - name: OAuth tokens + description: Endpoints returning OAuth tokens + - name: CSRF tokens + description: Endpoints returning CSRF tokens + - name: No authentication + description: Endpoints not protected by any authentication method + - name: Basic Authentication + description: Endpoints protected by Basic Authentication + - name: OAuth + description: Endpoints protected by OAuth method expecting valid OAuth token + - name: Basic Authentication and CSRF token + description: Endpoints protected by Basic Authentication and CSRF methods + - name: OAuth and CSRF + description: Endpoints protected by OAuth and CSRF methods + - name: Basic Authentication and request parameters + description: Endpoints protected by Basic Authentication and additional request parameters +paths: + /v1/api/oauth/token: + post: + tags: + - OAuth tokens + summary: Returns valid OAuth token + operationId: oauthToken + responses: + '200': + description: client_id, client_secret and grant_type values correct + '401': + description: Bad client_id, client_secret or grant_type value + /v1/api/oauth/bad-token: + post: + tags: + - OAuth tokens + summary: Returns invalid OAuth token + operationId: oauthBadToken + responses: + '200': + description: client_id, client_secret and grant_type values correct + '401': + description: Bad client_id, client_secret or grant_type value + /v1/api/csrf/token: + get: + tags: + - CSRF tokens + summary: Returns valid CSRF token + operationId: csrfToken + responses: + '200': + description: Token generated successfully + /v1/api/csrf/bad-token: + get: + tags: + - CSRF tokens + summary: Returns invalid CSRF token + responses: + '200': + description: Token generated successfully + + + /v1/api/unsecure/ok: + get: + tags: + - No authentication + summary: Returns status 200 OK + operationId: okNoAuth + responses: + '200': + description: Successful operation + /v1/api/unsecure/echo: + put: + tags: + - No authentication + summary: Responds with request body sent to the endpoint + operationId: echoNoAuthPut + responses: + '200': + description: Successful operation + post: + tags: + - No authentication + summary: Responds with request body sent to the endpoint + operationId: echoNoAuthPost + responses: + '200': + description: Successful operation + delete: + tags: + - No authentication + summary: Responds with request body sent to the endpoint + operationId: echoNoAuthDelete + responses: + '200': + description: Successful operation + /v1/api/unsecure/code/{code}: + get: + tags: + - No authentication + parameters: + - in: path + name: code + schema: + type: integer + required: true + summary: Responds with status code specified in the {code} parameter + operationId: codeNoAuth + responses: + '200': + description: Successful operation + /v1/api/unsecure/timeout: + get: + tags: + - No authentication + summary: Sleeps for 2 minutes before responding + operationId: timeoutNoAuth + responses: + '200': + description: Successful operation + /v1/api/basic/ok: + get: + tags: + - Basic Authentication + summary: Returns status 200 OK if authentication is successful + operationId: onBasicAuth + responses: + '200': + description: Authentication successful + '403': + description: Username or password doesn't match + /v1/api/oauth/ok: + get: + tags: + - OAuth + summary: Returns status 200 OK if authentication is successful + operationId: onOAuth + responses: + '200': + description: Authentication successful + '401': + description: Authorization header missing or contains invalid token + + /v1/api/csrf-basic/ok: + get: + tags: + - Basic Authentication and CSRF token + summary: Returns status 200 OK if authentication is successful + operationId: onCsrfBasic + responses: + '200': + description: Authentication successful + '403': + description: Username or password doesn't match or invalid CSRF token passed + + /v1/api/csrf-oauth/ok: + get: + tags: + - OAuth and CSRF + summary: Returns status 200 OK if authentication is successful + operationId: onCsrfOAuth + responses: + '200': + description: Authentication successful + '401': + description: Authorization header missing or contains invalid token + '403': + description: Username or password doesn't match or invalid CSRF token passed + + /v1/api/request-parameters-basic/ok: + get: + tags: + - Basic Authentication and request parameters + summary: Returns status 200 OK if authentication is successful + operationId: onRequestParamsBasic + responses: + '200': + description: Authentication successful + '400': + description: Expected headers and request params not passed + '403': + description: Username or password doesn't match + diff --git a/tests/docs/compass-runtime-agent-tests.md b/tests/docs/compass-runtime-agent-tests.md new file mode 100644 index 00000000..afc21a24 --- /dev/null +++ b/tests/docs/compass-runtime-agent-tests.md @@ -0,0 +1,138 @@ +# Compass Runtime Agent + +**Table of Contents** + +- [Compass Runtime Agent](#compass-runtime-agent) + - [Design and Architecture](#design-and-architecture) + - [Building](#building) + - [Running](#running) + - [Deploy a Kyma Cluster Locally](#deploy-a-kyma-cluster-locally) + - [Test Setup - Compass Runtime Agent Configuration](#test-setup---compass-runtime-agent-configuration) + - [Run the Tests](#run-the-tests) + - [Debugging](#debugging) + - [Running Without Cleanup](#running-without-cleanup) + - [Debugging in the IDE](#debugging-in-the-ide) + +## Design and Architecture + +The tests consist of: +- [Test resources](../resources/charts/compass-runtime-agent-test/) used to perform the test +- [Test runner](../test/application-connectivity-validator/) with all the test cases + +The tests are executed as a Kubernetes Job in a Kyma cluster where the tested Compass Runtime Agent is installed. The test Job is deployed in the `test` namespace. + +![Compass Runtime Agent tests architecture](assets/compass-runtime-agent-tests-architecture.svg) + +The interactions between components are the following: + +1. Compass Runtime Agent periodically fetches certificates from Compass Connector. +2. Compass Runtime Agent periodically fetches applications from Compass Director. +3. Compass Runtime Agent Test sends GraphQL mutations to Compass Director to create, modify, or delete Applications. +4. Compass Runtime Agent Test verifies whether corresponding Application CRs were created, modified, or deleted. +5. Compass Runtime Agent Test verifies whether the Secret with certificates used for communication with Director was created. +6. Compass Runtime Agent Test verifies whether the Secret with the CA root certificate used by Istio Gateway was created. +7. Compass Runtime Agent Test verifies the content of the CompassConnection CR. + +## Building + +Pipelines build the Compass Runtime Agent test using the **release** target from the `Makefile`. + +To build **and push** the Docker images of the tests, run: + +```bash +./scripts/local-build.sh {DOCKER_TAG} {DOCKER_PUSH_REPOSITORY} +``` + +This builds the following images: +- `{DOCKER_PUSH_REPOSITORY}/compass-runtime-agent-test:{DOCKER_TAG}` + +## Running + +Tests can be run on any Kyma cluster with Compass Runtime Agent. + +Pipelines run the tests using the **test-compass-runtime-agent** target from the `Makefile`. + +### Deploy a Kyma Cluster Locally + +1. Provision a local Kubernetes cluster with k3d: + ```bash + kyma provision k3d + ``` + +2. Install the minimal set of components required to run Compass Runtime Agent **for Kyma SKR (Compass mode)**: + + ```bash + kyma deploy --components-file ./resources/installation-config/mini-kyma-skr.yaml --value global.disableLegacyConnectivity=true --value compassRuntimeAgent.director.proxy.insecureSkipVerify=true + ``` + + >**TIP:** Read more about [Kyma installation](https://kyma-project.io/#/02-get-started/01-quick-install). + +### Test Setup - Compass Runtime Agent Configuration + +The [`values.yaml`](../resources/charts/compass-runtime-agent-test/values.yaml) file contains environment variables that are used in the Compass Runtime Agent tests. These values can be modified as needed. + +- **APP_DIRECTOR_URL** - Compass Director URL +- **APP_TESTING_TENANT** - Tenant used in GraphQL calls +- **APP_SKIP_DIRECTOR_CERT_VERIFICATION** - Skip certificate verification on the Director side +- **APP_OAUTH_CREDENTIALS_SECRET_NAME** - Secret name for Compass OAuth credentials +- **APP_OAUTH_CREDENTIALS_NAMESPACE** - Namespace for Compass OAuth credentials + +### Run the Tests + +1. Before running the test export the following environment variables + - **COMPASS_HOST** - host running Compass + - **COMPASS_CLIENT_ID** - client ID used for fetching authorization tokens + - **COMPASS_CLIENT_SECRET** - client Secret used for fetching authorization tokens + +2. To start the tests, run: + + ```bash + make test-compass-runtime-agent + ``` + +By default, the tests clean up after themselves, removing all the previously created resources and the `test` namespace. + +> **CAUTION:** If the names of your existing resources are the same as the names used in the tests, running this command overrides or removes the existing resources. + +## Debugging + +### Running Without Cleanup + +To run the tests without removing all the created resources afterwards, run them in the debugging mode. + +1. To start the tests in the debugging mode, run: + + ```bash + make test-compass-runtime-agent-debug + ``` + +2. Once you've finished debugging, run: + + ```bash + make clean-test-compass-runtime-agent-test + ``` + +### Debugging in the IDE + +To run the test in your IDE, perform the following steps. + +1. To prepare the cluster for debugging, run the test without cleanup: + + ```bash + make test-compass-runtime-agent-debug + ``` + +2. Before starting debugger in your IDE export the following environment variables: + - `KUBECONFIG={Your cluster kubeconfig}` + - `APP_DIRECTOR_URL=https://compass-gateway-auth-oauth.{COMPASS_HOST}/director/graphql` + - `APP_TESTING_TENANT=3e64ebae-38b5-46a0-b1ed-9ccee153a0ae` + - `APP_OAUTH_CREDENTIALS_SECRET_NAME=oauth-compass-credentials` + - `APP_OAUTH_CREDENTIALS_NAMESPACE=test` + +3. Start the debugging session. + +4. Once you've finished debugging, run: + + ```bash + make clean-test-compass-runtime-agent-test + ``` \ No newline at end of file diff --git a/tests/go.mod b/tests/go.mod new file mode 100644 index 00000000..1b27f217 --- /dev/null +++ b/tests/go.mod @@ -0,0 +1,90 @@ +module github.com/kyma-project/kyma/tests/components/application-connector + +go 1.18 + +require ( + github.com/avast/retry-go v3.0.0+incompatible + github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6 + github.com/google/uuid v1.3.0 + github.com/gorilla/mux v1.8.0 + github.com/hashicorp/go-multierror v1.1.1 + github.com/kyma-incubator/compass/components/director v0.0.0-20220126084901-92232f5eced0 + github.com/kyma-project/kyma/components/central-application-gateway v0.0.0-20230130154909-4c81ab2cee61 + github.com/kyma-project/kyma/components/compass-runtime-agent v0.0.0-20220927112044-a548531152a1 + github.com/matryer/is v1.4.0 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.8.1 + github.com/vrischmann/envconfig v1.3.0 + k8s.io/api v0.26.0 + k8s.io/apimachinery v0.26.0 + k8s.io/client-go v0.26.0 +) + +require ( + github.com/99designs/gqlgen v0.11.3 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/agnivade/levenshtein v1.1.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mitchellh/copystructure v1.1.2 // indirect + github.com/mitchellh/reflectwalk 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/onrik/logrus v0.9.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/vektah/gqlparser/v2 v2.1.0 // 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 + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) + +replace ( + golang.org/x/crypto => golang.org/x/crypto v0.17.0 + golang.org/x/net => golang.org/x/net v0.17.0 + golang.org/x/text => golang.org/x/text v0.12.0 + google.golang.org/protobuf => google.golang.org/protobuf v1.33.0 +) diff --git a/tests/go.sum b/tests/go.sum new file mode 100644 index 00000000..b6c0f238 --- /dev/null +++ b/tests/go.sum @@ -0,0 +1,557 @@ +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/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/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/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/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= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4= +github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4= +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/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= +github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM= +github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +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/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +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-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6 h1:R/ypabUA7vskKTRSlgP6rMUHTU6PBRgIcHVSU9qQ6qM= +github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6/go.mod h1:CpBLxS3WrxouNECP/Y1A3i6qDnUYs8BvcXjgOW4Vqcw= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/gogo/protobuf v1.0.0/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/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/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/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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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/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.1/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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/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/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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +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.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +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/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/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/kyma-incubator/compass/components/director v0.0.0-20220126084901-92232f5eced0 h1:5pDxnqY8TK59Zp+9PqsC8uEG6ZeeURJKHxUo19ez+jY= +github.com/kyma-incubator/compass/components/director v0.0.0-20220126084901-92232f5eced0/go.mod h1:62mrVWDkGdPxAqj+X97FKiA/e8jYSZ/MARgzoThk9AU= +github.com/kyma-project/kyma/components/central-application-gateway v0.0.0-20230130154909-4c81ab2cee61 h1:iviPUIyUTMKA322amhFURlXbIbj9NrojpvJFDI+DtnQ= +github.com/kyma-project/kyma/components/central-application-gateway v0.0.0-20230130154909-4c81ab2cee61/go.mod h1:NL5E+cv7oyD8xJtDywLrHnkublvqifMBt5HFdw94adc= +github.com/kyma-project/kyma/components/compass-runtime-agent v0.0.0-20220927112044-a548531152a1 h1:zhIQX99vZIS5nlWIQZE6nIVB3w7W+vSgE5r9+VxLZGE= +github.com/kyma-project/kyma/components/compass-runtime-agent v0.0.0-20220927112044-a548531152a1/go.mod h1:D80/HUyVanrVfAcUOt8xRWp5oZwd1IK4SAg0A9Hlj+8= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +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 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0= +github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= +github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onrik/logrus v0.9.0 h1:oT7VstCUxWBoX7fswYK61fi9bzRBSpROq5CR2b7wxQo= +github.com/onrik/logrus v0.9.0/go.mod h1:qfe9NeZVAJfIxviw3cYkZo3kvBtLoPRJriAO8zl7qTk= +github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= +github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +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.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.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= +github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= +github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns= +github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= +github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk= +github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI= +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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +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/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/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.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/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +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-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20191001151750-bb3f8db39f24/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-20200116001909-b77594299b42/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-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-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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/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-20190515012406-7d7faa4812bd/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-20200114235610-7ae403b6b589/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-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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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/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= +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/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 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +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-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +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/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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 h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.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.3.0/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= +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.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= +k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= +k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= +k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= +k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= +k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +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/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +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.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= diff --git a/tests/hack/ci/.srl b/tests/hack/ci/.srl new file mode 100644 index 00000000..0ef5e90b --- /dev/null +++ b/tests/hack/ci/.srl @@ -0,0 +1 @@ +8FC09AB8ECD2BADC diff --git a/tests/hack/ci/Makefile b/tests/hack/ci/Makefile new file mode 100644 index 00000000..51ee6a24 --- /dev/null +++ b/tests/hack/ci/Makefile @@ -0,0 +1,150 @@ +K3D_URL=https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh + +PROJECT_ROOT ?= ../../.. +CLUSTER_NAME ?= kyma +REGISTRY_PORT ?= 5001 +REGISTRY_NAME ?= ${CLUSTER_NAME}-registry + +# Operating system architecture +OS_ARCH ?= $(shell uname -m) + +# Operating system type +OS_TYPE ?= $(shell uname) + +.PHONY: application-connector-module-image +application-connector-module-image: + @echo "::group::application-connector-module-image" + @make -C ${PROJECT_ROOT}/hack/common module-image + @echo "::endgroup::" + +.PHONY: application-connector-deploy +application-connector-deploy: + @echo "::group::application-connector-deploy" + @make -C ${PROJECT_ROOT}/hack/common deploy \ + IMG=k3d-${REGISTRY_NAME}:${REGISTRY_PORT}/${MANAGER_IMAGE_NAME}:${MANAGER_IMAGE_TAG} + @echo "::endgroup::" + +.PHONY: install-application-connector +install-application-connector: application-connector-module-image application-connector-deploy apply-appcon + @echo "::group::install-application-connector" + kubectl wait -n kyma-system \ + applicationconnectors/applicationconnector-sample \ + --for=jsonpath='{.status.state}'=Ready \ + --timeout=300s + @echo "::endgroup::" + +.PHONY: install-istio +install-istio: create-kyma-system-ns + @echo "::group::install-istio" + kubectl apply -f https://github.com/kyma-project/istio/releases/latest/download/istio-manager.yaml + kubectl apply -f https://github.com/kyma-project/istio/releases/latest/download/istio-default-cr.yaml + kubectl wait -n kyma-system istios/default --for=jsonpath='{.status.state}'=Ready --timeout=300s + @echo "::endgroup::" + +.PHONY: create-kyma-system-ns +create-kyma-system-ns: ## Create kyma-system namespace. + @echo "::group::create-kyma-system-ns" + kubectl create ns kyma-system + kubectl label namespaces kyma-system istio-injection=enabled --overwrite=true + @echo "::endgroup::" + +.PHONY: create-k3d +create-k3d: ## Create k3d with kyma CRDs. + @echo "::group::create-k3d" + k3d cluster create ${CLUSTER_NAME} \ + --api-port 6550 \ + -p 8080:80@loadbalancer \ + -p 8443:443@loadbalancer \ + -p 9090-9099:9090-9099@loadbalancer \ + --agents 2 \ + --registry-create k3d-${REGISTRY_NAME}:${REGISTRY_PORT} + @echo "::endgroup::" + +.PHONY: apply-appcon +apply-appcon: ## Apply the k3d application-connector CR + @echo "::group::apply-appcon" + @make -C ${PROJECT_ROOT}/hack/common apply-appcon + @echo "::endgroup::" + +.PHONY: apply-appcon-crd +apply-appcon-crd: ## Apply the application-connector CRD + @echo "::group::apply-appcon-crd" + kubectl apply -f ${PROJECT_ROOT}/tests/hack/ci/deps/applications.applicationconnector.crd.yaml + @echo "::endgroup::" + +.PHONY: apply-compass-connection-crd +apply-compass-connection-crd: ## Apply the compass-connection CRD + @echo "::group::apply-compas-connection-crd" + kubectl apply -f ${PROJECT_ROOT}/tests/hack/ci/deps/compass-connection.crd.yaml + @echo "::endgroup::" + +.PHONY: gateway-tests +gateway-tests: + @echo "::group::gateway-tests" + @make -f ${PROJECT_ROOT}/tests/Makefile.test-application-gateway test + @echo "::endgroup::" + +.PHONY: k3d-gateway-tests +k3d-gateway-tests: create-k3d \ + install-istio \ + install-application-connector \ + apply-appcon-crd \ + gateway-tests + +.PHONY: patch-validator +patch-validator: + @echo "::group::patch-validator" + @make -f ${PROJECT_ROOT}/tests/Makefile.test-application-conn-validator patch-validator + @echo "::endgroup::" + +.PHONY: patch-compass-runtime-agent +patch-compass-runtime-agent: + @echo "::group::patch-compass-runtime-agent" + @make -f ${PROJECT_ROOT}/tests/Makefile.test-compass-runtime-agent patch-compass-runtime-agent + @echo "::endgroup::" + +.PHONY: validator-tests +validator-tests: + @echo "::group::validator-tests" + @make -f ${PROJECT_ROOT}/tests/Makefile.test-application-conn-validator test + @echo "::endgroup::" + +.PHONY: k3d-validator-tests +k3d-validator-tests: patch-validator \ + create-k3d \ + install-istio \ + install-application-connector \ + apply-appcon-crd \ + validator-tests + +.PHONY: k3d-agent-tests +k3d-agent-tests: create-k3d \ + install-istio \ + patch-compass-runtime-agent \ + install-application-connector \ + apply-compass-connection-crd \ + apply-appcon-crd \ + compass-runtime-agent-test-image \ + agent-tests + +.PHONY: agent-tests +agent-tests: + @echo "::group::agent-tests" + @make -f ${PROJECT_ROOT}/tests/Makefile.test-compass-runtime-agent test + @echo "::endgroup::" + +.PHONY: compass-runtime-agent-test-image +compass-runtime-agent-test-image: + @echo "::group::compass-runtime-agent-test-image" + @make -C ${PROJECT_ROOT}/tests compass-runtime-agent-test-image \ + DOCKER_PUSH_REPOSITORY=localhost:${REGISTRY_PORT} \ + DOCKER_TAG=002 + @echo "::endgroup::" + +.PHONY: install-compass-runtime-agent +install-compass-runtime-agent: compass-runtime-agent-test-image + @echo "::group::install-compass-runtime-agent" + @helm template ${PROJECT_ROOT}/tests/hack/ci/resources/charts/compass-runtime-agent \ + -n kyma-system \ + | kubectl apply -f - + @echo "::endgroup::" diff --git a/tests/hack/ci/deps/applications.applicationconnector.crd.yaml b/tests/hack/ci/deps/applications.applicationconnector.crd.yaml new file mode 100644 index 00000000..1a7b6518 --- /dev/null +++ b/tests/hack/ci/deps/applications.applicationconnector.crd.yaml @@ -0,0 +1,183 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + "helm.sh/resource-policy": keep + name: applications.applicationconnector.kyma-project.io +spec: + group: applicationconnector.kyma-project.io + preserveUnknownFields: false + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + properties: + compassMetadata: + type: object + required: + - "authentication" + properties: + applicationId: + type: string + authentication: + type: object + required: + - "clientIds" + properties: + clientIds: + type: array + items: + type: string + accessLabel: + type: string + maxLength: 63 + pattern: '^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$' + description: + type: string + skipInstallation: + type: boolean + skipVerify: + type: boolean + encodeUrl: + type: boolean + default: true + labels: + nullable: true + additionalProperties: + type: string + type: object + tenant: + type: string + group: + type: string + tags: + nullable: true + description: New fields used by V2 version + items: + type: string + type: array + displayName: + type: string + providerDisplayName: + type: string + longDescription: + type: string + services: + type: array + items: + type: object + required: + - "id" + - "name" + - "displayName" + - "providerDisplayName" + - "description" + - "entries" + properties: + id: + type: string + name: + type: string + identifier: + type: string + labels: + nullable: true + additionalProperties: + type: string + description: Deprecated + type: object + displayName: + type: string + description: + type: string + longDescription: + type: string + providerDisplayName: + type: string + authCreateParameterSchema: + description: New fields used by V2 version + type: string + entries: + type: array + items: + type: object + required: + - "type" + properties: + apiType: + type: string + type: + type: string + enum: + - "API" + - "Events" + gatewayUrl: + type: string + centralGatewayUrl: + type: string + accessLabel: + type: string + maxLength: 63 + pattern: '^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$' + targetUrl: + type: string + id: + type: string + name: + description: New fields used by V2 version + type: string + requestParametersSecretName: + type: string + specificationUrl: + type: string + credentials: + type: object + required: + - "type" + - "secretName" + properties: + type: + type: string + secretName: + type: string + authenticationUrl: + type: string + csrfInfo: + type: object + required: + - "tokenEndpointURL" + properties: + tokenEndpointURL: + type: string + tags: + type: array + items: + type: string + type: object + status: + properties: + installationStatus: + description: Represents the status of Application release installation + properties: + description: + type: string + status: + type: string + required: + - status + type: object + required: + - installationStatus + type: object + scope: Cluster + names: + plural: applications + singular: application + kind: Application + shortNames: + - app diff --git a/tests/hack/ci/deps/compass-connection.crd.yaml b/tests/hack/ci/deps/compass-connection.crd.yaml new file mode 100644 index 00000000..aae2958a --- /dev/null +++ b/tests/hack/ci/deps/compass-connection.crd.yaml @@ -0,0 +1,144 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + "helm.sh/resource-policy": keep + name: compassconnections.compass.kyma-project.io +spec: + group: compass.kyma-project.io + names: + kind: CompassConnection + listKind: CompassConnectionList + plural: compassconnections + singular: compassconnection + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + managementInfo: + properties: + connectorUrl: + description: 'URL used for maintaining the secure connection.' + type: string + directorUrl: + description: 'URL used for fetching Applications.' + type: string + required: + - connectorUrl + - directorUrl + type: object + refreshCredentialsNow: + description: 'If set to `true`, ignores certificate expiration date and refreshes in the next round.' + type: boolean + resyncNow: + description: 'If set to `true`, ignores `APP_MINIMAL_COMPASS_SYNC_TIME` and syncs in the next round.' + type: boolean + required: + - managementInfo + type: object + status: + properties: + connectionState: + type: string + connectionStatus: + description: 'Represents the status of the connection to + Compass.' + properties: + certificateStatus: + description: 'Specifies the certificate issue and expiration dates.' + properties: + acquired: + description: 'Specifies when the certificate was acquired.' + format: date-time + nullable: true + type: string + notAfter: + description: 'Specifies when the certificate stops being valid.' + format: date-time + nullable: true + type: string + notBefore: + description: 'Specifies when the certificate becomes valid.' + format: date-time + nullable: true + type: string + type: object + error: + type: string + established: + description: 'Specifies when the connection was established.' + format: date-time + nullable: true + type: string + lastSuccess: + description: 'Specifies the date of the last successful synchronization with the Connector.' + format: date-time + nullable: true + type: string + lastSync: + description: 'Specifies the date of the last synchronization attempt.' + format: date-time + nullable: true + type: string + renewed: + description: 'Specifies the date of the last certificate renewal.' + format: date-time + nullable: true + type: string + required: + - certificateStatus + type: object + synchronizationStatus: + description: 'Provides the status of the synchronization with the Director.' + nullable: true + properties: + error: + type: string + lastAttempt: + description: 'Specifies the date of the last synchronization attempt with the Director.' + format: date-time + nullable: true + type: string + lastSuccessfulApplication: + description: 'Specifies the date of the last successful application of resources fetched from Compass.' + format: date-time + nullable: true + type: string + lastSuccessfulFetch: + description: 'Specifies the date of the last successful fetch of resources from the Director.' + format: date-time + nullable: true + type: string + type: object + required: + - connectionState + - connectionStatus + type: object + required: + - spec + type: object + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/.helmignore b/tests/hack/ci/resources/charts/compass-runtime-agent/.helmignore new file mode 100644 index 00000000..f0c13194 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/Chart.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/Chart.yaml new file mode 100644 index 00000000..dfbe15f9 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +description: Kyma component 'compass-runtime-agent' +name: compass-runtime-agent +version: 1.0.0 +home: https://kyma-project.io +icon: https://github.com/kyma-project/kyma/blob/main/logo.png?raw=true diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/_helpers.tpl b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/_helpers.tpl new file mode 100644 index 00000000..a5e2cedf --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/_helpers.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a URL for container images +*/}} +{{- define "imageurl" -}} +{{- $registry := default $.reg.path $.img.containerRegistryPath -}} +{{- $path := ternary (print $registry) (print $registry "/" $.img.directory) (empty $.img.directory) -}} +{{- $version := ternary (print ":" $.img.version) (print "@sha256:" $.img.sha) (empty $.img.sha) -}} +{{- print $path "/" $.img.name $version -}} +{{- end -}} + diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/cluster-role-binding.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/cluster-role-binding.yaml new file mode 100644 index 00000000..c5f50e41 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/cluster-role-binding.yaml @@ -0,0 +1,48 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Chart.Name }} + labels: + app: {{ .Chart.Name }} + release: {{ .Release.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +rules: + - apiGroups: ["compass.kyma-project.io"] + resources: ["compassconnections"] + verbs: ["create", "get", "list", "update", "delete", "watch"] + - apiGroups: ["applicationconnector.kyma-project.io"] + resources: ["applications"] + verbs: ["get", "list", "create", "update", "delete"] + - apiGroups: [""] + resources: ["nodes", "persistentvolumes"] + verbs: ["get", "list"] + - apiGroups: ["metrics.k8s.io"] + resources: ["nodes"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["secrets"] + resourceNames: ["compass-agent-configuration","cluster-client-certificates"] + verbs: ["get", "delete"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Chart.Name }} + labels: + app: {{ .Chart.Name }} + release: {{ .Chart.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ .Chart.Name }} + apiGroup: rbac.authorization.k8s.io diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/deployment.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/deployment.yaml new file mode 100644 index 00000000..d71d76de --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/deployment.yaml @@ -0,0 +1,115 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Chart.Name }} + release: {{ .Release.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + selector: + matchLabels: + app: {{ .Chart.Name }} + release: {{ .Release.Name }} + template: + metadata: + annotations: + sidecar.istio.io/inject: "true" + labels: + app: {{ .Chart.Name }} + release: {{ .Release.Name }} + spec: + securityContext: + runAsUser: 65535 + runAsGroup: 65535 + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + serviceAccountName: {{ .Chart.Name }} + containers: + - name: {{ .Chart.Name }} + securityContext: + privileged: false + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: + - ALL + procMount: default + readOnlyRootFilesystem: true + ports: + - containerPort: {{ .Values.compassRuntimeAgent.healthCheck.port }} + hostPort: 0 + name: http-health + image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.compass_runtime_agent) }} + imagePullPolicy: {{ .Values.compassRuntimeAgent.image.pullPolicy }} + args: + - "/app/compass-runtime-agent" + env: + - name: APP_AGENT_CONFIGURATION_SECRET + value: "{{ .Values.compassRuntimeAgent.config.secret.namespace }}/{{ .Values.compassRuntimeAgent.config.secret.name }}" + - name: APP_CONTROLLER_SYNC_PERIOD + value: {{ .Values.compassRuntimeAgent.sync.controllerSyncPeriod | quote }} + - name: APP_MINIMAL_COMPASS_SYNC_TIME + value: {{ .Values.compassRuntimeAgent.sync.minimalConfigSyncTime | quote }} + - name: APP_CERT_VALIDITY_RENEWAL_THRESHOLD + value: {{ .Values.compassRuntimeAgent.certificates.renewal.validityThreshold | quote }} + - name: APP_CLUSTER_CERTIFICATES_SECRET + value: "{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.namespace }}/{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.name }}" + - name: APP_CA_CERTIFICATES_SECRET + value: "{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace }}/{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.name }}" + - name: APP_SKIP_COMPASS_TLS_VERIFY + value: {{ .Values.compassRuntimeAgent.compass.skipTLSVerification | quote }} + - name: APP_SKIP_APPS_TLS_VERIFY + value: {{ .Values.compassRuntimeAgent.config.skipAppsTLSVerification | quote }} + - name: APP_GATEWAY_PORT + value: {{ .Values.compassRuntimeAgent.resources.gatewayPort | quote }} + - name: APP_UPLOAD_SERVICE_URL + value: {{ .Values.compassRuntimeAgent.resources.uploadServiceUrl | quote }} + - name: APP_QUERY_LOGGING + value: {{ .Values.compassRuntimeAgent.debug.queryLogging | quote }} + - name: APP_METRICS_LOGGING_TIME_INTERVAL + value: {{ .Values.compassRuntimeAgent.metrics.loggingTimeInterval | quote }} + - name: APP_RUNTIME_EVENTS_URL + value: "https://gateway.{{ .Values.global.domainName }}" + - name: APP_RUNTIME_CONSOLE_URL + value: "https://console.{{ .Values.global.domainName }}" + - name: APP_HEALTH_PORT + value: {{ .Values.compassRuntimeAgent.healthCheck.port | quote }} + {{ if .Values.compassRuntimeAgent.certificates.caCertificate.secret.migration}} + - name: APP_CA_CERT_SECRET_TO_MIGRATE + value: "{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace }}/{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.migration.name | default "" }}" + - name: APP_CA_CERT_SECRET_KEYS_TO_MIGRATE + value: '{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.migration.keys | default "[]" | toJson }}' + {{ end }} + {{ if .Values.compassRuntimeAgent.config.secret.migration.enabled }} + - name: APP_AGENT_CONFIGURATION_SECRET_TO_MIGRATE + value: "{{ .Values.compassRuntimeAgent.config.secret.migration.namespace }}/{{ .Values.compassRuntimeAgent.config.secret.name | default "" }}" + {{ end }} + {{ if .Values.compassRuntimeAgent.certificates.clientCertificate.secret.migration.enabled }} + - name: APP_CLUSTER_CERTIFICATES_SECRET_TO_MIGRATE + value: "{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.migration.namespace }}/{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.name | default "" }}" + {{ end }} + - name: APP_CENTRAL_GATEWAY_SERVICE_URL + value: {{ .Values.compassRuntimeAgent.resources.centralGatewayServiceUrl | quote }} + livenessProbe: + httpGet: + port: {{ .Values.compassRuntimeAgent.healthCheck.port }} + path: "/healthz" + initialDelaySeconds: {{ .Values.compassRuntimeAgent.livenessProbe.initialDelaySeconds }} + timeoutSeconds: {{ .Values.compassRuntimeAgent.livenessProbe.timeoutSeconds }} + periodSeconds: {{.Values.compassRuntimeAgent.livenessProbe.periodSeconds }} + readinessProbe: + httpGet: + port: {{.Values.compassRuntimeAgent.healthCheck.port }} + path: "/healthz" + initialDelaySeconds: {{ .Values.compassRuntimeAgent.readinessProbe.initialDelaySeconds }} + timeoutSeconds: {{ .Values.compassRuntimeAgent.readinessProbe.timeoutSeconds }} + periodSeconds: {{.Values.compassRuntimeAgent.readinessProbe.periodSeconds }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/priority-class.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/priority-class.yaml new file mode 100644 index 00000000..037e0819 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/priority-class.yaml @@ -0,0 +1,7 @@ +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: {{ .Values.priorityClassName }} +value: 2000000 +globalDefault: false +description: "Scheduling priority of compass-runtime-agent component. By default, compass-runtime-agent should not be blocked by unschedulable user workloads." \ No newline at end of file diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/resources-role-binding.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/resources-role-binding.yaml new file mode 100644 index 00000000..b6c5c3f3 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/resources-role-binding.yaml @@ -0,0 +1,40 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Values.compassRuntimeAgent.resources.systemNamespace }} + labels: + app: {{ .Chart.Name }} + release: {{ .Chart.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +rules: + - apiGroups: [""] + resources: ["services"] + verbs: ["create", "get", "delete"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "update", "delete"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Values.compassRuntimeAgent.resources.systemNamespace }} + labels: + app: {{ .Chart.Name }} + release: {{ .Chart.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ .Chart.Name }} + apiGroup: rbac.authorization.k8s.io diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/role-binding.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/role-binding.yaml new file mode 100644 index 00000000..0c571918 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/role-binding.yaml @@ -0,0 +1,76 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Chart.Name }}-client-cert-role + namespace: {{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.namespace | default "default" }} + labels: + app: {{ .Chart.Name }} + release: {{ .Chart.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "update", "delete"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Chart.Name }}-client-cert-rolebinding + namespace: {{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.namespace | default "default" }} + labels: + app: {{ .Chart.Name }} + release: {{ .Release.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ .Chart.Name }}-client-cert-role + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Chart.Name }}-ca-cert-role + namespace: {{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace | default "default" }} + labels: + app: {{ .Chart.Name }} + release: {{ .Chart.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "update", "delete"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Chart.Name }}-ca-cert-rolebinding + namespace: {{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace | default "default" }} + labels: + app: {{ .Chart.Name }} + release: {{ .Chart.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +subjects: + - kind: ServiceAccount + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ .Chart.Name }}-ca-cert-role + apiGroup: rbac.authorization.k8s.io + diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service-account.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service-account.yaml new file mode 100644 index 00000000..3a930dc8 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service-account.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Chart.Name }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} \ No newline at end of file diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service.yaml new file mode 100644 index 00000000..119457d8 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }}-service + namespace: {{ .Values.global.namespace }} + labels: + control-plane: {{ .Chart.Name }} + controller-tools.k8s.io: "1.0" + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + app.kubernetes.io/name: {{ template "name" . }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + selector: + control-plane: {{ .Chart.Name }} + controller-tools.k8s.io: "1.0" + ports: + - port: 443 + diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/skr-configmap.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/skr-configmap.yaml new file mode 100644 index 00000000..387d761b --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/skr-configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: skr-configmap + namespace: {{ .Values.global.skrConfigmapNamespace }} +data: + is-managed-kyma-runtime: "true" diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/values.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/values.yaml new file mode 100644 index 00000000..dbad8ab5 --- /dev/null +++ b/tests/hack/ci/resources/charts/compass-runtime-agent/values.yaml @@ -0,0 +1,89 @@ +global: + domainName: kyma.example.com + containerRegistry: + path: europe-docker.pkg.dev/kyma-project + images: + compass_runtime_agent: + name: "compass-runtime-agent" + version: "v20231218-ff3777c4" + directory: "prod" + istio: + gateway: + name: kyma-gateway + namespace: kyma-system + skrConfigmapNamespace: kyma-system + +managementPlane: {} # default value + +priorityClassName: "compass-runtime-agent-priority-class" + +compassRuntimeAgent: + image: + pullPolicy: IfNotPresent + sync: + controllerSyncPeriod: 180s + minimalConfigSyncTime: 15s + resources: + systemNamespace: "kyma-system" + dexSecretNamespace: "kyma-system" + dexSecretName: "admin-user" + gatewayPort: 8080 + centralGatewayServiceUrl: http://central-application-gateway.kyma-system.svc.cluster.local:8082 + config: + insecureConfigurationFetch: true + skipAppsTLSVerification: false + secret: + name: compass-agent-configuration + namespace: kyma-system + migration: + namespace: compass-system + enabled: true + certificates: + renewal: + validityThreshold: "0.3" + clientCertificate: + secret: + name: cluster-client-certificates + namespace: kyma-system + migration: + namespace: compass-system + enabled: true + caCertificate: + secret: + name: kyma-gateway-certs-cacert + namespace: istio-system + migration: + name: app-connector-certs + keys: ["cacert"] + compass: + skipTLSVerification: true + debug: + queryLogging: false + metrics: + loggingTimeInterval: 30m + healthCheck: + port: 8090 + proxyStatusPort: 15020 + tests: + labels: + integration: true + after-upgrade: true + enabled: true + mockService: + port: 8080 + configApplicationWaitTime: 50s + proxyInvalidationWaitTime: 120s + applicationInstallationTimeout: 180s + graphqlLogs: false + director: + url: "https://compass-gateway.{{ .Values.global.domainName }}/director/graphql" + idProvider: + clientTimeout: 10s + livenessProbe: + initialDelaySeconds: 50 + timeoutSeconds: 1 + periodSeconds: 10 + readinessProbe: + initialDelaySeconds: 10 + timeoutSeconds: 1 + periodSeconds: 2 diff --git a/tests/internal/testkit/httpd/http.go b/tests/internal/testkit/httpd/http.go new file mode 100644 index 00000000..694b03e2 --- /dev/null +++ b/tests/internal/testkit/httpd/http.go @@ -0,0 +1,44 @@ +package httpd + +import ( + "io" + "net/http" + "testing" +) + +type LogHttp struct { + t *testing.T + httpCli *http.Client +} + +func NewCli(t *testing.T) LogHttp { + return LogHttp{t: t, httpCli: &http.Client{}} +} + +func (c LogHttp) Get(url string) (resp *http.Response, body []byte, err error) { + c.t.Helper() + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return + } + + return c.Do(req) + +} + +func (c LogHttp) Do(req *http.Request) (res *http.Response, body []byte, err error) { + c.t.Helper() + c.t.Logf("%s %s", req.Method, req.URL) + + res, err = c.httpCli.Do(req) + if err != nil { + return + } + + body, err = io.ReadAll(res.Body) + if err == nil && len(body) > 0 { + c.t.Logf("Body: %s", body) + } + + return +} diff --git a/tests/internal/testkit/test-api/apis.go b/tests/internal/testkit/test-api/apis.go new file mode 100644 index 00000000..98c04105 --- /dev/null +++ b/tests/internal/testkit/test-api/apis.go @@ -0,0 +1,122 @@ +package test_api + +import ( + "fmt" + "io" + "net/http" + + "github.com/go-http-utils/logger" + "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" +) + +func SetupRoutes(logOut io.Writer, basicAuthCredentials BasicAuthCredentials, oAuthCredentials OAuthCredentials, expectedRequestParameters ExpectedRequestParameters, oauthTokens map[string]OAuthToken, csrfTokens CSRFTokens) http.Handler { + router := mux.NewRouter() + + router.HandleFunc("/v1/health", alwaysOk).Methods("GET") + api := router.PathPrefix("/v1/api").Subrouter() + api.Use(Logger(logOut, logger.DevLoggerType)) + + oauth := NewOAuth(oAuthCredentials.ClientID, oAuthCredentials.ClientSecret, oauthTokens) + csrf := NewCSRF(csrfTokens) + + { + api.HandleFunc("/oauth/token", oauth.Token).Methods(http.MethodPost) + api.HandleFunc("/oauth/bad-token", oauth.BadToken).Methods(http.MethodPost) + api.HandleFunc("/csrf/token", csrf.Token).Methods(http.MethodGet) + api.HandleFunc("/csrf/bad-token", csrf.BadToken).Methods(http.MethodGet) + } + + { + r := api.PathPrefix("/unsecure").Subrouter() + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + r.HandleFunc("/echo", echo).Methods(http.MethodPut, http.MethodPost, http.MethodDelete) + r.HandleFunc("/code/{code:[0-9]+}", resCode).Methods(http.MethodGet) + r.HandleFunc("/timeout", timeout).Methods(http.MethodGet) + } + { + r := api.PathPrefix("/basic").Subrouter() + r.Use(BasicAuth(basicAuthCredentials)) + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + } + { + r := api.PathPrefix("/oauth").Subrouter() + r.Use(oauth.Middleware()) + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + } + { + r := api.PathPrefix("/csrf-basic").Subrouter() + r.Use(csrf.Middleware()) + r.Use(BasicAuth(basicAuthCredentials)) + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + } + { + r := api.PathPrefix("/csrf-oauth").Subrouter() + r.Use(csrf.Middleware()) + r.Use(oauth.Middleware()) + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + } + { + r := api.PathPrefix("/request-parameters-basic").Subrouter() + r.Use(RequestParameters(expectedRequestParameters)) + r.Use(BasicAuth(basicAuthCredentials)) + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + } + { + r := api.PathPrefix("/redirect").Subrouter() + + r.HandleFunc("/ok/target", alwaysOk).Methods(http.MethodGet) + + r.Handle("/ok", http.RedirectHandler("/v1/api/redirect/ok/target", http.StatusTemporaryRedirect)) + + ba := BasicAuth(basicAuthCredentials) + ok := http.HandlerFunc(alwaysOk) + r.Handle("/basic/target", ba(ok)).Methods(http.MethodGet) + r.Handle("/basic", http.RedirectHandler("/v1/api/redirect/basic/target", http.StatusTemporaryRedirect)) + + r.Handle("/external", http.RedirectHandler("http://central-application-gateway.kyma-system:8081/v1/health", http.StatusTemporaryRedirect)) + } + + return router +} + +func SetupMTLSRoutes(logOut io.Writer, oAuthCredentials OAuthCredentials, oauthTokens map[string]OAuthToken, csrfTokens CSRFTokens) http.Handler { + router := mux.NewRouter() + + router.HandleFunc("/v1/health", alwaysOk).Methods("GET") + api := router.PathPrefix("/v1/api").Subrouter() + api.Use(Logger(logOut, logger.DevLoggerType)) + + oauth := NewOAuth(oAuthCredentials.ClientID, oAuthCredentials.ClientSecret, oauthTokens) + csrf := NewCSRF(csrfTokens) + + { + r := api.PathPrefix("/mtls").Subrouter() + r.Use(oauth.Middleware()) + api.HandleFunc("/mtls-oauth/token", oauth.MTLSToken).Methods(http.MethodPost) + } + + { + r := api.PathPrefix("/mtls").Subrouter() + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + } + { + r := api.PathPrefix("/csrf-mtls").Subrouter() + r.Use(csrf.Middleware()) + r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet) + } + + return router +} + +func Logger(out io.Writer, t logger.Type) mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return logger.Handler(next, out, t) + } +} + +func handleError(w http.ResponseWriter, code int, format string, a ...interface{}) { + err := fmt.Errorf(format, a...) + log.Error(err) + w.WriteHeader(code) +} diff --git a/tests/internal/testkit/test-api/basicauth.go b/tests/internal/testkit/test-api/basicauth.go new file mode 100644 index 00000000..0307ae94 --- /dev/null +++ b/tests/internal/testkit/test-api/basicauth.go @@ -0,0 +1,30 @@ +package test_api + +import ( + "github.com/gorilla/mux" + "net/http" +) + +type BasicAuthCredentials struct { + User string + Password string +} + +func BasicAuth(credentials BasicAuthCredentials) mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + u, p, ok := r.BasicAuth() + if !ok { + handleError(w, http.StatusForbidden, "Basic auth header not found") + return + } + + if credentials.User != u || credentials.Password != p { + handleError(w, http.StatusForbidden, "Incorrect username or Password") + return + } + + next.ServeHTTP(w, r) + }) + } +} diff --git a/tests/internal/testkit/test-api/csrf.go b/tests/internal/testkit/test-api/csrf.go new file mode 100644 index 00000000..06f7993b --- /dev/null +++ b/tests/internal/testkit/test-api/csrf.go @@ -0,0 +1,95 @@ +package test_api + +import ( + "github.com/google/uuid" + "github.com/gorilla/mux" + "net/http" + "sync" +) + +const ( + csrfTokenHeader = "X-csrf-token" + csrfTokenCookie = "csrftokencookie" +) + +type CSRFTokens map[string]interface{} + +type CSRFHandler struct { + mutex sync.RWMutex + tokens map[string]interface{} +} + +func NewCSRF(tokens CSRFTokens) CSRFHandler { + return CSRFHandler{ + mutex: sync.RWMutex{}, + tokens: tokens, + } +} + +func (ch *CSRFHandler) Token(w http.ResponseWriter, _ *http.Request) { + token := uuid.New().String() + + ch.mutex.Lock() + ch.tokens[token] = nil + ch.mutex.Unlock() + + w.Header().Set(csrfTokenHeader, token) + http.SetCookie(w, &http.Cookie{ + Name: csrfTokenCookie, + Value: token, + }) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") +} + +func (ch *CSRFHandler) BadToken(w http.ResponseWriter, _ *http.Request) { + token := uuid.New().String() + + w.Header().Set(csrfTokenHeader, token) + http.SetCookie(w, &http.Cookie{ + Name: csrfTokenCookie, + Value: token, + }) + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") +} + +func (ch *CSRFHandler) Middleware() mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + headerToken := r.Header.Get(csrfTokenHeader) + if headerToken == "" { + handleError(w, http.StatusForbidden, "CSRF token header missing") + return + } + + ch.mutex.RLock() + _, found := ch.tokens[headerToken] + ch.mutex.RUnlock() + + if !found { + handleError(w, http.StatusForbidden, "Invalid CSRF token from the header") + return + } + + cookieToken, err := r.Cookie(csrfTokenCookie) + if err != nil { + handleError(w, http.StatusForbidden, "CSRF token cookie missing") + return + } + + ch.mutex.RLock() + _, found = ch.tokens[cookieToken.Value] + ch.mutex.RUnlock() + + if !found { + handleError(w, http.StatusForbidden, "Invalid CSRF token from the cookie") + return + } + + next.ServeHTTP(w, r) + }) + } +} diff --git a/tests/internal/testkit/test-api/handlers.go b/tests/internal/testkit/test-api/handlers.go new file mode 100644 index 00000000..26114863 --- /dev/null +++ b/tests/internal/testkit/test-api/handlers.go @@ -0,0 +1,69 @@ +package test_api + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "strconv" + "time" + + "github.com/gorilla/mux" +) + +func alwaysOk(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) +} + +type EchoResponse struct { + Body []byte `json:"body"` + Headers map[string][]string `json:"headers"` + Method string `json:"method"` + Query string `json:"query"` +} + +func echo(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Println("Couldn't read request body:", r.URL) + body = []byte("") + } + + res := EchoResponse{ + Method: r.Method, + Body: body, + Headers: r.Header, + Query: r.URL.RawQuery, + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(res) + + if err != nil { + log.Println("Couldn't encode the response body to JSON:", r.URL) + } +} + +// resCode should only be used in paths with `code` +// parameter, that is a valid int +func resCode(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + codeStr := vars["code"] // must exist, because path has a pattern + code, _ := strconv.Atoi(codeStr) // can't error, because path has a pattern + w.WriteHeader(code) + w.Write([]byte(codeStr)) +} + +func timeout(w http.ResponseWriter, r *http.Request) { + c := r.Context().Done() + if c == nil { + log.Println("Context has no timeout, sleeping for 2 minutes") + time.Sleep(2 * time.Minute) + return + } + log.Println("Context timeout, waiting until done") + + _ = <-c + + alwaysOk(w, r) +} diff --git a/tests/internal/testkit/test-api/oauth.go b/tests/internal/testkit/test-api/oauth.go new file mode 100644 index 00000000..2fe60ea3 --- /dev/null +++ b/tests/internal/testkit/test-api/oauth.go @@ -0,0 +1,190 @@ +package test_api + +import ( + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "net/http" + "strings" + "sync" + "time" + + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +type OAuthCredentials struct { + ClientID string + ClientSecret string +} + +const ( + clientIDKey = "client_id" + clientSecretKey = "client_secret" + grantTypeKey = "grant_type" + tokenLifetime = "token_lifetime" + defaultTokenExpiresIn = 5 * time.Minute +) + +type OauthResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in,omitempty"` +} + +type OAuthToken struct { + exp time.Time +} + +func (token OAuthToken) Valid() bool { + return token.exp.After(time.Now()) +} + +type OAuthHandler struct { + clientID string + clientSecret string + mutex sync.RWMutex + tokens map[string]OAuthToken +} + +func NewOAuth(clientID, clientSecret string, tokens map[string]OAuthToken) OAuthHandler { + return OAuthHandler{ + clientID: clientID, + clientSecret: clientSecret, + mutex: sync.RWMutex{}, + tokens: tokens, + } +} + +func (oh *OAuthHandler) Token(w http.ResponseWriter, r *http.Request) { + if ok, status, message := oh.isRequestValid(r); !ok { + handleError(w, status, message) + return + } + + token := uuid.New().String() + exp := defaultTokenExpiresIn + + if ttlStr := r.URL.Query().Get(tokenLifetime); ttlStr != "" { + parsedEXP, err := time.ParseDuration(ttlStr) + if err == nil { + log.Info("Received valid OAuth expiresIn:", parsedEXP) + exp = parsedEXP + } else { + log.Error("Received invalid OAuth expiresIn:", err) + } + } + + oh.storeTokenInCache(token, exp) + + response := OauthResponse{AccessToken: token, TokenType: "bearer", ExpiresIn: int64(exp.Seconds())} + oh.respondWithToken(w, response) +} + +func (oh *OAuthHandler) BadToken(w http.ResponseWriter, r *http.Request) { + if ok, status, message := oh.isRequestValid(r); !ok { + handleError(w, status, message) + return + } + + token := uuid.New().String() + response := OauthResponse{AccessToken: token, TokenType: "bearer"} + oh.respondWithToken(w, response) +} + +func (oh *OAuthHandler) MTLSToken(w http.ResponseWriter, r *http.Request) { + if ok, status, message := oh.isMTLSRequestValid(r); !ok { + handleError(w, status, message) + return + } + + token := uuid.New().String() + exp := defaultTokenExpiresIn + + oh.storeTokenInCache(token, exp) + response := OauthResponse{AccessToken: token, TokenType: "bearer", ExpiresIn: 3600} + oh.respondWithToken(w, response) +} + +func (oh *OAuthHandler) Middleware() mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + handleError(w, http.StatusUnauthorized, "Authorization header missing") + return + } + + splitToken := strings.Split(authHeader, "Bearer") + if len(splitToken) != 2 { + handleError(w, http.StatusUnauthorized, "Bearer token missing") + return + } + + token := strings.TrimSpace(splitToken[1]) + + oh.mutex.RLock() + data, found := oh.tokens[token] + oh.mutex.RUnlock() + + if !found || !data.Valid() { + handleError(w, http.StatusUnauthorized, "Invalid token") + return + } + + next.ServeHTTP(w, r) + }) + } +} + +func (oh *OAuthHandler) isRequestValid(r *http.Request) (bool, int, string) { + err := r.ParseForm() + if err != nil { + return false, http.StatusInternalServerError, fmt.Sprintf("Failed to parse form: %v", err) + } + + clientID := r.FormValue(clientIDKey) + clientSecret := r.FormValue(clientSecretKey) + grantType := r.FormValue(grantTypeKey) + + if !oh.verifyClient(clientID, clientSecret) || grantType != "client_credentials" { + return false, http.StatusForbidden, "Client verification failed" + } + + return true, 0, "" +} + +func (oh *OAuthHandler) verifyClient(id, secret string) bool { + return id == oh.clientID && secret == oh.clientSecret +} + +func (oh *OAuthHandler) respondWithToken(w http.ResponseWriter, response OauthResponse) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(response); err != nil { + handleError(w, http.StatusInternalServerError, "Failed to encode token response") + return + } + w.WriteHeader(http.StatusOK) +} + +func (oh *OAuthHandler) storeTokenInCache(token string, expIn time.Duration) { + oh.mutex.Lock() + oh.tokens[token] = OAuthToken{exp: time.Now().Add(expIn)} + oh.mutex.Unlock() +} + +func (oh *OAuthHandler) isMTLSRequestValid(r *http.Request) (bool, int, string) { + err := r.ParseForm() + if err != nil { + return false, http.StatusInternalServerError, fmt.Sprintf("Failed to parse form: %v", err) + } + + clientID := r.FormValue(clientIDKey) + grantType := r.FormValue(grantTypeKey) + + if r.TLS == nil || clientID != oh.clientID || grantType != "client_credentials" { + return false, http.StatusForbidden, "Client verification failed" + } + + return true, 0, "" +} diff --git a/tests/internal/testkit/test-api/requestparams.go b/tests/internal/testkit/test-api/requestparams.go new file mode 100644 index 00000000..774ff9fa --- /dev/null +++ b/tests/internal/testkit/test-api/requestparams.go @@ -0,0 +1,53 @@ +package test_api + +import ( + "github.com/gorilla/mux" + "net/http" +) + +type ExpectedRequestParameters struct { + Headers map[string][]string + QueryParameters map[string][]string +} + +func RequestParameters(expectedRequestParams ExpectedRequestParameters) mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for key, expectedVals := range expectedRequestParams.Headers { + actualVals := r.Header.Values(key) + if !containsSubset(actualVals, expectedVals) { + handleError(w, http.StatusBadRequest, "Incorrect additional headers. Expected %s header to contain %v, but found %v", key, expectedVals, actualVals) + return + } + } + + queryParameters := r.URL.Query() + for key, expectedVals := range expectedRequestParams.QueryParameters { + actualVals := queryParameters[key] + if !containsSubset(actualVals, expectedVals) { + handleError(w, http.StatusBadRequest, "Incorrect additional query parameters. Expected %s query parameter to contain %v, but found %v", key, expectedVals, actualVals) + return + } + } + + next.ServeHTTP(w, r) + }) + } +} + +func containsSubset(set, subset []string) bool { + for _, bVal := range subset { + found := false + for _, aVal := range set { + if aVal == bVal { + found = true + break + } + } + + if !found { + return false + } + } + return true +} diff --git a/tests/resources/charts/application-connectivity-validator-test/Chart.yaml b/tests/resources/charts/application-connectivity-validator-test/Chart.yaml new file mode 100644 index 00000000..0424adb3 --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: application-connectivity-validator-test +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: 1.16.0 diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/Chart.yaml b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/Chart.yaml new file mode 100644 index 00000000..807b2174 --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: echoserver +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: 1.16.0 diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/templates/echoserver.yml b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/templates/echoserver.yml new file mode 100644 index 00000000..d4f83b72 --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/templates/echoserver.yml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: echosever + name: echoserver + namespace: {{ .Values.global.namespace }} +spec: + replicas: 1 + selector: + matchLabels: + app: echoserver + template: + metadata: + labels: + app: echoserver + spec: + containers: + - image: ealen/echo-server:0.7.0 + name: echoserver + ports: + - containerPort: 80 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 3 + periodSeconds: 3 +--- +apiVersion: v1 +kind: Service +metadata: + name: echoserver + namespace: {{ .Values.global.namespace }} +spec: + selector: + app: echoserver + ports: + - name: "http" + protocol: TCP + port: 80 diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/Chart.yaml b/tests/resources/charts/application-connectivity-validator-test/charts/test/Chart.yaml new file mode 100644 index 00000000..6e99aa1f --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: test +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: 1.16.0 diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/_helpers.tpl b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/_helpers.tpl new file mode 100644 index 00000000..5acdb5e3 --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/_helpers.tpl @@ -0,0 +1,11 @@ +{{/* +Create a URL for container images +*/}} +{{- define "imageurl" -}} +{{- $registry := default $.reg.path $.img.containerRegistryPath -}} +{{- if hasKey $.img "directory" -}} +{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}} +{{- else -}} +{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-compass.yml b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-compass.yml new file mode 100644 index 00000000..8e1a498e --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-compass.yml @@ -0,0 +1,12 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: event-test-compass +spec: + compassMetadata: + applicationId: applicationId + authentication: + clientIds: ["clientId1", "clientId2"] + description: Test app-con-validator + skipVerify: true + diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-standalone.yml b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-standalone.yml new file mode 100644 index 00000000..424b7bd6 --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-standalone.yml @@ -0,0 +1,7 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: event-test-standalone +spec: + description: Test app-con-validator + skipVerify: true diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/test.yml b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/test.yml new file mode 100644 index 00000000..12eb1f67 --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/test.yml @@ -0,0 +1,17 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: application-connectivity-validator-test + namespace: {{ .Values.global.namespace }} +spec: + backoffLimit: 0 + template: + metadata: + annotations: + traffic.sidecar.istio.io/excludeOutboundPorts: "8080" + spec: + containers: + - name: application-connectivity-validator-test + image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.validatorTest) }} + imagePullPolicy: Always + restartPolicy: Never \ No newline at end of file diff --git a/tests/resources/charts/application-connectivity-validator-test/values.yaml b/tests/resources/charts/application-connectivity-validator-test/values.yaml new file mode 100644 index 00000000..5b84bcba --- /dev/null +++ b/tests/resources/charts/application-connectivity-validator-test/values.yaml @@ -0,0 +1,15 @@ +# Default values for application-connectivity-validator-test. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +global: + containerRegistry: + path: "europe-docker.pkg.dev/kyma-project" + + images: + validatorTest: + name: "connectivity-validator-test" + version: "v20230925-75c3a9a8" + directory: "prod" + + namespace: "test" diff --git a/tests/resources/charts/compass-runtime-agent-test/Chart.yaml b/tests/resources/charts/compass-runtime-agent-test/Chart.yaml new file mode 100644 index 00000000..9b3c49e9 --- /dev/null +++ b/tests/resources/charts/compass-runtime-agent-test/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: compass-runtime-agent-test +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/_helpers.tpl b/tests/resources/charts/compass-runtime-agent-test/templates/_helpers.tpl new file mode 100644 index 00000000..9cba1391 --- /dev/null +++ b/tests/resources/charts/compass-runtime-agent-test/templates/_helpers.tpl @@ -0,0 +1,12 @@ + +{{/* +Create a URL for container images +*/}} +{{- define "imageurl" -}} +{{- $registry := default $.reg.path $.img.containerRegistryPath -}} +{{- if hasKey $.img "directory" -}} +{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}} +{{- else -}} +{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}} +{{- end -}} +{{- end -}} diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/applications/test-create-app.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/applications/test-create-app.yaml new file mode 100644 index 00000000..e95e05cd --- /dev/null +++ b/tests/resources/charts/compass-runtime-agent-test/templates/applications/test-create-app.yaml @@ -0,0 +1,59 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + labels: + applicationconnector.kyma-project.io/managed-by: compass-runtime-agent + name: app1 +spec: + description: Test Application for testing Compass Runtime Agent + displayName: "" + longDescription: "" + providerDisplayName: "" + skipVerify: false + services: + - description: Foo bar + displayName: bndl-app-1 + entries: + - centralGatewayUrl: http://central-application-gateway.kyma-system.svc.cluster.local:8082/mp-app1gkhavxduzb/bndl-app-1/comments-v1 + credentials: + secretName: "" + type: "" + gatewayUrl: "" + id: 30747de1-4a87-4b67-a75d-9fe84af6e6f9 + name: comments-v1 + targetUrl: http://mywordpress.com/comments + type: API + id: e4148ee9-79c0-4d81-863c-311f32aeed9b + identifier: "" + name: bndl-app-1-0d79e + providerDisplayName: "" +--- +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + labels: + applicationconnector.kyma-project.io/managed-by: compass-runtime-agent + name: app1-updated +spec: + description: "The app was updated" + displayName: "" + longDescription: "" + providerDisplayName: "" + services: + - description: Foo bar + displayName: bndl-app-1 + entries: + - centralGatewayUrl: http://central-application-gateway.kyma-system.svc.cluster.local:8082/mp-app1gkhavxduzb/bndl-app-1/comments-v1 + credentials: + secretName: "" + type: "" + gatewayUrl: "" + id: 30747de1-4a87-4b67-a75d-9fe84af6e6f9 + name: comments-v1 + targetUrl: http://mywordpress.com/comments + type: API + id: e4148ee9-79c0-4d81-863c-311f32aeed9b + identifier: "" + name: bndl-app-1-0d79e + providerDisplayName: "" + skipVerify: false diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/secret-compass.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/secret-compass.yaml new file mode 100644 index 00000000..8a76d2cf --- /dev/null +++ b/tests/resources/charts/compass-runtime-agent-test/templates/secret-compass.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.oauthCredentialsSecretName }} + namespace: {{ .Values.oauthCredentialsNamespace }} +data: + client_id: {{ .Values.compassCredentials.clientID | b64enc | quote }} + client_secret: {{ .Values.compassCredentials.clientSecret | b64enc | quote }} + tokens_endpoint: {{ .Values.compassCredentials.tokensEndpoint | b64enc | quote }} +type: Opaque \ No newline at end of file diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/service-account.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/service-account.yaml new file mode 100644 index 00000000..92c53e1d --- /dev/null +++ b/tests/resources/charts/compass-runtime-agent-test/templates/service-account.yaml @@ -0,0 +1,59 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.serviceAccountName }} + namespace: {{ .Values.namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Values.serviceAccountName }} + namespace: {{ .Values.namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Values.serviceAccountName }} +subjects: + - kind: ServiceAccount + name: {{ .Values.serviceAccountName }} + namespace: {{ .Values.namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Values.serviceAccountName }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - get + - list + - delete + - apiGroups: + - "apps" + resources: + - deployments + verbs: + - get + - list + - update + - apiGroups: + - "applicationconnector.kyma-project.io" + resources: + - "applications" + verbs: + - get + - list + - apiGroups: + - "compass.kyma-project.io" + resources: + - "compassconnections" + verbs: + - create + - get + - delete + - update + - list \ No newline at end of file diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/test.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/test.yaml new file mode 100644 index 00000000..639a8b8d --- /dev/null +++ b/tests/resources/charts/compass-runtime-agent-test/templates/test.yaml @@ -0,0 +1,26 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: compass-runtime-agent-test + namespace: {{ .Values.namespace }} +spec: + template: + spec: + restartPolicy: Never + serviceAccountName: {{ .Values.serviceAccountName }} + containers: + - name: compass-runtime-agent-test + image: {{ include "imageurl" (dict "reg" .Values.containerRegistry "img" .Values.images.compassTest) }} + imagePullPolicy: Always + env: + - name: APP_DIRECTOR_URL + value: {{ .Values.directorUrl }} + - name: APP_TESTING_TENANT + value: {{ .Values.testTenant }} + - name: APP_SKIP_DIRECTOR_CERT_VERIFICATION + value: {{ .Values.skipDirectorCertVerification | quote }} + - name: APP_OAUTH_CREDENTIALS_SECRET_NAME + value: {{.Values.oauthCredentialsSecretName}} + - name: APP_OAUTH_CREDENTIALS_NAMESPACE + value: {{ .Values.oauthCredentialsNamespace }} + backoffLimit: 0 diff --git a/tests/resources/charts/compass-runtime-agent-test/values.yaml b/tests/resources/charts/compass-runtime-agent-test/values.yaml new file mode 100644 index 00000000..193f3e9e --- /dev/null +++ b/tests/resources/charts/compass-runtime-agent-test/values.yaml @@ -0,0 +1,19 @@ +namespace: "test" +testTenant: "461f6292-8085-41c8-af0c-e185f39b5e18" +oauthCredentialsSecretName: "oauth-compass-credentials" +oauthCredentialsNamespace: "test" +skipDirectorCertVerification: true +serviceAccountName: "test-compass-runtime-agent" + +containerRegistry: + path: "k3d-kyma-registry:5000" + +images: + compassTest: + name: "compass-runtime-agent-test" + version: "002" + +compassCredentials: + clientID: "" + clientSecret: "" + tokensEndpoint: "" diff --git a/tests/resources/charts/gateway-test/Chart.yaml b/tests/resources/charts/gateway-test/Chart.yaml new file mode 100644 index 00000000..ea515d61 --- /dev/null +++ b/tests/resources/charts/gateway-test/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: application-gateway-test +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" diff --git a/tests/resources/charts/gateway-test/charts/mock-app/Chart.yaml b/tests/resources/charts/gateway-test/charts/mock-app/Chart.yaml new file mode 100644 index 00000000..5671f8ac --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/mock-app/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: mock-app +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/_helpers.tpl b/tests/resources/charts/gateway-test/charts/mock-app/templates/_helpers.tpl new file mode 100644 index 00000000..9cba1391 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/_helpers.tpl @@ -0,0 +1,12 @@ + +{{/* +Create a URL for container images +*/}} +{{- define "imageurl" -}} +{{- $registry := default $.reg.path $.img.containerRegistryPath -}} +{{- if hasKey $.img "directory" -}} +{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}} +{{- else -}} +{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}} +{{- end -}} +{{- end -}} diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/expired-mtls-cert-secret.yaml b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/expired-mtls-cert-secret.yaml new file mode 100644 index 00000000..0a968871 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/expired-mtls-cert-secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: expired-mtls-cert-secret + namespace: test +type: Opaque +data: + # Server certificate expired on 02.08.2022 + server.crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1BdWcgIDIgMDA6MDA6MDAgMjAyMiBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVWnVOZ3FCQTdSUEs4bStSSnhuaXRTS21xZFdZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRJeU1EZ3dNakF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTQ4cTdiU21rSVZwQng5SWo3Si9qWGFJNU85dGs5dEJlTlhxcGNQM2lsTUtFMVpqTmhyb0wKRjNNV2xnWU10cCtBRlEvKzhYdjlESFVCMWtaNnU4QjZiQzFjV1NSZWMrUWY1ZThYNXZlZkFodng2ZHhCem0wbgo5MDhodTVhRFFQUXdCOHdsYSsyRHAzTjZVdTJQcW01cXk1U0xPcFZxN3hnaHBiUFBQSndoZGNIZFoxN0hvckl2CmcvdUt2M3JLdWJtY2U1MDdDaGFVWGhsWTUyeUtHelRvdXp5R3RqRjhOelRWSmQ1QmtFWGhmVW5HdWttbHBOSG8KRHNUWEJYcE9jNkdxb1VmL1FueUdQTTNtVEVkVUJEQzVEOGhtaUhSU3UyM3VHa2VLdGpWV2o1Ukdydkx2aUdRcgpoeHRhcStzNzBPTUtVbURaRDBMU3MyNXNwVzFnUWtkUzRRSURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUMrNnM3TmJlYSs2RnRibSt1V2FUQjZSSTkzMEpIRUhEd3Y5YWQvR2VXZllZd2tBVXpQMApja1VKSzZDWHVCa2FxYzVOSkpuRmVGZGpIOXZ4MHVBSkxvNXdiZUpGTjB6SXRVdTNBMGtEMnd2U3VXTGJCbEhBCnFiTWxQMm9FMWxsU3hZZDhRMFpPaDllUld1MkMzMitjdnorRWw2RXFzbE9OVmlrZWEyNWJvYnFnelJhY2FCM0UKMG5KQUgwOUFyMys3MllzSHFNd2EyUm51bWVORDJWdGNGT3JraWt3ZkxJbml5NFJpQkdSY3RhVTF6bk9oZVpMNQoxZFJDOURhMk5nZzNrb2tDb3ZibmtHcXZsWk80ZnJJeEw1U244WjFXcEl4dXNoTzBaWjdkeWdaT0FtSEluQzd0CmJYSHJsb3VDclh4TGlHKy9qRnVvMXRZYlFHN2VUWE8rWDNBPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNDhxN2JTbWtJVnBCeDlJajdKL2pYYUk1Tzl0azl0QmVOWHFwY1AzaWxNS0UxWmpOCmhyb0xGM01XbGdZTXRwK0FGUS8rOFh2OURIVUIxa1o2dThCNmJDMWNXU1JlYytRZjVlOFg1dmVmQWh2eDZkeEIKem0wbjkwOGh1NWFEUVBRd0I4d2xhKzJEcDNONlV1MlBxbTVxeTVTTE9wVnE3eGdocGJQUFBKd2hkY0hkWjE3SApvckl2Zy91S3Yzckt1Ym1jZTUwN0NoYVVYaGxZNTJ5S0d6VG91enlHdGpGOE56VFZKZDVCa0VYaGZVbkd1a21sCnBOSG9Ec1RYQlhwT2M2R3FvVWYvUW55R1BNM21URWRVQkRDNUQ4aG1pSFJTdTIzdUdrZUt0alZXajVSR3J2THYKaUdRcmh4dGFxK3M3ME9NS1VtRFpEMExTczI1c3BXMWdRa2RTNFFJREFRQUJBb0lCQVFEZlB1QWpZeTBsTnRURApKakxwQStZTDdTSVVoTGRWb083RGtOeWhEV0ZUazdRbHRpU3ZSb1A2VG1PelVtaUJUcDV6aGdMQTNsZ3BMajlICnBqbEE2cW5RZlVCRmFQeGNyaFdJL3FNNVRETjlHTEFsRnlVelR3MWROaU9FT2tXV2tmckVtWkdQVGU2NlhOVmsKa3NnN0t3M2xTVWFPZXNPYllkWVFGTUlrejR1SFlFZlEwbWprTUVzdWJLWDRVVmJpbzg3Q0ljbHVYTFRodnBLNwpMWGZVdjdIdXJFY0hxdkVPbDhZYXdJckdLL1VMUWJLV3ZPN0xxS2hzTzJiSmhpbWoyNjVBZGwrNjdpbXJabHRHCnExVWM3V2lVOXlkeVNycTgrbVd0elpwbVpWR1N4RFkzcXlDeEJIbmJKNDNJMFZ3SktLcURMUzVMQTkvc0ZyZUwKVUpFdWlxYmxBb0dCQVBhYU4rdmJuWGlrTEFnckhONDRjQ252QkxhUkV4YmFyVVdMT1ZHVStnWUVtenRveUdBdgpkSWdXeE1maExHL2J5Z29SWmpxenc1TTBHMGVBTTJEWUpZV1hJM1NYR3RXM3B1Vzl4b0pMS09WN2c3anorNjZJCkpSaTFqeUY0QVdOZXpZUWdUZG5ncndULys5bHhOSThMbUMxN2wrSWprNXR2Um5WVCtUQTRRSGw3QW9HQkFPeDUKQVFUZ1QxWEtiNDFGLzRFL3R2cGJBdEdneDE5VzYyb0IwcG83M2JKQjN2bUIzTzZpYzBna2VQS1dxUEFFakxINAo0MVQ0U2MxbU5qT0p2TC82Z2ZXZDRqUEllMTRFWDM5UW8xeEtPSkh0dDU4Qm5yaVZwZVBWUFprRklOYWwrRTJECk1rbzI1dzkzd3FsSDBuVEF4eHlFQ3RTNjdpS1BldVFPRXhLNlJOQlRBb0dCQU14em1jOTdHZmlPckU3dFo1YTUKMWd4K05Uc2oxbDdKV0lUaTQ5ZkdtdS9vVzhjS25hNVpTZFVXZzNsd0w4Wmh4QVZLM2FYbnFrdGVGUXZYdDBFZwpreU5KNWtSZ2p3Z0hwbUN0VVdwdTQrNDIxRVBBVExjcit3MmNZWm1QQkIrZDF1Z25YRVE2YXdETE5zUFZmb3ptClFQbmNrVlVVeCtsRGZYZ0M4Z05QYiswSEFvR0JBTnRmamlCMTcyT0pQMTl4OW94ekRVN0lLNTlKWm12OStMc0oKSWRWUGdHV2tValJwMHduV3p0ZTRiak91ck42dGVkQ0pNbXhiUWl3NGpFUFhuYkVEdHBpamRYdlFteEluUUdpZAo2RTd2MC9jYzd1R2w0UmNnVFJ0RmNiV0pXbU9HNlFrUGt4SGlTUXpDYjJZWGFSaEMxdlNQVW5UelRZUG1VMzFKCnlVdndYWEpkQW9HQkFMMC82Vlc1SGRsSkZCeHlTVGVBTVdVZ0lsVzNpSFhkeVFNRGdic3RPZHl2MittdkRJdEsKS2wzd0FPNitDZ25XNDAxek9yOFRkS2xsNC9paHdoakJaV3k4TWtXUnYrQUEyY21ZMjRRR2liY2cySjIrMkFneApRYllsbEkxWjR3dGVNVjVLTXdFS1FVaXdyTzhBVFZHZCt0QUx6MmRuZXEwNDZuRXFaa2NkZU93aQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + # CA root certificate valid till 17.12.2049 + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURrekNDQW51Z0F3SUJBZ0lVR1lXL0t2Mmo0ZTY4UkZ3S2hJTjEzYVkyRStNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRRNU1USXhOekF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQXY5Vk4vdldzalJTZ2dsd2NBRXR2TmN5NVNiRXZkMk9aSXFMa2lVMmZQWjgvUG5QZHorNnQKZ3d6UEtzczh5bkowUEVYeEt2TDMzSWtrbFNueDFCL0lSaHNlU3VFWUxjVUZXdys2MFFpb2Q5ZlJESEFaeFhCVQpaTGlUaUJFMkFEY2lvWnQ3cjBlZGV6cHM5dEUyU3RwZ0N3dmpGeE5JZGxBUkJTN0o3eTRiOTZ3Y3lzdHFOV1luCnI2eFRCYUovTE8xM29jN1V3ZUdRa0xaSy95OG5hOWR5cTlGVDdFbFZVbFEyT0ptYXErdENiK2NLK2hOcWhMaEoKcmpXemJjVHdTRmtNQWt3eDVHUGp2RHRJak5seEpFY1BsWlA2M0tSZEhyWnZWeXEyQllxUkhCb0VHT3F0ZmpYQgpTZkV4bFNLMXVZQ1NkU2oyMFQ4YUJ5Q1dqTEhiL3hrZFRRSURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVqTjRiCnVaRWg3SkFBaG9pdDE4ZXJXM2RlVFBnd0h3WURWUjBqQkJnd0ZvQVVqTjRidVpFaDdKQUFob2l0MThlclczZGUKVFBnd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWtSRGhlbHYvT3IvbwpQRzB1S3hReVVvMXJvRlhFNmRnN2tSd1FsYjhNbnlVQmZZWkZrWHFSU04yRGZKaFZ1OTVoWnR4RXVobTBKbjRSCmNKUzV5NUZHNmF5WmZJN3krN0Yvdk9pb0RZYUNWWXluWkRZRlluOENsZld1aWplbW1JSStUTEZIUnFiOEJQUDIKSVRtUGl1VkhEeWMrbUdmMU95WmVONnc5bTZRL0FmdEJ1d3R6Qmt3MGlQUFlHVHZFSFdiNkRWWGIyUjBFYnBJUApPYWsveUtUZ2VOR1htemY2ZUhqZnkxaVVla0VXV1N3YWhkSlo1WFYzbGRUY3Q4bmRwM0NRdHZ1Z3YzQzl6T3A5CkxnMUlGR1JhVElIU2NvOWhheWQ2eEtlL0kvTXY1OStHeTlITDhqeGJxMWpMbzFneEN0Mi9KUWZZRmxpb3RkVGMKQ0VJVjVNNGt2dz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/mtls-cert-secret.yml b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/mtls-cert-secret.yml new file mode 100644 index 00000000..aa8e6027 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/mtls-cert-secret.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-cert-secret + namespace: test +type: Opaque +data: + {{- $files := .Files }} + {{- range tuple "ca.crt" "server.crt" "server.key" }} + {{- $path := printf "certs/positive/%s" . }} + {{ . }}: >- + {{ $files.Get $path | b64enc }} + {{- end }} diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/mock-app.yml b/tests/resources/charts/gateway-test/charts/mock-app/templates/mock-app.yml new file mode 100644 index 00000000..a57dfb87 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/mock-app.yml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: {{ .Values.global.mockServiceName}} + name: {{ .Values.global.mockServiceName}} + namespace: {{.Values.global.namespace}} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.global.mockServiceName}} + template: + metadata: + annotations: + traffic.sidecar.istio.io/includeInboundPorts: "*" + traffic.sidecar.istio.io/excludeInboundPorts: "8090,8091" + labels: + app: {{ .Values.global.mockServiceName}} + spec: + containers: + - image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.mockApplication) }} + name: {{ .Values.global.mockServiceName}} + ports: + - containerPort: 8080 + - containerPort: 8090 + - containerPort: 8091 + imagePullPolicy: Always + volumeMounts: + - name: certs-secret-volume + mountPath: /etc/secret-volume + - name: expired-certs-secret-volume + mountPath: /etc/expired-server-cert-volume + livenessProbe: + httpGet: + path: /v1/health + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 3 + volumes: + - name: certs-secret-volume + secret: + secretName: mtls-cert-secret + - name: expired-certs-secret-volume + secret: + secretName: expired-mtls-cert-secret +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.global.mockServiceName}} + namespace: {{ .Values.global.namespace }} +spec: + selector: + app: {{ .Values.global.mockServiceName}} + ports: + - name: "http" + protocol: TCP + port: 8080 + - name: "https" + protocol: TCP + port: 8090 + - name: "httpsexp" + protocol: TCP + port: 8091 \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/Chart.yaml b/tests/resources/charts/gateway-test/charts/test/Chart.yaml new file mode 100644 index 00000000..ecec00c5 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: test +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.1.0" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/_helpers.tpl b/tests/resources/charts/gateway-test/charts/test/templates/_helpers.tpl new file mode 100644 index 00000000..9cba1391 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/_helpers.tpl @@ -0,0 +1,12 @@ + +{{/* +Create a URL for container images +*/}} +{{- define "imageurl" -}} +{{- $registry := default $.reg.path $.img.containerRegistryPath -}} +{{- if hasKey $.img "directory" -}} +{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}} +{{- else -}} +{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}} +{{- end -}} +{{- end -}} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/code-rewriting.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/code-rewriting.yaml new file mode 100644 index 00000000..19977c60 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/code-rewriting.yaml @@ -0,0 +1,47 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: code-rewriting + namespace: "{{ .Values.global.namespace }}" +spec: + description: Code Rewriting + skipVerify: true + labels: + app: code-rewriting + services: + - displayName: code 500 + name: code 500 + providerDisplayName: code 500 + description: Should return 502 given 500 + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/500" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-500" + - displayName: code 503 + name: code 503 + providerDisplayName: code 503 + description: Should return 502 given 503 + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/503" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-503" + - displayName: code 502 + name: code 502 + providerDisplayName: code 502 + description: Should return 502 given 502 + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/502" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-502" + - displayName: code 123 + name: code 123 + providerDisplayName: code 123 + description: Should return 200 given 123 + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/123" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-123" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-negative.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-negative.yaml new file mode 100644 index 00000000..1507127e --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-negative.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: basic-test-negative-case + namespace: kyma-system +type: Opaque +data: + password: {{ "passwd" | b64enc }} + username: {{ "user" | b64enc }} + diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-positive.yaml new file mode 100644 index 00000000..311f41fb --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-positive.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: basic-test + namespace: kyma-system +type: Opaque +data: + password: {{ "passwd" | b64enc }} + username: {{ "user" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-case.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-case.yaml new file mode 100644 index 00000000..f2025ecb --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-case.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-negative-case + namespace: kyma-system +type: Opaque +data: + {{- $files := .Files }} + crt: {{ $files.Get "certs/positive/client.crt" | b64enc }} + key: {{ $files.Get "certs/positive/client.key" | b64enc }} \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-client-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-client-cert.yaml new file mode 100644 index 00000000..b8e71c3b --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-client-cert.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-negative-expired-client-cert + namespace: kyma-system +type: Opaque +data: + # Client certificate expired on 02.08.2022 + crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1BdWcgIDIgMDA6MDA6MDAgMjAyMiBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVZldyTzlHRG1rR0FCRktMdzYvL0tVcDdYMnE0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRJeU1EZ3dNakF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTZSMU1VeVRTT2FyNGtPTUdMV1BWZUU4WXkwRTRlcGV3NS9aL2ZZNlQ5d1BWYUdSTE9RSEYKLzhCVjVEeHhGLzN0K0Y0S21tSHJ6TjlyUDN5T3VnVnY4bEhlek9VeFhmZWFrN0hMR2VGV3k5TGxmc3BtODhJMwpxR3BHM2FaUUhZS0VGRk1IUDREcGdkM3JUUVlhNVFnMDRNZDZuZjk1SFY1YzJtTlErb2pqUWt0cWROSUpPb2p5Clk4WjdlaWMyRURtMWxqYkdCTFZESEY1aUY3QjQ1bXp0OTgzZElaeC81TW9OMnpjdlF5OU9GMlkvcm1EL284QkQKd3llR2xvejVqTHNGRUJqbWdBSWFrSXBiVkZqekk4Tlp3cW0wOTZSM2JuZCtJdml3eS96YkRxYlNxQ2Qvdi9qRApIT0x2M0pIRXNLL0VxNng0VGZhL1lqTFozbjRrNmhCVHVRSURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUxpMVI2MjhpOXZXWkkrNnJKZHN4MEhtZUZLUGNVenV3R1ptU2t2TC9rTHliOUE3b2V6SQpPbkRLL0EzOFpGcmpzTE9YQWRJZkxXS0laMkh5b21FNG9HMldNL3phN1BCWDc4dXpycWw5bFR2eWtQOFRkdm5qCkpTN2l4cWZucW1UNDdLVHFOSjRleXMzd2NOU29kTHlydWNDUWlLeUllMEFUQ3RUSUY2WnNGR3Q2WkN0WS84czYKQTJ0Rk92dmhTWm01UytsZGVEQ3FMajJMOW1oZEY0RzlMRmJ5Q2pTbVNDWEFSbDVpanBjb2pEWEVDRTdobTk5YwpnbU1scjE2Uzh1Nm5mWEVRdXFwTys4MU4xTEVpTzVyUzRwUGxBdGgyVk5YOXc0WHBmbEM1VDA0bzA5S21HYzhLCjhVTjB1YXJpRGx2NW93SEZlWVRsZkZ6eGhUUDJBOWFPL3U4PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNlIxTVV5VFNPYXI0a09NR0xXUFZlRThZeTBFNGVwZXc1L1ovZlk2VDl3UFZhR1JMCk9RSEYvOEJWNUR4eEYvM3QrRjRLbW1IcnpOOXJQM3lPdWdWdjhsSGV6T1V4WGZlYWs3SExHZUZXeTlMbGZzcG0KODhJM3FHcEczYVpRSFlLRUZGTUhQNERwZ2QzclRRWWE1UWcwNE1kNm5mOTVIVjVjMm1OUStvampRa3RxZE5JSgpPb2p5WThaN2VpYzJFRG0xbGpiR0JMVkRIRjVpRjdCNDVtenQ5ODNkSVp4LzVNb04yemN2UXk5T0YyWS9ybUQvCm84QkR3eWVHbG96NWpMc0ZFQmptZ0FJYWtJcGJWRmp6SThOWndxbTA5NlIzYm5kK0l2aXd5L3piRHFiU3FDZC8Kdi9qREhPTHYzSkhFc0svRXE2eDRUZmEvWWpMWjNuNGs2aEJUdVFJREFRQUJBb0lCQUhWalJZNFEyclFqZm13bgpobkxROVN4U1dGL3lCZWpsL2pXeEVWNCtzQkFScENPZmJhblZWTW1ISnpsNW5sSEFrMWNndENJdDhUb0h2OUFHCmZ6RDVqL2ZzZGsramtvcUpKeFA4MGhQRVA1c0FKb1VFazNkb2MvS2hJZkozejV3c255cEU3VDl6UVNNZWgyRVEKRS9jRmZPczhTR2pMdjBla3Z3bFNQZk1MZjdWZm9vK2h6K2krUW5jMXFRR2lPVW90Tk11WmoxcU85Mm5Ib2toMApxcWRRWlFOa1pUQm1sd2Y0bGNpeVJsTVUwRHJJNmd1bDNRazNpd2hueU1Kb2h4dVJxT3RSNzJjTS9SVFpsMHdWClh4WmJ5VkM1R2dhRUR0aUtFaE9xdmpPN2N4WU9BU3U3aGVDbWRnRFdMMGFSVjE1ai9Sb0twRUtkeWt0SHFrRjQKcWVLZndBa0NnWUVBL2pKaHBqSWh5N09Vajh1cW1scHE4L0tyRG5zUG1FK0VqbzFxOWdiTDByRVZSaktMNHpLaAp2SDdrdHJjQUkrMkpaS0hlSDVmVUJSMVYxK3R5WXM5OHUwOFpHSmQ0TFdjNjFFUTQvbEtFVUhaUU41TnpJOHprCmhWbjBhNWZOQjlLMDQ4aU1XQTh6ZlgyQ2JvWWltT0V3ZC9BZUtwdzZjN0E0UXU5RVRGckNhMGNDZ1lFQTZzU2gKb2V4U0pxbE9RMjk1VjlVWWtNTkkrNkR1ZGNoSUZkamY2bUp2NEo1aGhQSmJDY1doZkZjdnZmNkQ0SzBDei8rVwpybmhreWNlU1dDRHNuN3laN1VBbHAwVXp2bk1IYXBqUXUwN3JpcGI0UGExdlE2MkxRYWhTWnFJZmkxeFVqQ1NmCm1GcDJZSXVRV2FWOWFlYUdmSnY0anRnZUJ6VUxFYngwMGdpb3lQOENnWUVBOUR5Q09JWm9sR2xxYjdOWHExRCsKL0grSVBiU2Q2bEZVNHdjYjQySHFTdmtjb01NR1Iza3BqNHc0d3hvWUIyMC9Hckt3VXBpMS9XZ1BTQlFRWnNKSApiVTExcG53NjJ4MFptRVFvb3F1ME4vOUYyZkJScSs4OURxZTh3ZmdyNXIxY1VwUXB6SjVtY2NlN0graS9xemFMCk5HSkJDZDNzQjZZa21LTitjd0t0VlJjQ2dZQkRKaFM1RkxmMm1PeHF1Mkt3cmFIR0hpVXMyNzM0OEYwMTZuODUKTWdpZjdZMGxFcERaZmE2UHV2eEwwcFZ6Mk9oNkI3ZllsVlQycGQrRTEzMzJ2bUlraXZsNkc0QU9WQ1psNWVtbAorWS9EWnlUL3R6Q2c0ZTEzelNZc2R1aWcycnJRRHRXYkpSekF4b3AyS2JCeWJ0NCttL24vR1crVlRpV3BZQWJsCjRGWXVqd0tCZ1FDT3VUenVZS1QrVHZuU3hDVWdZbUN3SWs5MlVxUHJCS3J5UE1sekxMdFBaMlF3UjNpRGlWTzEKUlpxNGtkc1NzU05SNUJKUzlrVW92dUpubG1hb0ZBM2NLZXhRdk9mcmFVdXNGcXVHb3NWR3hrSGo3S24ydnFadQppK0NDNTZPNHRRMnNhOXZ6MFNpTWZoMzdFRlh6eUFVODNpWGY2aTB0MGlzb0cvYmFpTlNDZ2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-server-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-server-cert.yaml new file mode 100644 index 00000000..e96af97d --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-server-cert.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-negative-expired-server-cert + namespace: kyma-system +type: Opaque +data: + # Valid client certificate expiring on 17.12.2049 + crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1EZWMgMTcgMDA6MDA6MDAgMjA0OSBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVWnVOZ3FCQTdSUEs4bStSSnhuaXRTS21xZFdjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRRNU1USXhOekF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTRpZHp6dFhjZG9Dd21YNm9HeSt5elFjYW5iZVpmZ2xBTFlrVktUUVhrYXoyWW8wWXV3cGQKS1VhSjZEbkVUTVVneGYwb3RUWENBSU9HM1cySTZyRzc1UXBsVWZTNDVzNWQ4MVE5WGhMRm9Oa0o1ZWlzUW1paQpCSURQOC9qQ2RHSk04Z2hpa1A1bk1xQ0hRdGk1bkR3MmtINCtiZDNIVzgyWTFWWkg5Y3ArdUpDa251clMvbDArCnFSUkxndWZUY0c0VU5iaGEwWnZaNUcwMStTdnJxVXBudmVaQUdlcTlOaWlhUXhmSkFkcTdISHMrMnFDOGtEczQKUllOVFJNTUMrYXVueDV3M1g4eEUyR0oxRkdjSjNmRkszTFhsNnRXaWFBajVHNjloSnpoNG5HMzdEVy9aMEF0UgovM3B2UnB2K0k1WCtTUWZUVHBqQzBGbld6Z1JrZ2hucWV3SURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUxQSmVJNGtGRlV4YlNVN2NtcHFjaFpWWUx0dmhYRkU4Y0YrUUZTRzhTOXZIOUVxdnlYNApNNW8wSkQzeDU3T0dnK0liVHJRQXRXVG5zbnNBbGk4d3FMQTJVYlZZTThOM0JRSFBSNHZUMkZjRndHQmpQR3hGCmlGcDZlUGxvbTJ0bTBEVjNXbzkzdG0wWnhmRXpQYXlHOHJhc0puc3psT0hVOEtqT2ppOUtjN0F4WXBhODh4MDIKdTN4RjNUMkhZMkExeS9NdmJpeFpwNG12YWZiTFJOOUFqUWY3QXJxNEQ5cThlS0ZhY3EwYndFaVlTK3daZG9kNQp0RDM5MktweVNPZDBFMld4dHozNTRsZmI0cEIzK2k5Z21Ma3lUbUhHZWJzMWdBUmV1TENQTW5OQThTZTdHVGNkCjA0ZlltR3pmNjVySDNqM1d2MzQ0dy81M2dtRWdrOXg0TGswPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBNGlkenp0WGNkb0N3bVg2b0d5K3l6UWNhbmJlWmZnbEFMWWtWS1RRWGthejJZbzBZCnV3cGRLVWFKNkRuRVRNVWd4ZjBvdFRYQ0FJT0czVzJJNnJHNzVRcGxVZlM0NXM1ZDgxUTlYaExGb05rSjVlaXMKUW1paUJJRFA4L2pDZEdKTThnaGlrUDVuTXFDSFF0aTVuRHcya0g0K2JkM0hXODJZMVZaSDljcCt1SkNrbnVyUwovbDArcVJSTGd1ZlRjRzRVTmJoYTBadlo1RzAxK1N2cnFVcG52ZVpBR2VxOU5paWFReGZKQWRxN0hIcysycUM4CmtEczRSWU5UUk1NQythdW54NXczWDh4RTJHSjFGR2NKM2ZGSzNMWGw2dFdpYUFqNUc2OWhKemg0bkczN0RXL1oKMEF0Ui8zcHZScHYrSTVYK1NRZlRUcGpDMEZuV3pnUmtnaG5xZXdJREFRQUJBb0lCQVFDWUFySzUzVkFocXlDSgpHL1E4eWRQaU1oczIxZGpyT2FhVXRPYXZXbDlaUUt3ZjAvMUNnNVhaRDV2VXB6ZUY3cDYzMWhGTnRFT2hlc2JsCkFTSWR0cmU0SFVPN1VjWVRCYlZxd0QyN2hOeW40QnJpR1lIbjVWSzV1aWVOTXJEcDc4VU9qb3BLTVdZR1JwYUUKWFE1dHNKOXdnaHJPV0ZzUEh1UFN5ZnIyZ0ZTckV2TEdYRUQxUVBFdUhqcllZeDBMcVJDVSt4NUhvajBkLzZyYgpOMEJjako5SHVwMDZLejN4QlBxemgyeHp1Q2kyYjdGc01vQndkTGcyNGM3bHJsZCtLb09ZdFczLzJSZUJPdXpHCk1HWjFPZlJJcERjdk9xNXlBWXZuYjcya0xJV3E2ZUxCVkx2ekFJZnBLRFBtbmVkd0p2dXVpcUZldktzQVY5eXAKSlo1NjdXeXBBb0dCQVBPbHZvT3pxRUwrN0c4RUM5TW9Vc0lOWVRMMXdYU3VUTzVvczlpc0doRzJHNlprajRWWgpPNlY4Qm1ET2tYT0NtRzNpTWY4M0NSVGs3Um5MVExiRVJJRkxzNFpnend0N2trVHVXSnlzTkRUeGZuMmFVeUlwCm9iNWFvWVozcHNiTXBIbmxNSGN2R1l4SXFVK0JzQVpnbGtOYTdSQVVwTnN5bkJWYzNpZEV5R0hmQW9HQkFPMmUKcWx1dlI4b096VC9jbzVCS1pWS05jQ2V2bHhJNmVXd1NrRDJaaGNuZU1aWFcyY3Z5NERzUWFHQWNKelJsaWpvNAo0RXFsN2VQM0VYbVZYNEtQWXlkSFpZWGZubEkwWVdrSHloR015anBueWFYUWZ2c3AyU1BpM0lKQTZCZ2htbThKCkxyMWFTcmtLaXE3bis3dmtVYitESktBSk0wZXoyc1dzTkNMMC9XTGxBb0dCQUpkcU1YTjNldUhudXRkakZGWXQKZ1FESGY5aERrZTRKUkJZRlMzOGp0Uys4bElKYmpEVzZ0cTZvM08zY2NkZnZHUHR3enRGa1NtaUp2QytEZ0RFMAoxNzNpWmJibEFzYUlET1o1bU9nRXZJMEtaeWwzZHFLTWJNLzNVdHBXRVhjS1JremFlYndYc1REVkZ5TXAzVktaClE4aW9BUnMxT1I1ZjNWQUpYcVhZd1E3UkFvR0Fid0ZvWkZ5R0ZRYkZLOGhQUU9FQVpJaGVsS3VhejVFeG1DTXoKN3hNQlJVVGZ0VGdobHYxbmN6QS9FbWNVaVkzRi9WMEVxdHJKUDIzMFkvQThKaW9HRUJ0eWVnLzFUa0hhSDg3Ygp2MGNlVWhxYVFUUWRuZ2Yyd0tVQ2puYno5aEg4cTFLRzJ6NkxHZGFxNHZyTXh3SHFqcVVkUHdZTlJybm13ZUdvCm1Zd0pzMkVDZ1lFQXROSHI2TEx6VkVIaWU3MzhZV0doYnRSUGRPWGdFMStqVTdhZGZ4RXJtckVLL0ZZdkl5dmoKVWZ1WHlrZ3ZBeWF1Q2tiS2RsRFZkd1d5K2NuTXNCSkFSbFY5a1BGK2xwaTVweUpBL2J6blF4VVJHYmZ1UHNkUQpHZmRjUUhyMjVjR0xxN3E2Z0xaRUJxS0JpMmFZc3BkcmtNQTFIY0txNDhmVUNtNmZyMGVyelVzPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-other-ca.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-other-ca.yaml new file mode 100644 index 00000000..6ae6ceac --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-other-ca.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-negative-other-ca + namespace: kyma-system +type: Opaque +data: + {{- $files := .Files }} + crt: {{ $files.Get "certs/negative/client.crt" | b64enc }} + key: {{ $files.Get "certs/negative/client.key" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-incorrect-clientid.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-incorrect-clientid.yaml new file mode 100644 index 00000000..cce16c4f --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-incorrect-clientid.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-oauth-negative-incorrect-clientid + namespace: kyma-system +type: Opaque +data: + {{- $files := .Files }} + crt: {{ $files.Get "certs/positive/client.crt" | b64enc }} + key: {{ $files.Get "certs/positive/client.key" | b64enc }} + clientId: {{ "incorrect" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-other-ca.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-other-ca.yaml new file mode 100644 index 00000000..3bbb0851 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-other-ca.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-oauth-negative-other-ca + namespace: kyma-system +type: Opaque +data: + {{- $files := .Files }} + crt: {{ $files.Get "certs/invalid-ca/client.crt" | b64enc }} + key: {{ $files.Get "certs/invalid-ca/client.key" | b64enc }} + clientId: {{ "clientID" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-case.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-case.yaml new file mode 100644 index 00000000..02377d6e --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-case.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-oauth-negative-case + namespace: kyma-system +type: Opaque +data: + {{- $files := .Files }} + crt: {{ $files.Get "certs/positive/client.crt" | b64enc }} + key: {{ $files.Get "certs/positive/client.key" | b64enc }} + clientId: {{ "clientID" | b64enc }} \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-client-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-client-cert.yaml new file mode 100644 index 00000000..743fd941 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-client-cert.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-oauth-negative-expired-client-cert + namespace: kyma-system +type: Opaque +data: + crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1BdWcgIDIgMDA6MDA6MDAgMjAyMiBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVZldyTzlHRG1rR0FCRktMdzYvL0tVcDdYMnE0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRJeU1EZ3dNakF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTZSMU1VeVRTT2FyNGtPTUdMV1BWZUU4WXkwRTRlcGV3NS9aL2ZZNlQ5d1BWYUdSTE9RSEYKLzhCVjVEeHhGLzN0K0Y0S21tSHJ6TjlyUDN5T3VnVnY4bEhlek9VeFhmZWFrN0hMR2VGV3k5TGxmc3BtODhJMwpxR3BHM2FaUUhZS0VGRk1IUDREcGdkM3JUUVlhNVFnMDRNZDZuZjk1SFY1YzJtTlErb2pqUWt0cWROSUpPb2p5Clk4WjdlaWMyRURtMWxqYkdCTFZESEY1aUY3QjQ1bXp0OTgzZElaeC81TW9OMnpjdlF5OU9GMlkvcm1EL284QkQKd3llR2xvejVqTHNGRUJqbWdBSWFrSXBiVkZqekk4Tlp3cW0wOTZSM2JuZCtJdml3eS96YkRxYlNxQ2Qvdi9qRApIT0x2M0pIRXNLL0VxNng0VGZhL1lqTFozbjRrNmhCVHVRSURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUxpMVI2MjhpOXZXWkkrNnJKZHN4MEhtZUZLUGNVenV3R1ptU2t2TC9rTHliOUE3b2V6SQpPbkRLL0EzOFpGcmpzTE9YQWRJZkxXS0laMkh5b21FNG9HMldNL3phN1BCWDc4dXpycWw5bFR2eWtQOFRkdm5qCkpTN2l4cWZucW1UNDdLVHFOSjRleXMzd2NOU29kTHlydWNDUWlLeUllMEFUQ3RUSUY2WnNGR3Q2WkN0WS84czYKQTJ0Rk92dmhTWm01UytsZGVEQ3FMajJMOW1oZEY0RzlMRmJ5Q2pTbVNDWEFSbDVpanBjb2pEWEVDRTdobTk5YwpnbU1scjE2Uzh1Nm5mWEVRdXFwTys4MU4xTEVpTzVyUzRwUGxBdGgyVk5YOXc0WHBmbEM1VDA0bzA5S21HYzhLCjhVTjB1YXJpRGx2NW93SEZlWVRsZkZ6eGhUUDJBOWFPL3U4PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNlIxTVV5VFNPYXI0a09NR0xXUFZlRThZeTBFNGVwZXc1L1ovZlk2VDl3UFZhR1JMCk9RSEYvOEJWNUR4eEYvM3QrRjRLbW1IcnpOOXJQM3lPdWdWdjhsSGV6T1V4WGZlYWs3SExHZUZXeTlMbGZzcG0KODhJM3FHcEczYVpRSFlLRUZGTUhQNERwZ2QzclRRWWE1UWcwNE1kNm5mOTVIVjVjMm1OUStvampRa3RxZE5JSgpPb2p5WThaN2VpYzJFRG0xbGpiR0JMVkRIRjVpRjdCNDVtenQ5ODNkSVp4LzVNb04yemN2UXk5T0YyWS9ybUQvCm84QkR3eWVHbG96NWpMc0ZFQmptZ0FJYWtJcGJWRmp6SThOWndxbTA5NlIzYm5kK0l2aXd5L3piRHFiU3FDZC8Kdi9qREhPTHYzSkhFc0svRXE2eDRUZmEvWWpMWjNuNGs2aEJUdVFJREFRQUJBb0lCQUhWalJZNFEyclFqZm13bgpobkxROVN4U1dGL3lCZWpsL2pXeEVWNCtzQkFScENPZmJhblZWTW1ISnpsNW5sSEFrMWNndENJdDhUb0h2OUFHCmZ6RDVqL2ZzZGsramtvcUpKeFA4MGhQRVA1c0FKb1VFazNkb2MvS2hJZkozejV3c255cEU3VDl6UVNNZWgyRVEKRS9jRmZPczhTR2pMdjBla3Z3bFNQZk1MZjdWZm9vK2h6K2krUW5jMXFRR2lPVW90Tk11WmoxcU85Mm5Ib2toMApxcWRRWlFOa1pUQm1sd2Y0bGNpeVJsTVUwRHJJNmd1bDNRazNpd2hueU1Kb2h4dVJxT3RSNzJjTS9SVFpsMHdWClh4WmJ5VkM1R2dhRUR0aUtFaE9xdmpPN2N4WU9BU3U3aGVDbWRnRFdMMGFSVjE1ai9Sb0twRUtkeWt0SHFrRjQKcWVLZndBa0NnWUVBL2pKaHBqSWh5N09Vajh1cW1scHE4L0tyRG5zUG1FK0VqbzFxOWdiTDByRVZSaktMNHpLaAp2SDdrdHJjQUkrMkpaS0hlSDVmVUJSMVYxK3R5WXM5OHUwOFpHSmQ0TFdjNjFFUTQvbEtFVUhaUU41TnpJOHprCmhWbjBhNWZOQjlLMDQ4aU1XQTh6ZlgyQ2JvWWltT0V3ZC9BZUtwdzZjN0E0UXU5RVRGckNhMGNDZ1lFQTZzU2gKb2V4U0pxbE9RMjk1VjlVWWtNTkkrNkR1ZGNoSUZkamY2bUp2NEo1aGhQSmJDY1doZkZjdnZmNkQ0SzBDei8rVwpybmhreWNlU1dDRHNuN3laN1VBbHAwVXp2bk1IYXBqUXUwN3JpcGI0UGExdlE2MkxRYWhTWnFJZmkxeFVqQ1NmCm1GcDJZSXVRV2FWOWFlYUdmSnY0anRnZUJ6VUxFYngwMGdpb3lQOENnWUVBOUR5Q09JWm9sR2xxYjdOWHExRCsKL0grSVBiU2Q2bEZVNHdjYjQySHFTdmtjb01NR1Iza3BqNHc0d3hvWUIyMC9Hckt3VXBpMS9XZ1BTQlFRWnNKSApiVTExcG53NjJ4MFptRVFvb3F1ME4vOUYyZkJScSs4OURxZTh3ZmdyNXIxY1VwUXB6SjVtY2NlN0graS9xemFMCk5HSkJDZDNzQjZZa21LTitjd0t0VlJjQ2dZQkRKaFM1RkxmMm1PeHF1Mkt3cmFIR0hpVXMyNzM0OEYwMTZuODUKTWdpZjdZMGxFcERaZmE2UHV2eEwwcFZ6Mk9oNkI3ZllsVlQycGQrRTEzMzJ2bUlraXZsNkc0QU9WQ1psNWVtbAorWS9EWnlUL3R6Q2c0ZTEzelNZc2R1aWcycnJRRHRXYkpSekF4b3AyS2JCeWJ0NCttL24vR1crVlRpV3BZQWJsCjRGWXVqd0tCZ1FDT3VUenVZS1QrVHZuU3hDVWdZbUN3SWs5MlVxUHJCS3J5UE1sekxMdFBaMlF3UjNpRGlWTzEKUlpxNGtkc1NzU05SNUJKUzlrVW92dUpubG1hb0ZBM2NLZXhRdk9mcmFVdXNGcXVHb3NWR3hrSGo3S24ydnFadQppK0NDNTZPNHRRMnNhOXZ6MFNpTWZoMzdFRlh6eUFVODNpWGY2aTB0MGlzb0cvYmFpTlNDZ2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + clientId: {{ "clientID" | b64enc }} \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-server-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-server-cert.yaml new file mode 100644 index 00000000..2a536df0 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-server-cert.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-oauth-negative-expired-server-cert + namespace: kyma-system +type: Opaque +data: + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJpZ0F3SUJBZ0lVSkV2L3RtUndLOXRzdWhpQVlaT2Q4NjN1UHlBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pqRUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREU5TURzRwpBMVVFQXd3MGJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNkR1Z6CmRDMXZkR2hsY2kxallUQWVGdzB5TWpBeE1ERXdPVEV3TVRCYUZ3MHlNakF4TURJd09URXdNVEJhTUdZeEN6QUoKQmdOVkJBWVRBbEJNTVFvd0NBWURWUVFJREFGQk1Rd3dDZ1lEVlFRS0RBTlRRVkF4UFRBN0JnTlZCQU1NTkcxdgpZMnN0WVhCd2JHbGpZWFJwYjI0dWRHVnpkQzV6ZG1NdVkyeDFjM1JsY2k1c2IyTmhiSFJsYzNRdGIzUm9aWEl0ClkyRXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFERzFDaHE2WmI3eUdaS0ZwUmgKV01sUDh6L0NtZEFZbktpRkxKNVhpMFlXakpZZXlUSUdXRWM4bm5SK1VOalZKejZjN0w5cCs4bFEvK1ZGZUpmRwpmYTM4WnhHQVZxNitpcXdJOVpwSTd2SlVZRnd0SDJLQXA5cDdZRHdOVllUbXZEb3lyTFh3L1c3c1p1Q2c3QWVBCkh5MGlBcGtBQkk1Tmg4dWFMNkk3eE8xK0dCSVhNYVkzNlFDZXpvejdvbkoyUURBVjlybVEzQndpZUtwMkNUV1QKS3ZHaVBac04rTlVLbndjdVoxbThpUHQxYmozaGsvS3p1MGxvR2RVaXJiOFFUNkFzRnJ2ZzdGNFBYVmFsb1FSNwpJMy9paWpxZFBERmlEOHVwOXltdE9ja2tjQlorSmhqVDI4OTZxcFIzQVJVaGl0dXhreENpMUZHeEd1YlJNREFpCitmcExBZ01CQUFHamRqQjBNRElHQTFVZEVRUXJNQ21DSjIxdlkyc3RZWEJ3YkdsallYUnBiMjR1ZEdWemRDNXoKZG1NdVkyeDFjM1JsY2k1c2IyTmhiREFkQmdOVkhRNEVGZ1FVY25wWGg2b2xoei9tV01LTE1sVTR1Z0gxRUpNdwpId1lEVlIwakJCZ3dGb0FVMzRJa3V5V2NoVlN2ZXlNQXZmdDZOdE1JZkdVd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBQmNvYmVNSmkrQVZ1MGZtY0szYzM1RXhQU0pZTWlOYms1VXU0QmgzUlloY0tJeFFyUjY4T1FYT3ZzMEkKVVNaYmRkRjhrQU1KOFpwTGJWQmJnZ2NmUktrVUxPYko3TklyTEF2UmR6cVVzeThMc0VMZUM5cVlVM0E0TUFEWApHWU8zZjlWbnFIelpmRFNMTjFzOGoyK1JQU0hGL3lZbGxETWhCbkVJa1NwdXdWbXprMGFoZEtXRmthcG1xbEZMCkRSZ2JsTThMMVZTbGpXb3hGYUdLNUZFQ2dGZzJIcHJDc2o3SHl0TFR0KzEwc1F5ZU9vVktUVU9lNjhWeFlzR2kKbXd3bnhzTGtHdkdGWTNaVU9JQk5BY0ovdlJLSER4R1pPS29QblIrRmlodkJvZkR1RlBzbnRSTnBIRncrNVJsUQpjL1lUdThmQ1U4QnRpeGwvYkRxdTZUWmtyazg9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRREcxQ2hxNlpiN3lHWksKRnBSaFdNbFA4ei9DbWRBWW5LaUZMSjVYaTBZV2pKWWV5VElHV0VjOG5uUitVTmpWSno2YzdMOXArOGxRLytWRgplSmZHZmEzOFp4R0FWcTYraXF3STlacEk3dkpVWUZ3dEgyS0FwOXA3WUR3TlZZVG12RG95ckxYdy9XN3NadUNnCjdBZUFIeTBpQXBrQUJJNU5oOHVhTDZJN3hPMStHQklYTWFZMzZRQ2V6b3o3b25KMlFEQVY5cm1RM0J3aWVLcDIKQ1RXVEt2R2lQWnNOK05VS253Y3VaMW04aVB0MWJqM2hrL0t6dTBsb0dkVWlyYjhRVDZBc0Zydmc3RjRQWFZhbApvUVI3STMvaWlqcWRQREZpRDh1cDl5bXRPY2trY0JaK0poalQyODk2cXBSM0FSVWhpdHV4a3hDaTFGR3hHdWJSCk1EQWkrZnBMQWdNQkFBRUNnZ0VBQlgrem1IVmFZQjlFT1BOVDZqZE81ZitudVVXUXZFV0U0WjRBeVJJSWY3SW0KcXJaTXhHRW5velVNcXJ1b3E0aDQwbFUzM0FJREtOTFM3KzlzWHloMXFkL2QyNHRLTE9uZjVTV0p2VStpY3hQeApLS3hRQ0pmYjBvS3dWbndSZjJJZ1IrdC80cWpYcXdFVFlFLzJ5eVBSbHptMEtveDF0UTQyNHM1RGNkeTU1cjFPCklNbEhPcXhFcE84YWtiY2ZacHE4cVdqMUFkU2VCRk5WdXJQaUxmQjMxN2pQd25RSkp2MU9NWWxsQXJWa1NDcisKNDlMcmk1QjRBL0UrQkh6SnhCQ1lyaC9IRCtCVlhxY3JDSDBKNEQ3TWEwVWJWMER5Vml5UWtwK2c5MGhoQ21HMwpnYVpKUmdkczJnQzI2cGRlR0E1and2Tjd3ZUYveUtsRVEwZXRib0NHS1FLQmdRREtJYUN3czVHbVZUYlRlaDJ0CnRFWmY1MWZwNVdRL1R2V1VJaWtsdHBXU1RRUHQwQWVtZFJxL1FQak5OS3YycnR0dDZIT0RHcW9iSnFVTENjaGYKbEU1cmc0N09EYnV5OHVPWmtVOEYyMTRjZDlSbnU3alNzcFd3ZkEwakpndFhiblZ2aVlXeUtjam90QVQwTXhQcgpON1VqV2dva20yQWg3amRMU2hvMFF4bkkwd0tCZ1FENzBUVzNCa2llYUdzN3IzY3hhUytvd1VoaUdMTnEyajN4Ckd2QmtXVmx5TzM5aHlCcm5aUk92ak9HRFE4eCtxSmRTN2V6Q2xGVmpsanc2V2dra1V6U1ZQSHkrb094RjE5T3UKM00xalhCZC9PMUh6VU1WUkcvYmJxaTJZeThhb0gyRWplbzE4VE9XbWhYUnFNb045TDV4Q3RkbDRnMGxMNnBJZgpPb3FlanltZHFRS0JnUURCZjZPbXhLQS96UCtwUHhPK1AvL0d1MTZycUU5cFU1dEFiZHRhSVFuYWZpT3V1eUUzCnRvOGVXNEpTWDRQbnFNaWkxSTRRQ2F5aVJVSmw2TDJLMGh5b1M4NmZid0lxY3Q1ekdtbTl2NXkrUC9CMFJYN1AKSk9xcmduWEpHaGh0WUc3SGthME5PM2I3WGFvSVpBVkRmWmJIK3VBTzN6Y09CRSttb1krb1REd1l4UUtCZ1FEcgo4TWpRZFEzRGhuaTYwcHZ1YXV6aHhEK3EwaFFCa1B5cWxLQWFsZkVON0J0ZEpkMjNZMmcvZXRPdFp2QUsyTEg0ClhMOFNUV040VE1LZnRjNk0vM3pzTzJGeVIxczUwWkFnYmZmdkdkRldQK0Y0QmZ6ckV6V0grZnFCQ0tWWXp4WDMKNVJMK0hScXJuSzFIOTQ1bDFCOG9EalQyQ3FTNWdjNXBmak4xZnhQeUNRS0JnQzVJR2FnbldPSG9XVEpnUG95YQptY1QrMk1NUGdJMzhaL2xzM0lmd1dIc2hUbE5sQ2k2RklnMkQrUUYrZk80ZGdYZS85K0dESERvM3J5MDU2Z2VhCng4T1FYZ2dxa0lJdkxDUnZGVlVId0M3Z0MyekpHakJ1V2dYUDhsYjRDNG4yemlQd1c5M1c2RkpXMnpwMzRacSsKU29JaUNXUEh6RXF3WjgxbEJwZ1VkVTZYCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K + clientId: {{ "clientID" | b64enc }} \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-positive.yaml new file mode 100644 index 00000000..1d83b2b2 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-positive.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-oauth-positive + namespace: kyma-system +type: Opaque +data: + {{- $files := .Files }} + crt: {{ $files.Get "certs/positive/client.crt" | b64enc }} + key: {{ $files.Get "certs/positive/client.key" | b64enc }} + clientId: {{ "clientID" | b64enc }} + diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-positive.yaml new file mode 100644 index 00000000..f5540a3e --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-positive.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mtls-positive + namespace: kyma-system +type: Opaque +data: + {{- $files := .Files }} + crt: {{ $files.Get "certs/positive/client.crt" | b64enc }} + key: {{ $files.Get "certs/positive/client.key" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-incorrect-id.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-incorrect-id.yaml new file mode 100644 index 00000000..94ffed21 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-incorrect-id.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: wrong-oauth-test + namespace: kyma-system +type: Opaque +data: + clientId: {{ "bad id" | b64enc }} + clientSecret: {{ "bad secret" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-invalid-token.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-invalid-token.yaml new file mode 100644 index 00000000..13d0df23 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-invalid-token.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: oauth-test-negative-case + namespace: kyma-system +type: Opaque +data: + clientId: {{ "clientID" | b64enc }} + clientSecret: {{ "clientSecret" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-positive.yaml new file mode 100644 index 00000000..af54df50 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-positive.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: oauth-test + namespace: kyma-system +type: Opaque +data: + clientId: {{ "clientID" | b64enc }} + clientSecret: {{ "clientSecret" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/redirect-basic-auth.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/redirect-basic-auth.yml new file mode 100644 index 00000000..e6405b0d --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/redirect-basic-auth.yml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: redirect-basic + namespace: kyma-system +type: Opaque +data: + password: {{ "passwd" | b64enc }} + username: {{ "user" | b64enc }} diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters-negative.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters-negative.yaml new file mode 100644 index 00000000..f62520b2 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters-negative.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: wrong-request-parameters-test + namespace: kyma-system +type: Opaque +stringData: + headers: |- + {"Hkey1":["Wrong-value"],"Wrong-key":["Hval22"]} + queryParameters: |- + {"Wrong-key":["Qval1"],"Qkey2":["Qval21","Qval22","Additional-value"]} \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters.yaml new file mode 100644 index 00000000..847ac1ee --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: request-parameters-test + namespace: kyma-system +type: Opaque +stringData: + headers: |- + {"Hkey1":["Hval1"],"Hkey2":["Hval21","Hval22"]} + queryParameters: |- + {"Qkey1":["Qval1"],"Qkey2":["Qval21","Qval22"]} \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/manual.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/manual.yml new file mode 100644 index 00000000..8a174203 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/manual.yml @@ -0,0 +1,23 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: complex-cases + namespace: "{{ .Values.global.namespace }}" +spec: + description: Endpoints for complex tests + skipVerify: true + labels: + app: complex-cases + services: + - displayName: oauth-expired-token-renewal + name: oauth-expired-token-renewal + providerDisplayName: Kyma + description: Should renew the OAuth token after the expiration time + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + credentials: + secretName: oauth-test + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token?token_lifetime=5s" + type: OAuth diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/methods-with-body.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/methods-with-body.yml new file mode 100644 index 00000000..1c8a7265 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/methods-with-body.yml @@ -0,0 +1,40 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: methods-with-body + namespace: "{{ .Values.global.namespace }}" +spec: + description: |- + Verify if methods, specified by `descritpion`, + are correctly forwarded, including their body + skipVerify: true + labels: + app: methods-with-body + services: + - displayName: post + name: post + providerDisplayName: post + description: POST + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/echo" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/methods-with-body/post" + - displayName: delete + name: delete + providerDisplayName: delete + description: DELETE + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/echo" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/methods-with-body/delete" + - displayName: put + name: put + providerDisplayName: put + description: PUT + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/echo" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/methods-with-body/put" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/missing-resources-error-handling.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/missing-resources-error-handling.yml new file mode 100644 index 00000000..0b87dc65 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/missing-resources-error-handling.yml @@ -0,0 +1,101 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: missing-resources-error-handling + namespace: "{{ .Values.global.namespace }}" +spec: + description: Missing resources + skipVerify: true + labels: + app: missing-resources-error-handling + services: + - displayName: application-doesnt-exist + name: application-doesnt-exist + providerDisplayName: Kyma + description: Should return 404 when application doesn't exist + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/where-is-app/idk" + - displayName: service-doesnt-exist + name: service-doesnt-exist + providerDisplayName: Kyma + description: Should return 404 when service doesn't exist + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/where-is-service" + - displayName: missing-secret-oauth + name: missing-secret-oauth + providerDisplayName: Kyma + description: Should return 500 when secret containing OAuth credentials is missing in the cluster + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-oauth" + credentials: + secretName: where-is-the-secret + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/server/oauth/token?client_secret=clientSecret" + type: OAuth + - displayName: missing-secret-basic-auth + name: missing-secret-basic-auth + providerDisplayName: Kyma + description: Should return 500 when secret containing Basic Auth credentials is missing in the cluster + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-basic-auth" + credentials: + secretName: where-is-the-secret + type: Basic + - displayName: missing-secret-oauth-mtls + name: missing-secret-oauth-mtls + providerDisplayName: Kyma + description: Should return 500 when secret containing OAuth mTLS credentials is missing in the cluster + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-oauth-mtls" + credentials: + secretName: where-is-the-secret + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/server/oauth/token?client_secret=clientSecret" + type: OAuthWithCert + - displayName: missing-secret-certgen-mtls + name: missing-secret-certgen-mtls + providerDisplayName: Kyma + description: Should return 500 when secret containing Cert Gen mTLS credentials is missing in the cluster + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-certgen-mtls" + credentials: + secretName: where-is-the-secret + type: CertificateGen + - displayName: missing-request-parameters-header + name: missing-request-parameters-header + providerDisplayName: Kyma + description: Should return 500 when secret and request parameters credentials is missing in the cluster + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-request-parameters-header" + requestParametersSecretName: where-are-the-paramterers + credentials: + secretName: basic-test + type: Basic + - displayName: non-existing-target-url + name: non-existing-target-url + providerDisplayName: Kyma + description: Should return 502 when target url is not resolvable + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://bad.bad.svc.cluster.local:8080/v1/api/unsecure/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/non-existing-target-url" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/negative-authorisation.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/negative-authorisation.yml new file mode 100644 index 00000000..79329289 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/negative-authorisation.yml @@ -0,0 +1,254 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: negative-authorisation + namespace: {{ .Values.global.namespace }} +spec: + description: Negative authorisation + skipVerify: true + labels: + app: negative-authorisation + services: + - displayName: bad oauth token + name: bad-oauth-token + providerDisplayName: OAuth + description: Should return 401 for OAuth with a wrong token + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-oauth-token" + credentials: + secretName: oauth-test-negative-case + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/bad-token" + type: OAuth + - displayName: wrong oauth secret + name: wrong-oauth-secret + providerDisplayName: OAuth + description: Should return 502 for OAuth with a wrong secret + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/wrong-oauth-secret" + credentials: + secretName: wrong-oauth-test + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token" + type: OAuth + - displayName: mtls-oauth-other-ca + name: mtls-oauth-other-ca + providerDisplayName: mTLS-OAuth + description: Should return 500 for mTLS Oauth with client certificate generated from other CA + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-other-ca" + credentials: + secretName: mtls-oauth-negative-other-ca + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token" + type: OAuthWithCert + - displayName: mtls-oauth-incorrect-clientid + name: mtls-oauth-incorrect-clientid + providerDisplayName: mTLS-OAuth + description: Should return 500 for mTLS Oauth with valid certificate but invalid client id + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-incorrect-clientid" + credentials: + secretName: mtls-oauth-negative-incorrect-clientid + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token" + type: OAuthWithCert + - displayName: mtls-oauth-negative-expired-client-cert + name: mtls-oauth-negative-expired-client-cert + providerDisplayName: mTLS-OAuth + description: Should return 500 for mTLS Oauth with expired client certificate + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-negative-expired-client-cert" + credentials: + secretName: mtls-oauth-negative-expired-client-cert + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token" + type: OAuthWithCert + - displayName: mtls-oauth-negative-expired-server-cert + name: mtls-oauth-negative-expired-server-cert + providerDisplayName: mTLS-OAuth + description: Should return 500 for mTLS Oauth with expired server certificate + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-negative-expired-server-cert" + credentials: + secretName: mtls-oauth-negative-expired-server-cert + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8091/v1/api/mtls-oauth/token" + type: OAuthWithCert + - displayName: mtls-negative-other-ca + name: mtls-negative-other-ca + providerDisplayName: mTLS + description: Should return 502 for mTLS with client certificate generated from other CA + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-negative-other-ca" + credentials: + secretName: mtls-negative-other-ca + type: CertificateGen + - displayName: mtls-negative-expired-client-cert + name: mtls-negative-expired-client-cert + providerDisplayName: mTLS + description: Should return 502 for mTLS with expired client certificate + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-negative-expired-client-cert" + credentials: + secretName: mtls-negative-expired-client-cert + type: CertificateGen + - displayName: mtls-negative-expired-server-cert + name: mtls-negative-expired-server-cert + providerDisplayName: mTLS + description: Should return 502 for mTLS with expired server certificate + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8091/v1/api/mtls/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-negative-expired-server-cert" + credentials: + secretName: mtls-negative-expired-client-cert + type: CertificateGen + - displayName: bad csrf token basic + name: bad-csrf-token-basic + providerDisplayName: Basic with CSRF + description: Should return 403 for Basic Auth with a bad CSRF token + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-basic" + credentials: + secretName: basic-test-negative-case + type: Basic + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token" + - displayName: bad csrf endpoint basic + name: bad-csrf-endpoint-basic + providerDisplayName: Basic with CSRF + description: Should return 502 for Basic Auth with a bad CSRF token endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-basic" + credentials: + secretName: basic-test-negative-case + type: Basic + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath" + - displayName: bad csrf token oauth + name: bad-csrf-token-oauth + providerDisplayName: OAuth with CSRF + description: Should return 403 for OAuth with a bad CSRF token + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-oauth" + credentials: + secretName: oauth-test-negative-case + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token" + type: OAuth + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token" + - displayName: bad csrf endpoint oauth + name: bad-csrf-endpoint-oauth + providerDisplayName: OAuth with CSRF + description: Should return 502 for OAuth with a bad CSRF token endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-oauth" + credentials: + secretName: oauth-test-negative-case + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token" + type: OAuth + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath" + - displayName: bad csrf token mtls oauth + name: bad-csrf-token-mtls-oauth + providerDisplayName: mTLS-OAuth with CSRF + description: Should return 403 for mTLS OAuth with a bad CSRF token + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-mtls-oauth" + credentials: + secretName: mtls-oauth-negative-case + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token" + type: OAuthWithCert + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token" + - displayName: bad csrf endpoint mtls oauth + name: bad-csrf-endpoint-mtls-oauth + providerDisplayName: mTLS-OAuth with CSRF + description: Should return 502 for mTLS OAuth with a bad CSRF token endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-mtls-oauth" + credentials: + secretName: mtls-oauth-negative-case + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token" + type: OAuthWithCert + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath" + - displayName: bad csrf token mtls + name: bad-csrf-token-mtls + providerDisplayName: mTLS with CSRF + description: Should return 403 for mTLS with a bad CSRF token + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/csrf-mtls/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-mtls" + credentials: + secretName: mtls-negative-case + type: CertificateGen + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token" + - displayName: bad csrf endpoint mtls + name: bad-csrf-endpoint-mtls + providerDisplayName: mTLS with CSRF + description: Should return 502 for mTLS with a bad CSRF token endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/csrf-mtls/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-mtls" + credentials: + secretName: mtls-negative-case + type: CertificateGen + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath" + - displayName: basic-auth-with-wrong-request-parameters + name: basic-auth-with-wrong-request-parameters + providerDisplayName: Basic + description: Should return 400 when calling endpoint protected with Basic Auth with wrong additional request parameters + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/request-parameters-basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/basic-auth-with-wrong-request-parameters" + requestParametersSecretName: wrong-request-parameters-test + credentials: + secretName: basic-test + type: Basic diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/path-related-error-handling.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/path-related-error-handling.yml new file mode 100644 index 00000000..acdcca70 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/path-related-error-handling.yml @@ -0,0 +1,29 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: path-related-error-handling + namespace: "{{ .Values.global.namespace }}" +spec: + description: Path handling + skipVerify: true + labels: + app: path-related-error-handling + services: + - displayName: missing-srv-app + name: missing-srv-app + providerDisplayName: Kyma + description: Should return 400 when service and application are missing in the path + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080" + - displayName: missing-srv + name: missing-srv + providerDisplayName: Kyma + description: Should return 400 when service is missing in the path + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/path-related-error-handling" \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/positive-authorisation.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/positive-authorisation.yml new file mode 100644 index 00000000..e22ca854 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/positive-authorisation.yml @@ -0,0 +1,141 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: positive-authorisation + namespace: {{ .Values.global.namespace }} +spec: + description: Authorisation + skipVerify: true + labels: + app: positive-authorisation + services: + - displayName: unsecure-always-ok + name: unsecure-always-ok + providerDisplayName: AlwaysOK + description: Should return 200 when calling unprotected endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/unsecure-always-ok" + - displayName: basic-auth-ok + name: basic-auth-ok + providerDisplayName: Basic + description: Should return 200 when calling endpoint protected with Basic Auth + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/basic-auth-ok" + credentials: + secretName: basic-test + type: Basic + - displayName: oauth + name: oauth + providerDisplayName: OAuth + description: Should return 200 when calling endpoint protected with OAuth + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/oauth" + credentials: + secretName: oauth-test + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token" + type: OAuth + - displayName: mtls-oauth + name: mtls-oauth + providerDisplayName: mTLS-OAuth + description: Should return 200 when calling endpoint protected with mTLS OAuth + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/mtls-oauth" + credentials: + secretName: mtls-oauth-positive + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token" + type: OAuthWithCert + - displayName: mtls + name: mtls + providerDisplayName: mTLS + description: Should return 200 when calling endpoint protected with mTLS + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/mtls" + credentials: + secretName: mtls-positive + type: CertificateGen + - displayName: csrf basic + name: csrf-basic + providerDisplayName: Basic with CSRF + description: Should return 200 for Basic Auth with CSRF optimistic scenario + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-basic" + credentials: + secretName: basic-test + type: Basic + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token" + - displayName: csrf-oauth + name: csrf-oauth + providerDisplayName: OAuth with CSRF + description: Should return 200 when calling endpoint protected with OAuth with CSRF + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-oauth" + credentials: + secretName: oauth-test + authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token" + type: OAuth + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token" + - displayName: csrf-mtls-oauth + name: csrf-mtls-oauth + providerDisplayName: mTLS-OAuth with CSRF + description: Should return 200 when calling endpoint protected with mTLS OAuth with CSRF + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-mtls-oauth" + credentials: + secretName: mtls-oauth-positive + authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token" + type: OAuthWithCert + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token" + - displayName: csrf-mtls + name: csrf-mtls + providerDisplayName: mTLS with CSRF + description: Should return 200 when calling endpoint protected with mTLS with CSRF + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/csrf-mtls/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-mtls" + credentials: + secretName: mtls-positive + type: CertificateGen + csrfInfo: + tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token" + - displayName: basic-auth-with-request-parameters + name: basic-auth-with-request-parameters + providerDisplayName: Basic + description: Should return 200 when calling endpoint protected with Basic Auth with additional request parameters + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/request-parameters-basic/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/basic-auth-with-request-parameters" + requestParametersSecretName: request-parameters-test + credentials: + secretName: basic-test + type: Basic diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-cases.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-cases.yml new file mode 100644 index 00000000..54b558e1 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-cases.yml @@ -0,0 +1,38 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: proxy-cases + namespace: "{{ .Values.global.namespace }}" +spec: + description: Proxying + skipVerify: true + labels: + app: proxy-cases + services: + - displayName: code 451 + name: code 451 + providerDisplayName: code 451 + description: Should return 451 forwarded from target endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/451" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-cases/code-451" + - displayName: code 307 + name: code 307 + providerDisplayName: code 307 + description: Should return 307 forwarded from target endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/307" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-cases/code-307" + - displayName: code 203 + name: code 203 + providerDisplayName: code 203 + description: Should return 203 forwarded from target endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/203" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-cases/code-203" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-errors.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-errors.yml new file mode 100644 index 00000000..c0b46d34 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-errors.yml @@ -0,0 +1,20 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: proxy-errors + namespace: "{{ .Values.global.namespace }}" +spec: + description: Proxying edge cases + skipVerify: true + labels: + app: proxy-errors + services: + - displayName: timeout + name: timeout + providerDisplayName: timeout + description: Should return 504 when target times out + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/timeout" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-errors/timeout" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/redirect.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/redirect.yml new file mode 100644 index 00000000..40d5137d --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/redirect.yml @@ -0,0 +1,41 @@ +apiVersion: applicationconnector.kyma-project.io/v1alpha1 +kind: Application +metadata: + name: redirects + namespace: "{{ .Values.global.namespace }}" +spec: + description: Endpoints for redirect cases + skipVerify: true + labels: + app: redirect-cases + services: + - displayName: unsecured + name: unsecured + providerDisplayName: unsecured + description: Should return 200 when redirected to unsecured endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.mockServiceName }}.{{ .Values.namespace }}.svc.cluster.local:8080/v1/api/redirect/ok" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/redirects/unsecured" + - displayName: basic + name: basic + providerDisplayName: basic + description: Should return 200 when redirected to basic-auth endpoint + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.mockServiceName }}.{{ .Values.namespace }}.svc.cluster.local:8080/v1/api/redirect/basic" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/redirects/basic" + credentials: + secretName: redirect-basic + type: Basic + - displayName: external + name: external + providerDisplayName: external + description: Should return 200 when redirected to external service + id: "{{ uuidv4 }}" + entries: + - type: API + targetUrl: "http://{{ .Values.mockServiceName }}.{{ .Values.namespace }}.svc.cluster.local:8080/v1/api/redirect/external" + centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/redirects/external" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/service-account.yml b/tests/resources/charts/gateway-test/charts/test/templates/service-account.yml new file mode 100644 index 00000000..80fd71f1 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/service-account.yml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.global.serviceAccountName }} + namespace: {{ .Values.global.namespace }} +automountServiceAccountToken: true +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Values.global.serviceAccountName }} +subjects: + - kind: ServiceAccount + name: {{ .Values.global.serviceAccountName }} + namespace: {{ .Values.global.namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Values.global.serviceAccountName }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Values.global.serviceAccountName }} +rules: + - verbs: + - get + - list + apiGroups: + - "" + - applicationconnector.kyma-project.io + resources: + - "*" diff --git a/tests/resources/charts/gateway-test/charts/test/templates/test.yml b/tests/resources/charts/gateway-test/charts/test/templates/test.yml new file mode 100644 index 00000000..b31e0115 --- /dev/null +++ b/tests/resources/charts/gateway-test/charts/test/templates/test.yml @@ -0,0 +1,15 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: application-gateway-test + namespace: {{ .Values.global.namespace }} +spec: + template: + spec: + containers: + - name: application-gateway-test + image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.gatewayTest) }} + imagePullPolicy: Always + restartPolicy: Never + serviceAccountName: {{ .Values.global.serviceAccountName }} + backoffLimit: 0 \ No newline at end of file diff --git a/tests/resources/charts/gateway-test/values.yaml b/tests/resources/charts/gateway-test/values.yaml new file mode 100644 index 00000000..63ec00cb --- /dev/null +++ b/tests/resources/charts/gateway-test/values.yaml @@ -0,0 +1,19 @@ +global: + containerRegistry: + path: "europe-docker.pkg.dev/kyma-project" + + images: + gatewayTest: + name: "gateway-test" + version: "v20230925-75c3a9a8" + directory: "prod" + mockApplication: + name: "mock-app" + version: "v20230925-75c3a9a8" + directory: "prod" + + serviceAccountName: "test-account" + namespace: "test" + + mockServiceName: "mock-application" + diff --git a/tests/resources/installation-config/mini-kyma-os.yaml b/tests/resources/installation-config/mini-kyma-os.yaml new file mode 100644 index 00000000..63eb2f7c --- /dev/null +++ b/tests/resources/installation-config/mini-kyma-os.yaml @@ -0,0 +1,9 @@ +--- +defaultNamespace: kyma-system +prerequisites: + - name: "istio" + namespace: "istio-system" + - name: "certificates" + namespace: "istio-system" +components: + - name: "application-connector" \ No newline at end of file diff --git a/tests/resources/installation-config/mini-kyma-skr.yaml b/tests/resources/installation-config/mini-kyma-skr.yaml new file mode 100644 index 00000000..d1a6e717 --- /dev/null +++ b/tests/resources/installation-config/mini-kyma-skr.yaml @@ -0,0 +1,10 @@ +--- +defaultNamespace: kyma-system +prerequisites: + - name: "istio" + namespace: "istio-system" + - name: "certificates" + namespace: "istio-system" +components: + - name: "application-connector" + - name: "compass-runtime-agent" \ No newline at end of file diff --git a/tests/resources/patches/central-application-connectivity-validator.json b/tests/resources/patches/central-application-connectivity-validator.json new file mode 100644 index 00000000..b676d8dd --- /dev/null +++ b/tests/resources/patches/central-application-connectivity-validator.json @@ -0,0 +1,24 @@ +[ + { + "op": "replace", + "path": "/spec/template/spec/containers/0/args", + "value": [ + "/app/centralapplicationconnectivityvalidator", + "--proxyPort=8080", + "--externalAPIPort=8081", + "--eventingPathPrefixV1=/%%APP_NAME%%/v1/events", + "--eventingPathPrefixV2=/%%APP_NAME%%/v2/events", + "--eventingPublisherHost=echoserver.test.svc.cluster.local", + "--eventingDestinationPath=/anything/rewrite", + "--eventingPathPrefixEvents=/%%APP_NAME%%/events", + "--appNamePlaceholder=%%APP_NAME%%", + ] + }, + { + "op": "add", + "path": "/spec/template/metadata/annotations", + "value": { + "traffic.sidecar.istio.io/excludeInboundPorts": "8080" + } + } +] diff --git a/tests/resources/patches/coredns.yaml b/tests/resources/patches/coredns.yaml new file mode 100644 index 00000000..1a7c466f --- /dev/null +++ b/tests/resources/patches/coredns.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +data: + Corefile: |2 + + .:53 { + errors + health + rewrite name regex (.*)\.local\.kyma\.dev istio-ingressgateway.istio-system.svc.cluster.local + ready + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + } + hosts /etc/coredns/NodeHosts { + reload 1s + fallthrough + } + prometheus :9153 + forward . tls://1.1.1.1 tls://1.0.0.1 { + tls_servername cloudflare-dns.com + health_check 5s + } + cache 30 + loop + reload + loadbalance + } + + NodeHosts: | + 172.18.0.3 k3d-kyma-server-0 + 172.18.0.2 k3d-kyma-registry + 172.18.0.4 k3d-kyma-agent-0 +kind: ConfigMap +metadata: + annotations: + objectset.rio.cattle.io/owner-gvk: k3s.cattle.io/v1, Kind=Addon + objectset.rio.cattle.io/owner-name: coredns + objectset.rio.cattle.io/owner-namespace: kube-system + name: coredns + namespace: kube-system diff --git a/tests/scripts/fetch-test-logs.sh b/tests/scripts/fetch-test-logs.sh new file mode 100755 index 00000000..3420b3da --- /dev/null +++ b/tests/scripts/fetch-test-logs.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# JOB_NAME - the name of the job that is runing tests (required) +# LOGS_OUT - path to directory where the logs will be stored (optional, default: ${PWD}) +# NAMESPACE the - namespace where the test job is runing (optional,default: test) +# TEST_TIMEOUT - duration to wait on tests to finish (optional, default: 900s) + +function fetch_tests() { + local JOB_NAME=$1 + # check if JOB_NAME is provided + if [ -z "$JOB_NAME" ]; then + echo "Usage: $0 [LOGS_OUT] [NAMESPACE] [TEST_TIMEOUT]" + exit 1 + fi + local LOGS_OUT=${2:-${PWD}} + local NAMESPACE=${3:-test} + local TEST_TIMEOUT=${4:-300s} + # wait for the job to finish + kubectl wait job/$JOB_NAME \ + -n $NAMESPACE \ + --for=condition=complete \ + --timeout=$TEST_TIMEOUT + # store the exit code of the job + local __job_result__=$? + # try to get the logs of the job and store them in the TEST_LOG file + kubectl logs \ + -n $NAMESPACE \ + -f job/$JOB_NAME \ + 2>&1 > "$LOGS_OUT"/$JOB_NAME.log + # exit with original job exit code + exit $__job_result__ +} + +fetch_tests $@ diff --git a/tests/scripts/generate-self-signed-certs.sh b/tests/scripts/generate-self-signed-certs.sh new file mode 100755 index 00000000..9cd6422e --- /dev/null +++ b/tests/scripts/generate-self-signed-certs.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +if [ $# -ne 2 ]; then + echo "Usage: generate-self-signed-certs.sh " + exit 1 +fi + +export APP_URL=$1 +export GATEWAY_TEST_CERTS_DIR=$2 +export SUBJECT="/C=PL/ST=A/O=SAP/CN=$APP_URL" + +mkdir -p "$GATEWAY_TEST_CERTS_DIR" + +echo "Generating certificate for domain: $APP_URL" +openssl version +openssl req -newkey rsa:2048 -nodes -x509 -days 365 -out "$GATEWAY_TEST_CERTS_DIR/ca.crt" -keyout "$GATEWAY_TEST_CERTS_DIR/ca.key" -subj $SUBJECT + +openssl genrsa -out "$GATEWAY_TEST_CERTS_DIR/server.key" 2048 +openssl genrsa -out "$GATEWAY_TEST_CERTS_DIR"/client.key 2048 + +openssl req -new \ + -key "$GATEWAY_TEST_CERTS_DIR/server.key" \ + -subj "$SUBJECT" \ + -reqexts SAN \ + -config <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL")) \ + -out "$GATEWAY_TEST_CERTS_DIR/server.csr" + +openssl x509 -req -sha256 -days 365 -CA "$GATEWAY_TEST_CERTS_DIR/ca.crt" -CAkey "$GATEWAY_TEST_CERTS_DIR/ca.key" -CAcreateserial \ + -extensions SAN \ + -extfile <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL" )) \ + -in "$GATEWAY_TEST_CERTS_DIR/server.csr" -out "$GATEWAY_TEST_CERTS_DIR/server.crt" + +openssl req -new \ + -key "$GATEWAY_TEST_CERTS_DIR/client.key" \ + -subj "$SUBJECT" \ + -reqexts SAN \ + -config <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL")) \ + -out "$GATEWAY_TEST_CERTS_DIR/client.csr" + +openssl x509 -req -sha256 -days 365 -CA "$GATEWAY_TEST_CERTS_DIR/ca.crt" -CAkey "$GATEWAY_TEST_CERTS_DIR/ca.key" -CAcreateserial \ + -extensions SAN \ + -extfile <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL")) \ + -in "$GATEWAY_TEST_CERTS_DIR/client.csr" -out "$GATEWAY_TEST_CERTS_DIR/client.crt" \ No newline at end of file diff --git a/tests/scripts/jobguard.sh b/tests/scripts/jobguard.sh new file mode 100755 index 00000000..f92a9653 --- /dev/null +++ b/tests/scripts/jobguard.sh @@ -0,0 +1,34 @@ +#!/bin/bash +export GO111MODULE=on + +ROOT_PATH=$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)") + +KYMA_PROJECT_DIR=${KYMA_PROJECT_DIR:-"/home/prow/go/src/github.com/kyma-project"} +JOB_NAME_PATTERN=${JOB_NAME_PATTERN:-"(pre-main-kyma-components-.*)|(pre-main-kyma-tests-.*)|(pre-kyma-components-.*)|(pre-kyma-tests-.*)|(pull-.*-build)"} +TIMEOUT=${JOBGUARD_TIMEOUT:-"15m"} + +export TEST_INFRA_SOURCES_DIR="${KYMA_PROJECT_DIR}/test-infra" + +if [ -z "$PULL_PULL_SHA" ]; then + echo "WORKAROUND: skip jobguard execution - not on PR commit" + exit 0 +fi + +args=( + "-github-endpoint=http://ghproxy" + "-github-endpoint=https://api.github.com" + "-github-token-path=/etc/github/token" + "-fail-on-no-contexts=false" + "-timeout=$TIMEOUT" + "-org=$REPO_OWNER" + "-repo=$REPO_NAME" + "-base-ref=$PULL_PULL_SHA" + "-expected-contexts-regexp=$JOB_NAME_PATTERN" +) + +if [ -x "/prow-tools/jobguard" ]; then + /prow-tools/jobguard "${args[@]}" +else + cd "${ROOT_PATH}/cmd/jobguard" || exit 1 + go run main.go "${args[@]}" +fi diff --git a/tests/scripts/local-build.sh b/tests/scripts/local-build.sh new file mode 100755 index 00000000..bb230665 --- /dev/null +++ b/tests/scripts/local-build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +if [ $# -ne 2 ]; then + echo "Usage: local_build.sh " + exit 1 +fi + +export DOCKER_TAG=$1 +export DOCKER_PUSH_REPOSITORY=$2 +make release diff --git a/tests/scripts/test-cra.sh b/tests/scripts/test-cra.sh new file mode 100755 index 00000000..f3313d10 --- /dev/null +++ b/tests/scripts/test-cra.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +./tests/components/application-connector/scripts/jobguard.sh + +service docker start +curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash +curl -Lo kyma.tar.gz "https://github.com/kyma-project/cli/releases/download/$(curl -s https://api.github.com/repos/kyma-project/cli/releases/latest | grep tag_name | cut -d '"' -f 4)/kyma_Linux_x86_64.tar.gz" && mkdir kyma-release && tar -C kyma-release -zxvf kyma.tar.gz && chmod +x kyma-release/kyma && rm -rf kyma.tar.gz +kyma-release/kyma provision k3d +kubectl cluster-info +kyma-release/kyma deploy --ci --components-file tests/components/application-connector/resources/installation-config/mini-kyma-skr.yaml --source local --workspace $PWD +cd tests/components/application-connector + +# reconfigure DNS +kubectl apply -f resources/patches/coredns.yaml +kubectl -n kube-system delete pods -l k8s-app=kube-dns + +make -f Makefile.test-compass-runtime-agent test-compass-runtime-agent +failed=$? + +k3d cluster delete kyma +exit $failed diff --git a/tests/test/application-connectivity-validator/suite_test.go b/tests/test/application-connectivity-validator/suite_test.go new file mode 100644 index 00000000..b08c4fde --- /dev/null +++ b/tests/test/application-connectivity-validator/suite_test.go @@ -0,0 +1,136 @@ +package application_connectivity_validator + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/httpd" +) + +const v1EventsFormat = "http://central-application-connectivity-validator.kyma-system:8080/%s/v1/events" +const v2EventsFormat = "http://central-application-connectivity-validator.kyma-system:8080/%s/v2/events" +const publishRoutedFormat = "http://central-application-connectivity-validator.kyma-system:8080/%s/events" + +const XForwardedClientCertFormat = "Hash=hash1;Cert=\"cert\";Subject=\"O=client organization,CN=%s\";URI=,By=spiffe://cluster.local/ns/default/sa/echoserver;Hash=hash;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account" + +const standaloneAppName = "event-test-standalone" +const compassAppName = "event-test-compass" + +type ValidatorSuite struct { + suite.Suite +} + +func (vs *ValidatorSuite) SetupSuite() { +} + +func (vs *ValidatorSuite) TearDownSuite() { + _, err := http.Post("http://localhost:15000/quitquitquit", "", nil) + vs.Nil(err) + _, err = http.Post("http://localhost:15020/quitquitquit", "", nil) + vs.Nil(err) +} + +func TestValidatorSuite(t *testing.T) { + suite.Run(t, new(ValidatorSuite)) +} + +func (vs *ValidatorSuite) TestGoodCert() { + cli := httpd.NewCli(vs.T()) + + for _, testCase := range []struct { + appName string + expectedCName string + }{{ + appName: standaloneAppName, + expectedCName: standaloneAppName, + }, { + appName: compassAppName, + expectedCName: "clientId1", + }} { + v1Events := fmt.Sprintf(v1EventsFormat, testCase.appName) + v2Events := fmt.Sprintf(v2EventsFormat, testCase.appName) + routedEvents := fmt.Sprintf(publishRoutedFormat, testCase.appName) + endpoints := []string{v1Events, v2Events, routedEvents} + + for _, url := range endpoints { + vs.Run(fmt.Sprintf("Send request to %s URL", url), func() { + req, err := http.NewRequest(http.MethodGet, url, nil) + vs.Nil(err) + + req.Header.Add("X-Forwarded-Client-Cert", certFields(testCase.expectedCName)) + + res, _, err := cli.Do(req) + vs.Require().Nil(err) + vs.Equal(http.StatusOK, res.StatusCode) + }) + } + } +} + +func (vs *ValidatorSuite) TestBadCert() { + cli := httpd.NewCli(vs.T()) + + appNames := []string{standaloneAppName, compassAppName} + + for _, appName := range appNames { + v1Events := fmt.Sprintf(v1EventsFormat, appName) + v2Events := fmt.Sprintf(v2EventsFormat, appName) + routedEvents := fmt.Sprintf(publishRoutedFormat, appName) + endpoints := []string{v1Events, v2Events, routedEvents} + + for _, url := range endpoints { + vs.Run(fmt.Sprintf("Send request to %s URL with incorrect cname in header", url), func() { + req, err := http.NewRequest(http.MethodGet, url, nil) + vs.Nil(err) + + req.Header.Add("X-Forwarded-Client-Cert", certFields("nonexistant")) + + res, _, err := cli.Do(req) + vs.Require().Nil(err) + vs.Equal(http.StatusForbidden, res.StatusCode) + }) + + vs.Run(fmt.Sprintf("Send request to %s URL without subject in header", url), func() { + req, err := http.NewRequest(http.MethodGet, url, nil) + vs.Nil(err) + + req.Header.Add("X-Forwarded-Client-Cert", "Hash=hash1;Cert=\"cert\"") + + res, _, err := cli.Do(req) + vs.Require().Nil(err) + vs.Equal(http.StatusForbidden, res.StatusCode) + }) + + vs.Run(fmt.Sprintf("Send request to %s URL without header", url), func() { + req, err := http.NewRequest(http.MethodGet, url, nil) + vs.Nil(err) + + res, _, err := cli.Do(req) + vs.Require().Nil(err) + vs.Equal(http.StatusInternalServerError, res.StatusCode) + }) + } + } +} + +func (vs *ValidatorSuite) TestInvalidPathPrefix() { + const v3vents = "http://central-application-connectivity-validator.kyma-system:8080/event-test-compass/v3/events" + + cli := httpd.NewCli(vs.T()) + + req, err := http.NewRequest(http.MethodGet, v3vents, nil) + vs.Nil(err) + + req.Header.Add("X-Forwarded-Client-Cert", certFields("clientId1")) + + res, _, err := cli.Do(req) + vs.Require().Nil(err) + vs.Equal(http.StatusNotFound, res.StatusCode) +} + +func certFields(cname string) string { + return fmt.Sprintf(XForwardedClientCertFormat, cname) +} diff --git a/tests/test/application-connectivity-validator/tools.go b/tests/test/application-connectivity-validator/tools.go new file mode 100644 index 00000000..0d136f35 --- /dev/null +++ b/tests/test/application-connectivity-validator/tools.go @@ -0,0 +1,5 @@ +package application_connectivity_validator + +func validatorURL(app, path string) string { + return "http://central-application-connectivity-validator.kyma-system:8080/" + app + "/" + path +} diff --git a/tests/test/application-gateway/complex_test.go b/tests/test/application-gateway/complex_test.go new file mode 100644 index 00000000..7d94b0a4 --- /dev/null +++ b/tests/test/application-gateway/complex_test.go @@ -0,0 +1,28 @@ +package application_gateway + +import ( + "time" + + "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/httpd" +) + +func (gs *GatewaySuite) TestComplex() { + gs.Run("OAuth token renewal", func() { + http := httpd.NewCli(gs.T()) + + url := gatewayURL("complex-cases", "oauth-expired-token-renewal") + gs.T().Log("Url:", url) + + // Authorize, then call endpoint + res, _, err := http.Get(url) + gs.Nil(err, "First request failed") + gs.Equal(200, res.StatusCode, "First request failed") + + time.Sleep(10 * time.Second) // wait for token to expire + + // Call endpoint, requiring token renewall + res, _, err = http.Get(url) + gs.Nil(err, "Second request failed") + gs.Equal(200, res.StatusCode, "Second request failed") + }) +} diff --git a/tests/test/application-gateway/runner_test.go b/tests/test/application-gateway/runner_test.go new file mode 100644 index 00000000..76e4860c --- /dev/null +++ b/tests/test/application-gateway/runner_test.go @@ -0,0 +1,109 @@ +package application_gateway + +import ( + "context" + "net/http" + "strconv" + "strings" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/httpd" +) + +var applications = []string{"positive-authorisation", "negative-authorisation", "path-related-error-handling", "missing-resources-error-handling", "proxy-cases", "proxy-errors", "redirects", "code-rewriting"} + +func (gs *GatewaySuite) TestGetRequest() { + + for _, app := range applications { + app, err := gs.cli.ApplicationconnectorV1alpha1().Applications().Get(context.Background(), app, v1.GetOptions{}) + gs.Nil(err) + + gs.Run(app.Spec.Description, func() { + for _, service := range app.Spec.Services { + gs.Run(service.Description, func() { + http := httpd.NewCli(gs.T()) + + for _, entry := range service.Entries { + if entry.Type != "API" { + gs.T().Log("Skipping event entry") + continue + } + + expectedCode, err := getExpectedHTTPCode(service) + if err != nil { + gs.T().Log("Error during getting the error code from description -> applicationCRD") + gs.T().Fail() + } + + res, _, err := http.Get(entry.CentralGatewayUrl) + gs.Nil(err, "Request failed") + gs.Equal(expectedCode, res.StatusCode, "Incorrect response code") + } + }) + } + }) + } +} + +func (gs *GatewaySuite) TestResponseBody() { + app, err := gs.cli.ApplicationconnectorV1alpha1().Applications().Get(context.Background(), "proxy-cases", v1.GetOptions{}) + gs.Nil(err) + for _, service := range app.Spec.Services { + gs.Run(service.Description, func() { + http := httpd.NewCli(gs.T()) + + for _, entry := range service.Entries { + if entry.Type != "API" { + gs.T().Log("Skipping event entry") + continue + } + + expectedCode, err := getExpectedHTTPCode(service) + if err != nil { + gs.T().Log("Error during getting the error code from description -> applicationCRD") + gs.T().Fail() + } + + _, body, err := http.Get(entry.CentralGatewayUrl) + gs.Nil(err, "Request failed") + + codeStr := strconv.Itoa(expectedCode) + + gs.Equal(codeStr, string(body), "Incorrect body") + } + }) + } +} + +func (gs *GatewaySuite) TestBodyPerMethod() { + app, err := gs.cli.ApplicationconnectorV1alpha1().Applications().Get(context.Background(), "methods-with-body", v1.GetOptions{}) + gs.Nil(err) + for _, service := range app.Spec.Services { + gs.Run(service.Description, func() { + httpCli := httpd.NewCli(gs.T()) + + for _, entry := range service.Entries { + if entry.Type != "API" { + gs.T().Log("Skipping event entry") + continue + } + + method := service.Description + bodyBuf := strings.NewReader(service.Description) + + req, err := http.NewRequest(method, entry.CentralGatewayUrl, bodyBuf) + gs.Nil(err, "Preparing request failed") + + _, body, err := httpCli.Do(req) + gs.Nil(err, "Request failed") + + res, err := unmarshalBody(body) + gs.Nil(err, "Response body wasn't correctly forwarded") + + gs.Equal(service.Description, string(res.Body), "Request body doesn't match") + gs.Equal(service.Description, res.Method, "Request method doesn't match") + } + }) + } +} diff --git a/tests/test/application-gateway/suite_test.go b/tests/test/application-gateway/suite_test.go new file mode 100644 index 00000000..764ebabf --- /dev/null +++ b/tests/test/application-gateway/suite_test.go @@ -0,0 +1,34 @@ +package application_gateway + +import ( + "net/http" + "testing" + + cli "github.com/kyma-project/kyma/components/central-application-gateway/pkg/client/clientset/versioned" + "github.com/stretchr/testify/suite" + "k8s.io/client-go/rest" +) + +type GatewaySuite struct { + suite.Suite + cli *cli.Clientset +} + +func (gs *GatewaySuite) SetupSuite() { + cfg, err := rest.InClusterConfig() + gs.Require().Nil(err) + + gs.cli, err = cli.NewForConfig(cfg) + gs.Require().Nil(err) +} + +func (gs *GatewaySuite) TearDownSuite() { + _, err := http.Post("http://localhost:15000/quitquitquit", "", nil) + gs.Nil(err) + _, err = http.Post("http://localhost:15020/quitquitquit", "", nil) + gs.Nil(err) +} + +func TestGatewaySuite(t *testing.T) { + suite.Run(t, new(GatewaySuite)) +} diff --git a/tests/test/application-gateway/tools.go b/tests/test/application-gateway/tools.go new file mode 100644 index 00000000..135c49c6 --- /dev/null +++ b/tests/test/application-gateway/tools.go @@ -0,0 +1,30 @@ +package application_gateway + +import ( + "encoding/json" + "regexp" + "strconv" + + "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1" + "github.com/pkg/errors" + + test_api "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/test-api" +) + +func getExpectedHTTPCode(service v1alpha1.Service) (int, error) { + re := regexp.MustCompile(`\d+`) + if codeStr := re.FindString(service.Description); len(codeStr) > 0 { + return strconv.Atoi(codeStr) + } + return 0, errors.New("Bad configuration") +} + +func gatewayURL(app, service string) string { + return "http://central-application-gateway.kyma-system:8080/" + app + "/" + service +} + +func unmarshalBody(body []byte) (test_api.EchoResponse, error) { + res := test_api.EchoResponse{} + err := json.Unmarshal(body, &res) + return res, err +} diff --git a/tests/test/compass-runtime-agent/config.go b/tests/test/compass-runtime-agent/config.go new file mode 100644 index 00000000..c004c398 --- /dev/null +++ b/tests/test/compass-runtime-agent/config.go @@ -0,0 +1,19 @@ +package compass_runtime_agent + +import "fmt" + +type config struct { + DirectorURL string `envconfig:"default=http://compass-director.compass-system.svc.cluster.local:3000/graphql"` + SkipDirectorCertVerification bool `envconfig:"default=false"` + OAuthCredentialsNamespace string `envconfig:"default=test"` + SystemNamespace string `envconfig:"default=kyma-system"` + CompassRuntimeAgentDeploymentName string `envconfig:"default=compass-runtime-agent"` + CompassNamespace string `envconfig:"default=kyma-system"` + OAuthCredentialsSecretName string `envconfig:"default=oauth-compass-credentials"` + TestingTenant string `envconfig:"default=tenant"` +} + +func (c *config) String() string { + return fmt.Sprintf("DirectorURL: %s, SkipDirectorCertVerification: %v, OAuthCredentialsNamespace: %s, IntegrationNamespace: %s, CompassNamespace: %s, OAuthCredentialsSecretName: %s, TestingTenant %s", + c.DirectorURL, c.SkipDirectorCertVerification, c.OAuthCredentialsNamespace, c.SystemNamespace, c.CompassNamespace, c.OAuthCredentialsSecretName, c.TestingTenant) +} diff --git a/tests/test/compass-runtime-agent/suite_test.go b/tests/test/compass-runtime-agent/suite_test.go new file mode 100644 index 00000000..c3e62ea2 --- /dev/null +++ b/tests/test/compass-runtime-agent/suite_test.go @@ -0,0 +1,166 @@ +package compass_runtime_agent + +import ( + "crypto/tls" + "fmt" + "net/http" + "os" + "testing" + "time" + + cli "github.com/kyma-project/kyma/components/central-application-gateway/pkg/client/clientset/versioned" + ccclientset "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/client/clientset/versioned" + "github.com/pkg/errors" + "github.com/stretchr/testify/suite" + "github.com/vrischmann/envconfig" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/applications" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/director" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/graphql" + initcra "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init" + compassruntimeagentinittypes "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/random" +) + +type CompassRuntimeAgentSuite struct { + suite.Suite + applicationsClientSet *cli.Clientset + compassConnectionClientSet *ccclientset.Clientset + coreClientSet *kubernetes.Clientset + compassRuntimeAgentConfigurator initcra.CompassRuntimeAgentConfigurator + directorClient director.Client + appComparator applications.Comparator + testConfig config + rollbackTestFunc compassruntimeagentinittypes.RollbackFunc + formationName string +} + +func (cs *CompassRuntimeAgentSuite) SetupSuite() { + + err := envconfig.InitWithPrefix(&cs.testConfig, "APP") + cs.Require().Nil(err) + + cs.T().Logf("Config: %s", cs.testConfig.String()) + + cs.T().Logf("Init Kubernetes APIs") + cs.initKubernetesApis() + + cs.T().Logf("Configure Compass Runtime Agent for test") + cs.initCompassRuntimeAgentConfigurator() + cs.initComparators() + cs.configureRuntimeAgent() +} + +func (cs *CompassRuntimeAgentSuite) initKubernetesApis() { + var cfg *rest.Config + var err error + + cs.T().Logf("Initializing with in cluster config") + cfg, err = rest.InClusterConfig() + cs.Assert().NoError(err) + + if err != nil { + cs.T().Logf("Initializing kubeconfig") + kubeconfig, ok := os.LookupEnv("KUBECONFIG") + cs.Require().True(ok) + + cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig) + cs.Require().NoError(err) + } + + cs.applicationsClientSet, err = cli.NewForConfig(cfg) + cs.Require().NoError(err) + + cs.compassConnectionClientSet, err = ccclientset.NewForConfig(cfg) + cs.Require().NoError(err) + + cs.coreClientSet, err = kubernetes.NewForConfig(cfg) + cs.Require().NoError(err) +} + +func (cs *CompassRuntimeAgentSuite) initComparators() { + secretComparator, err := applications.NewSecretComparator(cs.coreClientSet, cs.testConfig.OAuthCredentialsNamespace, cs.testConfig.SystemNamespace) + cs.Require().NoError(err) + + applicationGetter := cs.applicationsClientSet.ApplicationconnectorV1alpha1().Applications() + cs.appComparator, err = applications.NewComparator(secretComparator, applicationGetter, "kyma-system", "kyma-system") +} + +func (cs *CompassRuntimeAgentSuite) configureRuntimeAgent() { + cs.T().Helper() + + var err error + runtimeName := "cratest" + cs.formationName = "cratest" + random.RandomString(5) + + cs.rollbackTestFunc, err = cs.compassRuntimeAgentConfigurator.Do(runtimeName, cs.formationName) + cs.Require().NoError(err) +} + +func (cs *CompassRuntimeAgentSuite) initCompassRuntimeAgentConfigurator() { + var err error + cs.directorClient, err = cs.makeCompassDirectorClient() + cs.Require().NoError(err) + + cs.compassRuntimeAgentConfigurator = initcra.NewCompassRuntimeAgentConfigurator( + initcra.NewCompassConfigurator(cs.directorClient, cs.testConfig.TestingTenant), + initcra.NewCertificateSecretConfigurator(cs.coreClientSet), + initcra.NewConfigurationSecretConfigurator(cs.coreClientSet), + initcra.NewCompassConnectionCRConfiguration(cs.compassConnectionClientSet.CompassV1alpha1().CompassConnections()), + initcra.NewDeploymentConfiguration(cs.coreClientSet, "compass-runtime-agent", cs.testConfig.CompassNamespace), + cs.testConfig.OAuthCredentialsNamespace) +} + +func (cs *CompassRuntimeAgentSuite) TearDownSuite() { + if cs.rollbackTestFunc != nil { + cs.T().Logf("Restore Compass Runtime Agent configuration") + err := cs.rollbackTestFunc() + + if err != nil { + cs.T().Logf("Failed to rollback test configuration: %v", err) + } + } + _, err := http.Post("http://localhost:15000/quitquitquit", "", nil) + if err != nil { + cs.T().Logf("Failed to quit sidecar: %v", err) + } + _, err = http.Post("http://localhost:15020/quitquitquit", "", nil) + if err != nil { + cs.T().Logf("Failed to quit sidecar: %v", err) + } +} + +func TestCompassRuntimeAgentSuite(t *testing.T) { + suite.Run(t, new(CompassRuntimeAgentSuite)) +} + +func (cs *CompassRuntimeAgentSuite) makeCompassDirectorClient() (director.Client, error) { + + secretsRepo := cs.coreClientSet.CoreV1().Secrets(cs.testConfig.OAuthCredentialsNamespace) + + if secretsRepo == nil { + return nil, fmt.Errorf("could not access secrets in %s namespace", cs.testConfig.OAuthCredentialsNamespace) + } + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: cs.testConfig.SkipDirectorCertVerification}, + }, + Timeout: 10 * time.Second, + } + + gqlClient := graphql.NewGraphQLClient(cs.testConfig.DirectorURL, true, cs.testConfig.SkipDirectorCertVerification) + if gqlClient == nil { + return nil, fmt.Errorf("could not create GraphQLClient for endpoint %s", cs.testConfig.DirectorURL) + } + + oauthClient, err := oauth.NewOauthClient(client, secretsRepo, cs.testConfig.OAuthCredentialsSecretName) + if err != nil { + return nil, errors.Wrap(err, "Could not create OAuthClient client") + } + + return director.NewDirectorClient(gqlClient, oauthClient, cs.testConfig.TestingTenant), nil +} diff --git a/tests/test/compass-runtime-agent/synchronisation_test.go b/tests/test/compass-runtime-agent/synchronisation_test.go new file mode 100644 index 00000000..ac19f018 --- /dev/null +++ b/tests/test/compass-runtime-agent/synchronisation_test.go @@ -0,0 +1,157 @@ +package compass_runtime_agent + +import ( + "context" + "fmt" + "time" + + "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/executor" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/random" +) + +const checkAppExistsPeriod = 10 * time.Second +const appCreationTimeout = 2 * time.Minute +const appUpdateTimeout = 2 * time.Minute + +const updatedDescription = "The app was updated" + +type ApplicationReader interface { + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Application, error) +} + +func (cs *CompassRuntimeAgentSuite) TestApplication() { + expectedAppName := "app1" + updatedAppName := "app1-updated" + + compassAppName := expectedAppName + random.RandomString(10) + + correctState := false + //Create Application in Director + applicationID, err := cs.directorClient.RegisterApplication(compassAppName, "Test Application for testing Compass Runtime Agent") + cs.Require().NoError(err) + + synchronizedCompassAppName := fmt.Sprintf("mp-%s", compassAppName) + + applicationInterface := cs.applicationsClientSet.ApplicationconnectorV1alpha1().Applications() + err = cs.assignApplicationToFormationAndWaitForSync(applicationInterface, synchronizedCompassAppName, applicationID) + cs.NoError(err) + + // Compare Application created by Compass Runtime Agent with expected result + + cs.Run("Compass Runtime Agent should create Application", func() { + err = cs.appComparator.Compare(cs.T(), expectedAppName, synchronizedCompassAppName) + cs.NoError(err) + + correctState = err == nil + }) + + cs.Run("Update app", func() { + if !correctState { + cs.T().Skip("App not in correct state") + } + + _ = cs.updateAndWait(applicationInterface, synchronizedCompassAppName, applicationID) + + err = cs.appComparator.Compare(cs.T(), updatedAppName, synchronizedCompassAppName) + cs.NoError(err) + + correctState = err == nil + }) + + // Clean up + cs.Run("Compass Runtime Agent should remove Application", func() { + err = cs.removeApplicationAndWaitForSync(applicationInterface, synchronizedCompassAppName, applicationID) + cs.NoError(err) + }) +} + +func (cs *CompassRuntimeAgentSuite) updateAndWait(appReader ApplicationReader, compassAppName, applicationID string) error { + t := cs.T() + t.Helper() + + exec := func() error { + _, err := cs.directorClient.UpdateApplication(applicationID, updatedDescription) + return err + } + + verify := func() bool { + app, err := appReader.Get(context.Background(), compassAppName, v1.GetOptions{}) + if err != nil { + t.Logf("Couldn't get updated: %v", err) + } + + return err == nil && app.Spec.Description == updatedDescription + } + + return executor.ExecuteAndWaitForCondition{ + RetryableExecuteFunc: exec, + ConditionMetFunc: verify, + Tick: checkAppExistsPeriod, + Timeout: appUpdateTimeout, + }.Do() +} + +func (cs *CompassRuntimeAgentSuite) assignApplicationToFormationAndWaitForSync(appReader ApplicationReader, compassAppName, applicationID string) error { + t := cs.T() + t.Helper() + + exec := func() error { + return cs.directorClient.AssignApplicationToFormation(applicationID, cs.formationName) + } + + verify := func() bool { + _, err := appReader.Get(context.Background(), compassAppName, v1.GetOptions{}) + if err != nil { + t.Logf("Failed to get app: %v", err) + } + + return err == nil + } + + return executor.ExecuteAndWaitForCondition{ + RetryableExecuteFunc: exec, + ConditionMetFunc: verify, + Tick: checkAppExistsPeriod, + Timeout: appCreationTimeout, + }.Do() +} + +func (cs *CompassRuntimeAgentSuite) removeApplicationAndWaitForSync(appReader ApplicationReader, compassAppName, applicationID string) error { + t := cs.T() + t.Helper() + + exec := func() error { + err := cs.directorClient.UnassignApplication(applicationID, cs.formationName) + if err != nil { + return err + } + + err = cs.directorClient.UnregisterApplication(applicationID) + return err + } + + verify := func() bool { + _, err := appReader.Get(context.Background(), compassAppName, v1.GetOptions{}) + if errors.IsNotFound(err) { + t.Logf("Application was successfully removed by Compass Runtime Agent: %v", err) + return true + } + + if err != nil { + t.Logf("Failed to check whether Application was removed by Compass Runtime Agent: %v", err) + } + + return false + } + + return executor.ExecuteAndWaitForCondition{ + RetryableExecuteFunc: exec, + ConditionMetFunc: verify, + Tick: checkAppExistsPeriod, + Timeout: appCreationTimeout, + }.Do() +} diff --git a/tests/test/compass-runtime-agent/testkit/applications/comparator.go b/tests/test/compass-runtime-agent/testkit/applications/comparator.go new file mode 100644 index 00000000..1a9ab03c --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/applications/comparator.go @@ -0,0 +1,125 @@ +package applications + +import ( + "context" + "errors" + "testing" + + "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1" + "github.com/stretchr/testify/assert" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +//go:generate mockery --name=ApplicationGetter +type ApplicationGetter interface { + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Application, error) +} + +func NewComparator(secretComparer Comparator, applicationGetter ApplicationGetter, expectedNamespace, actualNamespace string) (Comparator, error) { + return &comparator{ + secretComparer: secretComparer, + applicationGetter: applicationGetter, + expectedNamespace: expectedNamespace, + actualNamespace: actualNamespace, + }, nil +} + +type comparator struct { + secretComparer Comparator + applicationGetter ApplicationGetter + expectedNamespace string + actualNamespace string +} + +func (c comparator) Compare(t *testing.T, expected, actual string) error { + t.Helper() + + if actual == "" || expected == "" { + return errors.New("empty actual or expected application name") + } + + actualApp, err := c.applicationGetter.Get(context.Background(), actual, v1.GetOptions{}) + if err != nil { + return err + } + + expectedApp, err := c.applicationGetter.Get(context.Background(), expected, v1.GetOptions{}) + if err != nil { + return err + } + + c.compareSpec(t, expectedApp, actualApp) + return nil +} + +func (c comparator) compareSpec(t *testing.T, expected, actual *v1alpha1.Application) { + t.Helper() + a := assert.New(t) + + a.Equal(expected.Spec.Description, actual.Spec.Description, "Description is incorrect") + a.Equal(expected.Spec.SkipInstallation, actual.Spec.SkipInstallation, "SkipInstallation is incorrect") + + c.compareServices(t, expected.Spec.Services, actual.Spec.Services) + + a.NotNil(actual.Spec.Labels) + a.Equal(actual.Name, actual.Spec.Labels["connected-app"]) + + a.Equal(expected.Spec.Tenant, actual.Spec.Tenant, "Tenant is incorrect") + a.Equal(expected.Spec.Group, actual.Spec.Group, "Group is incorrect") + + a.Equal(expected.Spec.Tags, actual.Spec.Tags, "Tags is incorrect") + a.Equal(expected.Spec.DisplayName, actual.Spec.DisplayName, "DisplayName is incorrect") + a.Equal(expected.Spec.ProviderDisplayName, actual.Spec.ProviderDisplayName, "ProviderDisplayName is incorrect") + a.Equal(expected.Spec.LongDescription, actual.Spec.LongDescription, "LongDescription is incorrect") + a.Equal(expected.Spec.SkipVerify, actual.Spec.SkipVerify, "SkipVerify is incorrect") +} + +func (c comparator) compareServices(t *testing.T, expected, actual []v1alpha1.Service) { + t.Helper() + a := assert.New(t) + + a.Equal(len(expected), len(actual)) + + for i := 0; i < len(actual); i++ { + a.Equal(expected[i].Identifier, actual[i].Identifier) + a.Equal(expected[i].DisplayName, actual[i].DisplayName) + a.Equal(expected[i].Description, actual[i].Description) + + c.compareEntries(t, expected[i].Entries, actual[i].Entries) + + a.Equal(expected[i].AuthCreateParameterSchema, actual[i].AuthCreateParameterSchema) + } +} + +func (c comparator) compareEntries(t *testing.T, expected, actual []v1alpha1.Entry) { + t.Helper() + a := assert.New(t) + + a.Equal(len(expected), len(actual)) + + for i := 0; i < len(actual); i++ { + a.Equal(expected[i].Type, actual[i].Type) + a.Equal(expected[i].TargetUrl, actual[i].TargetUrl) + a.Equal(expected[i].SpecificationUrl, actual[i].SpecificationUrl) + a.Equal(expected[i].ApiType, actual[i].ApiType) + + c.compareCredentials(t, expected[i].Credentials, actual[i].Credentials) + + a.Equal(expected[i].RequestParametersSecretName, actual[i].RequestParametersSecretName) + a.Equal(expected[i].Name, actual[i].Name) + } +} + +func (c comparator) compareCredentials(t *testing.T, expected, actual v1alpha1.Credentials) { + t.Helper() + a := assert.New(t) + + a.Equal(expected.Type, actual.Type) + + err := c.secretComparer.Compare(t, expected.SecretName, actual.SecretName) + a.NoError(err) + + a.Equal(expected.AuthenticationUrl, actual.AuthenticationUrl) + + a.Equal(expected.CSRFInfo, actual.CSRFInfo) +} diff --git a/tests/test/compass-runtime-agent/testkit/applications/comparator_test.go b/tests/test/compass-runtime-agent/testkit/applications/comparator_test.go new file mode 100644 index 00000000..5583b291 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/applications/comparator_test.go @@ -0,0 +1,195 @@ +package applications + +import ( + "errors" + "testing" + + "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/applications/mocks" +) + +func TestApplicationCrdCompare(t *testing.T) { + + t.Run("should compare applications", func(t *testing.T) { + secretComparatorMock := &mocks.Comparator{} + applicationGetterMock := &mocks.ApplicationGetter{} + actualApp := getTestApp("actual", "actualNamespace", "actualSecret") + expectedApp := getTestApp("expected", "expectedNamespace", "expectedSecret") + + secretComparatorMock.On("Compare", mock.Anything, "expectedSecret", "actualSecret").Return(nil) + applicationGetterMock.On("Get", mock.Anything, "actual", v1.GetOptions{}).Return(actualApp, nil).Once() + applicationGetterMock.On("Get", mock.Anything, "expected", v1.GetOptions{}).Return(expectedApp, nil).Once() + + //when + applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expectedNamespace", "actualNamespace") + err = applicationComparator.Compare(t, "expected", "actual") + + //then + require.NoError(t, err) + secretComparatorMock.AssertExpectations(t) + applicationGetterMock.AssertExpectations(t) + }) + + t.Run("should return error when expected or actual application name is empty", func(t *testing.T) { + //given + secretComparatorMock := &mocks.Comparator{} + applicationGetterMock := &mocks.ApplicationGetter{} + + { + //when + applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual") + err = applicationComparator.Compare(t, "expected", "") + + //then + require.Error(t, err) + } + + { + //when + applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual") + err = applicationComparator.Compare(t, "", "actual") + + //then + require.Error(t, err) + } + + }) + + t.Run("should return error when failed to get actual application", func(t *testing.T) { + //given + secretComparatorMock := &mocks.Comparator{} + applicationGetterMock := &mocks.ApplicationGetter{} + actualApp := v1alpha1.Application{} + + applicationGetterMock.On("Get", mock.Anything, "actual", v1.GetOptions{}).Return(&actualApp, errors.New("failed to get actual app")).Once() + + //when + applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual") + err = applicationComparator.Compare(t, "expected", "actual") + + //then + require.Error(t, err) + secretComparatorMock.AssertExpectations(t) + applicationGetterMock.AssertExpectations(t) + }) + + t.Run("should return error when failed to get expected application", func(t *testing.T) { + //given + secretComparatorMock := &mocks.Comparator{} + applicationGetterMock := &mocks.ApplicationGetter{} + expectedApp := v1alpha1.Application{} + actualApp := v1alpha1.Application{} + + applicationGetterMock.On("Get", mock.Anything, "actual", v1.GetOptions{}).Return(&actualApp, nil).Once() + applicationGetterMock.On("Get", mock.Anything, "expected", v1.GetOptions{}).Return(&expectedApp, errors.New("failed to get expected app")).Once() + + //when + applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual") + err = applicationComparator.Compare(t, "expected", "actual") + + //then + require.Error(t, err) + secretComparatorMock.AssertExpectations(t) + applicationGetterMock.AssertExpectations(t) + }) +} + +func getTestApp(name, namespace, secretName string) *v1alpha1.Application { + //given + services := make([]v1alpha1.Service, 0, 0) + entries := make([]v1alpha1.Entry, 0, 0) + + credentials := v1alpha1.Credentials{ + Type: "OAuth", + SecretName: secretName, + AuthenticationUrl: "authURL", + CSRFInfo: &v1alpha1.CSRFInfo{TokenEndpointURL: "csrfTokenURL"}, + } + + entries = append(entries, v1alpha1.Entry{ + Type: "api", + TargetUrl: "targetURL", + SpecificationUrl: "specURL", + ApiType: "v1", + Credentials: credentials, + RequestParametersSecretName: "paramSecret", + Name: "test2", + ID: "t2", + CentralGatewayUrl: "centralURL", + AccessLabel: "", //ignore for now + GatewayUrl: "", + }) + + entries = append(entries, v1alpha1.Entry{ + Type: "api", + TargetUrl: "targetURL", + SpecificationUrl: "specURL", + ApiType: "v1", + Credentials: credentials, + RequestParametersSecretName: "paramSecret", + Name: "test1", + ID: "t1", + CentralGatewayUrl: "centralURL", + AccessLabel: "", + GatewayUrl: "", + }) + + services = append(services, v1alpha1.Service{ + ID: "serviceTest", + Identifier: "st1", + Name: "srvTest1", + DisplayName: "srvTest1", + Description: "srvTest1", + Entries: entries, + AuthCreateParameterSchema: nil, + Labels: nil, + LongDescription: "", + ProviderDisplayName: "", + Tags: nil, + }) + + services = append(services, v1alpha1.Service{ + ID: "serviceTest2", + Identifier: "st2", + Name: "srvTest2", + DisplayName: "srvTest2", + Description: "srvTest2", + Entries: entries, + AuthCreateParameterSchema: nil, + Labels: nil, + LongDescription: "", + ProviderDisplayName: "", + Tags: nil, + }) + + return &v1alpha1.Application{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: v1alpha1.ApplicationSpec{ + Description: "testapp", + SkipInstallation: false, + Services: services, + Labels: map[string]string{"connected-app": name}, + + Tenant: "test", + Group: "test", + CompassMetadata: &v1alpha1.CompassMetadata{ + ApplicationID: "compassID1", + Authentication: v1alpha1.Authentication{ClientIds: []string{"11", "22"}}, + }, + Tags: []string{"tag1", "tag2"}, + DisplayName: "applicationOneDisplay", + ProviderDisplayName: "applicationOneDisplay", + LongDescription: "applicationOne Test", + SkipVerify: true, + }, + } + +} diff --git a/tests/test/compass-runtime-agent/testkit/applications/mocks/ApplicationGetter.go b/tests/test/compass-runtime-agent/testkit/applications/mocks/ApplicationGetter.go new file mode 100644 index 00000000..82933f9a --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/applications/mocks/ApplicationGetter.go @@ -0,0 +1,55 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1alpha1 "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1" +) + +// ApplicationGetter is an autogenerated mock type for the ApplicationGetter type +type ApplicationGetter struct { + mock.Mock +} + +// Get provides a mock function with given fields: ctx, name, opts +func (_m *ApplicationGetter) Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Application, error) { + ret := _m.Called(ctx, name, opts) + + var r0 *v1alpha1.Application + if rf, ok := ret.Get(0).(func(context.Context, string, v1.GetOptions) *v1alpha1.Application); ok { + r0 = rf(ctx, name, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1alpha1.Application) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, v1.GetOptions) error); ok { + r1 = rf(ctx, name, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewApplicationGetter interface { + mock.TestingT + Cleanup(func()) +} + +// NewApplicationGetter creates a new instance of ApplicationGetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewApplicationGetter(t mockConstructorTestingTNewApplicationGetter) *ApplicationGetter { + mock := &ApplicationGetter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/applications/mocks/Comparator.go b/tests/test/compass-runtime-agent/testkit/applications/mocks/Comparator.go new file mode 100644 index 00000000..abccb412 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/applications/mocks/Comparator.go @@ -0,0 +1,43 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + testing "testing" + + mock "github.com/stretchr/testify/mock" +) + +// Comparator is an autogenerated mock type for the Comparator type +type Comparator struct { + mock.Mock +} + +// Compare provides a mock function with given fields: test, expected, actual +func (_m *Comparator) Compare(test *testing.T, expected string, actual string) error { + ret := _m.Called(test, expected, actual) + + var r0 error + if rf, ok := ret.Get(0).(func(*testing.T, string, string) error); ok { + r0 = rf(test, expected, actual) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewComparator interface { + mock.TestingT + Cleanup(func()) +} + +// NewComparator creates a new instance of Comparator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewComparator(t mockConstructorTestingTNewComparator) *Comparator { + mock := &Comparator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/applications/secretcomparator.go b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator.go new file mode 100644 index 00000000..5573754d --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator.go @@ -0,0 +1,59 @@ +package applications + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +//go:generate mockery --name=Comparator +type Comparator interface { + Compare(test *testing.T, expected, actual string) error +} + +func NewSecretComparator(coreClientSet kubernetes.Interface, expectedNamespace, actualNamespace string) (Comparator, error) { + return &secretComparator{ + coreClientSet: coreClientSet, + expectedNamespace: expectedNamespace, + actualNamespace: actualNamespace, + }, nil +} + +type secretComparator struct { + coreClientSet kubernetes.Interface + expectedNamespace string + actualNamespace string +} + +func (c secretComparator) Compare(t *testing.T, expected, actual string) error { + t.Helper() + + if actual == "" && expected == "" { + return nil + } + + if actual == "" || expected == "" { + return errors.New("empty actual or expected secret name") + } + + expectedSecretRepo := c.coreClientSet.CoreV1().Secrets(c.expectedNamespace) + actualSecretRepo := c.coreClientSet.CoreV1().Secrets(c.actualNamespace) + + expectedSecret, err := expectedSecretRepo.Get(context.Background(), expected, metav1.GetOptions{}) + if err != nil { + return err + } + + actualSecret, err := actualSecretRepo.Get(context.Background(), actual, metav1.GetOptions{}) + if err != nil { + return err + } + + require.Equal(t, expectedSecret.Data, actualSecret.Data) + + return nil +} diff --git a/tests/test/compass-runtime-agent/testkit/applications/secretcomparator_test.go b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator_test.go new file mode 100644 index 00000000..1241a785 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator_test.go @@ -0,0 +1,117 @@ +package applications + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/kubernetes/typed/core/v1" +) + +func TestCompare(t *testing.T) { + + t.Run("should return true if secrets are equal", func(t *testing.T) { + //given + coreV1 := fake.NewSimpleClientset() + secretComparator, err := NewSecretComparator(coreV1, "test", "kyma-system") + require.NoError(t, err) + createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("test"), "expected", "test") + createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("kyma-system"), "actual", "kyma-system") + + //when + err = secretComparator.Compare(t, "expected", "actual") + + // then + require.NoError(t, err) + }) + + t.Run("should return error if failed to read actual secret", func(t *testing.T) { + //given + coreV1 := fake.NewSimpleClientset() + secretComparator, err := NewSecretComparator(coreV1, "test", "kyma-system") + require.NoError(t, err) + createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("test"), "expected", "test") + + //when + err = secretComparator.Compare(t, "actual", "expected") + + // then + require.Error(t, err) + }) + + t.Run("should return error if failed to read expected secret", func(t *testing.T) { + //given + coreV1 := fake.NewSimpleClientset() + secretComparator, err := NewSecretComparator(coreV1, "test", "kyma-system") + require.NoError(t, err) + createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("kyma-system"), "actual", "kyma-system") + + //when + err = secretComparator.Compare(t, "actual", "expected") + + // then + require.Error(t, err) + }) + + t.Run("should return error if expected secret name is empty", func(t *testing.T) { + //given + secretComparator, err := NewSecretComparator(nil, "test", "kyma-system") + require.NoError(t, err) + + //when + err = secretComparator.Compare(t, "actual", "") + + // then + require.Error(t, err) + }) + + t.Run("should return error if actual secret name is empty", func(t *testing.T) { + //given + secretComparator, err := NewSecretComparator(nil, "test", "kyma-system") + require.NoError(t, err) + + //when + err = secretComparator.Compare(t, "", "expected") + + // then + require.Error(t, err) + }) + + t.Run("should return no error if actual and expected secret name is empty", func(t *testing.T) { + //given + secretComparator, err := NewSecretComparator(nil, "test", "kyma-system") + require.NoError(t, err) + + //when + err = secretComparator.Compare(t, "", "") + + // then + require.NoError(t, err) + }) +} + +func createFakeCredentialsSecret(t *testing.T, secrets core.SecretInterface, secretName, namespace string) { + + secret := &v1.Secret{ + ObjectMeta: meta.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + TypeMeta: meta.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + Data: map[string][]byte{ + "key1": []byte("val1"), + "key2": []byte("val2"), + "key3": []byte("val3"), + }, + } + + _, err := secrets.Create(context.Background(), secret, meta.CreateOptions{}) + + require.NoError(t, err) +} diff --git a/tests/test/compass-runtime-agent/testkit/director/directorclient.go b/tests/test/compass-runtime-agent/testkit/director/directorclient.go new file mode 100644 index 00000000..6aa33363 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/director/directorclient.go @@ -0,0 +1,320 @@ +package director + +import ( + "fmt" + + "github.com/kyma-incubator/compass/components/director/pkg/graphql" + "github.com/kyma-incubator/compass/components/director/pkg/graphql/graphqlizer" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + + gql "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/graphql" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth" + gcli "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql" +) + +const ( + AuthorizationHeader = "Authorization" + TenantHeader = "Tenant" +) + +//go:generate mockery --name=Client +type Client interface { + RegisterApplication(appName, displayName string) (string, error) + UnregisterApplication(id string) error + AssignApplicationToFormation(appId, formationName string) error + UnassignApplication(appId, formationName string) error + RegisterRuntime(runtimeName string) (string, error) + UnregisterRuntime(id string) error + RegisterFormation(formationName string) error + UnregisterFormation(formationName string) error + AssignRuntimeToFormation(runtimeId, formationName string) error + GetConnectionToken(runtimeID string) (string, string, error) + UpdateApplication(id, newDesc string) (string, error) +} + +type directorClient struct { + gqlClient gql.Client + queryProvider queryProvider + graphqlizer graphqlizer.Graphqlizer + token oauth.Token + oauthClient oauth.Client + tenant string +} + +func NewDirectorClient(gqlClient gql.Client, oauthClient oauth.Client, tenant string) Client { + + return &directorClient{ + gqlClient: gqlClient, + oauthClient: oauthClient, + queryProvider: queryProvider{}, + graphqlizer: graphqlizer.Graphqlizer{}, + token: oauth.Token{}, + tenant: tenant, + } +} + +func (cc *directorClient) getToken() error { + token, err := cc.oauthClient.GetAuthorizationToken() + if err != nil { + return err + } + + if token.EmptyOrExpired() { + return errors.New("Obtained empty or expired token") + } + + cc.token = token + return nil +} + +func (cc *directorClient) RegisterFormation(formationName string) error { + log.Infof("Registering Formation") + + queryFunc := func() string { return cc.queryProvider.createFormation(formationName) } + execFunc := getExecGraphQLFunc[graphql.Formation](cc) + operationDescription := "register Formation" + successfulLogMessage := fmt.Sprintf("Successfully registered Formation %s in Director for tenant %s", formationName, cc.tenant) + + return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage) +} + +func (cc *directorClient) UnregisterFormation(formationName string) error { + log.Infof("Unregistering Formation") + queryFunc := func() string { return cc.queryProvider.deleteFormation(formationName) } + execFunc := getExecGraphQLFunc[graphql.Formation](cc) + operationDescription := "unregister Formation" + successfulLogMessage := fmt.Sprintf("Successfully unregistered Formation %s in Director for tenant %s", formationName, cc.tenant) + + return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage) +} + +func (cc *directorClient) RegisterRuntime(runtimeName string) (string, error) { + log.Infof("Registering Runtime") + queryFunc := func() string { return cc.queryProvider.registerRuntimeMutation(runtimeName) } + execFunc := getExecGraphQLFunc[graphql.Runtime](cc) + operationDescription := "register Runtime" + successfulLogMessage := fmt.Sprintf("Successfully registered Runtime %s in Director for tenant %s", runtimeName, cc.tenant) + + response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage) + if err != nil { + return "", err + } + + return response.Result.ID, nil +} + +func (cc *directorClient) UnregisterRuntime(id string) error { + log.Infof("Unregistering Runtime") + + queryFunc := func() string { return cc.queryProvider.deleteRuntimeMutation(id) } + execFunc := getExecGraphQLFunc[graphql.Runtime](cc) + operationDescription := "unregister Runtime" + successfulLogMessage := fmt.Sprintf("Successfully unregistered Runtime %s in Director for tenant %s", id, cc.tenant) + + response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage) + if err != nil { + return err + } + + if response.Result.ID != id { + return fmt.Errorf("Failed to unregister runtime %s in Director: received unexpected RuntimeID.", id) + } + + return nil +} + +func (cc *directorClient) GetConnectionToken(runtimeId string) (string, string, error) { + log.Infof("Requesting one time token for Runtime from Director service") + + queryFunc := func() string { return cc.queryProvider.requestOneTimeTokenMutation(runtimeId) } + execFunc := getExecGraphQLFunc[graphql.OneTimeTokenForRuntimeExt](cc) + operationDescription := "register application" + successfulLogMessage := fmt.Sprintf("Received OneTimeToken for Runtime %s in Director for tenant %s", runtimeId, cc.tenant) + + response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage) + if err != nil { + return "", "", err + } + return response.Result.Token, response.Result.ConnectorURL, nil +} + +func (cc *directorClient) RegisterApplication(appName, displayName string) (string, error) { + log.Infof("Registering Application") + + queryFunc := func() string { return cc.queryProvider.registerApplicationFromTemplateMutation(appName, displayName) } + execFunc := getExecGraphQLFunc[graphql.Application](cc) + operationDescription := "register application" + successfulLogMessage := fmt.Sprintf("Successfully registered application %s in Director for tenant %s", appName, cc.tenant) + + result, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage) + if err != nil { + return "", err + } + + id := result.Result.ID + _, err = cc.AddBundle(id) + return id, err +} + +func (cc *directorClient) AddBundle(appID string) (string, error) { + log.Infof("Adding Bundle to Application") + + queryFunc := func() string { return cc.queryProvider.addBundleMutation(appID) } + execFunc := getExecGraphQLFunc[graphql.Application](cc) + operationDescription := "add bundle" + successfulLogMessage := fmt.Sprintf("Successfully added bundle to application with ID %s in Director for tenant %s", appID, cc.tenant) + + result, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage) + if err != nil { + return "", err + } + return result.Result.ID, err +} + +func (cc *directorClient) UpdateApplication(id, newDesc string) (string, error) { + log.Infof("Updating Application %s", id) + + queryFunc := func() string { + return cc.queryProvider. + updateApplicationMutation(id, newDesc) + } + execFunc := getExecGraphQLFunc[graphql.Application](cc) + operationDescription := "update application" + successfulLogMessage := fmt.Sprintf("Successfully updated application %s in Director for tenant %s", id, cc.tenant) + + result, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage) + if err != nil { + return "", err + } + return result.Result.ID, err +} + +func (cc *directorClient) AssignApplicationToFormation(appId, formationName string) error { + log.Infof("Assigning Application to Formation") + + queryFunc := func() string { return cc.queryProvider.assignFormationForAppMutation(appId, formationName) } + execFunc := getExecGraphQLFunc[graphql.Formation](cc) + operationDescription := "assign Application to Formation" + successfulLogMessage := fmt.Sprintf("Successfully assigned application %s to Formation %s in Director for tenant %s", appId, formationName, cc.tenant) + + return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage) +} + +func (cc *directorClient) UnassignApplication(appId, formationName string) error { + log.Infof("Unregistering Application from Formation") + + queryFunc := func() string { return cc.queryProvider.unassignFormation(appId, formationName) } + execFunc := getExecGraphQLFunc[graphql.Formation](cc) + operationDescription := "unregister formation" + successfulLogMessage := fmt.Sprintf("Successfully unassigned application %s from Formation %s in Director for tenant %s", appId, formationName, cc.tenant) + + return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage) +} + +func (cc *directorClient) AssignRuntimeToFormation(runtimeId, formationName string) error { + log.Infof("Assigning Runtime to Formation") + + queryFunc := func() string { return cc.queryProvider.assignFormationForRuntimeMutation(runtimeId, formationName) } + execFunc := getExecGraphQLFunc[graphql.Formation](cc) + operationDescription := "assign Runtime to Formation" + successfulLogMessage := fmt.Sprintf("Successfully assigned runtime %s to Formation %s in Director for tenant %s", runtimeId, formationName, cc.tenant) + + return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage) +} + +func (cc *directorClient) UnregisterApplication(appID string) error { + log.Infof("Unregistering Application") + + queryFunc := func() string { return cc.queryProvider.unregisterApplicationMutation(appID) } + execFunc := getExecGraphQLFunc[graphql.Application](cc) + operationDescription := "Unregistering Application" + successfulLogMessage := fmt.Sprintf("Successfully unregister application %s in Director for tenant %s", appID, cc.tenant) + + response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage) + if err != nil { + return err + } + + if response.Result.ID != appID { + return fmt.Errorf("Failed to unregister Application %s in Director: received unexpected applicationID.", appID) + } + + return nil +} + +func (cc *directorClient) executeDirectorGraphQLCall(directorQuery string, tenant string, response interface{}) error { + if cc.token.EmptyOrExpired() { + log.Infof("Refreshing token to access Director Service") + if err := cc.getToken(); err != nil { + return err + } + } + + req := gcli.NewRequest(directorQuery) + req.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", cc.token.AccessToken)) + req.Header.Set(TenantHeader, tenant) + + if err := cc.gqlClient.Do(req, response); err != nil { + if egErr, ok := err.(gcli.ExtendedError); ok { + return errors.Wrap(egErr, "Failed to execute GraphQL request to Director") + } + return fmt.Errorf("Failed to execute GraphQL request to Director: %v", err) + } + + return nil +} + +type Response[T any] struct { + Result *T +} + +func executeQuerySkipResponse[T any](getQueryFunc func() string, executeQueryFunc func(string, *Response[T]) error, operationDescription, successfulLogMessage string) error { + _, err := executeQuery(getQueryFunc, executeQueryFunc, operationDescription, successfulLogMessage) + + return err +} + +func executeQuery[T any](getQueryFunc func() string, executeQueryFunc func(string, *Response[T]) error, operationDescription, successfulLogMessage string) (Response[T], error) { + query := getQueryFunc() + + var response Response[T] + err := executeQueryFunc(query, &response) + + if err != nil { + return Response[T]{}, errors.Wrap(err, fmt.Sprintf("Failed to %s in Director. Request failed", operationDescription)) + } + + // Nil check is necessary due to GraphQL client not checking response code + if response.Result == nil { + return Response[T]{}, errors.New(fmt.Sprintf("Failed to %s in Director: Received nil response.", operationDescription)) + } + + log.Infof(successfulLogMessage) + + return response, nil +} + +func getExecGraphQLFunc[T any](cc *directorClient) func(string, *Response[T]) error { + return func(query string, result *Response[T]) error { + if cc.token.EmptyOrExpired() { + log.Infof("Refreshing token to access Director Service") + if err := cc.getToken(); err != nil { + return err + } + } + + req := gcli.NewRequest(query) + req.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", cc.token.AccessToken)) + req.Header.Set(TenantHeader, cc.tenant) + + if err := cc.gqlClient.Do(req, result); err != nil { + if egErr, ok := err.(gcli.ExtendedError); ok { + return errors.Wrap(egErr, "Failed to execute GraphQL request to Director") + } + return fmt.Errorf("Failed to execute GraphQL request to Director: %v", err) + } + + return nil + } +} diff --git a/tests/test/compass-runtime-agent/testkit/director/directorclient_test.go b/tests/test/compass-runtime-agent/testkit/director/directorclient_test.go new file mode 100644 index 00000000..baa5480c --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/director/directorclient_test.go @@ -0,0 +1,1142 @@ +package director + +import ( + "errors" + "fmt" + "github.com/kyma-incubator/compass/components/director/pkg/graphql" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + gcli "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql" + + gql "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/graphql" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth" + oauthmocks "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth/mocks" +) + +const ( + runtimeTestingID = "test-runtime-ID-12345" + runtimeTestingName = "Runtime Test name" + testAppName = "Test-application-123" + applicationTestingID = "test-application-ID-12345" + testAppScenario = "Testing-scenario" + testAppDisplayName = "Testing-app-display-name" + validTokenValue = "12345" + tenantValue = "3e64ebae-38b5-46a0-b1ed-9ccee153a0ae" + oneTimeToken = "54321" + connectorURL = "https://kyma.cx/connector/graphql" + + expectedRegisterApplicationQuery = `mutation { + result: registerApplicationFromTemplate(in: { + templateName: "SAP Commerce Cloud" + values: [ + { placeholder: "name", value: "Test-application-123" } + { placeholder: "display-name", value: "Testing-app-display-name" } + ] + }) { id } }` + + expectedAssignAppToFormationQuery = `mutation { + result: assignFormation( + objectID: "test-application-ID-12345" + objectType: APPLICATION + formation: { name: "Testing-scenario" } + ) { id } }` + + expectedAssignRuntimeToFormationQuery = `mutation { + result: assignFormation( + objectID: "test-runtime-ID-12345" + objectType: RUNTIME + formation: { name: "Testing-scenario" } + ) { id } }` + + expectedDeleteApplicationQuery = `mutation { + result: unregisterApplication(id: "test-application-ID-12345") { + id + } }` + + expectedRegisterRuntimeQuery = `mutation { + result: registerRuntime(in: { + name: "Runtime Test name" + }) { id } }` + + expectedDeleteRuntimeQuery = `mutation { + result: unregisterRuntime(id: "test-runtime-ID-12345") { + id + }}` + + expectedOneTimeTokenQuery = `mutation { + result: requestOneTimeTokenForRuntime(id: "test-runtime-ID-12345") { + token connectorURL + }}` + + expectedRegisterFormationQuery = `mutation { + result: createFormation(formation: { + name: "Testing-scenario" + }) { id } }` + + expectedDeleteFormationQuery = `mutation { + result: deleteFormation(formation: { + name: "Testing-scenario" + }) { id } }` +) + +var ( + futureExpirationTime = time.Now().Add(time.Duration(60) * time.Minute).Unix() + passedExpirationTime = time.Now().Add(time.Duration(60) * time.Minute * -1).Unix() +) + +func TestDirectorClient_RuntimeRegistering(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedRegisterRuntimeQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should register runtime and return new runtime ID when the Director access token is valid", func(t *testing.T) { + // given + responseDescription := "runtime description" + expectedResponse := &graphql.Runtime{ + ID: runtimeTestingID, + Name: runtimeTestingName, + Description: &responseDescription, + } + + expectedID := runtimeTestingID + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName) + + // then + assert.NoError(t, err) + assert.Equal(t, expectedID, receivedRuntimeID) + }) + + t.Run("Should not register runtime and return an error when the Director access token is empty", func(t *testing.T) { + // given + token := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedRuntimeID) + }) + + t.Run("Should not register runtime and return an error when the Director access token is expired", func(t *testing.T) { + // given + expiredToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: passedExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedRuntimeID) + }) + + t.Run("Should not register Runtime and return error when the client fails to get an access token for Director", func(t *testing.T) { + // given + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error")) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedRuntimeID) + }) + + t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedRuntimeID) + }) + + t.Run("Should return error when Director fails to register Runtime ", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest) + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedRuntimeID) + }) +} + +func TestDirectorClient_RuntimeUnregistering(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedDeleteRuntimeQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should unregister runtime of given ID and return no error when the Director access token is valid", func(t *testing.T) { + // given + responseDescription := "runtime description" + expectedResponse := &graphql.Runtime{ + ID: runtimeTestingID, + Name: runtimeTestingName, + Description: &responseDescription, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterRuntime(runtimeTestingID) + + // then + assert.NoError(t, err) + }) + + t.Run("Should not unregister runtime and return an error when the Director access token is empty", func(t *testing.T) { + // given + emptyToken := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(emptyToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterRuntime(runtimeTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should not unregister register runtime and return an error when the Director access token is expired", func(t *testing.T) { + // given + expiredToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: passedExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterRuntime(runtimeTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should not unregister Runtime and return error when the client fails to get an access token for Director", func(t *testing.T) { + // given + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error")) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterRuntime(runtimeTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + // given + gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterRuntime(runtimeTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should return error when Director fails to delete Runtime", func(t *testing.T) { + // given + gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest) + + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterRuntime(runtimeTestingID) + + // then + assert.Error(t, err) + }) + + // unusual and strange case + t.Run("Should return error when Director returns bad ID after Deleting", func(t *testing.T) { + // given + responseDescription := "runtime description" + expectedResponse := &graphql.Runtime{ + ID: "BadId", + Name: runtimeTestingName, + Description: &responseDescription, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterRuntime(runtimeTestingID) + + // then + assert.Error(t, err) + }) +} + +func TestDirectorClient_FormationRegistering(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedRegisterFormationQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should register Formation and return no error when the Director access token is valid", func(t *testing.T) { + // given + expectedResponse := &graphql.Formation{ + Name: testAppScenario, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.RegisterFormation(testAppScenario) + + // then + assert.NoError(t, err) + }) + + t.Run("Should not register Formation and return an error when the Director access token is empty", func(t *testing.T) { + // given + token := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.RegisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) + + t.Run("Should not register Formation and return an error when the Director access token is expired", func(t *testing.T) { + // given + expiredToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: passedExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.RegisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) + + t.Run("Should not register Formation and return error when the client fails to get an access token for Director", func(t *testing.T) { + // given + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error")) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.RegisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) + + t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + gqlClient := gql.NewQueryAssertClient(t, nil, []*gcli.Request{expectedRequest}, func(t *testing.T, r interface{}) { + cfg, ok := r.(*Response[graphql.Formation]) + require.True(t, ok) + assert.Empty(t, cfg.Result) + cfg.Result = nil + }) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.RegisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) + + t.Run("Should return error when Director fails to register Formation ", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + gqlClient := newFailingQueryAssertClient[graphql.Formation](t, expectedRequest) + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.RegisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) +} + +func TestDirectorClient_FormationUnregistering(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedDeleteFormationQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should unregister Formation of given name and return no error when the Director access token is valid", func(t *testing.T) { + // given + expectedResponse := &graphql.Formation{ + Name: testAppScenario, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterFormation(testAppScenario) + + // then + assert.NoError(t, err) + }) + + t.Run("Should not unregister Formation and return an error when the Director access token is empty", func(t *testing.T) { + // given + emptyToken := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(emptyToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) + + t.Run("Should not unregister register Formation and return an error when the Director access token is expired", func(t *testing.T) { + // given + expiredToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: passedExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) + + t.Run("Should not unregister Formation and return error when the client fails to get an access token for Director", func(t *testing.T) { + // given + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error")) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) + + t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + // given + gqlClient := newFailingQueryAssertClient[graphql.Formation](t, expectedRequest) + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterFormation(testAppScenario) + // then + assert.Error(t, err) + }) + + t.Run("Should return error when Director fails to delete Formation", func(t *testing.T) { + // given + gqlClient := newFailingQueryAssertClient[graphql.Formation](t, expectedRequest) + + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterFormation(testAppScenario) + + // then + assert.Error(t, err) + }) +} + +func TestDirectorClient_ApplicationRegistering(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedRegisterApplicationQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should register application and return new application ID when the Director access token is valid", func(t *testing.T) { + // given + expectedResponse := &graphql.Application{ + Name: testAppName, + BaseEntity: &graphql.BaseEntity{ + ID: applicationTestingID, + }, + } + expectedID := applicationTestingID + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName) + + // then + assert.NoError(t, err) + assert.Equal(t, expectedID, receivedApplicationID) + }) + + t.Run("Should not register application and return an error when the Director access token is empty", func(t *testing.T) { + // given + token := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedApplicationID) + }) + + t.Run("Should not register Application and return an error when the Director access token is expired", func(t *testing.T) { + // given + expiredToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: passedExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedApplicationID) + }) + + t.Run("Should not register Application and return error when the client fails to get an access token for Director", func(t *testing.T) { + // given + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error")) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedApplicationID) + }) + + t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest) + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedApplicationID) + }) + + t.Run("Should return error when Director fails to register Runtime ", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest) + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedRuntimeID, err := configClient.RegisterApplication(testAppName, testAppDisplayName) + + // then + assert.Error(t, err) + assert.Empty(t, receivedRuntimeID) + }) +} + +func TestDirectorClient_ApplicationAssignToFormation(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedAssignAppToFormationQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should assign application to formation and return new application ID when the Director access token is valid", func(t *testing.T) { + // given + expectedResponse := &graphql.Formation{ + Name: testAppScenario, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.AssignApplicationToFormation(applicationTestingID, testAppScenario) + + // then + assert.NoError(t, err) + }) + + t.Run("Should not assign application to formation and return an error when the Director access token is empty", func(t *testing.T) { + // given + token := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.AssignApplicationToFormation(applicationTestingID, testAppScenario) + // then + assert.Error(t, err) + }) +} + +func TestDirectorClient_RuntimeAssignToFormation(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedAssignRuntimeToFormationQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should assign application to formation and return new application ID when the Director access token is valid", func(t *testing.T) { + // given + expectedResponse := &graphql.Formation{ + Name: testAppScenario, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.AssignRuntimeToFormation(runtimeTestingID, testAppScenario) + + // then + assert.NoError(t, err) + }) + + t.Run("Should not assign application to formation and return an error when the Director access token is empty", func(t *testing.T) { + // given + token := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.AssignRuntimeToFormation(runtimeTestingID, testAppScenario) + // then + assert.Error(t, err) + }) +} + +func TestDirectorClient_ApplicationUnregistering(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedDeleteApplicationQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should unregister runtime of given ID and return no error when the Director access token is valid", func(t *testing.T) { + // given + expectedResponse := &graphql.Application{ + Name: testAppName, + BaseEntity: &graphql.BaseEntity{ + ID: applicationTestingID, + }, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterApplication(applicationTestingID) + + // then + assert.NoError(t, err) + }) + + t.Run("Should not unregister runtime and return an error when the Director access token is empty", func(t *testing.T) { + // given + emptyToken := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(emptyToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterApplication(applicationTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should not unregister register runtime and return an error when the Director access token is expired", func(t *testing.T) { + // given + expiredToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: passedExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterApplication(applicationTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should not unregister Runtime and return error when the client fails to get an access token for Director", func(t *testing.T) { + // given + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error")) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterApplication(applicationTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) { + // given + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + // given + gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterApplication(applicationTestingID) + + // then + assert.Error(t, err) + }) + + t.Run("Should return error when Director fails to delete Runtime", func(t *testing.T) { + // given + gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest) + + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterApplication(applicationTestingID) + + // then + assert.Error(t, err) + }) + + // unusual and strange case + t.Run("Should return error when Director returns bad ID after Deleting", func(t *testing.T) { + // given + expectedResponse := &graphql.Application{ + Name: testAppName, + BaseEntity: &graphql.BaseEntity{ + ID: "badID", + }, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + + validToken := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + err := configClient.UnregisterApplication(applicationTestingID) + + // then + assert.Error(t, err) + }) +} + +func TestDirectorClient_GetConnectionToken(t *testing.T) { + expectedRequest := gcli.NewRequest(expectedOneTimeTokenQuery) + expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue)) + expectedRequest.Header.Set(TenantHeader, tenantValue) + + t.Run("Should return OneTimeToken when Oauth Token is valid", func(t *testing.T) { + // given + expectedResponse := &graphql.OneTimeTokenForRuntimeExt{ + OneTimeTokenForRuntime: graphql.OneTimeTokenForRuntime{ + TokenWithURL: graphql.TokenWithURL{ + Token: oneTimeToken, + ConnectorURL: connectorURL, + }, + }, + } + + gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse) + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID) + + // then + require.NoError(t, err) + require.NotEmpty(t, receivedOneTimeToken) + assert.Equal(t, oneTimeToken, receivedOneTimeToken) + assert.Equal(t, connectorURL, receivedConnectorURL) + }) + + t.Run("Should return error when Oauth Token is empty", func(t *testing.T) { + // given + token := oauth.Token{ + AccessToken: "", + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID) + + // then + require.Error(t, err) + require.Empty(t, receivedConnectorURL) + require.Empty(t, receivedOneTimeToken) + }) + + t.Run("Should return error when Oauth Token is expired", func(t *testing.T) { + // given + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: passedExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue) + + // when + receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID) + + // then + require.Error(t, err) + require.Empty(t, receivedConnectorURL) + require.Empty(t, receivedOneTimeToken) + }) + + t.Run("Should return error when Director call returns nil response", func(t *testing.T) { + // given + gqlClient := newQueryAssertClient[graphql.OneTimeTokenForRuntimeExt](t, expectedRequest, nil) + + token := oauth.Token{ + AccessToken: validTokenValue, + Expiration: futureExpirationTime, + } + + mockedOAuthClient := &oauthmocks.Client{} + mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil) + + configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue) + + // when + receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID) + + // then + require.Error(t, err) + require.Empty(t, receivedConnectorURL) + require.Empty(t, receivedOneTimeToken) + }) +} + +func newQueryAssertClient[T any](t *testing.T, expectedRequest *gcli.Request, expectedResponse *T) gql.Client { + return gql.NewQueryAssertClient(t, nil, []*gcli.Request{expectedRequest}, func(t *testing.T, r interface{}) { + cfg, ok := r.(*Response[T]) + require.True(t, ok) + assert.Empty(t, cfg.Result) + + if expectedResponse != nil { + cfg.Result = expectedResponse + } + }) +} + +func newFailingQueryAssertClient[T any](t *testing.T, expectedRequest *gcli.Request) gql.Client { + return gql.NewQueryAssertClient(t, nil, []*gcli.Request{expectedRequest}, func(t *testing.T, r interface{}) { + cfg, ok := r.(*Response[T]) + require.True(t, ok) + assert.Empty(t, cfg.Result) + cfg.Result = nil + }) +} diff --git a/tests/test/compass-runtime-agent/testkit/director/mocks/Client.go b/tests/test/compass-runtime-agent/testkit/director/mocks/Client.go new file mode 100644 index 00000000..ccfa1a77 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/director/mocks/Client.go @@ -0,0 +1,193 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +// AssignApplicationToFormation provides a mock function with given fields: appId, formationName +func (_m *Client) AssignApplicationToFormation(appId string, formationName string) error { + ret := _m.Called(appId, formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(appId, formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AssignRuntimeToFormation provides a mock function with given fields: runtimeId, formationName +func (_m *Client) AssignRuntimeToFormation(runtimeId string, formationName string) error { + ret := _m.Called(runtimeId, formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(runtimeId, formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetConnectionToken provides a mock function with given fields: runtimeID +func (_m *Client) GetConnectionToken(runtimeID string) (string, string, error) { + ret := _m.Called(runtimeID) + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(runtimeID) + } else { + r0 = ret.Get(0).(string) + } + + var r1 string + if rf, ok := ret.Get(1).(func(string) string); ok { + r1 = rf(runtimeID) + } else { + r1 = ret.Get(1).(string) + } + + var r2 error + if rf, ok := ret.Get(2).(func(string) error); ok { + r2 = rf(runtimeID) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// RegisterApplication provides a mock function with given fields: appName, displayName +func (_m *Client) RegisterApplication(appName string, displayName string) (string, error) { + ret := _m.Called(appName, displayName) + + var r0 string + if rf, ok := ret.Get(0).(func(string, string) string); ok { + r0 = rf(appName, displayName) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(appName, displayName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RegisterFormation provides a mock function with given fields: formationName +func (_m *Client) RegisterFormation(formationName string) error { + ret := _m.Called(formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RegisterRuntime provides a mock function with given fields: runtimeName +func (_m *Client) RegisterRuntime(runtimeName string) (string, error) { + ret := _m.Called(runtimeName) + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(runtimeName) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(runtimeName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnassignApplication provides a mock function with given fields: appId, formationName +func (_m *Client) UnassignApplication(appId string, formationName string) error { + ret := _m.Called(appId, formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(appId, formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnregisterApplication provides a mock function with given fields: id +func (_m *Client) UnregisterApplication(id string) error { + ret := _m.Called(id) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnregisterFormation provides a mock function with given fields: formationName +func (_m *Client) UnregisterFormation(formationName string) error { + ret := _m.Called(formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnregisterRuntime provides a mock function with given fields: id +func (_m *Client) UnregisterRuntime(id string) error { + ret := _m.Called(id) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t mockConstructorTestingTNewClient) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/director/mocks/DirectorClient.go b/tests/test/compass-runtime-agent/testkit/director/mocks/DirectorClient.go new file mode 100644 index 00000000..ee496d09 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/director/mocks/DirectorClient.go @@ -0,0 +1,60 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// DirectorClient is an autogenerated mock type for the DirectorClient type +type DirectorClient struct { + mock.Mock +} + +// RegisterApplication provides a mock function with given fields: appName, scenario, tenant +func (_m *DirectorClient) RegisterApplication(appName string, scenario string, tenant string) (string, error) { + ret := _m.Called(appName, scenario, tenant) + + var r0 string + if rf, ok := ret.Get(0).(func(string, string, string) string); ok { + r0 = rf(appName, scenario, tenant) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, string, string) error); ok { + r1 = rf(appName, scenario, tenant) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnregisterApplication provides a mock function with given fields: id, tenant +func (_m *DirectorClient) UnregisterApplication(id string, tenant string) error { + ret := _m.Called(id, tenant) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(id, tenant) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewDirectorClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewDirectorClient creates a new instance of DirectorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewDirectorClient(t mockConstructorTestingTNewDirectorClient) *DirectorClient { + mock := &DirectorClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/director/queryprovider.go b/tests/test/compass-runtime-agent/testkit/director/queryprovider.go new file mode 100644 index 00000000..c7cec324 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/director/queryprovider.go @@ -0,0 +1,127 @@ +package director + +import "fmt" + +type queryProvider struct{} + +func (qp queryProvider) createFormation(formationName string) string { + return fmt.Sprintf(`mutation { + result: createFormation(formation: { + name: "%s" + }) { id } }`, formationName) +} + +func (qp queryProvider) deleteFormation(formationName string) string { + return fmt.Sprintf(`mutation { + result: deleteFormation(formation: { + name: "%s" + }) { id } }`, formationName) +} + +func (qp queryProvider) registerApplicationFromTemplateMutation(appName, displayName string) string { + return fmt.Sprintf(`mutation { + result: registerApplicationFromTemplate(in: { + templateName: "SAP Commerce Cloud" + values: [ + { placeholder: "name", value: "%s" } + { placeholder: "display-name", value: "%s" } + ] + }) { id } }`, appName, displayName) +} + +func (qp queryProvider) addBundleMutation(appID string) string { + return fmt.Sprintf(`mutation { + result: addBundle( + applicationID: "%s" + in: { + name: "bndl-app-1" + description: "Foo bar" + apiDefinitions: [ + { + name: "comments-v1" + description: "api for adding comments" + targetURL: "http://mywordpress.com/comments" + group: "comments" + spec: { + data: "{\"openapi\":\"3.0.2\"}" + type: OPEN_API + format: YAML + } + version: { + value: "1.0.0" + deprecated: true + deprecatedSince: "v5" + forRemoval: false + } + } + ] + } + ) { id } }`, appID) +} + +func (qp queryProvider) updateApplicationMutation(id, description string) string { + return fmt.Sprintf(`mutation { + result: updateApplication( + id: "%s" + in: {description: "%s" + }) { id } }`, + id, description) +} + +func (qp queryProvider) assignFormationForAppMutation(applicationId, formationName string) string { + return fmt.Sprintf(`mutation { + result: assignFormation( + objectID: "%s" + objectType: APPLICATION + formation: { name: "%s" } + ) { id } }`, applicationId, formationName) +} + +func (qp queryProvider) unassignFormation(applicationId, formationName string) string { + return fmt.Sprintf(`mutation { + result: unassignFormation( + objectID: "%s" + objectType: APPLICATION + formation: { name: "%s" } + ) { + name + } +}`, applicationId, formationName) +} + +func (qp queryProvider) assignFormationForRuntimeMutation(runtimeId, formationName string) string { + return fmt.Sprintf(`mutation { + result: assignFormation( + objectID: "%s" + objectType: RUNTIME + formation: { name: "%s" } + ) { id } }`, runtimeId, formationName) +} + +func (qp queryProvider) unregisterApplicationMutation(applicationID string) string { + return fmt.Sprintf(`mutation { + result: unregisterApplication(id: "%s") { + id + } }`, applicationID) +} + +func (qp queryProvider) deleteRuntimeMutation(runtimeID string) string { + return fmt.Sprintf(`mutation { + result: unregisterRuntime(id: "%s") { + id + }}`, runtimeID) +} + +func (qp queryProvider) registerRuntimeMutation(runtimeName string) string { + return fmt.Sprintf(`mutation { + result: registerRuntime(in: { + name: "%s" + }) { id } }`, runtimeName) +} + +func (qp queryProvider) requestOneTimeTokenMutation(runtimeID string) string { + return fmt.Sprintf(`mutation { + result: requestOneTimeTokenForRuntime(id: "%s") { + token connectorURL + }}`, runtimeID) +} diff --git a/tests/test/compass-runtime-agent/testkit/executor/toolkit.go b/tests/test/compass-runtime-agent/testkit/executor/toolkit.go new file mode 100644 index 00000000..f132c9b5 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/executor/toolkit.go @@ -0,0 +1,53 @@ +package executor + +import ( + "context" + "github.com/avast/retry-go" + "github.com/pkg/errors" + "time" +) + +type RetryableExecuteFunc func() error +type ConditionMet func() bool + +type ExecuteAndWaitForCondition struct { + RetryableExecuteFunc RetryableExecuteFunc + ConditionMetFunc ConditionMet + Tick time.Duration + Timeout time.Duration +} + +func (e ExecuteAndWaitForCondition) Do() error { + + err := retry.Do(func() error { + return e.RetryableExecuteFunc() + }) + + if err != nil { + return err + } + ctx, cancel := context.WithTimeout(context.Background(), e.Timeout) + defer cancel() + + ticker := time.NewTicker(e.Tick) + + for { + select { + case <-ticker.C: + { + res := e.ConditionMetFunc() + + if res { + ticker.Stop() + return nil + } + + } + case <-ctx.Done(): + { + ticker.Stop() + return errors.New("Condition not met") + } + } + } +} diff --git a/tests/test/compass-runtime-agent/testkit/executor/toolkit_test.go b/tests/test/compass-runtime-agent/testkit/executor/toolkit_test.go new file mode 100644 index 00000000..8a634276 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/executor/toolkit_test.go @@ -0,0 +1,101 @@ +package executor + +import ( + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestToolkit(t *testing.T) { + t.Run("Should return no error when verify function returns true", func(t *testing.T) { + // given + executeAndWait := ExecuteAndWaitForCondition{ + RetryableExecuteFunc: func() error { + return nil + }, + ConditionMetFunc: func() bool { + return true + }, + Tick: 10 * time.Second, + Timeout: 1 * time.Minute, + } + + // when + err := executeAndWait.Do() + + //then + require.NoError(t, err) + }) + + t.Run("Retry when exec function fails", func(t *testing.T) { + // given + counter := 1 + + executeAndWait := ExecuteAndWaitForCondition{ + + RetryableExecuteFunc: func() error { + if counter < 3 { + counter++ + return errors.New("failed") + } + + return nil + }, + ConditionMetFunc: func() bool { + return true + }, + Tick: 10 * time.Second, + Timeout: 1 * time.Minute, + } + + // when + err := executeAndWait.Do() + + //then + require.NoError(t, err) + require.Greater(t, counter, 2) + }) + + t.Run("Return error when exec function constantly fails", func(t *testing.T) { + // given + executeAndWait := ExecuteAndWaitForCondition{ + + RetryableExecuteFunc: func() error { + return errors.New("call failed") + }, + ConditionMetFunc: func() bool { + return true + }, + Tick: 10 * time.Second, + Timeout: 1 * time.Minute, + } + + // when + err := executeAndWait.Do() + + //then + require.Error(t, err) + }) + + t.Run("Return error when verify function constantly returns false", func(t *testing.T) { + // given + executeAndWait := ExecuteAndWaitForCondition{ + + RetryableExecuteFunc: func() error { + return nil + }, + ConditionMetFunc: func() bool { + return false + }, + Tick: 10 * time.Second, + Timeout: 1 * time.Minute, + } + + // when + err := executeAndWait.Do() + + //then + require.Error(t, err) + }) +} diff --git a/tests/test/compass-runtime-agent/testkit/graphql/client.go b/tests/test/compass-runtime-agent/testkit/graphql/client.go new file mode 100644 index 00000000..888094ab --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/graphql/client.go @@ -0,0 +1,76 @@ +package graphql + +import ( + "context" + "crypto/tls" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql" + "net/http" + "time" + + "github.com/sirupsen/logrus" +) + +const ( + timeout = 30 * time.Second +) + +type ClientConstructor func(certificate *tls.Certificate, graphqlEndpoint string, enableLogging bool, insecureConfigFetch bool) (Client, error) + +//go:generate mockery --name=Client +type Client interface { + Do(req *graphql.Request, res interface{}) error +} + +type client struct { + gqlClient *graphql.Client + logs []string + logging bool +} + +func NewGraphQLClient(graphqlEndpoint string, enableLogging bool, insecureSkipVerify bool) Client { + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}, + }, + } + + gqlClient := graphql.NewClient(graphqlEndpoint, graphql.WithHTTPClient(httpClient)) + + client := &client{ + gqlClient: gqlClient, + logging: enableLogging, + logs: []string{}, + } + + client.gqlClient.Log = client.addLog + + return client +} + +func (c *client) Do(req *graphql.Request, res interface{}) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + c.clearLogs() + err := c.gqlClient.Run(ctx, req, res) + if err != nil { + for _, l := range c.logs { + if l != "" { + logrus.Info(l) + } + } + } + return err +} + +func (c *client) addLog(log string) { + if !c.logging { + return + } + + c.logs = append(c.logs, log) +} + +func (c *client) clearLogs() { + c.logs = []string{} +} diff --git a/tests/test/compass-runtime-agent/testkit/graphql/gql_client_testkit.go b/tests/test/compass-runtime-agent/testkit/graphql/gql_client_testkit.go new file mode 100644 index 00000000..1bfe2a77 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/graphql/gql_client_testkit.go @@ -0,0 +1,47 @@ +package graphql + +import ( + "errors" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql" + "testing" + + "github.com/stretchr/testify/assert" +) + +type QueryAssertClient struct { + t *testing.T + expectedRequests []*graphql.Request + err error + modifyResponseFunc ModifyResponseFunc +} + +type ModifyResponseFunc []func(t *testing.T, r interface{}) + +func (c *QueryAssertClient) Do(req *graphql.Request, res interface{}) error { + if len(c.expectedRequests) == 0 { + return errors.New("no more requests were expected") + } + + assert.Equal(c.t, c.expectedRequests[0], req) + if len(c.expectedRequests) > 1 { + c.expectedRequests = c.expectedRequests[1:] + } + + if len(c.modifyResponseFunc) > 0 { + c.modifyResponseFunc[0](c.t, res) + if len(c.modifyResponseFunc) > 1 { + c.modifyResponseFunc = c.modifyResponseFunc[1:] + } + } + + return c.err +} + +func NewQueryAssertClient(t *testing.T, err error, expectedReq []*graphql.Request, modifyResponseFunc ...func(t *testing.T, r interface{})) Client { + return &QueryAssertClient{ + t: t, + expectedRequests: expectedReq, + err: err, + modifyResponseFunc: modifyResponseFunc, + } +} diff --git a/tests/test/compass-runtime-agent/testkit/graphql/mocks/Client.go b/tests/test/compass-runtime-agent/testkit/graphql/mocks/Client.go new file mode 100644 index 00000000..e6a42c6c --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/graphql/mocks/Client.go @@ -0,0 +1,42 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + graphql "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql" + mock "github.com/stretchr/testify/mock" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +// Do provides a mock function with given fields: req, res +func (_m *Client) Do(req *graphql.Request, res interface{}) error { + ret := _m.Called(req, res) + + var r0 error + if rf, ok := ret.Get(0).(func(*graphql.Request, interface{}) error); ok { + r0 = rf(req, res) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t mockConstructorTestingTNewClient) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/init/certificatesecrets_test.go b/tests/test/compass-runtime-agent/testkit/init/certificatesecrets_test.go new file mode 100644 index 00000000..530e11af --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/certificatesecrets_test.go @@ -0,0 +1,78 @@ +package init + +import ( + "context" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "testing" +) + +func TestCertificateSecrets(t *testing.T) { + t.Run("should return rollback function that will remove secrets", func(t *testing.T) { + // given + fakeKubernetesInterface := fake.NewSimpleClientset() + + // when + configurator := NewCertificateSecretConfigurator(fakeKubernetesInterface) + rollbackFunc, err := configurator.Do("newCaSecret", "newClientSetSecret") + + // then + require.NoError(t, err) + + // given + caCertSecret := createSecret("newCaSecret", IstioSystemNamespace) + clientCertSecret := createSecret("newClientSetSecret", CompassSystemNamespace) + + _, err = fakeKubernetesInterface.CoreV1().Secrets(IstioSystemNamespace).Create(context.TODO(), caCertSecret, meta.CreateOptions{}) + require.NoError(t, err) + + _, err = fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Create(context.TODO(), clientCertSecret, meta.CreateOptions{}) + require.NoError(t, err) + + // when + err = rollbackFunc() + require.NoError(t, err) + + // then + _, err = fakeKubernetesInterface.CoreV1().Secrets("test").Get(context.TODO(), "newCaSecret", meta.GetOptions{}) + require.Error(t, err) + require.True(t, k8serrors.IsNotFound(err)) + + _, err = fakeKubernetesInterface.CoreV1().Secrets("test").Get(context.TODO(), "newClientSetSecret", meta.GetOptions{}) + require.Error(t, err) + require.True(t, k8serrors.IsNotFound(err)) + }) + + t.Run("should not return error when rollback function tries to delete non-existent secrets", func(t *testing.T) { + // given + fakeKubernetesInterface := fake.NewSimpleClientset() + + // when + configurator := NewCertificateSecretConfigurator(fakeKubernetesInterface) + rollbackFunc, err := configurator.Do("newCaSecret", "newClientSetSecret") + + // then + require.NoError(t, err) + + // when + err = rollbackFunc() + require.NoError(t, err) + }) + // TODO: consider a case when rollback function fails +} + +func createSecret(name, namespace string) *v1.Secret { + return &v1.Secret{ + ObjectMeta: meta.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + TypeMeta: meta.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + } +} diff --git a/tests/test/compass-runtime-agent/testkit/init/certificatessecrets.go b/tests/test/compass-runtime-agent/testkit/init/certificatessecrets.go new file mode 100644 index 00000000..3141ca9c --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/certificatessecrets.go @@ -0,0 +1,41 @@ +package init + +import ( + "github.com/hashicorp/go-multierror" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "k8s.io/client-go/kubernetes" +) + +type certificatesSecretsConfigurator struct { + kubernetesInterface kubernetes.Interface +} + +func NewCertificateSecretConfigurator(kubernetesInterface kubernetes.Interface) certificatesSecretsConfigurator { + return certificatesSecretsConfigurator{ + kubernetesInterface: kubernetesInterface, + } +} + +func (csc certificatesSecretsConfigurator) Do(newCASecretName, newClusterCertSecretName string) (types.RollbackFunc, error) { + // Original secrets created by Compass Runtime Agent are left intact so that they can be restored after the test. + // As part of the test preparation new secret names are passed to the Compass Runtime Agent Deployment. Rollback function needs to delete those. + return csc.getRollbackFunction(newCASecretName, newClusterCertSecretName), nil +} + +func (csc certificatesSecretsConfigurator) getRollbackFunction(caSecretName, clusterCertSecretName string) types.RollbackFunc { + return func() error { + var result *multierror.Error + + err := deleteSecretWithRetry(csc.kubernetesInterface, caSecretName, IstioSystemNamespace) + if err != nil { + multierror.Append(result, err) + } + + err = deleteSecretWithRetry(csc.kubernetesInterface, clusterCertSecretName, CompassSystemNamespace) + if err != nil { + multierror.Append(result, err) + } + + return result.ErrorOrNil() + } +} diff --git a/tests/test/compass-runtime-agent/testkit/init/compass.go b/tests/test/compass-runtime-agent/testkit/init/compass.go new file mode 100644 index 00000000..454b85bc --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/compass.go @@ -0,0 +1,53 @@ +package init + +import ( + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" +) + +type compassconfigurator struct { + directorClient types.DirectorClient + tenant string +} + +func NewCompassConfigurator(directorClient types.DirectorClient, tenant string) compassconfigurator { + return compassconfigurator{ + directorClient: directorClient, + tenant: tenant, + } +} + +func (cc compassconfigurator) Do(runtimeName, formationName string) (types.CompassRuntimeAgentConfig, types.RollbackFunc, error) { + runtimeID, err := cc.directorClient.RegisterRuntime(runtimeName) + if err != nil { + return types.CompassRuntimeAgentConfig{}, nil, err + } + + unregisterRuntimeRollbackFunc := func() error { return cc.directorClient.UnregisterRuntime(runtimeID) } + + err = cc.directorClient.RegisterFormation(formationName) + if err != nil { + return types.CompassRuntimeAgentConfig{}, unregisterRuntimeRollbackFunc, err + } + + unregisterFormationRollbackFunc := func() error { return cc.directorClient.UnregisterFormation(formationName) } + rollBackFunc := newRollbackFunc(unregisterRuntimeRollbackFunc, unregisterFormationRollbackFunc) + + err = cc.directorClient.AssignRuntimeToFormation(runtimeID, formationName) + if err != nil { + return types.CompassRuntimeAgentConfig{}, rollBackFunc, err + } + + token, compassConnectorUrl, err := cc.directorClient.GetConnectionToken(runtimeID) + if err != nil { + return types.CompassRuntimeAgentConfig{}, rollBackFunc, err + } + + config := types.CompassRuntimeAgentConfig{ + ConnectorUrl: compassConnectorUrl, + RuntimeID: runtimeID, + Token: token, + Tenant: cc.tenant, + } + + return config, rollBackFunc, nil +} diff --git a/tests/test/compass-runtime-agent/testkit/init/compass_test.go b/tests/test/compass-runtime-agent/testkit/init/compass_test.go new file mode 100644 index 00000000..d47ca08d --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/compass_test.go @@ -0,0 +1,159 @@ +package init + +import ( + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types/mocks" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "testing" +) + +func TestCompassConfigurator(t *testing.T) { + runtimeName := "runtime" + runtimeID := "runtimeID" + formationName := "formation" + connectionToken := "token" + connectorURL := "connector.com" + tenant := "tenant" + + t.Run("should register Runtime, Formation and get connection token", func(t *testing.T) { + // given + directorClientMock := &mocks.DirectorClient{} + directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil) + directorClientMock.On("RegisterFormation", formationName).Return(nil) + + directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil) + directorClientMock.On("UnregisterFormation", formationName).Return(nil) + + directorClientMock.On("GetConnectionToken", runtimeID).Return(connectionToken, connectorURL, nil) + directorClientMock.On("AssignRuntimeToFormation", runtimeID, formationName).Return(nil) + + // when + compassConfigurator := NewCompassConfigurator(directorClientMock, tenant) + require.NotNil(t, compassConfigurator) + + compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName) + + // then + require.NotNil(t, rollbackFunc) + require.NoError(t, err) + require.Equal(t, runtimeID, compassRuntimeAgentConfig.RuntimeID) + require.Equal(t, tenant, compassRuntimeAgentConfig.Tenant) + require.Equal(t, connectionToken, compassRuntimeAgentConfig.Token) + require.Equal(t, connectorURL, compassRuntimeAgentConfig.ConnectorUrl) + + // when + err = rollbackFunc() + + // then + require.NoError(t, err) + directorClientMock.AssertExpectations(t) + }) + + t.Run("should fail when failed to register Runtime", func(t *testing.T) { + // given + directorClientMock := &mocks.DirectorClient{} + directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, errors.New("some error")) + + // when + compassConfigurator := NewCompassConfigurator(directorClientMock, tenant) + require.NotNil(t, compassConfigurator) + + compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName) + + // then + require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig) + require.Nil(t, rollbackFunc) + require.Error(t, err) + directorClientMock.AssertExpectations(t) + }) + + t.Run("should fail when failed to register Formation", func(t *testing.T) { + // given + directorClientMock := &mocks.DirectorClient{} + directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil) + directorClientMock.On("RegisterFormation", formationName).Return(errors.New("some error")) + directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil) + + // when + compassConfigurator := NewCompassConfigurator(directorClientMock, tenant) + require.NotNil(t, compassConfigurator) + + compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName) + + // then + require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig) + require.NotNil(t, rollbackFunc) + require.Error(t, err) + + // when + err = rollbackFunc() + + // then + require.NoError(t, err) + directorClientMock.AssertExpectations(t) + }) + + t.Run("should fail when failed to assign Runtime to Formation", func(t *testing.T) { + // given + directorClientMock := &mocks.DirectorClient{} + directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil) + directorClientMock.On("RegisterFormation", formationName).Return(nil) + + directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil) + directorClientMock.On("UnregisterFormation", formationName).Return(nil) + + directorClientMock.On("AssignRuntimeToFormation", runtimeID, formationName).Return(errors.New("some error")) + + // when + compassConfigurator := NewCompassConfigurator(directorClientMock, tenant) + require.NotNil(t, compassConfigurator) + + compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName) + + // then + require.NotNil(t, compassConfigurator) + require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig) + require.NotNil(t, rollbackFunc) + require.Error(t, err) + + // when + err = rollbackFunc() + + // then + require.NoError(t, err) + directorClientMock.AssertExpectations(t) + }) + + t.Run("should fail when failed to get connection token", func(t *testing.T) { + // given + directorClientMock := &mocks.DirectorClient{} + directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil) + directorClientMock.On("RegisterFormation", formationName).Return(nil) + + directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil) + directorClientMock.On("UnregisterFormation", formationName).Return(nil) + + directorClientMock.On("AssignRuntimeToFormation", runtimeID, formationName).Return(nil) + directorClientMock.On("GetConnectionToken", runtimeID).Return("", "", errors.New("some error")) + + // when + compassConfigurator := NewCompassConfigurator(directorClientMock, tenant) + require.NotNil(t, compassConfigurator) + + compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName) + + // then + require.NotNil(t, compassConfigurator) + require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig) + require.NotNil(t, rollbackFunc) + require.Error(t, err) + + // when + err = rollbackFunc() + + // then + require.NoError(t, err) + directorClientMock.AssertExpectations(t) + }) +} diff --git a/tests/test/compass-runtime-agent/testkit/init/compassconnection.go b/tests/test/compass-runtime-agent/testkit/init/compassconnection.go new file mode 100644 index 00000000..a5ae506a --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/compassconnection.go @@ -0,0 +1,103 @@ +package init + +import ( + "context" + "github.com/avast/retry-go" + "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/apis/compass/v1alpha1" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "github.com/pkg/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type compassConnectionCRConfiguration struct { + compassConnectionInterface CompassConnectionInterface +} + +const ( + ConnectionCRName = "compass-connection" + ConnectionBackupCRName = "compass-connection-backup" +) + +//go:generate mockery --name=CompassConnectionInterface +type CompassConnectionInterface interface { + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.CompassConnection, error) + Create(ctx context.Context, compassConnection *v1alpha1.CompassConnection, opts v1.CreateOptions) (*v1alpha1.CompassConnection, error) + Update(ctx context.Context, compassConnection *v1alpha1.CompassConnection, opts v1.UpdateOptions) (*v1alpha1.CompassConnection, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error +} + +func NewCompassConnectionCRConfiguration(compassConnectionInterface CompassConnectionInterface) compassConnectionCRConfiguration { + return compassConnectionCRConfiguration{ + compassConnectionInterface: compassConnectionInterface, + } +} + +func (cc compassConnectionCRConfiguration) Do() (types.RollbackFunc, error) { + return newRollbackFunc(), nil +} +func (cc compassConnectionCRConfiguration) backup() (types.RollbackFunc, error) { + compassConnectionCR, err := cc.compassConnectionInterface.Get(context.TODO(), ConnectionCRName, meta.GetOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to get Compass Connection CR") + } + + compassConnectionCR.ResourceVersion = "" + + compassConnectionCRBackup := compassConnectionCR.DeepCopy() + compassConnectionCRBackup.ObjectMeta.Name = "compass-connection-backup" + _, err = cc.compassConnectionInterface.Create(context.TODO(), compassConnectionCRBackup, meta.CreateOptions{}) + if err != nil { + return nil, errors.Wrap(err, "failed to create Compass Connection CR") + } + + rollbackFunc := func() error { + return retry.Do(func() error { + err = cc.compassConnectionInterface.Delete(context.TODO(), "compass-connection-backup", meta.DeleteOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + return errors.Wrap(err, "failed to delete Compass Connection CR") + } + + return nil + }) + } + + return rollbackFunc, nil +} + +func (cc compassConnectionCRConfiguration) delete() (types.RollbackFunc, error) { + err := cc.compassConnectionInterface.Delete(context.TODO(), ConnectionCRName, meta.DeleteOptions{}) + + if err != nil { + return nil, errors.Wrap(err, "failed to delete Compass Connection CR") + } + + rollbackFunc := func() error { + return retry.Do(func() error { + restoredCompassConnection, err := cc.compassConnectionInterface.Get(context.TODO(), ConnectionCRName, meta.GetOptions{}) + if err != nil { + return err + } + + compassConnectionCRBackup, err := cc.compassConnectionInterface.Get(context.TODO(), ConnectionBackupCRName, meta.GetOptions{}) + if err != nil { + return err + } + + restoredCompassConnection.Spec = compassConnectionCRBackup.Spec + restoredCompassConnection.Status = compassConnectionCRBackup.Status + + _, err = cc.compassConnectionInterface.Update(context.TODO(), restoredCompassConnection, meta.UpdateOptions{}) + if err != nil { + return errors.Wrap(err, "failed to update Compass Connection CR") + } + return err + }) + } + + return rollbackFunc, nil +} diff --git a/tests/test/compass-runtime-agent/testkit/init/compassconnection_test.go b/tests/test/compass-runtime-agent/testkit/init/compassconnection_test.go new file mode 100644 index 00000000..36ec6ee1 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/compassconnection_test.go @@ -0,0 +1,139 @@ +package init + +import ( + "context" + "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/apis/compass/v1alpha1" + "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/client/clientset/versioned/fake" + "github.com/stretchr/testify/require" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" +) + +func TestCompassConnectionConfigurator(t *testing.T) { + t.Run("should delete CompassConnection CR and restore it when RollbackFunction is called", func(t *testing.T) { + // given + compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections() + compassConnection := &v1alpha1.CompassConnection{ + ObjectMeta: meta.ObjectMeta{ + Name: ConnectionCRName, + }, + TypeMeta: meta.TypeMeta{ + Kind: "CompassConnection", + APIVersion: "v1alpha", + }, + } + + _, err := compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{}) + require.NoError(t, err) + + // when + configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake) + rollbackFunc, err := configurator.Do() + + // then + require.NoError(t, err) + _, err = compassConnectionCRFake.Get(context.TODO(), "compass-connection", meta.GetOptions{}) + require.Error(t, err) + require.True(t, k8serrors.IsNotFound(err)) + + _, err = compassConnectionCRFake.Get(context.TODO(), "compass-connection-backup", meta.GetOptions{}) + require.NoError(t, err) + + _, err = compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{}) + require.NoError(t, err) + // when + err = rollbackFunc() + + // then + require.NoError(t, err) + _, err = compassConnectionCRFake.Get(context.TODO(), "compass-connection", meta.GetOptions{}) + require.NoError(t, err) + }) + + t.Run("should fail when CompassConnection CR doesn't exist", func(t *testing.T) { + // given + compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections() + + // when + configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake) + _, err := configurator.Do() + + // then + require.Error(t, err) + }) + + t.Run("should fail when CompassConnection CR backup already exist", func(t *testing.T) { + // given + compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections() + compassConnection := &v1alpha1.CompassConnection{ + ObjectMeta: meta.ObjectMeta{ + Name: ConnectionCRName, + }, + TypeMeta: meta.TypeMeta{ + Kind: "CompassConnection", + APIVersion: "v1alpha", + }, + } + + compassConnectionBackup := &v1alpha1.CompassConnection{ + ObjectMeta: meta.ObjectMeta{ + Name: ConnectionBackupCRName, + }, + TypeMeta: meta.TypeMeta{ + Kind: "CompassConnection", + APIVersion: "v1alpha", + }, + } + + _, err := compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{}) + require.NoError(t, err) + + _, err = compassConnectionCRFake.Create(context.TODO(), compassConnectionBackup, meta.CreateOptions{}) + require.NoError(t, err) + + // when + configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake) + rollbackFunc, err := configurator.Do() + + // then + require.Nil(t, rollbackFunc) + require.Error(t, err) + }) + + t.Run("rollback function should fail when CompassConnection CR backup doesn't exist", func(t *testing.T) { + // given + compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections() + compassConnection := &v1alpha1.CompassConnection{ + ObjectMeta: meta.ObjectMeta{ + Name: ConnectionCRName, + }, + TypeMeta: meta.TypeMeta{ + Kind: "CompassConnection", + APIVersion: "v1alpha", + }, + } + + _, err := compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{}) + require.NoError(t, err) + + // when + configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake) + rollbackFunc, err := configurator.Do() + + // then + require.NoError(t, err) + + // when + _, err = compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{}) + require.NoError(t, err) + + err = compassConnectionCRFake.Delete(context.TODO(), ConnectionBackupCRName, meta.DeleteOptions{}) + require.NoError(t, err) + + err = rollbackFunc() + + // then + require.Error(t, err) + }) +} diff --git a/tests/test/compass-runtime-agent/testkit/init/configurationsecret.go b/tests/test/compass-runtime-agent/testkit/init/configurationsecret.go new file mode 100644 index 00000000..eaa6b04a --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/configurationsecret.go @@ -0,0 +1,83 @@ +package init + +import ( + "context" + "github.com/avast/retry-go" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "time" +) + +const ( + connectorURLConfigKey = "CONNECTOR_URL" + tokenConfigKey = "TOKEN" + runtimeIdConfigKey = "RUNTIME_ID" + tenantConfigKey = "TENANT" +) + +type configurationSecretConfigurator struct { + kubernetesInterface kubernetes.Interface +} + +func NewConfigurationSecretConfigurator(kubernetesInterface kubernetes.Interface) configurationSecretConfigurator { + return configurationSecretConfigurator{ + kubernetesInterface: kubernetesInterface, + } +} + +func (s configurationSecretConfigurator) Do(newConfigSecretName string, config types.CompassRuntimeAgentConfig) (types.RollbackFunc, error) { + + secret := v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: newConfigSecretName, + Namespace: CompassSystemNamespace, + }, + Data: map[string][]byte{ + connectorURLConfigKey: []byte(config.ConnectorUrl), + tokenConfigKey: []byte(config.Token), + runtimeIdConfigKey: []byte(config.RuntimeID), + tenantConfigKey: []byte(config.Tenant), + }, + } + + err := retry.Do(func() error { + _, err := s.kubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Create(context.Background(), &secret, metav1.CreateOptions{}) + if err != nil { + if k8serrors.IsAlreadyExists(err) { + return retry.Unrecoverable(err) + } + return errors.Wrap(err, "failed to create configuration secret") + } + + return nil + }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second)) + + if err != nil { + return nil, err + } + + return s.newRollbackSecretFunc(newConfigSecretName, CompassSystemNamespace), nil +} + +func (s configurationSecretConfigurator) newRollbackSecretFunc(name, namespace string) types.RollbackFunc { + return func() error { + return deleteSecretWithRetry(s.kubernetesInterface, name, namespace) + } +} + +func deleteSecretWithRetry(kubernetesInterface kubernetes.Interface, name, namespace string) error { + return retry.Do(func() error { + err := kubernetesInterface.CoreV1().Secrets(namespace).Delete(context.Background(), name, metav1.DeleteOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return nil + } + } + + return errors.Wrap(err, "failed to delete secret") + }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second)) +} diff --git a/tests/test/compass-runtime-agent/testkit/init/configurationsecret_test.go b/tests/test/compass-runtime-agent/testkit/init/configurationsecret_test.go new file mode 100644 index 00000000..b3fd06fb --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/configurationsecret_test.go @@ -0,0 +1,73 @@ +package init + +import ( + "context" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "github.com/stretchr/testify/require" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "testing" +) + +func TestConfigurationSecret(t *testing.T) { + t.Run("should create configuration secret", func(t *testing.T) { + // given + fakeKubernetesInterface := fake.NewSimpleClientset() + secretConfigurator := NewConfigurationSecretConfigurator(fakeKubernetesInterface) + connectorURL := "www.example.com" + runtimeID := "runtimeID" + token := "token" + tenant := "tenant" + + config := types.CompassRuntimeAgentConfig{ + ConnectorUrl: connectorURL, + RuntimeID: runtimeID, + Token: token, + Tenant: tenant, + } + secretName := "config" + + // when + rollbackFunc, err := secretConfigurator.Do(secretName, config) + require.NotNil(t, rollbackFunc) + require.NoError(t, err) + + // then + secret, err := fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Get(context.TODO(), secretName, meta.GetOptions{}) + require.NoError(t, err) + + require.Equal(t, connectorURL, string(secret.Data[connectorURLConfigKey])) + require.Equal(t, token, string(secret.Data[tokenConfigKey])) + require.Equal(t, runtimeID, string(secret.Data[runtimeIdConfigKey])) + require.Equal(t, tenant, string(secret.Data[tenantConfigKey])) + + // when + err = rollbackFunc() + require.NoError(t, err) + + _, err = fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Get(context.TODO(), secretName, meta.GetOptions{}) + require.Error(t, err) + require.True(t, k8serrors.IsNotFound(err)) + }) + + t.Run("should return error when failed to create secret", func(t *testing.T) { + // given + fakeKubernetesInterface := fake.NewSimpleClientset() + secretConfigurator := NewConfigurationSecretConfigurator(fakeKubernetesInterface) + + config := types.CompassRuntimeAgentConfig{} + secretName := "config" + + // when + secret := createSecret(secretName, CompassSystemNamespace) + _, err := fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Create(context.Background(), secret, meta.CreateOptions{}) + require.NoError(t, err) + + rollbackFunc, err := secretConfigurator.Do(secretName, config) + + // then + require.Nil(t, rollbackFunc) + require.Error(t, err) + }) +} diff --git a/tests/test/compass-runtime-agent/testkit/init/deployment.go b/tests/test/compass-runtime-agent/testkit/init/deployment.go new file mode 100644 index 00000000..ffe11af0 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/deployment.go @@ -0,0 +1,160 @@ +package init + +import ( + "context" + "fmt" + "github.com/avast/retry-go" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "github.com/pkg/errors" + v12 "k8s.io/api/apps/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + v13 "k8s.io/client-go/kubernetes/typed/apps/v1" + "time" +) + +const ( + CRAContainerNumber = 0 + ConfigurationSecretEnvName = "APP_AGENT_CONFIGURATION_SECRET" + CASecretEnvName = "APP_CA_CERTIFICATES_SECRET" + ClusterCertSecretEnvName = "APP_CLUSTER_CERTIFICATES_SECRET" + ControllerSyncPeriodEnvTime = "APP_CONTROLLER_SYNC_PERIOD" +) + +type deploymentConfiguration struct { + kubernetesInterface kubernetes.Interface + deploymentName string + namespaceName string +} + +func NewDeploymentConfiguration(kubernetesInterface kubernetes.Interface, deploymentName, namespaceName string) deploymentConfiguration { + return deploymentConfiguration{ + kubernetesInterface: kubernetesInterface, + deploymentName: deploymentName, + namespaceName: namespaceName, + } +} + +func (dc deploymentConfiguration) Do(newCANamespacedSecretName, newClusterNamespacedCertSecretName, newConfigNamespacedSecretName, newControllerSyncPeriodTime string) (types.RollbackFunc, error) { + deploymentInterface := dc.kubernetesInterface.AppsV1().Deployments(dc.namespaceName) + + deployment, err := retryGetDeployment(dc.deploymentName, deploymentInterface) + if err != nil { + return nil, err + } + + if len(deployment.Spec.Template.Spec.Containers) < 1 { + return nil, fmt.Errorf("no containers found in %s/%s deployment", "kyma-system", dc.deploymentName) + } + + previousConfigSecretNamespacedName, found := replaceEnvValue(deployment, ConfigurationSecretEnvName, newConfigNamespacedSecretName) + if !found { + return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", ConfigurationSecretEnvName, dc.deploymentName) + } + + previousCASecretNamespacedName, found := replaceEnvValue(deployment, CASecretEnvName, newCANamespacedSecretName) + if !found { + return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", CASecretEnvName, dc.deploymentName) + } + + previousCertSecretNamespacedName, found := replaceEnvValue(deployment, ClusterCertSecretEnvName, newClusterNamespacedCertSecretName) + if !found { + return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", ClusterCertSecretEnvName, dc.deploymentName) + } + + previousControllerSyncPeriodTime, found := replaceEnvValue(deployment, ControllerSyncPeriodEnvTime, newControllerSyncPeriodTime) + if !found { + return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", ControllerSyncPeriodEnvTime, dc.deploymentName) + } + + err = retryUpdateDeployment(deployment, deploymentInterface) + if err != nil { + return nil, err + } + rollbackDeploymentFunc := newRollbackDeploymentFunc(dc.deploymentName, previousConfigSecretNamespacedName, previousCASecretNamespacedName, previousCertSecretNamespacedName, previousControllerSyncPeriodTime, deploymentInterface) + + err = waitForRollout(dc.deploymentName, deploymentInterface) + + return rollbackDeploymentFunc, err +} + +func newRollbackDeploymentFunc(name, previousConfigSecretNamespacedName, previousCASecretNamespacedName, previousCertSecretNamespacedName, previousControllerSyncPeriodTime string, deploymentInterface v13.DeploymentInterface) types.RollbackFunc { + return func() error { + deployment, err := retryGetDeployment(name, deploymentInterface) + if err != nil { + return err + } + + _, found := replaceEnvValue(deployment, ConfigurationSecretEnvName, previousConfigSecretNamespacedName) + if !found { + return fmt.Errorf("environment variable '%s' not found in %s deployment", ConfigurationSecretEnvName, name) + } + + _, found = replaceEnvValue(deployment, CASecretEnvName, previousCASecretNamespacedName) + if !found { + return fmt.Errorf("environment variable '%s' not found in %s deployment", CASecretEnvName, name) + } + + _, found = replaceEnvValue(deployment, ClusterCertSecretEnvName, previousCertSecretNamespacedName) + if !found { + return fmt.Errorf("environment variable '%s' not found in %s deployment", ClusterCertSecretEnvName, name) + } + + _, found = replaceEnvValue(deployment, ControllerSyncPeriodEnvTime, previousControllerSyncPeriodTime) + if !found { + return fmt.Errorf("environment variable '%s' not found in %s deployment", ControllerSyncPeriodEnvTime, name) + } + + return retryUpdateDeployment(deployment, deploymentInterface) + } +} + +func replaceEnvValue(deployment *v12.Deployment, name, newValue string) (string, bool) { + envs := deployment.Spec.Template.Spec.Containers[CRAContainerNumber].Env + for i := range envs { + if envs[i].Name == name { + previousValue := envs[i].Value + envs[i].Value = newValue + deployment.Spec.Template.Spec.Containers[CRAContainerNumber].Env = envs + + return previousValue, true + } + } + + return "", false +} + +func retryGetDeployment(name string, deploymentInterface v13.DeploymentInterface) (*v12.Deployment, error) { + var deployment *v12.Deployment + + err := retry.Do(func() error { + var err error + deployment, err = deploymentInterface.Get(context.TODO(), name, v1.GetOptions{}) + if err != nil { + return errors.Wrap(err, "failed to get Compass Runtime Agent deployment") + } + return nil + }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second)) + + return deployment, err +} + +func retryUpdateDeployment(deployment *v12.Deployment, deploymentInterface v13.DeploymentInterface) error { + return retry.Do(func() error { + _, err := deploymentInterface.Update(context.TODO(), deployment, v1.UpdateOptions{}) + return errors.Wrap(err, "failed to update Compass Runtime Agent deployment") + }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second)) +} + +func waitForRollout(name string, deploymentInterface v13.DeploymentInterface) error { + return retry.Do(func() error { + deployment, err := deploymentInterface.Get(context.TODO(), name, v1.GetOptions{}) + if err != nil { + return errors.Wrap(err, "failed to get Compass Runtime Agent deployment") + } + if deployment.Status.AvailableReplicas == 0 || deployment.Status.UnavailableReplicas != 0 { + return fmt.Errorf("deployment %s is not yet ready", name) + } + return nil + }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second)) +} diff --git a/tests/test/compass-runtime-agent/testkit/init/init.go b/tests/test/compass-runtime-agent/testkit/init/init.go new file mode 100644 index 00000000..94849b3c --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/init.go @@ -0,0 +1,141 @@ +package init + +import ( + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + log "github.com/sirupsen/logrus" +) + +const ( + CompassSystemNamespace = "kyma-system" + IstioSystemNamespace = "istio-system" + CompassRuntimeAgentDeployment = "compass-runtime-agent" + NewCompassRuntimeConfigName = "compass-agent-configuration" + NewCACertSecretName = "ca-cert-test" + NewClientCertSecretName = "client-cert-test" + NewControllerSyncPeriodTime = "15s" + RetryAttempts = 6 + RetrySeconds = 5 +) + +type CompassRuntimeAgentConfigurator interface { + Do(runtimeName, formationName string) (types.RollbackFunc, error) +} + +type Configurator interface { + Configure(runtimeName, formationName string) (types.RollbackFunc, error) +} + +type compassRuntimeAgentConfigurator struct { + compassConfigurator types.CompassConfigurator + certificateSecretConfigurator types.CertificateSecretConfigurator + configurationSecretConfigurator types.ConfigurationSecretConfigurator + compassConnectionConfigurator types.CompassConnectionConfigurator + deploymentConfigurator types.DeploymentConfigurator + testNamespace string +} + +func NewCompassRuntimeAgentConfigurator(compassConfigurator types.CompassConfigurator, + certificateSecretConfigurator types.CertificateSecretConfigurator, + configurationSecretConfigurator types.ConfigurationSecretConfigurator, + compassConnectionConfigurator types.CompassConnectionConfigurator, + deploymentConfigurator types.DeploymentConfigurator, + testNamespace string) CompassRuntimeAgentConfigurator { + return compassRuntimeAgentConfigurator{ + compassConfigurator: compassConfigurator, + certificateSecretConfigurator: certificateSecretConfigurator, + configurationSecretConfigurator: configurationSecretConfigurator, + compassConnectionConfigurator: compassConnectionConfigurator, + deploymentConfigurator: deploymentConfigurator, + testNamespace: testNamespace, + } +} + +func (crc compassRuntimeAgentConfigurator) Do(runtimeName, formationName string) (types.RollbackFunc, error) { + log.Info("Configuring Compass") + compassRuntimeAgentConfig, compassConfiguratorRollbackFunc, err := crc.compassConfigurator.Do(runtimeName, formationName) + if err != nil { + return nil, crc.rollbackOnError(err, + compassConfiguratorRollbackFunc) + } + + log.Info("Configuring certificate secrets") + certificateSecretsRollbackFunc, err := crc.certificateSecretConfigurator.Do(NewCACertSecretName, NewClientCertSecretName) + if err != nil { + return nil, crc.rollbackOnError(err, + compassConfiguratorRollbackFunc, + certificateSecretsRollbackFunc) + } + + log.Info("Preparing Compass Runtime Agent configuration secret") + configurationSecretRollbackFunc, err := crc.configurationSecretConfigurator.Do(NewCompassRuntimeConfigName, compassRuntimeAgentConfig) + if err != nil { + return nil, crc.rollbackOnError(err, + compassConfiguratorRollbackFunc, + certificateSecretsRollbackFunc, + configurationSecretRollbackFunc) + } + + newCACertNamespacedSecretName := fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName) + newClientCertNamespacedSecretName := fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName) + newCompassRuntimeNamespacedSecretConfigName := fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName) + newControllerSyncPeriodTime := NewControllerSyncPeriodTime + + log.Info("Preparing Compass Runtime Agent configuration secret") + deploymentRollbackFunc, err := crc.deploymentConfigurator.Do(newCACertNamespacedSecretName, + newClientCertNamespacedSecretName, + newCompassRuntimeNamespacedSecretConfigName, newControllerSyncPeriodTime) + if err != nil { + return nil, crc.rollbackOnError(err, + compassConfiguratorRollbackFunc, + certificateSecretsRollbackFunc, + configurationSecretRollbackFunc, + deploymentRollbackFunc) + } + + compassConnectionRollbackFunc, err := crc.compassConnectionConfigurator.Do() + if err != nil { + return nil, crc.rollbackOnError(err, + compassConfiguratorRollbackFunc, + certificateSecretsRollbackFunc, + configurationSecretRollbackFunc, + deploymentRollbackFunc, + compassConnectionRollbackFunc) + } + + return newRollbackFunc(compassConfiguratorRollbackFunc, + certificateSecretsRollbackFunc, + configurationSecretRollbackFunc, + deploymentRollbackFunc, + compassConnectionRollbackFunc), nil +} + +func (crc compassRuntimeAgentConfigurator) rollbackOnError(initialErr error, rollbackFunctions ...types.RollbackFunc) error { + var result *multierror.Error + result = multierror.Append(result, initialErr) + + err := newRollbackFunc(rollbackFunctions...)() + if err != nil { + result = multierror.Append(result, err) + } + + return result.ErrorOrNil() +} + +func newRollbackFunc(rollbackFunctions ...types.RollbackFunc) types.RollbackFunc { + var result *multierror.Error + + return func() error { + for _, f := range rollbackFunctions { + if f != nil { + if err := f(); err != nil { + result = multierror.Append(result, err) + } + } + } + + return result.ErrorOrNil() + } +} diff --git a/tests/test/compass-runtime-agent/testkit/init/init_test.go b/tests/test/compass-runtime-agent/testkit/init/init_test.go new file mode 100644 index 00000000..9078c01e --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/init_test.go @@ -0,0 +1,232 @@ +package init + +import ( + "fmt" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types/mocks" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestCompassRuntimeAgentInit(t *testing.T) { + runtimeName := "newRuntime" + runtimeID := "runtimeID" + token := "token" + connectorURL := "www.someurl.com" + tenant := "tenant" + formationName := "newFormation" + + t.Run("should succeed and return rollback function", func(t *testing.T) { + // given + compassConfiguratorMock := &mocks.CompassConfigurator{} + certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{} + configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{} + compassConnectionConfiguratorMock := &mocks.CompassConnectionConfigurator{} + deploymentConfiguratorMock := &mocks.DeploymentConfigurator{} + + compassConfiguratorRollbackFunc := RollbackFuncTest{} + certificateSecretsRollbackFunc := RollbackFuncTest{} + configurationSecretRollbackFunc := RollbackFuncTest{} + compassConnectionRollbackFunc := RollbackFuncTest{} + deploymentRollbackFunc := RollbackFuncTest{} + + config := types.CompassRuntimeAgentConfig{ + ConnectorUrl: connectorURL, + RuntimeID: runtimeID, + Token: token, + Tenant: tenant, + } + + compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil) + certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil) + configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(configurationSecretRollbackFunc.Func(), nil) + compassConnectionConfiguratorMock.On("Do").Return(compassConnectionRollbackFunc.Func(), nil) + deploymentConfiguratorMock.On("Do", + fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName), + fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName), + fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName), + fmt.Sprintf("%s", NewControllerSyncPeriodTime)). + Return(deploymentRollbackFunc.Func(), nil) + + configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, compassConnectionConfiguratorMock, deploymentConfiguratorMock, "tenant") + + // when + rollbackFunc, err := configurator.Do(runtimeName, formationName) + + // then + require.NoError(t, err) + certificateSecretConfiguratorMock.AssertExpectations(t) + compassConnectionConfiguratorMock.AssertExpectations(t) + deploymentConfiguratorMock.AssertExpectations(t) + + //when + err = rollbackFunc() + + // then + require.NoError(t, err) + require.True(t, compassConfiguratorRollbackFunc.invoked) + require.True(t, certificateSecretsRollbackFunc.invoked) + require.True(t, configurationSecretRollbackFunc.invoked) + require.True(t, compassConnectionRollbackFunc.invoked) + require.True(t, deploymentRollbackFunc.invoked) + }) + + t.Run("should fail if failed to register runtime", func(t *testing.T) { + // given + compassConfiguratorMock := &mocks.CompassConfigurator{} + compassConfiguratorRollbackFunc := RollbackFuncTest{} + + compassConfiguratorMock.On("Do", runtimeName, formationName).Return(types.CompassRuntimeAgentConfig{}, compassConfiguratorRollbackFunc.Func(), errors.New("some error")) + + configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, nil, nil, nil, nil, "tenant") + + // when + rollbackFunc, err := configurator.Do(runtimeName, formationName) + + // then + require.Error(t, err) + require.Nil(t, rollbackFunc) + assert.True(t, compassConfiguratorRollbackFunc.invoked) + }) + + t.Run("should fail if failed to create configuration secret", func(t *testing.T) { + // given + compassConfiguratorMock := &mocks.CompassConfigurator{} + certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{} + configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{} + + compassConfiguratorRollbackFunc := RollbackFuncTest{} + certificateSecretsRollbackFunc := RollbackFuncTest{} + + config := types.CompassRuntimeAgentConfig{ + ConnectorUrl: connectorURL, + RuntimeID: runtimeID, + Token: token, + Tenant: tenant, + } + + compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil) + certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil) + configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(nil, errors.New("some error")) + + configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, nil, nil, "tenant") + + // when + rollbackFunc, err := configurator.Do(runtimeName, formationName) + + // then + require.Error(t, err) + require.Nil(t, rollbackFunc) + compassConfiguratorMock.AssertExpectations(t) + certificateSecretConfiguratorMock.AssertExpectations(t) + certificateSecretConfiguratorMock.AssertExpectations(t) + require.True(t, compassConfiguratorRollbackFunc.invoked) + require.True(t, certificateSecretsRollbackFunc.invoked) + }) + + t.Run("should fail if failed to modify deployment", func(t *testing.T) { + // given + compassConfiguratorMock := &mocks.CompassConfigurator{} + certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{} + configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{} + deploymentConfiguratorMock := &mocks.DeploymentConfigurator{} + + compassConfiguratorRollbackFunc := RollbackFuncTest{} + certificateSecretsRollbackFunc := RollbackFuncTest{} + configurationSecretRollbackFunc := RollbackFuncTest{} + + config := types.CompassRuntimeAgentConfig{ + ConnectorUrl: connectorURL, + RuntimeID: runtimeID, + Token: token, + Tenant: tenant, + } + + compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil) + certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil) + configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(configurationSecretRollbackFunc.Func(), nil) + deploymentConfiguratorMock.On("Do", + fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName), + fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName), + fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName), + fmt.Sprintf("%s", NewControllerSyncPeriodTime)). + Return(nil, errors.New("some error")) + + configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, nil, deploymentConfiguratorMock, "tenant") + + // when + rollbackFunc, err := configurator.Do(runtimeName, formationName) + + // then + require.Error(t, err) + require.Nil(t, rollbackFunc) + certificateSecretConfiguratorMock.AssertExpectations(t) + deploymentConfiguratorMock.AssertExpectations(t) + require.True(t, compassConfiguratorRollbackFunc.invoked) + require.True(t, certificateSecretsRollbackFunc.invoked) + require.True(t, configurationSecretRollbackFunc.invoked) + }) + + t.Run("should fail if failed to configure Compass Connection CR", func(t *testing.T) { + // given + compassConfiguratorMock := &mocks.CompassConfigurator{} + certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{} + configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{} + compassConnectionConfiguratorMock := &mocks.CompassConnectionConfigurator{} + deploymentConfiguratorMock := &mocks.DeploymentConfigurator{} + + compassConfiguratorRollbackFunc := RollbackFuncTest{} + certificateSecretsRollbackFunc := RollbackFuncTest{} + configurationSecretRollbackFunc := RollbackFuncTest{} + compassConnectionRollbackFunc := RollbackFuncTest{} + deploymentRollbackFunc := RollbackFuncTest{} + + config := types.CompassRuntimeAgentConfig{ + ConnectorUrl: connectorURL, + RuntimeID: runtimeID, + Token: token, + Tenant: tenant, + } + + compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil) + certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil) + configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(configurationSecretRollbackFunc.Func(), nil) + compassConnectionConfiguratorMock.On("Do").Return(compassConnectionRollbackFunc.Func(), errors.New("some error")) + deploymentConfiguratorMock.On("Do", + fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName), + fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName), + fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName), + fmt.Sprintf("%s", NewControllerSyncPeriodTime)). + Return(deploymentRollbackFunc.Func(), nil) + + configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, compassConnectionConfiguratorMock, deploymentConfiguratorMock, "tenant") + + // when + rollbackFunc, err := configurator.Do(runtimeName, formationName) + + // then + require.Error(t, err) + require.Nil(t, rollbackFunc) + certificateSecretConfiguratorMock.AssertExpectations(t) + compassConnectionConfiguratorMock.AssertExpectations(t) + deploymentConfiguratorMock.AssertExpectations(t) + require.True(t, compassConfiguratorRollbackFunc.invoked) + require.True(t, certificateSecretsRollbackFunc.invoked) + require.True(t, configurationSecretRollbackFunc.invoked) + //require.True(t, compassConnectionRollbackFunc.invoked) + require.True(t, deploymentRollbackFunc.invoked) + }) +} + +type RollbackFuncTest struct { + invoked bool +} + +func (rfc *RollbackFuncTest) Func() types.RollbackFunc { + return func() error { + rfc.invoked = true + return nil + } +} diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/CertificateSecretConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CertificateSecretConfigurator.go new file mode 100644 index 00000000..3130678b --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CertificateSecretConfigurator.go @@ -0,0 +1,51 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + mock "github.com/stretchr/testify/mock" +) + +// CertificateSecretConfigurator is an autogenerated mock type for the CertificateSecretConfigurator type +type CertificateSecretConfigurator struct { + mock.Mock +} + +// Do provides a mock function with given fields: caSecretName, clusterCertSecretName +func (_m *CertificateSecretConfigurator) Do(caSecretName string, clusterCertSecretName string) (types.RollbackFunc, error) { + ret := _m.Called(caSecretName, clusterCertSecretName) + + var r0 types.RollbackFunc + if rf, ok := ret.Get(0).(func(string, string) types.RollbackFunc); ok { + r0 = rf(caSecretName, clusterCertSecretName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.RollbackFunc) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(caSecretName, clusterCertSecretName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCertificateSecretConfigurator interface { + mock.TestingT + Cleanup(func()) +} + +// NewCertificateSecretConfigurator creates a new instance of CertificateSecretConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCertificateSecretConfigurator(t mockConstructorTestingTNewCertificateSecretConfigurator) *CertificateSecretConfigurator { + mock := &CertificateSecretConfigurator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConfigurator.go new file mode 100644 index 00000000..aa11389a --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConfigurator.go @@ -0,0 +1,58 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + mock "github.com/stretchr/testify/mock" +) + +// CompassConfigurator is an autogenerated mock type for the CompassConfigurator type +type CompassConfigurator struct { + mock.Mock +} + +// Do provides a mock function with given fields: runtimeName, formationName +func (_m *CompassConfigurator) Do(runtimeName string, formationName string) (types.CompassRuntimeAgentConfig, types.RollbackFunc, error) { + ret := _m.Called(runtimeName, formationName) + + var r0 types.CompassRuntimeAgentConfig + if rf, ok := ret.Get(0).(func(string, string) types.CompassRuntimeAgentConfig); ok { + r0 = rf(runtimeName, formationName) + } else { + r0 = ret.Get(0).(types.CompassRuntimeAgentConfig) + } + + var r1 types.RollbackFunc + if rf, ok := ret.Get(1).(func(string, string) types.RollbackFunc); ok { + r1 = rf(runtimeName, formationName) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(types.RollbackFunc) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(string, string) error); ok { + r2 = rf(runtimeName, formationName) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewCompassConfigurator interface { + mock.TestingT + Cleanup(func()) +} + +// NewCompassConfigurator creates a new instance of CompassConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCompassConfigurator(t mockConstructorTestingTNewCompassConfigurator) *CompassConfigurator { + mock := &CompassConfigurator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConnectionConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConnectionConfigurator.go new file mode 100644 index 00000000..cea552f1 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConnectionConfigurator.go @@ -0,0 +1,51 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + mock "github.com/stretchr/testify/mock" +) + +// CompassConnectionConfigurator is an autogenerated mock type for the CompassConnectionConfigurator type +type CompassConnectionConfigurator struct { + mock.Mock +} + +// Do provides a mock function with given fields: +func (_m *CompassConnectionConfigurator) Do() (types.RollbackFunc, error) { + ret := _m.Called() + + var r0 types.RollbackFunc + if rf, ok := ret.Get(0).(func() types.RollbackFunc); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.RollbackFunc) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewCompassConnectionConfigurator interface { + mock.TestingT + Cleanup(func()) +} + +// NewCompassConnectionConfigurator creates a new instance of CompassConnectionConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewCompassConnectionConfigurator(t mockConstructorTestingTNewCompassConnectionConfigurator) *CompassConnectionConfigurator { + mock := &CompassConnectionConfigurator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/ConfigurationSecretConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/ConfigurationSecretConfigurator.go new file mode 100644 index 00000000..33ae8a41 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/ConfigurationSecretConfigurator.go @@ -0,0 +1,51 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + mock "github.com/stretchr/testify/mock" +) + +// ConfigurationSecretConfigurator is an autogenerated mock type for the ConfigurationSecretConfigurator type +type ConfigurationSecretConfigurator struct { + mock.Mock +} + +// Do provides a mock function with given fields: configurationSecretName, config +func (_m *ConfigurationSecretConfigurator) Do(configurationSecretName string, config types.CompassRuntimeAgentConfig) (types.RollbackFunc, error) { + ret := _m.Called(configurationSecretName, config) + + var r0 types.RollbackFunc + if rf, ok := ret.Get(0).(func(string, types.CompassRuntimeAgentConfig) types.RollbackFunc); ok { + r0 = rf(configurationSecretName, config) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.RollbackFunc) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, types.CompassRuntimeAgentConfig) error); ok { + r1 = rf(configurationSecretName, config) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewConfigurationSecretConfigurator interface { + mock.TestingT + Cleanup(func()) +} + +// NewConfigurationSecretConfigurator creates a new instance of ConfigurationSecretConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewConfigurationSecretConfigurator(t mockConstructorTestingTNewConfigurationSecretConfigurator) *ConfigurationSecretConfigurator { + mock := &ConfigurationSecretConfigurator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/DeploymentConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DeploymentConfigurator.go new file mode 100644 index 00000000..f49a52b2 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DeploymentConfigurator.go @@ -0,0 +1,54 @@ +// Code generated by mockery v2.22.1. DO NOT EDIT. + +package mocks + +import ( + types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types" + mock "github.com/stretchr/testify/mock" +) + +// DeploymentConfigurator is an autogenerated mock type for the DeploymentConfigurator type +type DeploymentConfigurator struct { + mock.Mock +} + +// Do provides a mock function with given fields: caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime +func (_m *DeploymentConfigurator) Do(caSecretName string, clusterCertSecretName string, runtimeAgentConfigSecretName string, controllerSyncPeriodTime string) (types.RollbackFunc, error) { + ret := _m.Called(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime) + + var r0 types.RollbackFunc + var r1 error + if rf, ok := ret.Get(0).(func(string, string, string, string) (types.RollbackFunc, error)); ok { + return rf(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime) + } + if rf, ok := ret.Get(0).(func(string, string, string, string) types.RollbackFunc); ok { + r0 = rf(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.RollbackFunc) + } + } + + if rf, ok := ret.Get(1).(func(string, string, string, string) error); ok { + r1 = rf(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewDeploymentConfigurator interface { + mock.TestingT + Cleanup(func()) +} + +// NewDeploymentConfigurator creates a new instance of DeploymentConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewDeploymentConfigurator(t mockConstructorTestingTNewDeploymentConfigurator) *DeploymentConfigurator { + mock := &DeploymentConfigurator{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/DirectorClient.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DirectorClient.go new file mode 100644 index 00000000..da03b2b6 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DirectorClient.go @@ -0,0 +1,130 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// DirectorClient is an autogenerated mock type for the DirectorClient type +type DirectorClient struct { + mock.Mock +} + +// AssignRuntimeToFormation provides a mock function with given fields: runtimeId, formationName +func (_m *DirectorClient) AssignRuntimeToFormation(runtimeId string, formationName string) error { + ret := _m.Called(runtimeId, formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(runtimeId, formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetConnectionToken provides a mock function with given fields: runtimeID +func (_m *DirectorClient) GetConnectionToken(runtimeID string) (string, string, error) { + ret := _m.Called(runtimeID) + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(runtimeID) + } else { + r0 = ret.Get(0).(string) + } + + var r1 string + if rf, ok := ret.Get(1).(func(string) string); ok { + r1 = rf(runtimeID) + } else { + r1 = ret.Get(1).(string) + } + + var r2 error + if rf, ok := ret.Get(2).(func(string) error); ok { + r2 = rf(runtimeID) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// RegisterFormation provides a mock function with given fields: formationName +func (_m *DirectorClient) RegisterFormation(formationName string) error { + ret := _m.Called(formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RegisterRuntime provides a mock function with given fields: runtimeName +func (_m *DirectorClient) RegisterRuntime(runtimeName string) (string, error) { + ret := _m.Called(runtimeName) + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(runtimeName) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(runtimeName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UnregisterFormation provides a mock function with given fields: formationName +func (_m *DirectorClient) UnregisterFormation(formationName string) error { + ret := _m.Called(formationName) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(formationName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UnregisterRuntime provides a mock function with given fields: id +func (_m *DirectorClient) UnregisterRuntime(id string) error { + ret := _m.Called(id) + + var r0 error + if rf, ok := ret.Get(0).(func(string) error); ok { + r0 = rf(id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewDirectorClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewDirectorClient creates a new instance of DirectorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewDirectorClient(t mockConstructorTestingTNewDirectorClient) *DirectorClient { + mock := &DirectorClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/init/types/types.go b/tests/test/compass-runtime-agent/testkit/init/types/types.go new file mode 100644 index 00000000..c657ad61 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/init/types/types.go @@ -0,0 +1,45 @@ +package types + +type CompassRuntimeAgentConfig struct { + ConnectorUrl string + RuntimeID string + Token string + Tenant string +} + +type RollbackFunc func() error + +//go:generate mockery --name=DirectorClient +type DirectorClient interface { + RegisterRuntime(runtimeName string) (string, error) + RegisterFormation(formationName string) error + AssignRuntimeToFormation(runtimeId, formationName string) error + UnregisterRuntime(id string) error + UnregisterFormation(formationName string) error + GetConnectionToken(runtimeID string) (string, string, error) +} + +//go:generate mockery --name=CompassConfigurator +type CompassConfigurator interface { + Do(runtimeName, formationName string) (CompassRuntimeAgentConfig, RollbackFunc, error) +} + +//go:generate mockery --name=DeploymentConfigurator +type DeploymentConfigurator interface { + Do(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime string) (RollbackFunc, error) +} + +//go:generate mockery --name=CertificateSecretConfigurator +type CertificateSecretConfigurator interface { + Do(caSecretName, clusterCertSecretName string) (RollbackFunc, error) +} + +//go:generate mockery --name=ConfigurationSecretConfigurator +type ConfigurationSecretConfigurator interface { + Do(configurationSecretName string, config CompassRuntimeAgentConfig) (RollbackFunc, error) +} + +//go:generate mockery --name=CompassConnectionConfigurator +type CompassConnectionConfigurator interface { + Do() (RollbackFunc, error) +} diff --git a/tests/test/compass-runtime-agent/testkit/oauth/client.go b/tests/test/compass-runtime-agent/testkit/oauth/client.go new file mode 100644 index 00000000..a845175d --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/oauth/client.go @@ -0,0 +1,120 @@ +package oauth + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + "net/url" + "strings" + "time" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" +) + +//go:generate mockery --name=Client +type Client interface { + GetAuthorizationToken() (Token, error) +} + +type oauthClient struct { + httpClient *http.Client + secretsClient v1.SecretInterface + secretName string +} + +func NewOauthClient(client *http.Client, secrets v1.SecretInterface, secretName string) (Client, error) { + + _, err := secrets.Get(context.Background(), secretName, metav1.GetOptions{}) + + if err != nil { + return nil, fmt.Errorf("Cound not access oauthCredential secret %s", secretName) + } + + return &oauthClient{ + httpClient: client, + secretsClient: secrets, + secretName: secretName, + }, nil +} + +func (c *oauthClient) GetAuthorizationToken() (Token, error) { + credentials, err := c.getCredentials() + + if err != nil { + return Token{}, err + } + + return c.getAuthorizationToken(credentials) +} + +func (c *oauthClient) getCredentials() (credentials, error) { + secret, err := c.secretsClient.Get(context.Background(), c.secretName, metav1.GetOptions{}) + + if err != nil { + return credentials{}, err + } + + return credentials{ + clientID: string(secret.Data[clientIDKey]), + clientSecret: string(secret.Data[clientSecretKey]), + tokensEndpoint: string(secret.Data[tokensEndpointKey]), + }, nil +} + +func (c *oauthClient) getAuthorizationToken(credentials credentials) (Token, error) { + log.Infof("Getting authorisation token for credentials to access Director from endpoint: %s", credentials.tokensEndpoint) + + form := url.Values{} + form.Add(grantTypeFieldName, credentialsGrantType) + form.Add(scopeFieldName, scopes) + + request, err := http.NewRequest(http.MethodPost, credentials.tokensEndpoint, strings.NewReader(form.Encode())) + if err != nil { + log.Errorf("Failed to create authorisation token request") + return Token{}, errors.Wrap(err, "Failed to create authorisation token request") + } + + now := time.Now().Unix() + + request.SetBasicAuth(credentials.clientID, credentials.clientSecret) + request.Header.Set(contentTypeHeader, contentTypeApplicationURLEncoded) + + response, err := c.httpClient.Do(request) + if err != nil { + return Token{}, errors.Wrap(err, "Failed to execute http call") + } + + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + dump, err := httputil.DumpResponse(response, true) + if err != nil { + dump = []byte("failed to dump response body") + } + return Token{}, fmt.Errorf("Get token call returned unexpected status: %s. Response dump: %s", response.Status, string(dump)) + } + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return Token{}, fmt.Errorf("Failed to read token response body from '%s': %s", credentials.tokensEndpoint, err.Error()) + } + + tokenResponse := Token{} + + err = json.Unmarshal(body, &tokenResponse) + if err != nil { + return Token{}, fmt.Errorf("failed to unmarshal token response body: %s", err.Error()) + } + + log.Infof("Successfully unmarshal response oauth token for accessing Director") + + tokenResponse.Expiration += now + + return tokenResponse, nil +} diff --git a/tests/test/compass-runtime-agent/testkit/oauth/client_test.go b/tests/test/compass-runtime-agent/testkit/oauth/client_test.go new file mode 100644 index 00000000..5bfdb5f2 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/oauth/client_test.go @@ -0,0 +1,109 @@ +package oauth + +import ( + "bytes" + "context" + "encoding/json" + "io/ioutil" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/kubernetes/typed/core/v1" +) + +const ( + namespace = "test" + secretName = "oauth-compass-credentials" +) + +func TestOauthClient_GetAuthorizationToken(t *testing.T) { + t.Run("Should return oauth token", func(t *testing.T) { + //given + credentials := credentials{ + clientID: "12345", + clientSecret: "some dark and scary secret", + tokensEndpoint: "http://hydra:4445", + } + + token := Token{ + AccessToken: "12345", + Expiration: 1234, + } + + client := NewTestClient(func(req *http.Request) *http.Response { + username, secret, ok := req.BasicAuth() + + if ok && username == credentials.clientID && secret == credentials.clientSecret { + jsonToken, err := json.Marshal(&token) + + require.NoError(t, err) + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(jsonToken)), + } + } + return &http.Response{ + StatusCode: http.StatusForbidden, + } + }) + + coreV1 := fake.NewSimpleClientset() + secrets := coreV1.CoreV1().Secrets(namespace) + + createFakeCredentialsSecret(t, secrets, credentials) + + oauthClient, err := NewOauthClient(client, secrets, secretName) + require.NoError(t, err) + + //when + responseToken, err := oauthClient.GetAuthorizationToken() + require.NoError(t, err) + token.Expiration += time.Now().Unix() + + //then + assert.Equal(t, token.AccessToken, responseToken.AccessToken) + assert.Equal(t, token.Expiration, responseToken.Expiration) + }) +} + +func NewTestClient(fn RoundTripFunc) *http.Client { + return &http.Client{ + Transport: fn, + } +} + +type RoundTripFunc func(req *http.Request) *http.Response + +func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +func createFakeCredentialsSecret(t *testing.T, secrets core.SecretInterface, credentials credentials) { + + secret := &v1.Secret{ + ObjectMeta: meta.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + TypeMeta: meta.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + Data: map[string][]byte{ + clientIDKey: []byte(credentials.clientID), + clientSecretKey: []byte(credentials.clientSecret), + tokensEndpointKey: []byte(credentials.tokensEndpoint), + }, + } + + _, err := secrets.Create(context.Background(), secret, meta.CreateOptions{}) + + require.NoError(t, err) +} diff --git a/tests/test/compass-runtime-agent/testkit/oauth/mocks/Client.go b/tests/test/compass-runtime-agent/testkit/oauth/mocks/Client.go new file mode 100644 index 00000000..3a357903 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/oauth/mocks/Client.go @@ -0,0 +1,49 @@ +// Code generated by mockery v2.15.0. DO NOT EDIT. + +package mocks + +import ( + oauth "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth" + mock "github.com/stretchr/testify/mock" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +// GetAuthorizationToken provides a mock function with given fields: +func (_m *Client) GetAuthorizationToken() (oauth.Token, error) { + ret := _m.Called() + + var r0 oauth.Token + if rf, ok := ret.Get(0).(func() oauth.Token); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(oauth.Token) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewClient(t mockConstructorTestingTNewClient) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/test/compass-runtime-agent/testkit/oauth/types.go b/tests/test/compass-runtime-agent/testkit/oauth/types.go new file mode 100644 index 00000000..86fc07c2 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/oauth/types.go @@ -0,0 +1,38 @@ +package oauth + +import "time" + +const ( + contentTypeHeader = "Content-Type" + contentTypeApplicationURLEncoded = "application/x-www-form-urlencoded" + + grantTypeFieldName = "grant_type" + credentialsGrantType = "client_credentials" + + scopeFieldName = "scope" + scopes = "application:read application:write formation:write runtime:read runtime:write" + + clientIDKey = "client_id" + clientSecretKey = "client_secret" + tokensEndpointKey = "tokens_endpoint" +) + +type Token struct { + AccessToken string `json:"access_token"` + Expiration int64 `json:"expires_in"` +} + +type credentials struct { + clientID string + clientSecret string + tokensEndpoint string +} + +func (token Token) EmptyOrExpired() bool { + if token.AccessToken == "" { + return true + } + + expiration := time.Unix(token.Expiration, 0) + return time.Now().After(expiration) +} diff --git a/tests/test/compass-runtime-agent/testkit/oauth/types_test.go b/tests/test/compass-runtime-agent/testkit/oauth/types_test.go new file mode 100644 index 00000000..e1f8b0f7 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/oauth/types_test.go @@ -0,0 +1,53 @@ +package oauth + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestToken_EmptyOrExpired(t *testing.T) { + t.Run("Should return true when token is empty", func(t *testing.T) { + //given + token := Token{} + + //when + empty := token.EmptyOrExpired() + + //then + assert.True(t, empty) + }) + + t.Run("Should return true when expired", func(t *testing.T) { + //given + time2000 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix() + + token := Token{ + AccessToken: "token", + Expiration: time2000, + } + + //when + expired := token.EmptyOrExpired() + + //then + assert.True(t, expired) + }) + + t.Run("Should return false when not empty or expired", func(t *testing.T) { + //given + time3000 := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC).Unix() + + token := Token{ + AccessToken: "token", + Expiration: time3000, + } + + //when + notExpired := token.EmptyOrExpired() + + //then + assert.False(t, notExpired) + }) +} diff --git a/tests/test/compass-runtime-agent/testkit/random/randomstring.go b/tests/test/compass-runtime-agent/testkit/random/randomstring.go new file mode 100644 index 00000000..7fbb0075 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/random/randomstring.go @@ -0,0 +1,25 @@ +package random + +import ( + "math/rand" + "strings" + "time" +) + +const charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + +func StringWithCharset(length int, charset string) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +func RandomString(length int) string { + return strings.ToLower(StringWithCharset(length, charset)) +} diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/LICENSE b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/LICENSE new file mode 100644 index 00000000..9b0dfaa8 --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Machine Box, Ltd. + + 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. diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/README.md b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/README.md new file mode 100644 index 00000000..fabd7c8e --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/README.md @@ -0,0 +1,67 @@ +# graphql [![GoDoc](https://godoc.org/github.com/machinebox/graphql?status.png)](http://godoc.org/github.com/machinebox/graphql) [![Build Status](https://travis-ci.org/machinebox/graphql.svg?branch=master)](https://travis-ci.org/machinebox/graphql) [![Go Report Card](https://goreportcard.com/badge/github.com/machinebox/graphql)](https://goreportcard.com/report/github.com/machinebox/graphql) + +Low-level GraphQL client for Go. + +* Simple, familiar API +* Respects `context.Context` timeouts and cancellation +* Build and execute any kind of GraphQL request +* Use strong Go types for response data +* Use variables and upload files +* Simple error handling + +## Installation +Make sure you have a working Go environment. To install graphql, simply run: + +``` +$ go get github.com/machinebox/graphql +``` + +## Usage + +```go +import "context" + +// create a client (safe to share across requests) +client := graphql.NewClient("https://machinebox.io/graphql") + +// make a request +req := graphql.NewRequest(` + query ($key: String!) { + items (id:$key) { + field1 + field2 + field3 + } + } +`) + +// set any variables +req.Var("key", "value") + +// set header fields +req.Header.Set("Cache-Control", "no-cache") + +// define a Context for the request +ctx := context.Background() + +// run it and capture the response +var respData ResponseStruct +if err := client.Run(ctx, req, &respData); err != nil { + log.Fatal(err) +} +``` + +### File Support via Multipart Form Data + +By default, the package will send a JSON body. To enable the sending of files, you can opt to +use multipart form data instead using the `UseMultipartForm` option when you create your `Client`: + +``` +client := graphql.NewClient("https://machinebox.io/graphql", graphql.UseMultipartForm()) +``` + +For more information, [read the godoc package documentation](http://godoc.org/github.com/machinebox/graphql). + +## Thanks + +Thanks to [Chris Broadfoot](https://github.com/broady) for design help. diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql.go b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql.go new file mode 100644 index 00000000..dc005dad --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql.go @@ -0,0 +1,354 @@ +// Package graphql provides a low level GraphQL client. +// +// // create a client (safe to share across requests) +// client := graphql.NewClient("https://machinebox.io/graphql") +// +// // make a request +// req := graphql.NewRequest(` +// query ($key: String!) { +// items (id:$key) { +// field1 +// field2 +// field3 +// } +// } +// `) +// +// // set any variables +// req.Var("key", "value") +// +// // run it and capture the response +// var respData ResponseStruct +// if err := client.Run(ctx, req, &respData); err != nil { +// log.Fatal(err) +// } +// +// Specify client +// +// To specify your own http.Client, use the WithHTTPClient option: +// httpclient := &http.Client{} +// client := graphql.NewClient("https://machinebox.io/graphql", graphql.WithHTTPClient(httpclient)) +package graphql + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "strings" + "unicode/utf8" + + "github.com/pkg/errors" +) + +// Client is a client for interacting with a GraphQL API. +type Client struct { + endpoint string + httpClient *http.Client + useMultipartForm bool + + // closeReq will close the request body immediately allowing for reuse of client + closeReq bool + + // Log is called with various debug information. + // To log to standard out, use: + // client.Log = func(s string) { log.Println(s) } + Log func(s string) +} + +// NewClient makes a new Client capable of making GraphQL requests. +func NewClient(endpoint string, opts ...ClientOption) *Client { + c := &Client{ + endpoint: endpoint, + Log: func(string) {}, + } + for _, optionFunc := range opts { + optionFunc(c) + } + if c.httpClient == nil { + c.httpClient = http.DefaultClient + } + return c +} + +func (c *Client) logf(format string, args ...interface{}) { + c.Log(fmt.Sprintf(format, args...)) +} + +// Run executes the query and unmarshals the response from the data field +// into the response object. +// Pass in a nil response object to skip response parsing. +// If the request fails or the server returns an error, the first error +// will be returned. +func (c *Client) Run(ctx context.Context, req *Request, resp interface{}) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if len(req.files) > 0 && !c.useMultipartForm { + return errors.New("cannot send files with PostFields option") + } + if c.useMultipartForm { + return c.runWithPostFields(ctx, req, resp) + } + return c.runWithJSON(ctx, req, resp) +} + +func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{}) error { + var requestBody bytes.Buffer + requestBodyObj := struct { + Query string `json:"query"` + Variables map[string]interface{} `json:"variables"` + }{ + Query: req.q, + Variables: req.vars, + } + if err := json.NewEncoder(&requestBody).Encode(requestBodyObj); err != nil { + return errors.Wrap(err, "encode body") + } + c.logf(">> variables: %v", req.vars) + c.logf(">> query: %s", req.q) + gr := &graphResponse{ + Data: resp, + } + r, err := http.NewRequest(http.MethodPost, c.endpoint, &requestBody) + if err != nil { + return err + } + r.Close = c.closeReq + r.Header.Set("Content-Type", "application/json; charset=utf-8") + r.Header.Set("Accept", "application/json; charset=utf-8") + for key, values := range req.Header { + for _, value := range values { + r.Header.Add(key, value) + } + } + c.logf(">> headers: %v", hideHeaders(r.Header)) + r = r.WithContext(ctx) + res, err := c.httpClient.Do(r) + if err != nil { + return err + } + defer res.Body.Close() + var buf bytes.Buffer + if _, err := io.Copy(&buf, res.Body); err != nil { + return errors.Wrap(err, "reading body") + } + c.logf("<< %s", buf.String()) + if err := json.NewDecoder(&buf).Decode(&gr); err != nil { + if res.StatusCode != http.StatusOK { + return fmt.Errorf("graphql: server returned a non-200 status code: %v", res.StatusCode) + } + return errors.Wrap(err, "decoding response") + } + if len(gr.Errors) > 0 { + // return first error + return gr.Errors[0] + } + return nil +} + +func (c *Client) runWithPostFields(ctx context.Context, req *Request, resp interface{}) error { + var requestBody bytes.Buffer + writer := multipart.NewWriter(&requestBody) + if err := writer.WriteField("query", req.q); err != nil { + return errors.Wrap(err, "write query field") + } + var variablesBuf bytes.Buffer + if len(req.vars) > 0 { + variablesField, err := writer.CreateFormField("variables") + if err != nil { + return errors.Wrap(err, "create variables field") + } + if err := json.NewEncoder(io.MultiWriter(variablesField, &variablesBuf)).Encode(req.vars); err != nil { + return errors.Wrap(err, "encode variables") + } + } + for i := range req.files { + part, err := writer.CreateFormFile(req.files[i].Field, req.files[i].Name) + if err != nil { + return errors.Wrap(err, "create form file") + } + if _, err := io.Copy(part, req.files[i].R); err != nil { + return errors.Wrap(err, "preparing file") + } + } + if err := writer.Close(); err != nil { + return errors.Wrap(err, "close writer") + } + c.logf(">> variables: %s", variablesBuf.String()) + c.logf(">> files: %d", len(req.files)) + c.logf(">> query: %s", req.q) + gr := &graphResponse{ + Data: resp, + } + r, err := http.NewRequest(http.MethodPost, c.endpoint, &requestBody) + if err != nil { + return err + } + r.Close = c.closeReq + r.Header.Set("Content-Type", writer.FormDataContentType()) + r.Header.Set("Accept", "application/json; charset=utf-8") + for key, values := range req.Header { + for _, value := range values { + r.Header.Add(key, value) + } + } + c.logf(">> headers: %v", hideHeaders(r.Header)) + r = r.WithContext(ctx) + res, err := c.httpClient.Do(r) + if err != nil { + return err + } + defer res.Body.Close() + var buf bytes.Buffer + if _, err := io.Copy(&buf, res.Body); err != nil { + return errors.Wrap(err, "reading body") + } + c.logf("<< %s", buf.String()) + if err := json.NewDecoder(&buf).Decode(&gr); err != nil { + if res.StatusCode != http.StatusOK { + return fmt.Errorf("graphql: server returned a non-200 status code: %v", res.StatusCode) + } + return errors.Wrap(err, "decoding response") + } + if len(gr.Errors) > 0 { + // return first error + return gr.Errors[0] + } + return nil +} + +// WithHTTPClient specifies the underlying http.Client to use when +// making requests. +// NewClient(endpoint, WithHTTPClient(specificHTTPClient)) +func WithHTTPClient(httpclient *http.Client) ClientOption { + return func(client *Client) { + client.httpClient = httpclient + } +} + +// UseMultipartForm uses multipart/form-data and activates support for +// files. +func UseMultipartForm() ClientOption { + return func(client *Client) { + client.useMultipartForm = true + } +} + +// ImmediatelyCloseReqBody will close the req body immediately after each request body is ready +func ImmediatelyCloseReqBody() ClientOption { + return func(client *Client) { + client.closeReq = true + } +} + +// ClientOption are functions that are passed into NewClient to +// modify the behaviour of the Client. +type ClientOption func(*Client) + +type ExtendedError interface { + Error() string + Extensions() map[string]interface{} +} + +type graphErr struct { + Message string `json:"message,omitempty"` + ErrorExtensions map[string]interface{} `json:"extensions,omitempty"` +} + +func (e graphErr) Error() string { + return "graphql: " + e.Message +} + +func (e graphErr) Extensions() map[string]interface{} { + return e.ErrorExtensions +} + +type graphResponse struct { + Data interface{} + Errors []graphErr +} + +// Request is a GraphQL request. +type Request struct { + q string + vars map[string]interface{} + files []File + + // Header represent any request headers that will be set + // when the request is made. + Header http.Header +} + +// NewRequest makes a new Request with the specified string. +func NewRequest(q string) *Request { + req := &Request{ + q: q, + Header: make(map[string][]string), + } + return req +} + +// Var sets a variable. +func (req *Request) Var(key string, value interface{}) { + if req.vars == nil { + req.vars = make(map[string]interface{}) + } + req.vars[key] = value +} + +// Vars gets the variables for this Request. +func (req *Request) Vars() map[string]interface{} { + return req.vars +} + +// Files gets the files in this request. +func (req *Request) Files() []File { + return req.files +} + +// Query gets the query string of this request. +func (req *Request) Query() string { + return req.q +} + +// File sets a file to upload. +// Files are only supported with a Client that was created with +// the UseMultipartForm option. +func (req *Request) File(fieldname, filename string, r io.Reader) { + req.files = append(req.files, File{ + Field: fieldname, + Name: filename, + R: r, + }) +} + +// File represents a file to upload. +type File struct { + Field string + Name string + R io.Reader +} + +// hideHeaders creates a copy of headers +// with specified fields censored (eg 'password' -> '********') +// Additionally by default censors: +// - Authorization +func hideHeaders(headers http.Header, toHide ...string) http.Header { + toHide = append(toHide, "Authorization") + hs := headers.Clone() + for _, h := range toHide { + v, ok := hs[h] + if ok { + for i := range v { + hs[h][i] = strings.Repeat("*", utf8.RuneCountInString(v[i])) + } + } + } + return hs +} diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_json_test.go b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_json_test.go new file mode 100644 index 00000000..f2a9b23e --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_json_test.go @@ -0,0 +1,233 @@ +package graphql + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/matryer/is" +) + +func TestDoJSON(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + b, err := ioutil.ReadAll(r.Body) + is.NoErr(err) + is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n") + io.WriteString(w, `{ + "data": { + "something": "yes" + } + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.NoErr(err) + is.Equal(calls, 1) // calls + is.Equal(responseData["something"], "yes") +} + +func TestDoJSONServerError(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + b, err := ioutil.ReadAll(r.Body) + is.NoErr(err) + is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n") + w.WriteHeader(http.StatusInternalServerError) + io.WriteString(w, `Internal Server Error`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.Equal(calls, 1) // calls + is.Equal(err.Error(), "graphql: server returned a non-200 status code: 500") +} + +func TestDoJSONBadRequestErr(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + b, err := ioutil.ReadAll(r.Body) + is.NoErr(err) + is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, `{ + "errors": [{ + "message": "miscellaneous message as to why the the request was bad" + }] + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.Equal(calls, 1) // calls + is.Equal(err.Error(), "graphql: miscellaneous message as to why the the request was bad") +} + +func TestDoJSONErrWithExtensions(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + b, err := ioutil.ReadAll(r.Body) + is.NoErr(err) + is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n") + w.WriteHeader(http.StatusOK) + io.WriteString(w, `{ + "errors": [{ + "message": "miscellaneous message as to why the the request was bad", + "extensions": { + "code": "400" + } + }] + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.Equal(calls, 1) // calls + is.Equal(err.Error(), "graphql: miscellaneous message as to why the the request was bad") + is.Equal(err.(ExtendedError).Extensions()["code"], "400") +} + +func TestQueryJSON(t *testing.T) { + is := is.New(t) + + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + b, err := ioutil.ReadAll(r.Body) + is.NoErr(err) + is.Equal(string(b), `{"query":"query {}","variables":{"username":"matryer"}}`+"\n") + _, err = io.WriteString(w, `{"data":{"value":"some data"}}`) + is.NoErr(err) + })) + defer srv.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + client := NewClient(srv.URL) + + req := NewRequest("query {}") + req.Var("username", "matryer") + + // check variables + is.True(req != nil) + is.Equal(req.vars["username"], "matryer") + + var resp struct { + Value string + } + err := client.Run(ctx, req, &resp) + is.NoErr(err) + is.Equal(calls, 1) + + is.Equal(resp.Value, "some data") +} + +func TestHeader(t *testing.T) { + is := is.New(t) + + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Header.Get("X-Custom-Header"), "123") + + _, err := io.WriteString(w, `{"data":{"value":"some data"}}`) + is.NoErr(err) + })) + defer srv.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + client := NewClient(srv.URL) + + req := NewRequest("query {}") + req.Header.Set("X-Custom-Header", "123") + + var resp struct { + Value string + } + err := client.Run(ctx, req, &resp) + is.NoErr(err) + is.Equal(calls, 1) + + is.Equal(resp.Value, "some data") +} + +func TestHideAuthInJSON(t *testing.T) { + is := is.New(t) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, `{ + "data": { + "something": "yes" + } + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL) + + var cout bytes.Buffer + client.Log = func(s string) { + _, err := cout.WriteString(s) + is.NoErr(err) + } + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + + header := make(http.Header) + header["Authorization"] = []string{"some secret key", "another secret key"} + req := Request{ + q: "query {}", + Header: header, + } + + err := client.Run(ctx, &req, &responseData) + is.NoErr(err) + is.Equal(responseData["something"], "yes") + is.True(!strings.Contains(cout.String(), "secret key")) +} diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_multipart_test.go b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_multipart_test.go new file mode 100644 index 00000000..4bf3fb8c --- /dev/null +++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_multipart_test.go @@ -0,0 +1,302 @@ +package graphql + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/matryer/is" +) + +func TestWithClient(t *testing.T) { + is := is.New(t) + var calls int + testClient := &http.Client{ + Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) { + calls++ + resp := &http.Response{ + Body: ioutil.NopCloser(strings.NewReader(`{"data":{"key":"value"}}`)), + } + return resp, nil + }), + } + + ctx := context.Background() + client := NewClient("", WithHTTPClient(testClient), UseMultipartForm()) + + req := NewRequest(``) + client.Run(ctx, req, nil) + + is.Equal(calls, 1) // calls +} + +func TestDoUseMultipartForm(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + query := r.FormValue("query") + is.Equal(query, `query {}`) + io.WriteString(w, `{ + "data": { + "something": "yes" + } + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL, UseMultipartForm()) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.NoErr(err) + is.Equal(calls, 1) // calls + is.Equal(responseData["something"], "yes") +} + +func TestImmediatelyCloseReqBody(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + query := r.FormValue("query") + is.Equal(query, `query {}`) + io.WriteString(w, `{ + "data": { + "something": "yes" + } + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL, ImmediatelyCloseReqBody(), UseMultipartForm()) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.NoErr(err) + is.Equal(calls, 1) // calls + is.Equal(responseData["something"], "yes") +} + +func TestDoErr(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + query := r.FormValue("query") + is.Equal(query, `query {}`) + io.WriteString(w, `{ + "errors": [{ + "message": "Something went wrong" + }] + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL, UseMultipartForm()) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.True(err != nil) + is.Equal(err.Error(), "graphql: Something went wrong") +} + +func TestDoServerErr(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + query := r.FormValue("query") + is.Equal(query, `query {}`) + w.WriteHeader(http.StatusInternalServerError) + io.WriteString(w, `Internal Server Error`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL, UseMultipartForm()) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.Equal(err.Error(), "graphql: server returned a non-200 status code: 500") +} + +func TestDoBadRequestErr(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + query := r.FormValue("query") + is.Equal(query, `query {}`) + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, `{ + "errors": [{ + "message": "miscellaneous message as to why the the request was bad" + }] + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL, UseMultipartForm()) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &Request{q: "query {}"}, &responseData) + is.Equal(err.Error(), "graphql: miscellaneous message as to why the the request was bad") +} + +func TestDoNoResponse(t *testing.T) { + is := is.New(t) + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + is.Equal(r.Method, http.MethodPost) + query := r.FormValue("query") + is.Equal(query, `query {}`) + io.WriteString(w, `{ + "data": { + "something": "yes" + } + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL, UseMultipartForm()) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + err := client.Run(ctx, &Request{q: "query {}"}, nil) + is.NoErr(err) + is.Equal(calls, 1) // calls +} + +func TestQuery(t *testing.T) { + is := is.New(t) + + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + query := r.FormValue("query") + is.Equal(query, "query {}") + is.Equal(r.FormValue("variables"), `{"username":"matryer"}`+"\n") + _, err := io.WriteString(w, `{"data":{"value":"some data"}}`) + is.NoErr(err) + })) + defer srv.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + client := NewClient(srv.URL, UseMultipartForm()) + + req := NewRequest("query {}") + req.Var("username", "matryer") + + // check variables + is.True(req != nil) + is.Equal(req.vars["username"], "matryer") + + var resp struct { + Value string + } + err := client.Run(ctx, req, &resp) + is.NoErr(err) + is.Equal(calls, 1) + + is.Equal(resp.Value, "some data") +} + +func TestFile(t *testing.T) { + is := is.New(t) + + var calls int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls++ + file, header, err := r.FormFile("file") + is.NoErr(err) + defer file.Close() + is.Equal(header.Filename, "filename.txt") + + b, err := ioutil.ReadAll(file) + is.NoErr(err) + is.Equal(string(b), `This is a file`) + + _, err = io.WriteString(w, `{"data":{"value":"some data"}}`) + is.NoErr(err) + })) + defer srv.Close() + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + client := NewClient(srv.URL, UseMultipartForm()) + f := strings.NewReader(`This is a file`) + req := NewRequest("query {}") + req.File("file", "filename.txt", f) + err := client.Run(ctx, req, nil) + is.NoErr(err) +} + +type roundTripperFunc func(req *http.Request) (*http.Response, error) + +func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return fn(req) +} + +func TestHideAuthInMultipartForm(t *testing.T) { + is := is.New(t) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, `{ + "data": { + "something": "yes" + } + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL, UseMultipartForm()) + + var cout bytes.Buffer + client.Log = func(s string) { + _, err := cout.WriteString(s) + is.NoErr(err) + } + + header := make(http.Header) + header["Authorization"] = []string{"some secret key", "another secret key"} + req := Request{ + q: "query {}", + Header: header, + } + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + var responseData map[string]interface{} + err := client.Run(ctx, &req, &responseData) + is.NoErr(err) + is.Equal(responseData["something"], "yes") + is.True(!strings.Contains(cout.String(), "secret key")) +} diff --git a/tests/tools/external-api-mock-app/config.go b/tests/tools/external-api-mock-app/config.go new file mode 100644 index 00000000..19927178 --- /dev/null +++ b/tests/tools/external-api-mock-app/config.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" +) + +type mTLS struct { + caCertPath string + serverCertPath string + serverKeyPath string + port int +} + +type Config struct { + LogLevel string + Port int + BasicAuthUser string + BasicAuthPassword string + OAuthClientID string + OAuthClientSecret string + RequestHeaders map[string][]string + RequestQueryParameters map[string][]string + mTLS mTLS + mTLSExpiredCerts mTLS +} + +func NewConfig() *Config { + return &Config{ + LogLevel: "info", + Port: 8080, + BasicAuthUser: "user", + BasicAuthPassword: "passwd", + OAuthClientID: "clientID", + OAuthClientSecret: "clientSecret", + RequestHeaders: map[string][]string{"Hkey1": {"Hval1"}, "Hkey2": {"Hval21", "Hval22"}}, + RequestQueryParameters: map[string][]string{"Qkey1": {"Qval1"}, "Qkey2": {"Qval21", "Qval22"}}, + mTLS: mTLS{ + port: 8090, + caCertPath: "/etc/secret-volume/ca.crt", + serverCertPath: "/etc/secret-volume/server.crt", + serverKeyPath: "/etc/secret-volume/server.key", + }, + mTLSExpiredCerts: mTLS{ + port: 8091, + caCertPath: "/etc/expired-server-cert-volume/ca.crt", + serverCertPath: "/etc/expired-server-cert-volume/server.crt", + serverKeyPath: "/etc/expired-server-cert-volume/server.key", + }, + } +} + +func (c *Config) String() string { + return fmt.Sprintf("LogLevel: %s", c.LogLevel) +} diff --git a/tests/tools/external-api-mock-app/server.go b/tests/tools/external-api-mock-app/server.go new file mode 100644 index 00000000..b469380a --- /dev/null +++ b/tests/tools/external-api-mock-app/server.go @@ -0,0 +1,79 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/test-api" + log "github.com/sirupsen/logrus" + "io/ioutil" + "net/http" + "os" + "sync" +) + +func main() { + cfg := NewConfig() + logLevel, err := log.ParseLevel(cfg.LogLevel) + if err != nil { + log.Warnf("Invalid log level: '%s', defaulting to 'info'", cfg.LogLevel) + logLevel = log.InfoLevel + } + log.SetLevel(logLevel) + + log.Infof("Starting mock application") + log.Infof("Config: %s", cfg.String()) + + wg := sync.WaitGroup{} + wg.Add(3) + + basicAuthCredentials := test_api.BasicAuthCredentials{User: cfg.BasicAuthUser, Password: cfg.BasicAuthPassword} + oAuthCredentials := test_api.OAuthCredentials{ClientID: cfg.OAuthClientID, ClientSecret: cfg.OAuthClientSecret} + expectedRequestParameters := test_api.ExpectedRequestParameters{Headers: cfg.RequestHeaders, QueryParameters: cfg.RequestQueryParameters} + oauthTokens := make(map[string]test_api.OAuthToken) + csrfTokens := make(test_api.CSRFTokens) + + go func() { + address := fmt.Sprintf(":%d", cfg.Port) + router := test_api.SetupRoutes(os.Stdout, basicAuthCredentials, oAuthCredentials, expectedRequestParameters, oauthTokens, csrfTokens) + log.Fatal(http.ListenAndServe(address, router)) + }() + + go func() { + address := fmt.Sprintf(":%d", cfg.mTLS.port) + router := test_api.SetupMTLSRoutes(os.Stdout, oAuthCredentials, oauthTokens, csrfTokens) + mtlsServer := newMTLSServer(cfg.mTLS.caCertPath, address, router) + log.Fatal(mtlsServer.ListenAndServeTLS(cfg.mTLS.serverCertPath, cfg.mTLS.serverKeyPath)) + }() + + go func() { + address := fmt.Sprintf(":%d", cfg.mTLSExpiredCerts.port) + router := test_api.SetupMTLSRoutes(os.Stdout, oAuthCredentials, oauthTokens, csrfTokens) + mtlsServer := newMTLSServer(cfg.mTLSExpiredCerts.caCertPath, address, router) + log.Fatal(mtlsServer.ListenAndServeTLS(cfg.mTLSExpiredCerts.serverCertPath, cfg.mTLSExpiredCerts.serverKeyPath)) + }() + + wg.Wait() +} + +func newMTLSServer(caCertPath, address string, handler http.Handler) *http.Server { + // Create a CA certificate pool and add cert.pem to it + caCert, err := ioutil.ReadFile(caCertPath) + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + // Create the TLS Config with the CA pool and enable Client certificate validation + tlsConfig := &tls.Config{ + ClientCAs: caCertPool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + + return &http.Server{ + Addr: address, + Handler: handler, + TLSConfig: tlsConfig, + } +}