diff --git a/.github/actions/kind-create/action.yaml b/.github/actions/kind-create/action.yaml index 3e063854f..52a059431 100644 --- a/.github/actions/kind-create/action.yaml +++ b/.github/actions/kind-create/action.yaml @@ -13,6 +13,10 @@ inputs: # adding these parameters to make this reusable later on description: 'Path to the kind config to use' required: true default: 'tests/e2e/platforms/kind/kind_config.yaml' + kubeconfig_dir_path: + description: 'Relative dir path of the created kind cluster kubeconfig' + required: true + default: 'tests/e2e/kubeconfigs' outputs: kubeconfig: description: 'Path of the resulting kubeconfig' @@ -38,10 +42,12 @@ runs: kubectl cluster-info kubectl get pods -n kube-system echo "current-context:" $(kubectl config current-context) - echo "environment-kubeconfig:" ${KUBECONFIG} - mkdir tests/e2e/platforms/kind/${{ inputs.kind_k8s_version }} - kubeconfig_path=$(pwd)/tests/e2e/platforms/kind/${{ inputs.kind_k8s_version }}/kind.kconf + kubeconfig_dir_path=$(pwd)/${{ inputs.kubeconfig_dir_path }} + mkdir -p $kubeconfig_dir_path + kubeconfig_filename=${{ inputs.kind_cluster_name }}-${{ inputs.kind_k8s_version }}.yaml + kubeconfig_path=$kubeconfig_dir_path/$kubeconfig_filename kind get kubeconfig --name ${{ inputs.kind_cluster_name }} > $kubeconfig_path chmod 600 $kubeconfig_path + echo "kubeconfig path: $kubeconfig_path" echo "kubeconfig=$(echo $kubeconfig_path)" >> $GITHUB_OUTPUT shell: bash diff --git a/.github/workflows/e2e-test.yaml b/.github/workflows/e2e-test.yaml index 30b7a6b3c..ff1d8464e 100644 --- a/.github/workflows/e2e-test.yaml +++ b/.github/workflows/e2e-test.yaml @@ -9,6 +9,9 @@ on: jobs: build: runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write steps: - name: Set up Go @@ -20,13 +23,39 @@ jobs: uses: actions/checkout@v3 - name: Setup Kind cluster - id: setup-kind + id: setup-kind1 uses: ./.github/actions/kind-create + with: + kind_cluster_name: kind1 + kind_k8s_version: v1.24.13 + kubeconfig_dir_path: tests/e2e/kubeconfigs + + # - name: Setup Kind cluster2 + # id: setup-kind2 + # uses: ./.github/actions/kind-create + # with: + # kind_cluster_name: kind2 + # kind_k8s_version: v1.23.17 + # kubeconfig_dir_path: tests/e2e/kubeconfigs - - name: run tests + - name: Run E2E tests env: - KUBECONFIG: ${{ steps.setup-kind.outputs.kubeconfig }} + TEST_STRATEGY: version + KUBECONFIG_DIR: kubeconfigs + MAX_TIMEOUT: "1h" run: | go work init go work use -r . - make test-e2e + make test-e2e-parallel + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action/composite@master + if: always() + with: + commit: ${{ github.event.workflow_run.head_sha }} + report_individual_runs: "true" + check_name: "E2E test report" + large_files: true + comment_mode: off + files: | + tests/e2e/reports/e2e_*.xml diff --git a/Makefile b/Makefile index 0b12076a5..4a719d085 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ CONTROLLER_GEN_VERSION = v0.9.2 CONTROLLER_GEN = $(PWD)/bin/controller-gen ENVTEST_K8S_VERSION = 1.24.2 +# Ginkgo version should be the same that is used in the module imports +GINKGO_VERSION := 2.11.0 +MOCKGEN_VERSION := 1.6.0 + KUSTOMIZE_BASE = config/overlays/specific-manager-version @@ -86,8 +90,10 @@ install-kustomize: fi # Run tests -test: generate fmt vet manifests bin/setup-envtest - cd api && go test ./... +test: generate fmt vet manifests bin/setup-envtest + cd api && go test -failfast ./... + cd tests/e2e && go test -failfast ./... + cd properties && go test -failfast ./... KUBEBUILDER_ASSETS=$$($(BIN_DIR)/setup-envtest --print path --bin-dir $(BIN_DIR) use $(ENVTEST_K8S_VERSION)) \ go test ./... \ -coverprofile cover.out \ @@ -96,17 +102,23 @@ test: generate fmt vet manifests bin/setup-envtest -test.v \ -test.paniconexit0 \ -timeout 1h - cd properties && go test -coverprofile cover.out -cover -failfast -v -covermode=count ./pkg/... ./internal/... -# Run e2e tests -test-e2e: - go test github.com/banzaicloud/koperator/tests/e2e \ - -v \ - -timeout 20m \ - -tags e2e \ - --ginkgo.show-node-events \ - --ginkgo.trace \ - --ginkgo.v +bin/ginkgo: $(BIN_DIR)/ginkgo-$(GINKGO_VERSION) + @ln -sf ginkgo-$(GINKGO_VERSION) $(BIN_DIR)/ginkgo + +$(BIN_DIR)/ginkgo-$(GINKGO_VERSION): + @mkdir -p $(BIN_DIR) + @GOBIN=$(BIN_DIR) go install github.com/onsi/ginkgo/v2/ginkgo@v$(GINKGO_VERSION) + @mv $(BIN_DIR)/ginkgo $(BIN_DIR)/ginkgo-$(GINKGO_VERSION) + + +# Run e2e tests serial +test-e2e: bin/ginkgo + ginkgo -v --tags e2e tests/e2e + +# Run e2e tests parallel +test-e2e-parallel: bin/ginkgo + ginkgo -v -p --tags e2e tests/e2e # Build manager binary manager: generate fmt vet @@ -145,12 +157,14 @@ fmt: go fmt ./... cd api && go fmt ./... cd properties && go fmt ./... + cd tests/e2e && go fmt ./... # Run go vet against code vet: go vet ./... cd api && go fmt ./... cd properties && go vet ./... + cd tests/e2e && go vet ./... # Generate code generate: bin/controller-gen gen-license-header ## Generate source code for APIs, Mocks, etc @@ -252,9 +266,6 @@ gen-license-header: bin/gotemplate ## Generate license header used in source cod --import="$(BOILERPLATE_DIR)/vars.yml" \ --source="$(BOILERPLATE_DIR)" - -MOCKGEN_VERSION := 1.6.0 - bin/mockgen: $(BIN_DIR)/mockgen-$(MOCKGEN_VERSION) @ln -sf mockgen-$(MOCKGEN_VERSION) $(BIN_DIR)/mockgen diff --git a/go.mod b/go.mod index 2d7ef2c1f..c24b2447e 100644 --- a/go.mod +++ b/go.mod @@ -18,8 +18,8 @@ require ( github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.13 - github.com/onsi/ginkgo/v2 v2.9.2 - github.com/onsi/gomega v1.27.6 + github.com/onsi/ginkgo/v2 v2.11.0 + github.com/onsi/gomega v1.27.8 github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1 github.com/prometheus/common v0.37.0 github.com/stretchr/testify v1.8.1 @@ -38,8 +38,8 @@ require ( require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + golang.org/x/tools v0.9.3 // indirect github.com/stretchr/objx v0.5.0 // indirect - golang.org/x/tools v0.7.0 // indirect ) require ( @@ -114,11 +114,11 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.7.0 // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 3b2fa7f1d..73d0f5c38 100644 --- a/go.sum +++ b/go.sum @@ -363,12 +363,12 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1 h1:FyBdsRqqHH4LctMLL+BL2oGO+ONcIPwn96ctofCVtNE= github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -535,6 +535,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -578,8 +579,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 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= @@ -602,7 +603,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -658,13 +659,13 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -674,8 +675,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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= @@ -728,8 +729,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= 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= diff --git a/tests/e2e/const.go b/tests/e2e/const.go index 8a1c9b3db..1747bc86d 100644 --- a/tests/e2e/const.go +++ b/tests/e2e/const.go @@ -46,16 +46,17 @@ const ( zookeeperClusterName = "zookeeper-server" managedByHelmLabelTemplate = "app.kubernetes.io/managed-by=Helm,app.kubernetes.io/instance=%s" - cruiseControlPodReadinessTimeout = 50 * time.Second - kafkaClusterResourceReadinessTimeout = 60 * time.Second - defaultDeletionTimeout = 20 * time.Second - defaultPodReadinessWaitTime = 10 * time.Second - defaultTopicCreationWaitTime = 10 * time.Second - defaultUserCreationWaitTime = 10 * time.Second - kafkaClusterCreateTimeout = 600 * time.Second + cruiseControlPodReadinessTimeout = 50 * time.Second + kafkaClusterResourceReadinessTimeout = 60 * time.Second + defaultDeletionTimeout = 20 * time.Second + defaultPodReadinessWaitTime = 60 * time.Second + defaultTopicCreationWaitTime = 10 * time.Second + defaultUserCreationWaitTime = 10 * time.Second + + kafkaClusterCreateTimeout = 800 * time.Second kafkaClusterResourceCleanupTimeout = 120 * time.Second kcatDeleetionTimeout = 40 * time.Second - zookeeperClusterCreateTimeout = 4 * time.Minute + zookeeperClusterCreateTimeout = 5 * time.Minute zookeeperClusterResourceCleanupTimeout = 60 * time.Second externalConsumerTimeout = 5 * time.Second externalProducerTimeout = 5 * time.Second diff --git a/tests/e2e/go.mod b/tests/e2e/go.mod index 8b99c5037..e4b8eee37 100644 --- a/tests/e2e/go.mod +++ b/tests/e2e/go.mod @@ -7,11 +7,12 @@ require ( github.com/banzaicloud/koperator v0.25.0 github.com/cisco-open/k8s-objectmatcher v1.9.0 github.com/gruntwork-io/terratest v0.41.24 - github.com/onsi/ginkgo/v2 v2.9.5 - github.com/onsi/gomega v1.27.6 + github.com/onsi/ginkgo/v2 v2.11.0 + github.com/onsi/gomega v1.27.8 + github.com/spf13/viper v1.16.0 github.com/twmb/franz-go v1.13.5 - k8s.io/apiextensions-apiserver v0.27.2 - k8s.io/apimachinery v0.27.2 + k8s.io/apiextensions-apiserver v0.26.4 + k8s.io/apimachinery v0.26.4 sigs.k8s.io/yaml v1.3.0 ) @@ -29,9 +30,11 @@ require ( github.com/eapache/queue v1.1.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.13.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/iancoleman/orderedmap v0.2.0 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect @@ -40,20 +43,27 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/klauspost/compress v1.16.3 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/tidwall/gjson v1.9.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/twmb/franz-go/pkg/kmsg v1.4.0 // indirect github.com/wayneashleyberry/terminal-dimensions v1.0.0 // indirect go.uber.org/zap v1.24.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect sigs.k8s.io/controller-runtime v0.14.6 // indirect ) @@ -103,25 +113,26 @@ require ( github.com/pquerna/otp v1.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.8.1 // indirect + github.com/stretchr/testify v1.8.3 github.com/urfave/cli v1.22.2 // indirect go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.7.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect + golang.org/x/sys v0.9.0 // indirect golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.9.3 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.27.2 // indirect - k8s.io/client-go v0.27.2 // indirect + k8s.io/api v0.26.4 + k8s.io/client-go v0.26.4 k8s.io/klog/v2 v2.90.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect diff --git a/tests/e2e/go.sum b/tests/e2e/go.sum index a0fab5d55..259dfa936 100644 --- a/tests/e2e/go.sum +++ b/tests/e2e/go.sum @@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT 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.44.3/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= @@ -13,6 +14,9 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 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= @@ -30,6 +34,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0= emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE= @@ -94,6 +99,7 @@ github.com/cisco-open/k8s-objectmatcher v1.9.0 h1:/sfuO0BD09fpynZjXsqeZrh28Juc4V github.com/cisco-open/k8s-objectmatcher v1.9.0/go.mod h1:CH4E6qAK+q+JwKFJn0DaTNqxrbmWCaDQzGthKLK4nZ0= 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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cppforlife/go-patch v0.2.0 h1:Y14MnCQjDlbw7WXT4k+u6DPAA9XnygN4BfrSpI/19RU= @@ -112,11 +118,13 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1 h1:yY9rWGoXv1U5pl4gxqlULARMQD7x0QG85lqEXTWysik= 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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -131,9 +139,11 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= @@ -215,6 +225,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -224,6 +235,7 @@ 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/martian/v3 v3.1.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= @@ -231,6 +243,9 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -239,6 +254,7 @@ 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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= @@ -258,6 +274,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -304,6 +322,7 @@ github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0P github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -314,6 +333,8 @@ 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -336,6 +357,8 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= @@ -358,14 +381,16 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1 h1:FyBdsRqqHH4LctMLL+BL2oGO+ONcIPwn96ctofCVtNE= github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -373,6 +398,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= @@ -404,7 +430,7 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -414,11 +440,17 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -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/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -432,8 +464,11 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -464,14 +499,16 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= @@ -481,9 +518,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 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= @@ -494,6 +532,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 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= @@ -506,6 +546,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/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= @@ -514,6 +555,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -547,6 +590,10 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -561,9 +608,13 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 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= @@ -614,12 +665,18 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -629,8 +686,9 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= @@ -640,6 +698,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -693,10 +752,17 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= 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= @@ -718,6 +784,9 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/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= @@ -756,6 +825,13 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -769,7 +845,11 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -785,8 +865,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -797,6 +877,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -822,15 +904,15 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= -k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= -k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= -k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= +k8s.io/api v0.26.4 h1:qSG2PmtcD23BkYiWfoYAcak870eF/hE7NNYBYavTT94= +k8s.io/api v0.26.4/go.mod h1:WwKEXU3R1rgCZ77AYa7DFksd9/BAIKyOmRlbVxgvjCk= +k8s.io/apiextensions-apiserver v0.26.4 h1:9D2RTxYGxrG5uYg6D7QZRcykXvavBvcA59j5kTaedQI= +k8s.io/apiextensions-apiserver v0.26.4/go.mod h1:cd4uGFGIgzEqUghWpRsr9KE8j2KNTjY8Ji8pnMMazyw= k8s.io/apimachinery v0.0.0-20190704094733-8f6ac2502e51/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= -k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= -k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= +k8s.io/apimachinery v0.26.4 h1:rZccKdBLg9vP6J09JD+z8Yr99Ce8gk3Lbi9TCx05Jzs= +k8s.io/apimachinery v0.26.4/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= +k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= diff --git a/tests/e2e/k8s.go b/tests/e2e/k8s.go index e1c42cefb..f34d6570b 100644 --- a/tests/e2e/k8s.go +++ b/tests/e2e/k8s.go @@ -20,13 +20,13 @@ import ( "io" "net/http" "os" - "path" "strings" "text/template" "time" "emperror.dev/errors" "github.com/Masterminds/sprig" + "github.com/banzaicloud/koperator/tests/e2e/pkg/common" "github.com/cisco-open/k8s-objectmatcher/patch" "github.com/gruntwork-io/terratest/modules/k8s" . "github.com/onsi/ginkgo/v2" @@ -101,46 +101,6 @@ func createOrReplaceK8sResourcesFromManifest( //nolint:unused // Note: this migh } } -// currentKubernetesContext returns the currently set Kubernetes context based -// on the the environment variables and the KUBECONFIG file. -func currentEnvK8sContext() (kubeconfigPath string, kubecontextName string, err error) { - kubeconfigPath, isExisting := os.LookupEnv("KUBECONFIG") - if !isExisting { - homePath, err := os.UserHomeDir() - if err != nil { - return "", "", errors.WrapIf(err, "retrieving user home directory failed") - } - - kubeconfigPath = path.Join(homePath, ".kube", "config") - } - - kubeconfigBytes, err := os.ReadFile(kubeconfigPath) - if err != nil { - return "", "", errors.WrapIfWithDetails(err, "reading KUBECONFIG file failed", "path", kubeconfigPath) - } - - structuredKubeconfig := make(map[string]interface{}) - err = yaml.Unmarshal(kubeconfigBytes, &structuredKubeconfig) - if err != nil { - return "", "", errors.WrapIfWithDetails( - err, - "parsing kubeconfig failed", - "kubeconfig", string(kubeconfigBytes), - ) - } - - kubecontext, isOk := structuredKubeconfig["current-context"].(string) - if !isOk { - return "", "", errors.WrapIfWithDetails( - err, - "kubeconfig current-context is not string", - "current-context", structuredKubeconfig["current-context"], - ) - } - - return kubeconfigPath, kubecontext, nil -} - // getK8sCRD queries and returns the CRD of the specified CRD name from the // provided Kubernetes context. func getK8sCRD(kubectlOptions k8s.KubectlOptions, crdName string) ([]byte, error) { //nolint:unused // Note: this might come in handy for manual CRD operations. @@ -381,7 +341,7 @@ func kubectlOptions(kubecontextName, kubeconfigPath, namespace string) k8s.Kubec // kubectlOptionsForCurrentContext returns a kubectlOptions object for the // current Kubernetes context or alternatively an error. func kubectlOptionsForCurrentContext() (k8s.KubectlOptions, error) { - kubeconfigPath, kubecontextName, err := currentEnvK8sContext() + kubeconfigPath, kubecontextName, err := common.CurrentEnvK8sContext() if err != nil { return k8s.KubectlOptions{}, errors.WrapIf(err, "retrieving current environment Kubernetes context failed") } diff --git a/tests/e2e/koperator_suite_test.go b/tests/e2e/koperator_suite_test.go index a7ee2f8c3..3f59c4ecf 100644 --- a/tests/e2e/koperator_suite_test.go +++ b/tests/e2e/koperator_suite_test.go @@ -17,54 +17,190 @@ package e2e import ( + "fmt" + "log" + "os" + "path/filepath" "testing" + "time" - "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/banzaicloud/koperator/tests/e2e/pkg/common/config" + "github.com/banzaicloud/koperator/tests/e2e/pkg/tests" "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/reporters" . "github.com/onsi/gomega" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/onsi/gomega/format" + "github.com/spf13/viper" ) +var testPool tests.TestPool + +func beforeSuite() (tests.TestPool, error) { + var k8sClusterPool tests.K8sClusterPool + if err := k8sClusterPool.FeedFomDirectory(viper.GetString(config.Tests.KubeConfigDirectoryPath)); err != nil { + return nil, err + } + + classifier := tests.NewClassifier(k8sClusterPool, alltestCase) + + var testPool tests.TestPool + testStrategy := viper.GetString(config.Tests.TestStrategy) + + switch testStrategy { + case config.TestStrategyMinimal: + testPool = classifier.Minimal() + case config.TestStrategyVersionComplete: + testPool = classifier.VersionComplete() + case config.TestStrategyProviderComplete: + testPool = classifier.ProviderComplete() + case config.TestStrategyComplete: + testPool = classifier.Complete() + } + + return testPool, nil +} + func TestKoperator(t *testing.T) { - RegisterFailHandler(Fail) // Note: Ginkgo - Gomega connector. - RunSpecs(t, "Koperator end to end test suite") + var err error + + err = runGinkgoTests(t) + if err != nil { + t.Errorf("Koperator E2E start failed: %v", err) + } } -var _ = BeforeSuite(func() { - By("Acquiring K8s cluster") - var kubeconfigPath string - var kubecontextName string +func runGinkgoTests(t *testing.T) error { + RegisterFailHandler(Fail) + suiteConfig, _ := GinkgoConfiguration() - By("Acquiring K8s config and context", func() { - var err error - kubeconfigPath, kubecontextName, err = currentEnvK8sContext() - Expect(err).NotTo(HaveOccurred()) - }) + // Gomega configurations + format.MaxLength = 0 - By("Listing kube-system pods", func() { - pods := k8s.ListPods( - ginkgo.GinkgoT(), - k8s.NewKubectlOptions(kubecontextName, kubeconfigPath, "kube-system"), - v1.ListOptions{}, - ) + // Run only selected tests by testID label e.g: "testID:4e980f5b5c" + if labelFilter := viper.GetString(config.Tests.LabelFilter); labelFilter != "" { + suiteConfig.LabelFilter = labelFilter + } - Expect(len(pods)).To(Not(BeZero())) - }) + var err error + // Generated and load tests into the pool + testPool, err = beforeSuite() + if err != nil { + return fmt.Errorf("beforeSuite ran into err: %w", err) + } + + runningSuiteProgress.allSpecCount = testPool.GetTestSuiteSpecsCount() + + testSuiteDuration := testPool.GetTestSuiteDurationSerial() + if suiteConfig.ParallelTotal > 1 { + testSuiteDuration = testPool.GetTestSuiteDurationParallel() + } + + maxTimeout, err := time.ParseDuration(viper.GetString(config.Tests.MaxTimeout)) + if err != nil { + return fmt.Errorf("could not parse MaxTimeout into time.Duration: %w", err) + } + + // Calculated timeout can be overran with the specified time length + allowedOverrun, err := time.ParseDuration(viper.GetString(config.Tests.AllowedOverrunDuration)) + if err != nil { + return fmt.Errorf("could not parse AllowedOverrunDuration into time.Duration: %w", err) + } + + // Set TestSuite timeout based on the generated tests + suiteConfig.Timeout = testSuiteDuration + allowedOverrun + + // Protection against too long test suites + if suiteConfig.Timeout > maxTimeout { + return fmt.Errorf("tests estimated duration: '%s' longer then maxTimeout: '%s'", suiteConfig.Timeout.String(), maxTimeout.String()) + } + + if viper.GetBool(config.Tests.CreateTestReportFile) { + if err := createTestReportFile(); err != nil { + return err + } + } + + testDescription := fmt.Sprintf("\n%s\nConfigurations: \n%s%s\nPoolInfo: \n%s%s\n", + sectionStringDelimiter(), config.Tests, sectionStringDelimiter(), testPool.PoolInfo(), sectionStringDelimiter()) + + func() { + defer ginkgo.GinkgoRecover() + RunSpecs(t, testDescription, suiteConfig) + }() + + return nil + +} + +type runningSuiteData struct { + passedTestCount int + skippedTestCount int + failedTestCount int + allSpecCount int +} + +var runningSuiteProgress = runningSuiteData{} + +// Report suit progress only into the std output +var _ = ReportAfterEach(func(report SpecReport) { + switch report.State.String() { + case "failed": + runningSuiteProgress.failedTestCount += 1 + case "passed": + runningSuiteProgress.passedTestCount += 1 + case "skipped": + runningSuiteProgress.skippedTestCount += 1 + } + + entry := fmt.Sprintf("{{red}}%s(TOTAL:%d PROGRESS/PROC:%d/%d/%d PID: %d){{/}}", + report.State, + runningSuiteProgress.allSpecCount, + runningSuiteProgress.passedTestCount, + runningSuiteProgress.failedTestCount, + runningSuiteProgress.skippedTestCount, + report.ParallelProcess) + + // TODO: it would be better to calculate somehow the total specs count per process + // Im not sure about that is possible because specs number can be different on each processes + // This is because the classifier sort the tests the best possible way so when there are + // more available cluster then tests it is possible that one of the test is executed on a cluster and another on another one + // e.g: [MockTest2(testContextName2) MockTest1(testContextName1) MockTest2(testContextName1) MockTest1(testContextName2) MockTest1(testContextName3) MockTest2(testContextName4)] + AddReportEntry(entry) }) -var _ = When("Testing e2e test altogether", Ordered, func() { - var snapshottedInfo = &clusterSnapshot{} - snapshotCluster(snapshottedInfo) - testInstall() - testInstallZookeeperCluster() - testInstallKafkaCluster("../../config/samples/simplekafkacluster.yaml") - testProduceConsumeInternal() - testUninstallKafkaCluster() - testInstallKafkaCluster("../../config/samples/simplekafkacluster_ssl.yaml") - testProduceConsumeInternalSSL(defaultTLSSecretName) - testUninstallKafkaCluster() - testUninstallZookeeperCluster() - testUninstall() - snapshotClusterAndCompare(snapshottedInfo) +// Root Describe container +var _ = Describe("", func() { + // In the root container there is no Ordered decorator + // ginkgo execute parallel the generated tests by K8sClusters + testPool.BuildParallelByK8sCluster() }) + +func sectionStringDelimiter() string { + delimiter := "" + for i := 0; i < 100; i++ { + delimiter += "-" + } + return delimiter +} + +func createTestReportFile() error { + reportDir := viper.GetString(config.Tests.ReportDir) + if _, err := os.Stat(reportDir); os.IsNotExist(err) { + if err := os.Mkdir(reportDir, os.FileMode(0o777)); err != nil { + return fmt.Errorf("error while creating report directory %s err: %s", reportDir, err.Error()) + } + } + // Generate JUnit report once all tests have finished with customized settings + _ = ginkgo.ReportAfterSuite("Koperator e2e", func(report ginkgo.Report) { + err := reporters.GenerateJUnitReportWithConfig( + report, + filepath.Join(reportDir, fmt.Sprintf("e2e_%s_%v.xml", viper.GetString(config.Tests.TestStrategy), time.Now().Format(time.RFC3339))), + reporters.JunitReportConfig{OmitSpecLabels: false, OmitLeafNodeType: false}, + ) + if err != nil { + log.Printf("error creating junit report file %s", err.Error()) + } + }) + return nil +} diff --git a/tests/e2e/pkg/common/common.go b/tests/e2e/pkg/common/common.go new file mode 100644 index 000000000..7e272b4ee --- /dev/null +++ b/tests/e2e/pkg/common/common.go @@ -0,0 +1,115 @@ +// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "os" + "path" + + "emperror.dev/errors" + "github.com/gruntwork-io/terratest/modules/k8s" + "golang.org/x/exp/maps" + "gopkg.in/yaml.v2" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" +) + +// CurrentEnvK8sContext returns the currently set Kubernetes context based +// on the the environment variables and the KUBECONFIG file. +func CurrentEnvK8sContext() (kubeconfigPath string, kubecontextName string, err error) { + kubeconfigPath, isExisting := os.LookupEnv("KUBECONFIG") + + if !isExisting { + homePath, err := os.UserHomeDir() + if err != nil { + return "", "", errors.WrapIf(err, "retrieving user home directory failed") + } + + kubeconfigPath = path.Join(homePath, ".kube", "config") + } + + kubecontext, err := GetDefaultKubeContext(kubeconfigPath) + if err != nil { + return "", "", err + } + + return kubeconfigPath, kubecontext, nil +} + +// kubectlOptionsForCurrentContext returns a kubectlOptions object for the +// current Kubernetes context or alternatively an error. +func KubectlOptionsForCurrentContext() (k8s.KubectlOptions, error) { + kubeconfigPath, kubecontextName, err := CurrentEnvK8sContext() + if err != nil { + return k8s.KubectlOptions{}, errors.WrapIf(err, "retrieving current environment Kubernetes context failed") + } + + return k8s.KubectlOptions{ + ConfigPath: kubeconfigPath, + ContextName: kubecontextName, + Namespace: "", + }, nil +} + +// GetDefaultKubeContext returns the default kubeContext name from the given kubeconfig file +func GetDefaultKubeContext(kubeconfigPath string) (string, error) { + kubeconfigBytes, err := os.ReadFile(kubeconfigPath) + if err != nil { + return "", errors.WrapIfWithDetails(err, "reading KUBECONFIG file failed", "path", kubeconfigPath) + } + + structuredKubeconfig := make(map[string]interface{}) + err = yaml.Unmarshal(kubeconfigBytes, &structuredKubeconfig) + if err != nil { + return "", errors.WrapIfWithDetails( + err, + "parsing kubeconfig failed", + "kubeconfig", string(kubeconfigBytes), + ) + } + + kubecontext, isOk := structuredKubeconfig["current-context"].(string) + if !isOk { + return "", errors.WrapIfWithDetails( + err, + "kubeconfig current-context is not string", + "current-context", structuredKubeconfig["current-context"], + ) + } + + return kubecontext, nil +} + +// CreateRawConfig creates a raw clientcmd api config +func CreateRawConfig(kubeconfigPath string) (api.Config, error) { + rules := clientcmd.NewDefaultClientConfigLoadingRules() + if kubeconfigPath == "" { + return api.Config{}, errors.New("missing kubeconfigPath") + } + rules.ExplicitPath = kubeconfigPath + + clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, nil) + + return clientConfig.RawConfig() +} + +// GetKubeContexts returns the available kubecontext names in the kubeconfig file +func GetKubeContexts(kubeconfigPath string) ([]string, error) { + configs, err := CreateRawConfig(kubeconfigPath) + if err != nil { + return nil, err + } + return maps.Keys(configs.Contexts), nil +} diff --git a/tests/e2e/pkg/common/config/config.go b/tests/e2e/pkg/common/config/config.go new file mode 100644 index 000000000..64b02f7c7 --- /dev/null +++ b/tests/e2e/pkg/common/config/config.go @@ -0,0 +1,98 @@ +// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + + "github.com/spf13/viper" +) + +type TestStrategy struct{} + +const ( + TestStrategyMinimal = "minimal" + TestStrategyVersionComplete = "version" + TestStrategyProviderComplete = "provider" + TestStrategyComplete = "complete" +) + +const ( + defaultReportDir = "reports" + defaultCreateTestReportFile = "true" + defaultMaxTimeout = "1h" + defaultAllowedOverrunDuration = "10m" + defaultTestStrategy = TestStrategyVersionComplete + defaultKubeConfigDirectoryPath = "kubeconfigs" +) + +type TestsType struct { + ReportDir string + CreateTestReportFile string + MaxTimeout string + AllowedOverrunDuration string + TestStrategy string + LabelFilter string + KubeConfigDirectoryPath string +} + +func (t TestsType) String() string { + return fmt.Sprintf(` +ReportDir: %s +CreateTestReportFile: %s +MaxTimeout: %s +AllowedOverrunDuration: %s +TestStrategy: %s +LabelFilter: %s +KubeConfigDirectoryPath: %s +`, viper.GetString(t.ReportDir), viper.GetString(t.CreateTestReportFile), + viper.GetString(t.MaxTimeout), viper.GetString(t.AllowedOverrunDuration), + viper.GetString(t.TestStrategy), viper.GetString(t.LabelFilter), + viper.GetString(t.KubeConfigDirectoryPath)) +} + +var Tests = TestsType{ + CreateTestReportFile: "tests.CreateTestReportFile", + MaxTimeout: "tests.MaxTimeout", + AllowedOverrunDuration: "tests.AllowedOverrunDuration", + TestStrategy: "tests.TestStrategy", + ReportDir: "tests.ReportDir", + LabelFilter: "tests.LabelFilter", + KubeConfigDirectoryPath: "tests.KubeConfigDirectoryPath", +} + +func init() { + viper.AutomaticEnv() + + viper.BindEnv(Tests.CreateTestReportFile, "CREATE_TEST_REPORT_FILE") + viper.SetDefault(Tests.CreateTestReportFile, defaultCreateTestReportFile) + + viper.BindEnv(Tests.ReportDir, "REPORT_DIR") + viper.SetDefault(Tests.ReportDir, defaultReportDir) + + viper.BindEnv(Tests.MaxTimeout, "MAX_TIMEOUT") + viper.SetDefault(Tests.MaxTimeout, defaultMaxTimeout) + + viper.BindEnv(Tests.AllowedOverrunDuration, "ALLOWED_OVERRUN_DURATION") + viper.SetDefault(Tests.AllowedOverrunDuration, defaultAllowedOverrunDuration) + + viper.BindEnv(Tests.TestStrategy, "TEST_STRATEGY") + viper.SetDefault(Tests.TestStrategy, defaultTestStrategy) + + viper.BindEnv(Tests.KubeConfigDirectoryPath, "KUBECONFIG_DIR") + viper.SetDefault(Tests.KubeConfigDirectoryPath, defaultKubeConfigDirectoryPath) + + viper.BindEnv(Tests.LabelFilter, "LABEL_FILTER") +} diff --git a/tests/e2e/pkg/tests/mocks.go b/tests/e2e/pkg/tests/mocks.go new file mode 100644 index 000000000..a36589a03 --- /dev/null +++ b/tests/e2e/pkg/tests/mocks.go @@ -0,0 +1,270 @@ +// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +import ( + "github.com/gruntwork-io/terratest/modules/k8s" + . "github.com/onsi/ginkgo/v2" + + "time" +) + +// MockTestsMinimal returns a Classifier that has 3 different K8s clusters using 2 different K8s versions and 3 different providers. +// The Classifier contains 2 tests. +// +// Expected (minimal strategy): +// - 2 testCases +// - 1 testCase on any of the available K8sClusters +// - runtime parallel: 1x(3) = 3sec (time of the longest testCase) +func MockTestsMinimal() Classifier { + k8sClusterPool := K8sClusterPool{ + NewMockK8sCluster( + "testContextPath1", + "testContextName1", + "1.24", + "provider1", + "clusterID1", + ), + NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.25", + "provider2", + "clusterID2", + ), + NewMockK8sCluster( + "testContextPath3", + "testContextName3", + "1.24", + "provider3", + "clusterID3", + ), + } + return NewClassifier(k8sClusterPool, mockTest1, mockTest2) +} + +// MockTestsProvider returns a Classifier that has 3 different K8s clusters using 3 different K8s provider. +// The Classifier contains 2 tests. +// +// Expected (provider strategy): +// - 6 testCases +// - 2 testCases on different providers +// - runtime parallel: 2 + 3 = 5sec +func MockTestsProvider() Classifier { + k8sClusterPool := K8sClusterPool{ + NewMockK8sCluster( + "testContextPath1", + "testContextName1", + "1.24", + "provider1", + "clusterID1", + ), + NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.24", + "provider2", + "clusterID2", + ), + NewMockK8sCluster( + "testContextPath3", + "testContextName3", + "1.24", + "provider3", + "clusterID3", + ), + } + return NewClassifier(k8sClusterPool, mockTest1, mockTest2) +} + +// MockTestsProviderMoreTestsThenProvider returns a Classifier that has 2 different K8s clusters using 2 different K8s provider. +// The Classifier contains 3 tests. +// +// Expected (provider strategy): +// - 6 testCases +// - 3 testCases on different providers +// - runtime parallel: 2 + 3 + 4 = 9sec +func MockTestsProviderMoreTestsThenProvider() Classifier { + k8sClusterPool := K8sClusterPool{ + NewMockK8sCluster( + "testContextPath1", + "testContextName1", + "1.24", + "provider1", + "clusterID1", + ), + NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.24", + "provider2", + "clusterID2", + ), + } + return NewClassifier(k8sClusterPool, mockTest1, mockTest2, mockTest3) +} + +// MockTestsVersionOne returns a Classifier that has 2 different K8s clusters using same K8s versions and 2 different providers. +// The Classifier contains 2 tests. +// +// Expected (version strategy): +// - 2 testCases +// - 1 testCase on any of the available K8sClusters +// - runtime parallel: 1 x 3 = 3sec +func MockTestsVersionOne() Classifier { + k8sClusterPool := K8sClusterPool{ + NewMockK8sCluster( + "testContextPath1", + "testContextName1", + "1.24", + "provider1", + "clusterID1", + ), + NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.24", + "provider2", + "clusterID2", + ), + } + return NewClassifier(k8sClusterPool, mockTest1, mockTest2) +} + +// MockTestsVersion returns a Classifier that has 3 different K8s clusters using 2 different K8s versions and 2 different providers. +// The Classifier contains 2 tests. +// +// Expected (version strategy): +// - 4 testCases +// - 2 testCase on any of the available K8sClusters +// - runtime parallel: 1 x 5 = 5sec +func MockTestsVersion() Classifier { + k8sClusterPool := K8sClusterPool{ + NewMockK8sCluster( + "testContextPath1", + "testContextName1", + "1.24", + "provider1", + "clusterID1", + ), + NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.24", + "provider2", + "clusterID2", + ), + NewMockK8sCluster( + "testContextPath4", + "testContextName4", + "1.26", + "provider3", + "clusterID4", + ), + } + return NewClassifier(k8sClusterPool, mockTest1, mockTest2) +} + +// MockTestsComplete returns a Classifier that has 4 different K8s clusters using 2 different K8s versions and 3 different providers. +// The Classifier contains 2 tests. +// +// Expected (complete strategy): +// - 6 testCases +// - 2 testCase on every different K8sClusters provider and version +// - runtime parallel: 4 + 5 = 9sec +func MockTestsComplete() Classifier { + k8sClusterPool := K8sClusterPool{ + NewMockK8sCluster( + "testContextPath1", + "testContextName1", + "1.24", + "provider1", + "clusterID1", + ), + NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.24", + "provider2", + "clusterID2", + ), + NewMockK8sCluster( + "testContextPath3", + "testContextName3", + "1.25", + "provider3", + "clusterID3", + ), + NewMockK8sCluster( + "testContextPath4", + "testContextName4", + "1.25", + "provider3", + "clusterID4", + ), + } + return NewClassifier(k8sClusterPool, mockTest1, mockTest2) +} + +var mockTest1 = TestCase{ + SpecsCount: 2, + Duration: 2 * time.Second, + Name: "MockTest1", + TestFn: testMockTest1, +} + +func testMockTest1(kubectlOptions k8s.KubectlOptions) { + It("MockTest1-1", func() { + time.Sleep(time.Second * 1) + }) + It("MockTest1-2", func() { + time.Sleep(time.Second * 1) + }) +} + +var mockTest2 = TestCase{ + SpecsCount: 3, + Duration: 3 * time.Second, + Name: "MockTest2", + TestFn: testMockTest2, +} + +func testMockTest2(kubectlOptions k8s.KubectlOptions) { + It("MockTest2-1", func() { + time.Sleep(time.Second * 1) + }) + It("MockTest2-2", func() { + time.Sleep(time.Second * 1) + }) + It("MockTest2-3", func() { + time.Sleep(time.Second * 1) + }) +} + +var mockTest3 = TestCase{ + SpecsCount: 2, + Duration: 4 * time.Second, + Name: "MockTest3", + TestFn: testMockTest3, +} + +func testMockTest3(kubectlOptions k8s.KubectlOptions) { + It("MockTest3-1", func() { + time.Sleep(time.Second * 2) + }) + It("MockTest3-2", func() { + time.Sleep(time.Second * 2) + }) +} diff --git a/tests/e2e/pkg/tests/tests.go b/tests/e2e/pkg/tests/tests.go new file mode 100644 index 000000000..9132b197c --- /dev/null +++ b/tests/e2e/pkg/tests/tests.go @@ -0,0 +1,576 @@ +// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +import ( + "crypto/md5" + "encoding/hex" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "time" + + "github.com/banzaicloud/koperator/tests/e2e/pkg/common" + "github.com/banzaicloud/koperator/tests/e2e/pkg/common/config" + "github.com/gruntwork-io/terratest/modules/k8s" + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/dsl/decorators" + "github.com/spf13/viper" + "golang.org/x/exp/maps" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// []testCase +// | +// Classifier ==> TestPool <-- []Test <-- K8sCluster <-- ClusterInfo +// ^ ^ +// | | +// K8sClusterPool testCase + +type TestPool []Test + +// Equal returns true when the TestPools are equal +func (tests TestPool) Equal(other TestPool) bool { + if len(tests) != len(other) { + return false + } + + tests.sort() + other.sort() + + for i := range tests { + if !tests[i].equal(other[i]) { + return false + } + } + return true +} + +// PoolInfo returns a formatted string as information about the current testPool +func (tests TestPool) PoolInfo() string { + testsByContextName := tests.getTestsByContextName() + testsByProviders := tests.getTestsByProviders() + testsByVersions := tests.getTestsByVersions() + + return fmt.Sprintf(` +NumberOfTests: %d +NumberOfSpecs: %d +NumberOfK8sClusters: %d +K8sVersions: %v +K8sProviders: %v +TestK8sMapping: %v +TestStrategy: %s +ExpectedDurationSerial: %s +ExpectedDurationParallel: %s +`, len(tests), tests.GetTestSuiteSpecsCount(), len(maps.Keys(testsByContextName)), maps.Keys(testsByVersions), maps.Keys(testsByProviders), + tests, viper.GetString(config.Tests.TestStrategy), tests.GetTestSuiteDurationSerial().String(), + tests.GetTestSuiteDurationParallel().String()) +} + +// BuildParallelByK8sCluster builds Describe container blocks from tests and K8sClusters. +// In this way we can have guarantee to the tests serial execution for a K8sCluster. +func (tests TestPool) BuildParallelByK8sCluster() { + testsByClusterID := tests.getSortedTestsByClusterID() + testsByClusterIDKeys := sortedKeys(testsByClusterID) + describeID := 0 + // Because of the "Ordered" decorator testCases inside that container will run each after another + // thus this K8s cluster (container) is dedicated for certain test for serial execution. + for _, clusterID := range testsByClusterIDKeys { + describeID += 1 + tests := testsByClusterID[clusterID] + // Describe name has to be unique and consistent between processes for proper parallelization. + Describe(fmt.Sprintf("%d:", describeID), decorators.Label(fmt.Sprintf("K8sID:%s", clusterID)), Ordered, func() { + for _, test := range tests { + test.Run() + } + }) + } +} + +func (tests TestPool) sort() { + sort.SliceStable(tests, func(i, j int) bool { + return tests[i].less(tests[j]) + }) +} + +func (tests TestPool) getSortedTestsByClusterID() map[string][]Test { + testsByClusterID := make(map[string][]Test) + // Need to be sorted to achieve test specs tree consistency between processes + // otherwise it can happen that specs order will be different for each process + tests.sort() + + for _, test := range tests { + testsByClusterID[test.k8sCluster.clusterInfo.clusterID] = append(testsByClusterID[test.k8sCluster.clusterInfo.clusterID], test) + } + + return testsByClusterID +} + +func (tests TestPool) getTestsByContextName() map[string][]Test { + testsByContextName := make(map[string][]Test) + for _, test := range tests { + testsByContextName[test.k8sCluster.kubectlOptions.ContextName] = append(testsByContextName[test.k8sCluster.kubectlOptions.ContextName], test) + } + + return testsByContextName +} + +func (tests TestPool) getTestsByVersions() map[string][]Test { + testsByVersions := make(map[string][]Test) + for _, test := range tests { + testsByVersions[test.k8sCluster.clusterInfo.version] = append(testsByVersions[test.k8sCluster.clusterInfo.version], test) + } + + return testsByVersions +} + +func (tests TestPool) getTestsByProviders() map[string][]Test { + testsByProviders := make(map[string][]Test) + for _, test := range tests { + testsByProviders[test.k8sCluster.clusterInfo.provider] = append(testsByProviders[test.k8sCluster.clusterInfo.provider], test) + } + + return testsByProviders +} + +// GetTestSuiteDurationParallel calculates the expected duration of the tests when +// suite is executed in parallel. +func (tests TestPool) GetTestSuiteDurationParallel() time.Duration { + testsByClusterID := tests.getSortedTestsByClusterID() + + var max time.Duration + for _, tests := range testsByClusterID { + var localDuration time.Duration + for _, test := range tests { + localDuration += test.testCase.Duration + } + + if localDuration > max { + max = localDuration + } + } + return max +} + +// GetTestSuiteDurationSerial calculates the expected duration of the tests when +// test suite is executed in serial. +func (tests TestPool) GetTestSuiteDurationSerial() time.Duration { + var allDuration time.Duration + for _, test := range tests { + allDuration += test.testCase.Duration + } + return allDuration +} + +// GetTestSuiteSpecsCount returns the number of specs in the pool based on the testCase explicit information. +func (tests TestPool) GetTestSuiteSpecsCount() int { + var specsCount int + for _, test := range tests { + specsCount += test.testCase.SpecsCount + } + return specsCount +} + +// GetParallelTotal returns the maximum number of the possible parallelization +func (tests TestPool) GetParallelTotal() int { + return len(maps.Keys(tests.getSortedTestsByClusterID())) +} + +// NewClassifier creates a test classifier from K8sClusterPool and TestCases. +func NewClassifier(k8sClusterPool K8sClusterPool, testCases ...TestCase) Classifier { + return Classifier{ + k8sClusterPool: k8sClusterPool, + testCases: testCases, + } +} + +// Classifier makes pairs from testCases and K8sClusters based on the specified test strategy +type Classifier struct { + k8sClusterPool K8sClusterPool + testCases []TestCase +} + +// Minimal creates a TestPool based on minimal test strategy. +// It means running every test case exactly once each on any provider and +// any version of the K8sCluster pool (the ones first available to use). +func (t Classifier) Minimal() TestPool { + tests := make([]Test, 0, len(t.testCases)) + + for i, testCase := range t.testCases { + tests = append(tests, NewTest(testCase, t.k8sClusterPool[i%len(t.k8sClusterPool)])) + } + return tests +} + +// VersionComplete creates a TestPool based on version complete test strategy. +// It means running every test case exactly len(versions) times each, +// using any provider and every version once per case of the K8sCluster pool. +func (t Classifier) VersionComplete() TestPool { + k8sClustersByVersion := t.k8sClusterPool.getByVersions() + tests := make(TestPool, 0, len(t.testCases)*len(maps.Keys(k8sClustersByVersion))) + + for _, k8sClusters := range k8sClustersByVersion { + for i, testCase := range t.testCases { + tests = append(tests, NewTest(testCase, k8sClusters[i%len(k8sClusters)])) + } + } + return tests +} + +// ProviderComplete creates a TestPool based on provider complete test strategy. +// It means running every test case exactly len(providers) times each, +// using any version and every provider once per case of the K8sCluster pool. +func (t Classifier) ProviderComplete() TestPool { + k8sClustersByProviders := t.k8sClusterPool.getByProviders() + tests := make(TestPool, 0, len(t.testCases)*len(maps.Keys(k8sClustersByProviders))) + + for _, k8sClusters := range k8sClustersByProviders { + for i, testCase := range t.testCases { + tests = append(tests, NewTest(testCase, k8sClusters[i%len(k8sClusters)])) + } + } + return tests +} + +// Complete creates a TestPool based on complete test strategy. +// It means running every test case exactly len(providers)*len(versions) times each, +// using every provider and every version combination once of the pool. +func (t Classifier) Complete() TestPool { + k8sClustersByProvidersVersions := t.k8sClusterPool.getByProvidersVersions() + var tests TestPool + + for _, byVersions := range k8sClustersByProvidersVersions { + for _, k8sClusters := range byVersions { + for i, testCase := range t.testCases { + tests = append(tests, NewTest(testCase, k8sClusters[i%len(k8sClusters)])) + } + } + } + return tests +} + +// K8sClusterPool is a representation of the K8sClusters. +type K8sClusterPool []K8sCluster + +// FeedFomDirectory creates K8sClusters from kubeconfigs in the specified directory and add them into the pool. +func (k8sPool *K8sClusterPool) FeedFomDirectory(kubeConfigDirectoryPath string) error { + files, err := os.ReadDir(kubeConfigDirectoryPath) + if err != nil { + return fmt.Errorf("unable to read kubeConfig directory '%s' error: %w", kubeConfigDirectoryPath, err) + } + + if len(files) == 0 { + return fmt.Errorf("kubeConfig directory '%s' is empty", kubeConfigDirectoryPath) + } + + for _, file := range files { + if file != nil { + kubectlPath := filepath.Join(kubeConfigDirectoryPath, file.Name()) + k8sClusters, err := NewK8sClustersFromParams(kubectlPath) + + if err != nil { + return fmt.Errorf("could not get K8sClusters from file '%s' err: %w", kubectlPath, err) + } + *k8sPool = append(*k8sPool, k8sClusters...) + } + } + return nil +} + +func (k8sPool K8sClusterPool) getByVersions() map[string][]K8sCluster { + byVersions := make(map[string][]K8sCluster) + + for _, k8sCluster := range k8sPool { + byVersions[k8sCluster.clusterInfo.version] = append(byVersions[k8sCluster.clusterInfo.version], k8sCluster) + } + + return byVersions +} + +func (k8sPool K8sClusterPool) getByProviders() map[string][]K8sCluster { + byProviders := make(map[string][]K8sCluster) + + for _, k8sCluster := range k8sPool { + byProviders[k8sCluster.clusterInfo.provider] = append(byProviders[k8sCluster.clusterInfo.provider], k8sCluster) + } + + return byProviders +} + +func (k8sPool K8sClusterPool) getByProvidersVersions() map[string]map[string][]K8sCluster { + byProvidersVersions := make(map[string]map[string][]K8sCluster) + byProviders := k8sPool.getByProviders() + + for provider, k8sClusters := range byProviders { + for _, k8sCluster := range k8sClusters { + if byProvidersVersions[provider] == nil { + byProvidersVersions[provider] = make(map[string][]K8sCluster) + } + byProvidersVersions[provider][k8sCluster.clusterInfo.version] = append(byProvidersVersions[provider][k8sCluster.clusterInfo.version], k8sCluster) + } + } + return byProvidersVersions +} + +// TestCase is a representation of an E2E test case. +// - SpecsCount is the number of the gingko specs in the test. +// - Duration specifies the expected length of the test. +// - Name is the name of the test. +// - TestFn specifies the test function. +type TestCase struct { + SpecsCount int + Duration time.Duration + Name string + TestFn func(kubectlOptions k8s.KubectlOptions) +} + +// NewTest creates a Test from a TestCase and from a K8sCluster. +// The testID for Test is generated automatically based on Name and K8s clusterInfo. +func NewTest(testCase TestCase, k8sCluster K8sCluster) Test { + test := Test{ + testCase: testCase, + k8sCluster: k8sCluster, + } + test.generateTestID() + + return test +} + +// Test represents an E2E test. +// It is a combination of a testCase and a k8sCluster. +type Test struct { + testID string + testCase TestCase + k8sCluster K8sCluster +} + +func (t Test) equal(test Test) bool { + return t.TestID() == test.TestID() +} + +func (t Test) less(test Test) bool { + return t.TestID() < test.TestID() +} + +// generateTestID generates the test UID based on the name of the test and the K8sCluster. +func (t *Test) generateTestID() string { + text := fmt.Sprintf("%v%#v", t.testCase.Name, t.k8sCluster) + hash := md5.Sum([]byte(text)) + t.testID = hex.EncodeToString(hash[:5]) + + return t.testID +} + +// TestID returns the test UID based on the name of the test and the K8sCluster. +func (t *Test) TestID() string { + if t.testID == "" { + t.generateTestID() + } + return t.testID +} + +func (t Test) String() string { + return fmt.Sprintf("%s(%s)", + t.testCase.Name, t.k8sCluster) +} + +// Run encapsulates the testCase in a Describe container with +// K8s cluster health check and testID label. +// When K8s cluster is not available the further tests will be skipped. +func (t Test) Run() { + Describe(fmt.Sprint(t), Ordered, decorators.Label(fmt.Sprintf("testID:%s", t.TestID())), func() { + kubectlOptions, err := t.k8sCluster.KubectlOptions() + + if err == nil { + _, err = k8s.ListPodsE( + GinkgoT(), + k8s.NewKubectlOptions(kubectlOptions.ContextName, kubectlOptions.ConfigPath, "kube-system"), + metav1.ListOptions{}, + ) + } + + It("Checking K8s cluster health", func() { + if err != nil { + Fail(fmt.Sprintf("skipping testCase... K8s cluster connection problem: %v", err)) + } + }) + t.testCase.TestFn(kubectlOptions) + }) +} + +func newK8sClusterInfo(kubectlOptions k8s.KubectlOptions) (k8sClusterInfo, error) { + version, err := k8s.GetKubernetesClusterVersionWithOptionsE(GinkgoT(), &kubectlOptions) + if err != nil { + return k8sClusterInfo{}, fmt.Errorf("could not get version: %w", err) + } + version, err = versionIdentifier(version) + if err != nil { + return k8sClusterInfo{}, fmt.Errorf("could not get version: %w", err) + } + kubeSystemNS, err := k8s.GetNamespaceE(GinkgoT(), &kubectlOptions, "kube-system") + if err != nil { + return k8sClusterInfo{}, fmt.Errorf("could not get UUID: %w", err) + } + nodes, err := k8s.GetNodesE(GinkgoT(), &kubectlOptions) + if err != nil { + return k8sClusterInfo{}, fmt.Errorf("could not get node count: %w", err) + } + if len(nodes) == 0 { + return k8sClusterInfo{}, errors.New("node count is: 0") + } + + provider, err := providerIdentifier(nodes[0]) + if err != nil { + return k8sClusterInfo{}, fmt.Errorf("could not provider: %w", err) + } + + return k8sClusterInfo{ + workerCount: len(nodes), + clusterID: string(kubeSystemNS.UID), + provider: string(provider), + version: version, + }, nil +} + +// k8sClusterInfo contains K8s cluster informations about K8sCluster +type k8sClusterInfo struct { + workerCount int + clusterID string + provider string + version string +} + +// NewK8sCluster retrieves the K8s cluster information and creates the K8sCluster resource. +// When it cannot get K8s cluster information it returns error. +func NewK8sCluster(kubectlOptions k8s.KubectlOptions) (K8sCluster, error) { + clusterInfo, err := newK8sClusterInfo(kubectlOptions) + if err != nil { + return K8sCluster{}, fmt.Errorf("could not get clusterInfo for K8s cluster (path: '%s' context: '%s') err: %w", + kubectlOptions.ConfigPath, kubectlOptions.ContextName, err) + } + return K8sCluster{ + clusterInfo: clusterInfo, + kubectlOptions: kubectlOptions, + }, nil +} + +// NewK8sClusterWithProvider creates K8sCluster from kubeconfig path and kubecontext with the +// specified provider information +func NewK8sClusterWithProvider(kubectlOptions k8s.KubectlOptions, provider string) (K8sCluster, error) { + clusterInfo, err := newK8sClusterInfo(kubectlOptions) + if err != nil { + return K8sCluster{}, fmt.Errorf("could not get clusterInfo for K8s cluster (path: '%s' context: '%s') err: %w", + kubectlOptions.ConfigPath, kubectlOptions.ContextName, err) + } + + if provider != "" { + clusterInfo.provider = provider + } + + return K8sCluster{ + clusterInfo: clusterInfo, + kubectlOptions: kubectlOptions, + }, nil +} + +// NewMockK8sCluster creates K8sCluster objects from the provided argument for testing purpose +func NewMockK8sCluster(kubeConfigPath, kubeContext string, version, provider, clusterID string) K8sCluster { + return K8sCluster{ + clusterInfo: k8sClusterInfo{ + version: version, + provider: provider, + clusterID: clusterID, + }, + kubectlOptions: k8s.KubectlOptions{ + ConfigPath: kubeConfigPath, + ContextName: kubeContext, + }, + } +} + +// NewK8sClusterFromParams creates K8sCluster from kubectlConfigPath path and kubectlContext. +// Cluster information is fetched from the K8s cluster automatically. +func NewK8sClusterFromParams(kubectlConfigPath, kubectlContext string) (K8sCluster, error) { + return NewK8sCluster(*k8s.NewKubectlOptions(kubectlContext, kubectlConfigPath, "")) +} + +// NewK8sClustersFromParams creates K8sClusters based on kubeconfig path. +// When multiple context is found in the kubeconfig it creates multiple K8sClusters from all of them. +// Cluster information is fetched from the K8s cluster automatically at creation time. +func NewK8sClustersFromParams(kubectlConfigPath string) ([]K8sCluster, error) { + kubeContexts, err := common.GetKubeContexts(kubectlConfigPath) + if err != nil { + return nil, fmt.Errorf("could not get kubecontexts: %w", err) + } + + var k8sClusters []K8sCluster + + for _, kubeContext := range kubeContexts { + k8sCluster, err := NewK8sCluster(*k8s.NewKubectlOptions(kubeContext, kubectlConfigPath, "")) + if err != nil { + return nil, err + } + k8sClusters = append(k8sClusters, k8sCluster) + } + return k8sClusters, nil +} + +// NewK8sClusterFromCurrentConfig creates K8sCluster from the local KUBECONFIG env and it's current context. +// Cluster information is fetched from the K8s cluster automatically. +func NewK8sClusterFromCurrentConfig() (K8sCluster, error) { + kubectlOptions, err := common.KubectlOptionsForCurrentContext() + if err != nil { + return K8sCluster{}, err + } + + clusterInfo, err := newK8sClusterInfo(kubectlOptions) + if err != nil { + return K8sCluster{}, fmt.Errorf("could not get clusterInfo for K8s cluster (path: '%s' context: '%s') err: %w", + kubectlOptions.ConfigPath, kubectlOptions.ContextName, err) + } + + return K8sCluster{ + clusterInfo: clusterInfo, + kubectlOptions: kubectlOptions, + }, nil +} + +// K8sCluster represents a K8s cluster. +// It contains informations and access related configurations about K8s cluster. +type K8sCluster struct { + clusterInfo k8sClusterInfo + kubectlOptions k8s.KubectlOptions +} + +func (c K8sCluster) isKubectlOptionsFilled() bool { + return c.kubectlOptions.ConfigPath != "" && c.kubectlOptions.ContextName != "" +} + +// KubectlOptions returns K8s access related configurations for kubectl. +func (c K8sCluster) KubectlOptions() (k8s.KubectlOptions, error) { + if !c.isKubectlOptionsFilled() { + return k8s.KubectlOptions{}, errors.New("kubectlOptions is unfilled") + } + return c.kubectlOptions, nil +} + +func (c K8sCluster) String() string { + if !c.isKubectlOptionsFilled() { + return "Unknown" + } + return fmt.Sprintf(c.kubectlOptions.ContextName) +} diff --git a/tests/e2e/pkg/tests/tests_test.go b/tests/e2e/pkg/tests/tests_test.go new file mode 100644 index 000000000..9d93ec195 --- /dev/null +++ b/tests/e2e/pkg/tests/tests_test.go @@ -0,0 +1,772 @@ +// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tests + +import ( + "testing" + "time" + + "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/stretchr/testify/assert" +) + +func Test_Classifier_minimal(t *testing.T) { + type fields struct { + k8sClusterPool K8sClusterPool + testCases []TestCase + } + tests := []struct { + name string + fields fields + want TestPool + }{ + { + name: "simpleCase", + fields: fields{ + k8sClusterPool: K8sClusterPool{ + { + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + { + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider1", + }, + }, + }, + testCases: []TestCase{ + { + Name: "testCase1", + }, + { + Name: "testCase2", + }, + }, + }, + want: []Test{ + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider1", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := Classifier{ + k8sClusterPool: tt.fields.k8sClusterPool, + testCases: tt.fields.testCases, + } + + got := tr.Minimal() + if !got.Equal(tt.want) { + t.Errorf("want: %v\ngot: %v", tt.want, got) + } + }) + } +} + +func Test_Classifier_providerComplete(t *testing.T) { + type fields struct { + k8sClusterPool K8sClusterPool + testCases []TestCase + } + tests := []struct { + name string + fields fields + want []Test + }{ + { + name: "simpleCase", + fields: fields{ + k8sClusterPool: K8sClusterPool{ + { + + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + { + + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + testCases: []TestCase{ + { + Name: "testCase1", + }, + { + Name: "testCase2", + }, + }, + }, + want: []Test{ + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := Classifier{ + k8sClusterPool: tt.fields.k8sClusterPool, + testCases: tt.fields.testCases, + } + + got := tr.ProviderComplete() + if !got.Equal(tt.want) { + t.Errorf("want: %v\ngot: %v", tt.want, got) + } + }) + } +} + +func Test_Classifier_versionComplete(t *testing.T) { + type fields struct { + k8sClusterPool K8sClusterPool + testCases []TestCase + } + tests := []struct { + name string + fields fields + want []Test + }{ + { + name: "simpleCase", + fields: fields{ + k8sClusterPool: K8sClusterPool{ + { + kubectlOptions: k8s.KubectlOptions{ContextName: "local1", ConfigPath: "local1"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + { + kubectlOptions: k8s.KubectlOptions{ContextName: "local2", ConfigPath: "local2"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider1", + }, + }, + }, + testCases: []TestCase{ + { + Name: "testCase1", + }, + { + Name: "testCase2", + }, + }, + }, + want: []Test{ + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local1", ConfigPath: "local1"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local1", ConfigPath: "local1"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local2", ConfigPath: "local2"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local2", ConfigPath: "local2"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider1", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := Classifier{ + k8sClusterPool: tt.fields.k8sClusterPool, + testCases: tt.fields.testCases, + } + + got := tr.VersionComplete() + if !got.Equal(tt.want) { + t.Errorf("want: %v\ngot: %v", tt.want, got) + } + }) + } +} + +func Test_Classifier_complete(t *testing.T) { + type fields struct { + k8sClusterPool K8sClusterPool + testCases []TestCase + } + tests := []struct { + name string + fields fields + want []Test + }{ + { + name: "simpleCase", + fields: fields{ + k8sClusterPool: K8sClusterPool{ + { + + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + { + + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + testCases: []TestCase{ + { + Name: "testCase1", + }, + { + Name: "testCase2", + }, + }, + }, + want: []Test{ + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + }, + }, + { + name: "complexCase", + fields: fields{ + k8sClusterPool: K8sClusterPool{ + { + kubectlOptions: k8s.KubectlOptions{ContextName: "local1", ConfigPath: "local1"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + { + kubectlOptions: k8s.KubectlOptions{ContextName: "local2", ConfigPath: "local2"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + { + kubectlOptions: k8s.KubectlOptions{ContextName: "local3", ConfigPath: "local3"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local3", + version: "1.25", + provider: "provider3", + }, + }, + { + kubectlOptions: k8s.KubectlOptions{ContextName: "local4", ConfigPath: "local4"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local4", + version: "1.25", + provider: "provider3", + }, + }, + }, + testCases: []TestCase{ + { + Name: "testCase1", + }, + { + Name: "testCase2", + }, + }, + }, + want: []Test{ + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local1", ConfigPath: "local1"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local1", ConfigPath: "local1"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local1", + version: "1.24", + provider: "provider1", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local2", ConfigPath: "local2"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local2", ConfigPath: "local2"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local2", + version: "1.25", + provider: "provider2", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase1", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local3", ConfigPath: "local3"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local3", + version: "1.25", + provider: "provider3", + }, + }, + }, + { + testCase: TestCase{ + Name: "testCase2", + }, + k8sCluster: K8sCluster{ + kubectlOptions: k8s.KubectlOptions{ContextName: "local4", ConfigPath: "local4"}, + clusterInfo: k8sClusterInfo{ + clusterID: "local4", + version: "1.25", + provider: "provider3", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := Classifier{ + k8sClusterPool: tt.fields.k8sClusterPool, + testCases: tt.fields.testCases, + } + + got := tr.Complete() + if !got.Equal(tt.want) { + t.Errorf("want: %v\ngot: %v", tt.want, got) + } + }) + } +} + +func TestTestPool_GetTestSuiteDurationParallel(t *testing.T) { + tests := []struct { + name string + tests TestPool + want time.Duration + }{ + { + name: "MockTestsProvider", + tests: MockTestsProvider().ProviderComplete(), + want: 5 * time.Second, + }, + { + name: "MockTestsProviderMoreTestsThenProvider", + tests: MockTestsProviderMoreTestsThenProvider().ProviderComplete(), + want: 9 * time.Second, + }, + + { + name: "MockTestsVersionOne", + tests: MockTestsVersionOne().VersionComplete(), + want: 3 * time.Second, + }, + { + name: "MockTestsComplete", + tests: MockTestsComplete().Complete(), + want: 5 * time.Second, + }, + { + name: "MockTestsVersion", + tests: MockTestsVersion().VersionComplete(), + want: 5 * time.Second, + }, + { + name: "MockTestsMinimal", + tests: MockTestsMinimal().Minimal(), + want: 3 * time.Second, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.tests.GetTestSuiteDurationParallel() + assert.Equal(t, tt.want, got) + }) + } +} + +func TestTestPool_Equal(t *testing.T) { + type args struct { + other TestPool + } + tests := []struct { + name string + tests TestPool + args args + want bool + }{ + { + name: "Simple case true", + tests: []Test{ + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.25", + "provider2", + "clusterID1", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName2", + "1.25", + "provider2", + "clusterID2", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName3", + "1.25", + "provider2", + "clusterID3", + ), + }, + }, + args: args{ + other: []Test{ + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName2", + "1.25", + "provider2", + "clusterID2", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.25", + "provider2", + "clusterID1", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName3", + "1.25", + "provider2", + "clusterID3", + ), + }, + }, + }, + want: true, + }, + { + name: "Simple case false", + tests: []Test{ + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.25", + "provider2", + "clusterID1", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName2", + "1.25", + "provider2", + "clusterID2", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName3", + "1.25", + "provider2", + "clusterID3", + ), + }, + }, + args: args{ + other: []Test{ + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName2", + "1.25", + "provider2", + "clusterID2", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath2", + "testContextName2", + "1.25", + "provider2", + "clusterID1", + ), + }, + { + testCase: mockTest1, + k8sCluster: NewMockK8sCluster( + "testContextPath3", + "testContextName3", + "1.25", + "provider2", + "clusterID4", + ), + }, + }, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + got := tt.tests.Equal(tt.args.other) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/tests/e2e/pkg/tests/utils.go b/tests/e2e/pkg/tests/utils.go new file mode 100644 index 000000000..562edbc58 --- /dev/null +++ b/tests/e2e/pkg/tests/utils.go @@ -0,0 +1,69 @@ +package tests + +import ( + "errors" + "fmt" + "regexp" + "sort" + + "golang.org/x/exp/constraints" + "golang.org/x/exp/maps" + corev1 "k8s.io/api/core/v1" +) + +type k8sProvider string + +const ( + k8sProviderGKE k8sProvider = "GKE" + k8sProviderPKE k8sProvider = "PKE" + k8sProviderAKS k8sProvider = "AKS" + k8sProviderKind k8sProvider = "Kind" + k8sProviderEKS k8sProvider = "EKS" + k8sProviderUnknown k8sProvider = "Unknown" + + k8sVersionRegexp = "^v[0-9].[0-9]+.[0-9]+" +) + +var k8sProviderRegexp = map[k8sProvider]string{ + k8sProviderGKE: "^gke-", + k8sProviderAKS: "^aks-", + k8sProviderPKE: ".compute.internal@v[0-9].[0-9]+.[0-9]+$", //TODO (marbarta): this is not so good (is not unique PKE) + k8sProviderEKS: ".[0-9]-eks-", + k8sProviderKind: "^kind", +} + +func versionIdentifier(version string) (string, error) { + versionMatch := regexp.MustCompile(k8sVersionRegexp).FindString(version) + if versionMatch == "" { + return "", fmt.Errorf("K8s cluster version could not be recognized: '%s'", version) + } + return versionMatch, nil +} + +func providerIdentifier(node corev1.Node) (k8sProvider, error) { + checkString := node.Name + "@" + node.Status.NodeInfo.KubeletVersion + var foundProvider k8sProvider + for provider, regexpProvider := range k8sProviderRegexp { + if regexp.MustCompile(regexpProvider).MatchString(checkString) { + if foundProvider != "" { + return "", fmt.Errorf("K8s cluster provider name matched for multiple patterns: '%s' and '%s'", foundProvider, provider) + } + foundProvider = provider + } + } + if foundProvider == "" { + return "", errors.New("provider could not been identified") + } + return foundProvider, nil +} + +func sortedKeys[K constraints.Ordered, V any](m map[K]V) []K { + keys := make([]K, len(maps.Keys(m))) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) + return keys +} diff --git a/tests/e2e/pkg/tests/utils_test.go b/tests/e2e/pkg/tests/utils_test.go new file mode 100644 index 000000000..d146868ed --- /dev/null +++ b/tests/e2e/pkg/tests/utils_test.go @@ -0,0 +1,196 @@ +package tests + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_providerIdentifier(t *testing.T) { + type args struct { + node corev1.Node + } + tests := []struct { + name string + args args + want k8sProvider + wantErr error + }{ + { + name: "Unrecognized provider", + args: args{corev1.Node{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "almafa", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: " v1.24.14", + }, + }, + }}, + wantErr: errors.New("provider could not been identified"), + }, + { + name: "GKE provider", + args: args{corev1.Node{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "gke-name-pool1-a9e92295-f4ml", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.24.14-gke.2700", + }, + }, + }}, + want: "GKE", + wantErr: nil, + }, + { + name: "AKS provider", + args: args{corev1.Node{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "aks-name-pool1-a9e92295-f4ml", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.24.14", + }, + }, + }}, + want: "AKS", + wantErr: nil, + }, + { + name: "EKS provider", + args: args{corev1.Node{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "ip-192-168-64-60.eu-west-1.compute.internal", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.23.9-eks-ba74326", + }, + }, + }}, + want: "EKS", + wantErr: nil, + }, + { + name: "Kind provider", + args: args{corev1.Node{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "kind.node1", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.24.14", + }, + }, + }}, + want: "Kind", + wantErr: nil, + }, + { + name: "PKE provider", + args: args{corev1.Node{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "ip-192-168-64-60.eu-west-1.compute.internal", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.24.14", + }, + }, + }}, + want: "PKE", + wantErr: nil, + }, + { + name: "AKS and EKS provider", + args: args{corev1.Node{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "aks-name-pool1-a9e92295-f4ml", + }, + Status: corev1.NodeStatus{ + NodeInfo: corev1.NodeSystemInfo{ + KubeletVersion: "v1.23.9-eks-ba74326", + }, + }, + }}, + want: "", + wantErr: errors.New("K8s cluster provider name matched for multiple patterns: 'EKS' and 'AKS'"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := providerIdentifier(tt.args.node) + if err != nil && tt.wantErr == nil { + require.NoError(t, err) + } + if tt.wantErr != nil { + require.Error(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_versionIdentifier(t *testing.T) { + type args struct { + version string + } + tests := []struct { + name string + args args + want string + wantErr error + }{ + { + name: "Unrecognized version", + args: args{"123"}, + want: "", + wantErr: errors.New("K8s cluster version could not be recognized: '123'"), + }, + { + name: "Standard version format", + args: args{"v1.24.14"}, + want: "v1.24.14", + wantErr: nil, + }, + { + name: "Subversion format", + args: args{"v1.24.14-gke.270"}, + want: "v1.24.14", + wantErr: nil, + }, + { + name: "Edge case version format", + args: args{"v1.3.0"}, + want: "v1.3.0", + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := versionIdentifier(tt.args.version) + if err != nil && tt.wantErr == nil { + require.NoError(t, err) + } + if tt.wantErr != nil { + require.EqualError(t, err, tt.wantErr.Error()) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/tests/e2e/test_alltestcases.go b/tests/e2e/test_alltestcases.go new file mode 100644 index 000000000..5011867cd --- /dev/null +++ b/tests/e2e/test_alltestcases.go @@ -0,0 +1,48 @@ +// Copyright © 2023 Cisco Systems, Inc. and/or its affiliates +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "time" + + "github.com/banzaicloud/koperator/tests/e2e/pkg/tests" + "github.com/gruntwork-io/terratest/modules/k8s" +) + +var alltestCase = tests.TestCase{ + SpecsCount: 45, + Duration: 20 * time.Minute, + Name: "ALL_TESTCASE", + TestFn: allTestCase, +} + +func allTestCase(kubectlOptions k8s.KubectlOptions) { + var snapshottedInfo = &clusterSnapshot{} + snapshotCluster(kubectlOptions, snapshottedInfo) + testInstall(kubectlOptions) + testInstallZookeeperCluster(kubectlOptions) + testInstallKafkaCluster(kubectlOptions, "../../config/samples/simplekafkacluster.yaml") + //testProduceConsumeExternal(kubectlOptions, "") + testProduceConsumeInternal(kubectlOptions) + testUninstallKafkaCluster(kubectlOptions) + testInstallKafkaCluster(kubectlOptions, "../../config/samples/simplekafkacluster_ssl.yaml") + //testProduceConsumeExternal(kubectlOptions, "") + //testProduceConsumeInternal(kubectlOptions) + testProduceConsumeInternalSSL(kubectlOptions, defaultTLSSecretName) + testUninstallKafkaCluster(kubectlOptions) + testUninstallZookeeperCluster(kubectlOptions) + testUninstall(kubectlOptions) + snapshotClusterAndCompare(kubectlOptions, snapshottedInfo) +} diff --git a/tests/e2e/test_install.go b/tests/e2e/test_install.go index 185065644..4ee497265 100644 --- a/tests/e2e/test_install.go +++ b/tests/e2e/test_install.go @@ -15,21 +15,23 @@ package e2e import ( + "time" + + "github.com/banzaicloud/koperator/tests/e2e/pkg/tests" "github.com/gruntwork-io/terratest/modules/k8s" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) -func testInstall() bool { - return When("Installing Koperator and dependencies", Ordered, func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) +var testCaseInstall = tests.TestCase{ + Duration: 4 * time.Minute, + Name: "INSTALL_TESTCASE", + TestFn: testInstall, +} +func testInstall(kubectlOptions k8s.KubectlOptions) { + When("Installing Koperator and dependencies", Ordered, func() { + var err error When("Installing cert-manager", func() { It("Installing cert-manager Helm chart", func() { err = certManagerHelmDescriptor.installHelmChart(kubectlOptions) diff --git a/tests/e2e/test_install_cluster.go b/tests/e2e/test_install_cluster.go index 4f1f82788..6c845b830 100644 --- a/tests/e2e/test_install_cluster.go +++ b/tests/e2e/test_install_cluster.go @@ -17,34 +17,17 @@ package e2e import ( "github.com/gruntwork-io/terratest/modules/k8s" . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) -func testInstallZookeeperCluster() bool { +func testInstallZookeeperCluster(kubectlOptions k8s.KubectlOptions) bool { return When("Installing Zookeeper cluster", func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - kubectlOptions.Namespace = zookeeperOperatorHelmDescriptor.Namespace requireCreatingZookeeperCluster(kubectlOptions) }) } -func testInstallKafkaCluster(kafkaClusterManifestPath string) bool { +func testInstallKafkaCluster(kubectlOptions k8s.KubectlOptions, kafkaClusterManifestPath string) bool { return When("Installing Kafka cluster", func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - kubectlOptions.Namespace = koperatorLocalHelmDescriptor.Namespace requireCreatingKafkaCluster(kubectlOptions, kafkaClusterManifestPath) }) diff --git a/tests/e2e/test_produce_consume.go b/tests/e2e/test_produce_consume.go index 429b2b8be..d4bfc9aba 100644 --- a/tests/e2e/test_produce_consume.go +++ b/tests/e2e/test_produce_consume.go @@ -3,19 +3,10 @@ package e2e import ( "github.com/gruntwork-io/terratest/modules/k8s" . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) -func testProduceConsumeInternal() bool { +func testProduceConsumeInternal(kubectlOptions k8s.KubectlOptions) bool { return When("Internally produce and consume message to/from Kafka cluster", func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - kubectlOptions.Namespace = koperatorLocalHelmDescriptor.Namespace requireDeployingKcatPod(kubectlOptions, kcatPodName, "") @@ -26,16 +17,8 @@ func testProduceConsumeInternal() bool { }) } -func testProduceConsumeInternalSSL(tlsSecretName string) bool { +func testProduceConsumeInternalSSL(kubectlOptions k8s.KubectlOptions, tlsSecretName string) bool { return When("Internally produce and consume message to/from Kafka cluster using SSL", func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - kubectlOptions.Namespace = koperatorLocalHelmDescriptor.Namespace requireDeployingKafkaUser(kubectlOptions, kafkaUserName, tlsSecretName) @@ -48,16 +31,8 @@ func testProduceConsumeInternalSSL(tlsSecretName string) bool { }) } -func testProduceConsumeExternal(tlsSecretName string) bool { +func testProduceConsumeExternal(kubectlOptions k8s.KubectlOptions, tlsSecretName string) bool { return When("Externally produce and consume message to/from Kafka cluster", func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - kubectlOptions.Namespace = koperatorLocalHelmDescriptor.Namespace requireDeployingKafkaTopic(kubectlOptions, testExternalTopicName) diff --git a/tests/e2e/test_snapshot.go b/tests/e2e/test_snapshot.go index c6a0206c3..c8a7955a6 100644 --- a/tests/e2e/test_snapshot.go +++ b/tests/e2e/test_snapshot.go @@ -59,19 +59,11 @@ type localComparisonPartialObjectMetadataType struct { // snapshotCluster takes a clusterSnapshot of a K8s cluster and // stores it into the snapshotCluster instance referenced as input -func snapshotCluster(snapshottedInfo *clusterSnapshot) bool { +func snapshotCluster(kubectlOptions k8s.KubectlOptions, snapshottedInfo *clusterSnapshot) bool { return When("Get cluster resources state", Ordered, func() { - var kubectlOptions k8s.KubectlOptions - var err error - - BeforeAll(func() { - By("Acquiring K8s config and context") - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - var clusterResourceNames []string var namespacedResourceNames []string + var err error When("Get api-resources names", func() { It("Get cluster-scoped api-resources names", func() { @@ -135,10 +127,10 @@ func snapshotCluster(snapshottedInfo *clusterSnapshot) bool { // snapshotClusterAndCompare takes a current snapshot of the K8s cluster and // compares it against a snapshot provided as input -func snapshotClusterAndCompare(snapshottedInitialInfo *clusterSnapshot) bool { +func snapshotClusterAndCompare(kubectlOptions k8s.KubectlOptions, snapshottedInitialInfo *clusterSnapshot) bool { return When("Verifying cluster resources state", Ordered, func() { var snapshottedCurrentInfo = &clusterSnapshot{} - snapshotCluster(snapshottedCurrentInfo) + snapshotCluster(kubectlOptions, snapshottedCurrentInfo) It("Checking resources list", func() { // Temporarily increase maximum output length (default 4000) to fit more objects in the printed diff. diff --git a/tests/e2e/test_uninstall.go b/tests/e2e/test_uninstall.go index 6c1bdaf3d..3cb9f6b91 100644 --- a/tests/e2e/test_uninstall.go +++ b/tests/e2e/test_uninstall.go @@ -20,24 +20,14 @@ import ( . "github.com/onsi/gomega" ) -func testUninstall() bool { +func testUninstall(kubectlOptions k8s.KubectlOptions) bool { return When("Uninstalling Koperator and dependencies", Ordered, func() { - var kubectlOptions k8s.KubectlOptions - var err error - When("Initializing", func() { - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - It("Setting globals", func() { err := dependencyCRDs.Initialize(kubectlOptions) Expect(err).NotTo(HaveOccurred()) }) - }) - requireUninstallingKoperator(k8s.KubectlOptions{ ContextName: kubectlOptions.ContextName, ConfigPath: kubectlOptions.ConfigPath, @@ -58,6 +48,5 @@ func testUninstall() bool { ConfigPath: kubectlOptions.ConfigPath, Namespace: certManagerHelmDescriptor.Namespace, }) - }) } diff --git a/tests/e2e/test_uninstall_cluster.go b/tests/e2e/test_uninstall_cluster.go index 78d71aad1..e39c381ad 100644 --- a/tests/e2e/test_uninstall_cluster.go +++ b/tests/e2e/test_uninstall_cluster.go @@ -17,35 +17,18 @@ package e2e import ( "github.com/gruntwork-io/terratest/modules/k8s" . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) -func testUninstallZookeeperCluster() bool { +func testUninstallZookeeperCluster(kubectlOptions k8s.KubectlOptions) bool { return When("Uninstalling Zookeeper cluster", func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - kubectlOptions.Namespace = zookeeperOperatorHelmDescriptor.Namespace requireDeleteZookeeperCluster(kubectlOptions, zookeeperClusterName) }) } -func testUninstallKafkaCluster() bool { +func testUninstallKafkaCluster(kubectlOptions k8s.KubectlOptions) bool { return When("Uninstalling Kafka cluster", func() { - var kubectlOptions k8s.KubectlOptions - var err error - - It("Acquiring K8s config and context", func() { - kubectlOptions, err = kubectlOptionsForCurrentContext() - Expect(err).NotTo(HaveOccurred()) - }) - kubectlOptions.Namespace = koperatorLocalHelmDescriptor.Namespace requireDeleteKafkaCluster(kubectlOptions, kafkaClusterName) })