From f6b0c6e7effe190d97fd8063b3283f36586e0f50 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Thu, 29 Sep 2022 22:07:19 +0100 Subject: [PATCH 1/9] Add refactored e2e tests Signed-off-by: Somtochi Onyekwere --- .github/workflows/e2e-azure.yaml | 74 ++- Makefile | 1 + tests/.gitignore | 6 +- tests/integration/.env.sample | 17 + tests/integration/Makefile | 24 + tests/integration/README.md | 158 +++++ tests/integration/azure_specific_test.go | 383 ++++++++++++ tests/integration/azure_test.go | 136 +++++ tests/integration/flux_test.go | 168 +++++ tests/integration/go.mod | 131 ++++ tests/integration/go.sum | 575 ++++++++++++++++++ tests/integration/image_repo_test.go | 192 ++++++ tests/integration/oci_test.go | 137 +++++ tests/integration/sops_encryption_test.go | 138 +++++ tests/integration/suite_test.go | 297 +++++++++ tests/integration/terraform/azure/aks.tf | 19 + .../terraform/azure/azuredevops.tf | 26 + .../integration/terraform/azure/event-hub.tf | 27 + tests/integration/terraform/azure/keyvault.tf | 61 ++ tests/integration/terraform/azure/main.tf | 35 ++ tests/integration/terraform/azure/outputs.tf | 41 ++ .../integration/terraform/azure/variables.tf | 21 + tests/integration/util_test.go | 413 +++++++++++++ 23 files changed, 3069 insertions(+), 11 deletions(-) create mode 100644 tests/integration/.env.sample create mode 100644 tests/integration/Makefile create mode 100644 tests/integration/README.md create mode 100644 tests/integration/azure_specific_test.go create mode 100644 tests/integration/azure_test.go create mode 100644 tests/integration/flux_test.go create mode 100644 tests/integration/go.mod create mode 100644 tests/integration/go.sum create mode 100644 tests/integration/image_repo_test.go create mode 100644 tests/integration/oci_test.go create mode 100644 tests/integration/sops_encryption_test.go create mode 100644 tests/integration/suite_test.go create mode 100644 tests/integration/terraform/azure/aks.tf create mode 100644 tests/integration/terraform/azure/azuredevops.tf create mode 100644 tests/integration/terraform/azure/event-hub.tf create mode 100644 tests/integration/terraform/azure/keyvault.tf create mode 100644 tests/integration/terraform/azure/main.tf create mode 100644 tests/integration/terraform/azure/outputs.tf create mode 100644 tests/integration/terraform/azure/variables.tf create mode 100644 tests/integration/util_test.go diff --git a/.github/workflows/e2e-azure.yaml b/.github/workflows/e2e-azure.yaml index 9962dd1e..3236c73a 100644 --- a/.github/workflows/e2e-azure.yaml +++ b/.github/workflows/e2e-azure.yaml @@ -23,7 +23,10 @@ permissions: jobs: e2e-amd64-aks: runs-on: ubuntu-22.04 - # This job is currently disabled since if always evaluates to false. Remove the false check when Azure subscription is enabled + defaults: + run: + working-directory: ./tests/azure + # This job is currently disabled. Remove the false check when Azure subscription is enabled. if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' steps: - name: Checkout @@ -32,20 +35,18 @@ jobs: uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: go-version: 1.20.x - cache-dependency-path: | - **/go.sum - **/go.mod + cache-dependency-path: tests/azure/go.sum - name: Setup Flux CLI run: | make build mkdir -p $HOME/.local/bin mv ./bin/flux $HOME/.local/bin + working-directory: ./ - name: Setup SOPS run: | - wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux - chmod +x sops-v3.7.1.linux mkdir -p $HOME/.local/bin - mv sops-v3.7.1.linux $HOME/.local/bin/sops + wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux -O $HOME/.local/bin/sops + chmod +x $HOME/.local/bin/sops - name: Setup Terraform uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2 with: @@ -61,9 +62,62 @@ jobs: ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} run: | - echo $HOME - echo $PATH ls $HOME/.local/bin az login --service-principal -u ${ARM_CLIENT_ID} -p ${ARM_CLIENT_SECRET} -t ${ARM_TENANT_ID} - cd ./tests/azure go test -v -coverprofile cover.out -timeout 60m . + + refactored-e2e-amd64-aks: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: ./tests/integration + steps: + - name: CheckoutD + uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + - name: Setup Go + uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + with: + go-version: 1.20.x + cache-dependency-path: tests/integration/go.sum + - name: Setup Flux CLI + run: make build + working-directory: ./ + - name: Setup SOPS + run: | + mkdir -p $HOME/.local/bin + wget -O $HOME/.local/bin/sops https://github.com/mozilla/sops/releases/download/v$SOPS_VER/sops-v$SOPS_VER.linux + chmod +x $HOME/.local/bin/sops + env: + SOPS_VER: 3.7.1 + - name: Authenticate to Azure + uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6 + with: + creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}' + - name: Set dynamic variables in .env + run: | + cat > .env < build/ssh/key + export AZUREDEVOPS_SSH=build/ssh/key + touch ./build/ssh/key.pub + echo $AZUREDEVOPS_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub + export AZUREDEVOPS_SSH_PUB=build/ssh/key.pub + make test-azure diff --git a/Makefile b/Makefile index 563cd0b1..44317f17 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ all: test build tidy: go mod tidy -compat=1.20 cd tests/azure && go mod tidy -compat=1.20 + cd tests/integration && go mod tidy -compat=1.20 fmt: go fmt ./... diff --git a/tests/.gitignore b/tests/.gitignore index 3ca910aa..0acf4705 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -5,11 +5,14 @@ ### Terraform ### # Local .terraform directories **/.terraform/* +*.terraform.lock.hcl + +# test files +build/ # .tfstate files *.tfstate *.tfstate.* - # Crash log files crash.log @@ -37,4 +40,5 @@ override.tf.json .terraformrc terraform.rc +.env # End of https://www.toptal.com/developers/gitignore/api/terraform diff --git a/tests/integration/.env.sample b/tests/integration/.env.sample new file mode 100644 index 00000000..c77e3ea4 --- /dev/null +++ b/tests/integration/.env.sample @@ -0,0 +1,17 @@ +## Azure +export TF_VAR_azuredevops_org= +export TF_VAR_azuredevops_pat= +export TF_VAR_azure_location= +## These are not terraform variables +## but they are needed for the azure tests +export AZUREDEVOPS_SSH= +export AZUREDEVOPS_SSH_PUB= +## Set the following only when authenticating using Service Principal (suited +## for CI environment). +# export ARM_CLIENT_ID= +# export ARM_CLIENT_SECRET= +# export ARM_SUBSCRIPTION_ID= +# export ARM_TENANT_ID= + +## Common variables +# export TF_VAR_tags='{"environment"="dev", "createdat"='"\"$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)\""'}' diff --git a/tests/integration/Makefile b/tests/integration/Makefile new file mode 100644 index 00000000..a924232f --- /dev/null +++ b/tests/integration/Makefile @@ -0,0 +1,24 @@ +GO_TEST_ARGS ?= +PROVIDER_ARG ?= +TEST_TIMEOUT ?= 60m +FLUX_BINARY ?= ../../bin/flux + +test: sops-check + mkdir -p build + cp $(FLUX_BINARY) build/flux + # These two versions of podinfo are pushed to the cloud registry and used in tests for ImageUpdateAutomation + docker pull ghcr.io/stefanprodan/podinfo:6.0.0 + docker pull ghcr.io/stefanprodan/podinfo:6.0.1 + go test -timeout $(TEST_TIMEOUT) -v ./ $(GO_TEST_ARGS) $(PROVIDER_ARG) + +test-azure: + $(MAKE) test PROVIDER_ARG="-provider azure" GO_TEST_ARGS="--tags azure $(GO_TEST_ARGS)" + +test-gcp: + $(MAKE) test PROVIDER_ARG="-provider gcp" + + +sops-check: +ifeq ($(shell which sops),) + $(error "no sops in PATH, consider installing") +endif diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 00000000..35484802 --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,158 @@ +# E2E Tests + +The goal is to verify that Flux integration with cloud providers are actually working now and in the future. +Currently, we only have tests for Azure. + +## General requirements + +These CLI tools need to be installed for each of the tests to run successfully. + +- Docker CLI for registry login. +- [SOPS CLI](https://github.com/mozilla/sops) for encrypting files +- kubectl for applying certain install manifests. + +## Azure + +### Architecture + +The [azure](./terraform/azure) Terraform creates the AKS cluster and related resources to run the tests. It creates: +- An Azure Container Registry +- An Azure Kubernetes Cluster +- Two Azure DevOps repositories +- Azure EventHub for sending notifications +- An Azure Key Vault + +### Requirements + +- Azure account with an active subscription to be able to create AKS and ACR, + and permission to assign roles. Role assignment is required for allowing AKS workloads to access ACR. +- Azure CLI, need to be logged in using `az login` as a User or as a Service Principal +- An Azure DevOps organization, personal access token and ssh keys for accessing repositories + within the organization. The scope required for the personal access token is: + - `Project and Team` - read, write and manage access + - `Code` - Full + - Please take a look at the [terraform provider](https://registry.terraform.io/providers/microsoft/azuredevops/latest/docs/guides/authenticating_using_the_personal_access_token#create-a-personal-access-token) + for more explanation. + - Azure DevOps only supports RSA keys. Please see + [documentation](https://learn.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops#set-up-ssh-key-authentication) + for how to set up SSH key authentication. + +**NOTE:** To use Service Principal (for example in CI environment), set the +`ARM-*` variables in `.env`, source it and authenticate Azure CLI with: +```console +$ az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID +``` + +### Permissions + +Following permissions are needed for provisioning the infrastructure and running +the tests: +- `Microsoft.Kubernetes/*` +- `Microsoft.Resources/*` +- `Microsoft.Authorization/roleAssignments/{Read,Write,Delete}` +- `Microsoft.ContainerRegistry/*` +- `Microsoft.ContainerService/*` +- `Microsoft.KeyVault/*` +- `Microsoft.EventHub/*` + + +## Tests + +Each test run is initiated by running `terraform apply` in the provider's terraform directory e.g terraform apply, +it does this by using the [tftestenv package](https://github.com/fluxcd/test-infra/blob/main/tftestenv/testenv.go) +within the `fluxcd/test-infra` repository. It then reads the output of the Terraform to get information needed +for the tests like the kubernetes client ID, the azure DevOps repository urls, the key vault ID etc. This means that +a lot of the communication with the Azure API is offset to Terraform instead of requiring it to be implemented in the test. + +The following tests are currently implemented: + +- Flux can be successfully installed on the cluster using the Flux CLI +- source-controller can clone cloud provider repositories (Azure DevOps, Google Cloud Source Repositories) (https+ssh) +- image-reflector-controller can list tags from provider container Registry image repositories +- kustomize-controller can decrypt secrets using SOPS and provider key vault +- image-automation-controller can create branches and push to cloud repositories (https+ssh) +- source-controller can pull charts from cloud provider container registry Helm repositories + +The following tests are run only for Azure since it is supported in the notification-controller: + +- notification-controller can send commit status to Azure DevOps +- notification-controller can forward events to Azure Event Hub + +### Running tests locally + +1. Ensure that you have the Flux CLI binary that is to be tested built and ready. You can build it by running + `make build` at the root of this repository. The binary is located at `./bin` directory at the root and by default + this is where the Makefile copies the binary for the tests from. If you have it in a different location, you can set it + with the `FLUX_BINARY` variable +2. Copy `.env.sample` to `.env` and add the values for the different variables for the provider that you are running the tests for. +3. Run `make test-`, setting the location of the flux binary with `FLUX_BINARY` variable + +```console +$ make test-azure +make test PROVIDER_ARG="-provider azure" +# These two versions of podinfo are pushed to the cloud registry and used in tests for ImageUpdateAutomation +mkdir -p build +cp ../../bin/flux build/flux +docker pull ghcr.io/stefanprodan/podinfo:6.0.0 +6.0.0: Pulling from stefanprodan/podinfo +Digest: sha256:e7eeab287181791d36c82c904206a845e30557c3a4a66a8143fa1a15655dae97 +Status: Image is up to date for ghcr.io/stefanprodan/podinfo:6.0.0 +ghcr.io/stefanprodan/podinfo:6.0.0 +docker pull ghcr.io/stefanprodan/podinfo:6.0.1 +6.0.1: Pulling from stefanprodan/podinfo +Digest: sha256:1169f220a670cf640e45e1a7ac42dc381a441e9d4b7396432cadb75beb5b5d68 +Status: Image is up to date for ghcr.io/stefanprodan/podinfo:6.0.1 +ghcr.io/stefanprodan/podinfo:6.0.1 +go test -timeout 60m -v ./ -existing -provider azure --tags=integration +2023/03/24 02:32:25 Setting up azure e2e test infrastructure +2023/03/24 02:32:25 Terraform binary: /usr/local/bin/terraform +2023/03/24 02:32:25 Init Terraform +....[some output has been cut out] +2023/03/24 02:39:33 helm repository condition not ready +--- PASS: TestACRHelmRelease (15.31s) +=== RUN TestKeyVaultSops +--- PASS: TestKeyVaultSops (15.98s) +PASS +2023/03/24 02:40:12 Destroying environment... +ok github.com/fluxcd/flux2/tests/integration 947.341s +``` + +In the above, the test created a build directory build/ and the flux cli binary is copied build/flux. It would be used +to bootstrap Flux on the cluster. You can configure the location of the Flux CLI binary by setting the FLUX_BINARY variable. +We also pull two version of `ghcr.io/stefanprodan/podinfo` image. These images are pushed to the Azure Container Registry +and used to test `ImageRepository` and `ImageUpdateAutomation`. The terraform resources get created and the tests are run. + +**IMPORTANT:** In case the terraform infrastructure results in a bad state, maybe due to a crash during the apply, +the whole infrastructure can be destroyed by running terraform destroy in terraform/ directory. + +### Debugging the tests + +For debugging environment provisioning, enable verbose output with `-verbose` test flag. + +```sh +make test-azure GO_TEST_ARGS="-verbose" +``` + +The test environment is destroyed at the end by default. Run the tests with -retain flag to retain the created test infrastructure. + +```sh +make test-azure GO_TEST_ARGS="-retain" +``` +The tests require the infrastructure state to be clean. For re-running the tests with a retained infrastructure, set -existing flag. + +```sh +make test-azure GO_TEST_ARGS="-retain -existing" +``` + +To delete an existing infrastructure created with -retain flag: + +```sh +make test-azure GO_TEST_ARGS="-existing" +``` + +To debug issues on the cluster created by the test (provided you passed in the `-retain` flag): + +```sh +export KUBECONFIG=./build/kubeconfig +kubectl get pods +``` diff --git a/tests/integration/azure_specific_test.go b/tests/integration/azure_specific_test.go new file mode 100644 index 00000000..63f78825 --- /dev/null +++ b/tests/integration/azure_specific_test.go @@ -0,0 +1,383 @@ +//go:build azure +// +build azure + +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "strings" + "testing" + "time" + + eventhub "github.com/Azure/azure-event-hubs-go/v3" + "github.com/microsoft/azure-devops-go-api/azuredevops" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" + . "github.com/onsi/gomega" + giturls "github.com/whilp/git-urls" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + notiv1 "github.com/fluxcd/notification-controller/api/v1" + notiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2" + events "github.com/fluxcd/pkg/apis/event/v1beta1" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func TestEventHubNotification(t *testing.T) { + g := NewWithT(t) + + ctx := context.TODO() + branchName := "test-notification" + testID := branchName + "-" + randStringRunes(5) + + // Start listening to eventhub with latest offset + // TODO(somtochiama): Make here provider agnostic + hub, err := eventhub.NewHubFromConnectionString(cfg.notificationURL) + g.Expect(err).ToNot(HaveOccurred()) + c := make(chan string, 10) + handler := func(ctx context.Context, event *eventhub.Event) error { + c <- string(event.Data) + return nil + } + runtimeInfo, err := hub.GetRuntimeInformation(ctx) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(len(runtimeInfo.PartitionIDs)).To(Equal(1)) + listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset()) + g.Expect(err).ToNot(HaveOccurred()) + + // Setup Flux resources + manifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar` + repoUrl := getTransportURL(cfg.applicationRepository) + client, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + files := make(map[string]io.Reader) + files["configmap.yaml"] = strings.NewReader(manifest) + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + }, + } + g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed()) + defer testEnv.Delete(ctx, &namespace) + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + StringData: map[string]string{ + "address": cfg.notificationURL, + }, + } + g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) + defer testEnv.Delete(ctx, &secret) + + provider := notiv1beta2.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + Spec: notiv1beta2.ProviderSpec{ + Type: "azureeventhub", + Address: repoUrl, + SecretRef: &meta.LocalObjectReference{ + Name: testID, + }, + }, + } + g.Expect(testEnv.Create(ctx, &provider)).To(Succeed()) + defer testEnv.Delete(ctx, &provider) + + alert := notiv1beta2.Alert{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + Spec: notiv1beta2.AlertSpec{ + ProviderRef: meta.LocalObjectReference{ + Name: provider.Name, + }, + EventSources: []notiv1.CrossNamespaceObjectReference{ + { + Kind: "Kustomization", + Name: testID, + Namespace: testID, + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, &alert)).ToNot(HaveOccurred()) + defer testEnv.Delete(ctx, &alert) + + g.Eventually(func() bool { + nn := types.NamespacedName{Name: alert.Name, Namespace: alert.Namespace} + alertObj := ¬iv1beta2.Alert{} + err := testEnv.Get(ctx, nn, alertObj) + if err != nil { + return false + } + if err := checkReadyCondition(alertObj); err != nil { + t.Log(err) + return false + } + + return true + }, testTimeout, testInterval).Should(BeTrue()) + + modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { + spec.Interval = metav1.Duration{Duration: 30 * time.Second} + spec.HealthChecks = []meta.NamespacedObjectKindReference{ + { + APIVersion: "v1", + Kind: "ConfigMap", + Name: "foobar", + Namespace: testID, + }, + } + } + g.Expect(setUpFluxConfig(ctx, testID, nsConfig{ + repoURL: repoUrl, + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + path: "./", + modifyKsSpec: modifyKsSpec, + })).To(Succeed()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + t.Logf("failed to delete resources in '%s' namespace: %s", testID, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv, testID, testID) + if err != nil { + t.Log(err) + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) + + // Wait to read even from event hub + g.Eventually(func() bool { + select { + case eventJson := <-c: + event := &events.Event{} + err := json.Unmarshal([]byte(eventJson), event) + if err != nil { + t.Logf("the received event type does not match Flux format, error: %v", err) + return false + } + + if event.InvolvedObject.Kind == kustomizev1.KustomizationKind && + event.InvolvedObject.Name == testID && event.InvolvedObject.Namespace == testID { + return true + } + + return false + default: + return false + } + }, testTimeout, 1*time.Second).Should(BeTrue()) + err = listenerHandler.Close(ctx) + g.Expect(err).ToNot(HaveOccurred()) + err = hub.Close(ctx) + g.Expect(err).ToNot(HaveOccurred()) +} + +func TestAzureDevOpsCommitStatus(t *testing.T) { + g := NewWithT(t) + + ctx := context.TODO() + branchName := "commit-status" + testID := branchName + randStringRunes(5) + manifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar` + + repoUrl := getTransportURL(cfg.applicationRepository) + tmpDir := t.TempDir() + c, err := getRepository(ctx, tmpDir, repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + files := make(map[string]io.Reader) + files["configmap.yaml"] = strings.NewReader(manifest) + err = commitAndPushAll(ctx, c, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { + spec.HealthChecks = []meta.NamespacedObjectKindReference{ + { + APIVersion: "v1", + Kind: "ConfigMap", + Name: "foobar", + Namespace: testID, + }, + } + } + err = setUpFluxConfig(ctx, testID, nsConfig{ + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + repoURL: repoUrl, + path: "./", + modifyKsSpec: modifyKsSpec, + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + log.Printf("failed to delete resources in '%s' namespace: %s", testID, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv, testID, testID) + if err != nil { + return false + } + return true + }, testTimeout, testInterval) + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azuredevops-token", + Namespace: testID, + }, + StringData: map[string]string{ + "token": cfg.gitPat, + }, + } + g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) + defer testEnv.Delete(ctx, &secret) + + provider := notiv1beta2.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azuredevops", + Namespace: testID, + }, + Spec: notiv1beta2.ProviderSpec{ + Type: "azuredevops", + Address: repoUrl, + SecretRef: &meta.LocalObjectReference{ + Name: "azuredevops-token", + }, + }, + } + g.Expect(testEnv.Create(ctx, &provider)).To(Succeed()) + defer testEnv.Delete(ctx, &provider) + + alert := notiv1beta2.Alert{ + ObjectMeta: metav1.ObjectMeta{ + Name: "azuredevops", + Namespace: testID, + }, + Spec: notiv1beta2.AlertSpec{ + ProviderRef: meta.LocalObjectReference{ + Name: provider.Name, + }, + EventSources: []notiv1.CrossNamespaceObjectReference{ + { + Kind: "Kustomization", + Name: testID, + Namespace: testID, + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, &alert)).To(Succeed()) + defer testEnv.Delete(ctx, &alert) + + url, err := ParseAzureDevopsURL(repoUrl) + g.Expect(err).ToNot(HaveOccurred()) + + rev, err := c.Head() + g.Expect(err).ToNot(HaveOccurred()) + + connection := azuredevops.NewPatConnection(url.OrgURL, cfg.gitPat) + client, err := git.NewClient(ctx, connection) + g.Expect(err).ToNot(HaveOccurred()) + getArgs := git.GetStatusesArgs{ + Project: &url.Project, + RepositoryId: &url.Repo, + CommitId: &rev, + } + g.Eventually(func() bool { + statuses, err := client.GetStatuses(ctx, getArgs) + if err != nil { + return false + } + if len(*statuses) != 1 { + return false + } + return true + }, 500*time.Second, 5*time.Second) +} + +type AzureDevOpsURL struct { + OrgURL string + Project string + Repo string +} + +// TODO(somtochiama): move this into fluxcd/pkg and reuse in NC +func ParseAzureDevopsURL(s string) (AzureDevOpsURL, error) { + var args AzureDevOpsURL + u, err := giturls.Parse(s) + if err != nil { + return args, nil + } + + scheme := u.Scheme + if u.Scheme == "ssh" { + scheme = "https" + } + + id := strings.TrimLeft(u.Path, "/") + id = strings.TrimSuffix(id, ".git") + + comp := strings.Split(id, "/") + if len(comp) != 4 { + return args, fmt.Errorf("invalid repository id %q", id) + } + + args = AzureDevOpsURL{ + OrgURL: fmt.Sprintf("%s://%s/%s", scheme, u.Host, comp[0]), + Project: comp[1], + Repo: comp[3], + } + + return args, nil + +} diff --git a/tests/integration/azure_test.go b/tests/integration/azure_test.go new file mode 100644 index 00000000..e88fc49d --- /dev/null +++ b/tests/integration/azure_test.go @@ -0,0 +1,136 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "fmt" + "os" + + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/test-infra/tftestenv" + tfjson "github.com/hashicorp/terraform-json" +) + +const ( + azureDevOpsKnownHosts = "ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H" +) + +// createKubeConfigAKS constructs kubeconfig for an AKS cluster from the +// terraform state output at the given kubeconfig path. +func createKubeConfigAKS(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error { + kubeconfigYaml, ok := state["aks_kubeconfig"].Value.(string) + if !ok || kubeconfigYaml == "" { + return fmt.Errorf("failed to obtain kubeconfig from tf output") + } + return tftestenv.CreateKubeconfigAKS(ctx, kubeconfigYaml, kcPath) +} + +func getTestConfigAKS(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) { + fleetInfraRepository := outputs["fleet_infra_repository"].Value.(map[string]interface{}) + applicationRepository := outputs["application_repository"].Value.(map[string]interface{}) + + eventHubSas := outputs["event_hub_sas"].Value.(string) + sharedSopsId := outputs["sops_id"].Value.(string) + + kustomizeYaml := ` +resources: + - gotk-components.yaml + - gotk-sync.yaml +patchesStrategicMerge: + - |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: kustomize-controller + namespace: flux-system + spec: + template: + spec: + containers: + - name: manager + env: + - name: AZURE_AUTH_METHOD + value: msi +` + + privateKeyFile, ok := os.LookupEnv("AZUREDEVOPS_SSH") + if !ok { + return nil, fmt.Errorf("AZUREDEVOPS_SSH env variable isn't set") + } + privateKeyData, err := os.ReadFile(privateKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting azure devops private key, '%s': %w", privateKeyFile, err) + } + + pubKeyFile, ok := os.LookupEnv("AZUREDEVOPS_SSH_PUB") + if !ok { + return nil, fmt.Errorf("AZUREDEVOPS_SSH_PUB env variable isn't set") + } + pubKeyData, err := os.ReadFile(pubKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting ssh pubkey '%s', %w", pubKeyFile, err) + } + + config := &testConfig{ + defaultGitTransport: git.HTTP, + gitUsername: git.DefaultPublicKeyAuthUser, + gitPat: outputs["azure_devops_access_token"].Value.(string), + gitPrivateKey: string(privateKeyData), + gitPublicKey: string(pubKeyData), + knownHosts: azureDevOpsKnownHosts, + fleetInfraRepository: repoConfig{ + http: fleetInfraRepository["http"].(string), + ssh: fleetInfraRepository["ssh"].(string), + }, + applicationRepository: repoConfig{ + http: applicationRepository["http"].(string), + ssh: applicationRepository["ssh"].(string), + }, + notificationURL: eventHubSas, + sopsArgs: fmt.Sprintf("--azure-kv %s", sharedSopsId), + sopsSecretData: map[string]string{ + "sops.azure-kv": fmt.Sprintf(`clientId: %s`, outputs["aks_client_id"].Value.(string)), + }, + kustomizationYaml: kustomizeYaml, + } + + opts, err := authOpts(config.fleetInfraRepository.http, map[string][]byte{ + "password": []byte(config.gitPat), + "username": []byte("git"), + }) + if err != nil { + return nil, err + } + + config.defaultAuthOpts = opts + + return config, nil +} + +// registryLoginACR logs into the Azure Container Registries using the +// provider's CLI tools and returns the test repositories. +func registryLoginACR(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) { + // NOTE: ACR registry accept dynamic repository creation by just pushing a + // new image with a new repository name. + registryURL := output["acr_url"].Value.(string) + if err := tftestenv.RegistryLoginACR(ctx, registryURL); err != nil { + return "", err + } + + return registryURL, nil +} diff --git a/tests/integration/flux_test.go b/tests/integration/flux_test.go new file mode 100644 index 00000000..6a82f1e6 --- /dev/null +++ b/tests/integration/flux_test.go @@ -0,0 +1,168 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "fmt" + "io" + "strings" + "testing" + "time" + + "github.com/fluxcd/pkg/git" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestFluxInstallation(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, "flux-system", "flux-system") + if err != nil { + return false + } + return true + }, 60*time.Second, 5*time.Second) +} + +func TestRepositoryCloning(t *testing.T) { + ctx := context.TODO() + branchName := "feature/branch" + tagName := "v1" + + g := NewWithT(t) + + type testStruct struct { + name string + refType string + cloneType git.TransportType + } + + tests := []testStruct{ + { + name: "ssh-feature-branch", + refType: "branch", + cloneType: git.SSH, + }, + { + name: "ssh-v1", + refType: "tag", + cloneType: git.SSH, + }, + } + + // Not all cloud providers have repositories that support authentication with an accessToken + // we don't run http tests for these. + if cfg.gitPat != "" { + httpTests := []testStruct{ + { + name: "https-feature-branch", + refType: "branch", + cloneType: git.HTTP, + }, + { + name: "https-v1", + refType: "tag", + cloneType: git.HTTP, + }, + } + + tests = append(tests, httpTests...) + } + + t.Log("Creating application sources") + url := getTransportURL(cfg.applicationRepository) + tmpDir := t.TempDir() + client, err := getRepository(ctx, tmpDir, url, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + + files := make(map[string]io.Reader) + for _, tt := range tests { + manifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar + ` + name := fmt.Sprintf("cloning-test/%s/configmap.yaml", tt.name) + files[name] = strings.NewReader(manifest) + } + + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + err = createTagAndPush(ctx, client, branchName, tagName) + g.Expect(err).ToNot(HaveOccurred()) + + t.Log("Verifying application-gitops namespaces") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + ref := &sourcev1.GitRepositoryRef{ + Branch: branchName, + } + if tt.refType == "tag" { + ref = &sourcev1.GitRepositoryRef{ + Tag: tagName, + } + } + + url := cfg.applicationRepository.http + if tt.cloneType == git.SSH { + url = cfg.applicationRepository.ssh + } + + testID := fmt.Sprintf("%s-%s", tt.name, randStringRunes(5)) + err := setUpFluxConfig(ctx, testID, nsConfig{ + repoURL: url, + ref: ref, + protocol: tt.cloneType, + objectName: testID, + path: fmt.Sprintf("./cloning-test/%s", tt.name), + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + t.Logf("failed to delete resources in '%s' namespace: %s", tt.name, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID) + if err != nil { + return false + } + return true + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + + // Wait for configmap to be deployed + g.Eventually(func() bool { + nn := types.NamespacedName{Name: "foobar", Namespace: testID} + cm := &corev1.ConfigMap{} + err = testEnv.Get(ctx, nn, cm) + if err != nil { + return false + } + + return true + }, 120*time.Second, 5*time.Second).Should(BeTrue()) + }) + } +} diff --git a/tests/integration/go.mod b/tests/integration/go.mod new file mode 100644 index 00000000..ab27dbd2 --- /dev/null +++ b/tests/integration/go.mod @@ -0,0 +1,131 @@ +module github.com/fluxcd/flux2/tests/integration + +go 1.18 + +require ( + github.com/Azure/azure-event-hubs-go/v3 v3.6.0 + github.com/fluxcd/helm-controller/api v0.34.1 + github.com/fluxcd/image-automation-controller/api v0.34.1 + github.com/fluxcd/image-reflector-controller/api v0.28.0 + github.com/fluxcd/kustomize-controller/api v1.0.0-rc.4 + github.com/fluxcd/notification-controller/api v1.0.0-rc.4 + github.com/fluxcd/pkg/apis/event v0.5.1 + github.com/fluxcd/pkg/apis/meta v1.1.1 + github.com/fluxcd/pkg/git v0.12.2 + github.com/fluxcd/pkg/git/gogit v0.12.0 + github.com/fluxcd/pkg/runtime v0.39.0 + github.com/fluxcd/source-controller/api v1.0.0-rc.5 + github.com/fluxcd/test-infra/tftestenv v0.0.0-20230531151340-931581bd0a3e + github.com/go-git/go-git/v5 v5.7.0 + github.com/google/go-containerregistry v0.14.0 + github.com/hashicorp/terraform-json v0.16.0 + github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 + github.com/onsi/gomega v1.27.8 + github.com/whilp/git-urls v1.0.0 + k8s.io/api v0.27.3 + k8s.io/apimachinery v0.27.3 + k8s.io/client-go v0.27.3 + sigs.k8s.io/controller-runtime v0.15.0 +) + +require ( + github.com/Azure/azure-amqp-common-go/v4 v4.2.0 // indirect + github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect + github.com/Azure/go-amqp v1.0.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.28 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect + github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect + github.com/acomagu/bufpipe v1.0.4 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/devigned/tab v0.1.1 // indirect + github.com/docker/cli v23.0.1+incompatible // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/docker v23.0.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/emicklei/go-restful/v3 v3.10.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v1.1.0 // indirect + github.com/fluxcd/pkg/ssh v0.7.4 // indirect + github.com/fluxcd/pkg/version v0.2.2 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.4.1 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hc-install v0.5.0 // indirect + github.com/hashicorp/terraform-exec v0.18.1 // indirect + github.com/imdario/mergo v0.3.15 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.16.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/skeema/knownhosts v1.1.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/vbatts/tar-split v0.11.2 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zclconf/go-cty v1.13.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.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 + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.27.3 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect + k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/tests/integration/go.sum b/tests/integration/go.sum new file mode 100644 index 00000000..65f998c6 --- /dev/null +++ b/tests/integration/go.sum @@ -0,0 +1,575 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= +github.com/Azure/azure-amqp-common-go/v4 v4.2.0 h1:q/jLx1KJ8xeI8XGfkOWMN9XrXzAfVTkyvCxPvHCjd2I= +github.com/Azure/azure-amqp-common-go/v4 v4.2.0/go.mod h1:GD3m/WPPma+621UaU6KNjKEo5Hl09z86viKwQjTpV0Q= +github.com/Azure/azure-event-hubs-go/v3 v3.6.0 h1:UXRi5KewXYoTiekVjrj0gyGfbyGvtbYdot6/4IMf4I4= +github.com/Azure/azure-event-hubs-go/v3 v3.6.0/go.mod h1:UgyRnRU7H5e33igaLHJTqbkoNR1uj0j3MA/n7dABU24= +github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= +github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-amqp v1.0.0 h1:QfCugi1M+4F2JDTRgVnRw7PYXLXZ9hmqk3+9+oJh3OA= +github.com/Azure/go-amqp v1.0.0/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= +github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= +github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= +github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA= +github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= +github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= +github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= +github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY= +github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= +github.com/emicklei/go-restful/v3 v3.10.0 h1:X4gma4HM7hFm6WMeAsTfqA0GOfdNoCzBIkHGoRLGXuM= +github.com/emicklei/go-restful/v3 v3.10.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.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= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= +github.com/fluxcd/helm-controller/api v0.34.1 h1:oL6GG7weRIqkTlTFRoTY3DJpxqKpAFEoDDsYoxQCa8g= +github.com/fluxcd/helm-controller/api v0.34.1/go.mod h1:1v1UqS5jOgWdMDzvJBgdcY40RminDUO6A0by4IkHd5s= +github.com/fluxcd/image-automation-controller/api v0.34.1 h1:Y2RFhmltELcSGm3lsGgmRcJCBf0SdGlH/PwCxewk4w0= +github.com/fluxcd/image-automation-controller/api v0.34.1/go.mod h1:vRJscxpWXuDMmBj8vlFM3pgpVHqh5hf65RVnCaOnSGo= +github.com/fluxcd/image-reflector-controller/api v0.28.0 h1:2a1UxPU1RHTxl+5YFFB0KuOCHrR3hL0B7fvAPJo2UXY= +github.com/fluxcd/image-reflector-controller/api v0.28.0/go.mod h1:bY28TT8Jv/pvdF/m+c64QCEiCY2BShMe22l1zRDYm4s= +github.com/fluxcd/kustomize-controller/api v1.0.0-rc.4 h1:e5dO5HaFISFNzhfi4zuPniE545vVnEi3VnSBcbxcZqU= +github.com/fluxcd/kustomize-controller/api v1.0.0-rc.4/go.mod h1:UTJu1JMr+rkabWkUWMNiOeFeDu+4ZKfJIK+oqEwOjzc= +github.com/fluxcd/notification-controller/api v1.0.0-rc.4 h1:bDqIirpscGUY0+1u+RKvTEmX43iiZ2xeQLz27FoJJD8= +github.com/fluxcd/notification-controller/api v1.0.0-rc.4/go.mod h1:XL5h5/x46e41rtNc8mwxTKX4kAtgTNuzJS0PV2c0iIQ= +github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= +github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= +github.com/fluxcd/pkg/apis/event v0.5.1 h1:UrEmKwTK/lt42gMZunl8BQBMzjf8PSqGbWDs/GB839c= +github.com/fluxcd/pkg/apis/event v0.5.1/go.mod h1:GzBAzS8bq7751wvNkaSnr3kuwFVuWTPL20D77UgSNJQ= +github.com/fluxcd/pkg/apis/kustomize v1.1.0 h1:Fbv4dCB57r2+fiusozN7at8r7upTz58Z4wWw1njHPyU= +github.com/fluxcd/pkg/apis/kustomize v1.1.0/go.mod h1:CAe9Mjf9KVoTm1V4wpvq/FGXFDSnpBwfww/IG7mw3gM= +github.com/fluxcd/pkg/apis/meta v1.1.1 h1:sLAKLbEu7rRzJ+Mytffu3NcpfdbOBTa6hcpOQzFWm+M= +github.com/fluxcd/pkg/apis/meta v1.1.1/go.mod h1:soCfzjFWbm1mqybDcOywWKTCEYlH3skpoNGTboVk234= +github.com/fluxcd/pkg/git v0.12.2 h1:96xH3hy3WfwiD0DioyJZcGapYT3lmPc2s7jU5UM8buw= +github.com/fluxcd/pkg/git v0.12.2/go.mod h1:9TG4fEfGCF1XHLt9Xs7X2YOmkmWOiwfjH9tdGIQs8/8= +github.com/fluxcd/pkg/git/gogit v0.12.0 h1:0mCwQND0WpCVZYHLWcXJxRvKVcyWxH4JjMQFMaea8Q4= +github.com/fluxcd/pkg/git/gogit v0.12.0/go.mod h1:Kn+GfYfZBBIaXmQj39cQvrDxT/6y8leQxXZ5/B+YYTQ= +github.com/fluxcd/pkg/gittestserver v0.8.4 h1:rA/QUZnfH77ZZG+5xfMqjgEHJdzeeE6Nn1o8cops/bU= +github.com/fluxcd/pkg/runtime v0.39.0 h1:vgmzYS+DT0w8ikX9MqGsOdmMagoiKys2RMGdl/EDbgc= +github.com/fluxcd/pkg/runtime v0.39.0/go.mod h1:0A/0kZv/MPciAj5AoSEDKVeqUFEF6371q7o+zk6l81g= +github.com/fluxcd/pkg/ssh v0.7.4 h1:8GYneCKH2dxrHQBalcDgOCC2NtqD0JO91FlWgvnzrfo= +github.com/fluxcd/pkg/ssh v0.7.4/go.mod h1:9Syc8nVJaZEToPTU4E99j0jZ99w39oZtov+uiNX17sc= +github.com/fluxcd/pkg/version v0.2.2 h1:ZpVXECeLA5hIQMft11iLp6gN3cKcz6UNuVTQPw/bRdI= +github.com/fluxcd/pkg/version v0.2.2/go.mod h1:NGnh/no8S6PyfCDxRFrPY3T5BUnqP48MxfxNRU0z8C0= +github.com/fluxcd/source-controller/api v1.0.0-rc.5 h1:muoGOb/VitVEIOh877Ledi9AvibbyevPqvuH5byWU6g= +github.com/fluxcd/source-controller/api v1.0.0-rc.5/go.mod h1:W6tNXr3mRPhdc5+Jke9OZnuk/3THNzGzRJVhAtLfzss= +github.com/fluxcd/test-infra/tftestenv v0.0.0-20230531151340-931581bd0a3e h1:71jXb0t9pQAGleqRklVtdW38nXVTZ/KqeLSENnBldNk= +github.com/fluxcd/test-infra/tftestenv v0.0.0-20230531151340-931581bd0a3e/go.mod h1:liFlLEXgambGVdWSJ4JzbIHf1Vjpp1HwUyPazPIVZug= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= +github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.14.0 h1:z58vMqHxuwvAsVwvKEkmVBz2TlgBgH5k6koEXBtlYkw= +github.com/google/go-containerregistry v0.14.0/go.mod h1:aiJ2fp/SXvkWgmYHioXnbMdlgB8eXiiYOY55gfN91Wk= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.5.0 h1:D9bl4KayIYKEeJ4vUDe9L5huqxZXczKaykSRcmQ0xY0= +github.com/hashicorp/hc-install v0.5.0/go.mod h1:JyzMfbzfSBSjoDCRPna1vi/24BEDxFaCPfdHtM5SCdo= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.18.1 h1:LAbfDvNQU1l0NOQlTuudjczVhHj061fNX5H8XZxHlH4= +github.com/hashicorp/terraform-exec v0.18.1/go.mod h1:58wg4IeuAJ6LVsLUeD2DWZZoc/bYi6dzhLHzxM41980= +github.com/hashicorp/terraform-json v0.16.0 h1:UKkeWRWb23do5LNAFlh/K3N0ymn1qTOO8c+85Albo3s= +github.com/hashicorp/terraform-json v0.16.0/go.mod h1:v0Ufk9jJnk6tcIZvScHvetlKfiNTC+WS21mnXIlc0B0= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= +github.com/klauspost/compress v1.16.0/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 h1:YH424zrwLTlyHSH/GzLMJeu5zhYVZSx5RQxGKm1h96s= +github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5/go.mod h1:PoGiBqKSQK1vIfQ+yVaFcGjDySHvym6FM1cNYnwzbrY= +github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +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.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= +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/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= +github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME= +github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= +github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= +github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= +github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/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-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.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.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.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.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/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= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +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= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= +k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= +k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4= +k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84= +k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= +k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8= +k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= +k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= +k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= +sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/tests/integration/image_repo_test.go b/tests/integration/image_repo_test.go new file mode 100644 index 00000000..96abb1f3 --- /dev/null +++ b/tests/integration/image_repo_test.go @@ -0,0 +1,192 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + "time" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + reflectorv1beta2 "github.com/fluxcd/image-reflector-controller/api/v1beta2" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func TestImageRepositoryAndAutomation(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + branchName := "image-repository" + testID := branchName + "-" + randStringRunes(5) + imageURL := fmt.Sprintf("%s/podinfo", cfg.testRegistry) + + manifest := fmt.Sprintf(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: podinfo + namespace: %[1]s +spec: + selector: + matchLabels: + app: podinfo + template: + metadata: + labels: + app: podinfo + spec: + containers: + - name: podinfod + image: %[2]s:%[3]s # {"$imagepolicy": "%[1]s:podinfo"} + readinessProbe: + exec: + command: + - podcli + - check + - http + - localhost:9898/readyz + initialDelaySeconds: 5 + timeoutSeconds: 5 +`, testID, imageURL, oldPodinfoVersion) + + repoUrl := getTransportURL(cfg.applicationRepository) + client, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + files := make(map[string]io.Reader) + files[testID+"/podinfo.yaml"] = strings.NewReader(manifest) + + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + err = setUpFluxConfig(ctx, testID, nsConfig{ + repoURL: repoUrl, + path: testID, + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + t.Logf("failed to delete resources in '%s' namespace: %s", testID, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID) + if err != nil { + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) + + imageRepository := reflectorv1beta2.ImageRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: testID, + }, + Spec: reflectorv1beta2.ImageRepositorySpec{ + Image: imageURL, + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + Provider: infraOpts.Provider, + }, + } + g.Expect(testEnv.Create(ctx, &imageRepository)).To(Succeed()) + defer testEnv.Delete(ctx, &imageRepository) + + imagePolicy := reflectorv1beta2.ImagePolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: testID, + }, + Spec: reflectorv1beta2.ImagePolicySpec{ + ImageRepositoryRef: meta.NamespacedObjectReference{ + Name: imageRepository.Name, + }, + Policy: reflectorv1beta2.ImagePolicyChoice{ + SemVer: &reflectorv1beta2.SemVerPolicy{ + Range: "6.0.x", + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, &imagePolicy)).To(Succeed()) + defer testEnv.Delete(ctx, &imagePolicy) + + imageAutomation := automationv1beta1.ImageUpdateAutomation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "podinfo", + Namespace: testID, + }, + Spec: automationv1beta1.ImageUpdateAutomationSpec{ + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + SourceRef: automationv1beta1.CrossNamespaceSourceReference{ + Kind: "GitRepository", + Name: testID, + }, + GitSpec: &automationv1beta1.GitSpec{ + Checkout: &automationv1beta1.GitCheckoutSpec{ + Reference: sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + }, + Commit: automationv1beta1.CommitSpec{ + Author: automationv1beta1.CommitUser{ + Email: "imageautomation@example.com", + Name: "imageautomation", + }, + }, + }, + Update: &automationv1beta1.UpdateStrategy{ + Path: testID, + Strategy: automationv1beta1.UpdateStrategySetters, + }, + }, + } + g.Expect(testEnv.Create(ctx, &imageAutomation)).To(Succeed()) + defer testEnv.Delete(ctx, &imageAutomation) + + // Wait for image repository to be ready + g.Eventually(func() bool { + client, err := getRepository(ctx, t.TempDir(), repoUrl, branchName, cfg.defaultAuthOpts) + if err != nil { + return false + } + + b, err := os.ReadFile(filepath.Join(client.Path(), testID, "podinfo.yaml")) + if err != nil { + return false + } + if bytes.Contains(b, []byte(newPodinfoVersion)) == false { + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) +} diff --git a/tests/integration/oci_test.go b/tests/integration/oci_test.go new file mode 100644 index 00000000..c95e07d6 --- /dev/null +++ b/tests/integration/oci_test.go @@ -0,0 +1,137 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "fmt" + "testing" + "time" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" +) + +func TestOCIHelmRelease(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + + // Create namespace for test + testID := "oci-helm-" + randStringRunes(5) + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + }, + } + g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed()) + defer testEnv.Delete(ctx, &namespace) + + repoURL := fmt.Sprintf("%s/charts/podinfo", cfg.testRegistry) + err := pushImagesFromURL(repoURL, "ghcr.io/stefanprodan/charts/podinfo:6.2.0", []string{"6.2.0"}) + g.Expect(err).ToNot(HaveOccurred()) + + // Create HelmRepository and wait for it to sync + helmRepository := sourcev1.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{Name: testID, Namespace: testID}, + Spec: sourcev1.HelmRepositorySpec{ + URL: fmt.Sprintf("oci://%s", cfg.testRegistry), + Interval: metav1.Duration{ + Duration: 5 * time.Minute, + }, + Provider: infraOpts.Provider, + PassCredentials: true, + Type: "oci", + }, + } + + g.Expect(testEnv.Create(ctx, &helmRepository)).To(Succeed()) + defer testEnv.Delete(ctx, &helmRepository) + + g.Eventually(func() bool { + obj := &sourcev1.HelmRepository{} + nn := types.NamespacedName{Name: helmRepository.Name, Namespace: helmRepository.Namespace} + err := testEnv.Get(ctx, nn, obj) + if err != nil { + t.Logf("error getting helm repository %s", err.Error()) + return false + } + if err := checkReadyCondition(obj); err != nil { + t.Logf("%v", err) + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) + + // create helm release + helmRelease := helmv2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{Name: testID, Namespace: testID}, + Spec: helmv2.HelmReleaseSpec{ + Chart: helmv2.HelmChartTemplate{ + Spec: helmv2.HelmChartTemplateSpec{ + Interval: &metav1.Duration{ + Duration: 10 * time.Minute, + }, + Chart: "charts/podinfo", + Version: "6.2.0", + SourceRef: helmv2.CrossNamespaceObjectReference{ + Kind: sourcev1.HelmRepositoryKind, + Name: helmRepository.Name, + Namespace: helmRepository.Namespace, + }, + }, + }, + }, + } + + g.Expect(testEnv.Create(ctx, &helmRelease)).To(Succeed()) + defer testEnv.Delete(ctx, &helmRelease) + + g.Eventually(func() bool { + chart := &sourcev1.HelmChart{} + nn := types.NamespacedName{ + Name: fmt.Sprintf("%s-%s", helmRelease.Name, helmRelease.Namespace), + Namespace: helmRelease.Namespace, + } + if err := testEnv.Get(ctx, nn, chart); err != nil { + t.Logf("error getting helm chart %s\n", err.Error()) + return false + } + if err := checkReadyCondition(chart); err != nil { + t.Log(err) + return false + } + + obj := &helmv2.HelmRelease{} + nn = types.NamespacedName{Name: helmRelease.Name, Namespace: helmRelease.Namespace} + if err := testEnv.Get(ctx, nn, obj); err != nil { + t.Logf("error getting helm release %s\n", err.Error()) + return false + } + + if err := checkReadyCondition(obj); err != nil { + t.Log(err) + return false + } + + return true + }, testTimeout, testInterval).Should(BeTrue()) +} diff --git a/tests/integration/sops_encryption_test.go b/tests/integration/sops_encryption_test.go new file mode 100644 index 00000000..053c7ed1 --- /dev/null +++ b/tests/integration/sops_encryption_test.go @@ -0,0 +1,138 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "fmt" + "io" + "log" + "os" + "testing" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + "github.com/fluxcd/test-infra/tftestenv" +) + +func TestKeyVaultSops(t *testing.T) { + g := NewWithT(t) + ctx := context.TODO() + branchName := "key-vault" + testID := branchName + "-" + randStringRunes(5) + secretYaml := `apiVersion: v1 +kind: Secret +metadata: + name: "test" +stringData: + foo: "bar"` + + repoUrl := getTransportURL(cfg.applicationRepository) + tmpDir := t.TempDir() + client, err := getRepository(ctx, tmpDir, repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + + dir := client.Path() + "/key-vault-sops" + g.Expect(os.Mkdir(dir, 0o700)).To(Succeed()) + + filename := dir + "secret.enc.yaml" + f, err := os.Create(filename) + g.Expect(err).ToNot(HaveOccurred()) + defer f.Close() + + _, err = f.Write([]byte(secretYaml)) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(f.Sync()).To(Succeed()) + + err = tftestenv.RunCommand(ctx, client.Path(), + fmt.Sprintf("sops --encrypt --encrypted-regex '^(data|stringData)$' %s --in-place %s", cfg.sopsArgs, filename), + tftestenv.RunCommandOptions{}) + g.Expect(err).ToNot(HaveOccurred()) + + r, err := os.Open(filename) + g.Expect(err).ToNot(HaveOccurred()) + + files := make(map[string]io.Reader) + files["key-vault-sops/secret.enc.yaml"] = r + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { + spec.Decryption = &kustomizev1.Decryption{ + Provider: "sops", + } + if cfg.sopsSecretData != nil { + spec.Decryption.SecretRef = &meta.LocalObjectReference{ + Name: "sops-keys", + } + } + } + + err = setUpFluxConfig(ctx, testID, nsConfig{ + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + repoURL: repoUrl, + path: "./key-vault-sops", + modifyKsSpec: modifyKsSpec, + protocol: cfg.defaultGitTransport, + }) + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + log.Printf("failed to delete resources in '%s' namespace", testID) + } + }) + + if cfg.sopsSecretData != nil { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sops-keys", + Namespace: testID, + }, + StringData: cfg.sopsSecretData, + } + g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) + defer testEnv.Delete(ctx, &secret) + } + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv.Client, testID, testID) + if err != nil { + return false + } + nn := types.NamespacedName{Name: "test", Namespace: testID} + secret := &corev1.Secret{} + err = testEnv.Get(ctx, nn, secret) + if err != nil { + return false + } + + if string(secret.Data["foo"]) == "bar" { + return true + } + + return false + }, testTimeout, testInterval).Should(BeTrue()) +} diff --git a/tests/integration/suite_test.go b/tests/integration/suite_test.go new file mode 100644 index 00000000..f8696caa --- /dev/null +++ b/tests/integration/suite_test.go @@ -0,0 +1,297 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "flag" + "fmt" + "log" + "math/rand" + "os" + "testing" + "time" + + tfjson "github.com/hashicorp/terraform-json" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes/scheme" + + helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1" + automationv1beta1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + reflectorv1beta2 "github.com/fluxcd/image-reflector-controller/api/v1beta2" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + notiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2" + "github.com/fluxcd/pkg/git" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" + "github.com/fluxcd/test-infra/tftestenv" +) + +const ( + // azureTerraformPath is the path to the folder containing the + // terraform files for azure infra + azureTerraformPath = "./terraform/azure" + + // kubeconfigPath is the path of the file containing the kubeconfig + kubeconfigPath = "./build/kubeconfig" + + // default branch to be used when cloning git repositories + defaultBranch = "main" +) + +var ( + // supportedProviders are the providers supported by the test. + supportedProviders = []string{"azure"} + + // cfg is a struct containing different variables needed for the test. + cfg *testConfig + + // infraOpts are the options for running the terraform environment + infraOpts tftestenv.Options + + // versions to tag and push for the podinfo image + oldPodinfoVersion = "6.0.0" + newPodinfoVersion = "6.0.1" + podinfoTags = []string{oldPodinfoVersion, newPodinfoVersion} + + // testEnv is the test environment. It contains test infrastructure and + // kubernetes client of the created cluster. + testEnv *tftestenv.Environment + + // testTimeout is used as a timeout when testing a condition with gomega's eventually + testTimeout = 60 * time.Second + // testInterval is used as an interval when testing a condition with gomega's eventually + testInterval = 5 * time.Second + + random *rand.Rand + + letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890") + + localImg = "ghcr.io/stefanprodan/podinfo" +) + +// testConfig hold different variable that will be needed by the different test functions. +type testConfig struct { + // authentication info for git repositories + gitPat string + gitUsername string + gitPrivateKey string + gitPublicKey string + defaultGitTransport git.TransportType + defaultAuthOpts *git.AuthOptions + knownHosts string + fleetInfraRepository repoConfig + applicationRepository repoConfig + + notificationURL string + + // cloud provider dependent argument to pass to the sops cli + sopsArgs string + // secret data for sops + sopsSecretData map[string]string + // envCredsData are data field for a secret containing environment variables that the Flux deployments + // may need + envCredsData map[string]string + // kustomizationYaml is the content of the kustomization.yaml for customizing the Flux manifests + kustomizationYaml string + + // testRegistry is the registry of the cloud provider. + testRegistry string +} + +// repoConfig contains the http/ssh urls for the created git repositories +// on the various cloud providers. +type repoConfig struct { + http string + ssh string +} + +// getTestConfig gets the test configuration that contains different variables for running the tests +type getTestConfig func(ctx context.Context, output map[string]*tfjson.StateOutput) (*testConfig, error) + +// registryLoginFunc is used to perform registry login against a provider based +// on the terraform state output values. It returns the test registry +// to test against, read from the terraform state output. +type registryLoginFunc func(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) + +// providerConfig contains the test configurations for the different cloud providers +type providerConfig struct { + terraformPath string + createKubeconfig tftestenv.CreateKubeconfig + getTestConfig getTestConfig + // registryLogin is used to perform registry login. + registryLogin registryLoginFunc +} + +func init() { + utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme)) + utilruntime.Must(sourcev1beta2.AddToScheme(scheme.Scheme)) + utilruntime.Must(kustomizev1.AddToScheme(scheme.Scheme)) + utilruntime.Must(helmv2beta1.AddToScheme(scheme.Scheme)) + utilruntime.Must(reflectorv1beta2.AddToScheme(scheme.Scheme)) + utilruntime.Must(automationv1beta1.AddToScheme(scheme.Scheme)) + utilruntime.Must(notiv1beta2.AddToScheme(scheme.Scheme)) + + random = rand.New(rand.NewSource(time.Now().UnixNano())) +} + +func TestMain(m *testing.M) { + ctx := context.TODO() + + infraOpts.Bindflags(flag.CommandLine) + flag.Parse() + + // Validate the provider. + if infraOpts.Provider == "" { + log.Fatalf("-provider flag must be set to one of %v", supportedProviders) + } + var supported bool + for _, p := range supportedProviders { + if p == infraOpts.Provider { + supported = true + break + } + } + if !supported { + log.Fatalf("Unsupported provider %q, must be one of %v", infraOpts.Provider, supportedProviders) + } + // get provider specific configuration + providerCfg := getProviderConfig(infraOpts.Provider) + if providerCfg == nil { + log.Fatalf("Failed to get provider config for %q", infraOpts.Provider) + } + + // Initialize with non-zero exit code to indicate failure by default unless + // set by a successful test run. + exitCode := 1 + + // Setup Terraform binary and init state + log.Printf("Setting up %s e2e test infrastructure", infraOpts.Provider) + envOpts := []tftestenv.EnvironmentOption{ + tftestenv.WithExisting(infraOpts.Existing), + tftestenv.WithRetain(infraOpts.Retain), + tftestenv.WithVerbose(infraOpts.Verbose), + tftestenv.WithCreateKubeconfig(providerCfg.createKubeconfig), + } + + // Create terraform infrastructure + var err error + testEnv, err = tftestenv.New(ctx, scheme.Scheme, providerCfg.terraformPath, kubeconfigPath, envOpts...) + if err != nil { + log.Fatalf("Failed to provision the test infrastructure: %v", err) + } + + defer func() { + if err := testEnv.Stop(ctx); err != nil { + log.Printf("Failed to stop environment: %v", err) + } + + // Calling exit on panic prevents logging of panic error. + // Exit only on normal return. Explicitly detect panic and log the error + // on panic. + if err := recover(); err == nil { + os.Exit(exitCode) + } else { + log.Printf("panic: %v", err) + } + }() + + // get terrraform infrastructure + outputs, err := testEnv.StateOutput(ctx) + if err != nil { + panic(fmt.Sprintf("Failed to get the terraform state output: %v", err)) + } + + // get provider specific test configuration + cfg, err = providerCfg.getTestConfig(ctx, outputs) + if err != nil { + panic(fmt.Sprintf("Failed to get provider config for %v", err)) + } + + regUrl, err := providerCfg.registryLogin(ctx, outputs) + if err != nil { + panic(fmt.Sprintf("Failed to log into registry: %v", err)) + } + + cfg.testRegistry = regUrl + err = pushTestImages(ctx, cfg.testRegistry, podinfoTags) + if err != nil { + panic(fmt.Sprintf("Failed to push test images: %v", err)) + } + + tmpDir, err := os.MkdirTemp("", "*-flux-test") + if err != nil { + panic(fmt.Sprintf("Failed to create tmp dir: %v", err)) + } + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + log.Printf("error removing tmp dir: %s\n", err) + } + }() + + log.Println("Installing flux") + err = installFlux(ctx, tmpDir, kubeconfigPath) + defer func() { + log.Println("Uninstalling Flux") + if err := uninstallFlux(ctx); err != nil { + log.Printf("Failed to uninstall: %v", err) + } + }() + if err != nil { + panic(fmt.Sprintf("error installing Flux: %v", err)) + } + + log.Println("Running e2e tests") + exitCode = m.Run() +} + +func getProviderConfig(provider string) *providerConfig { + switch provider { + case "azure": + return &providerConfig{ + terraformPath: azureTerraformPath, + createKubeconfig: createKubeConfigAKS, + getTestConfig: getTestConfigAKS, + registryLogin: registryLoginACR, + } + } + + return nil +} + +// pushTestImages pushes the local podinfo image to the remote repository specified +// by repoURL. The image should be existing on the machine. +func pushTestImages(ctx context.Context, repoURL string, tags []string) error { + for _, tag := range tags { + remoteImg := fmt.Sprintf("%s/podinfo:%s", repoURL, tag) + err := tftestenv.RetagAndPush(ctx, fmt.Sprintf("%s:%s", localImg, tag), remoteImg) + if err != nil { + return err + } + + } + return nil +} + +func randStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[random.Intn(len(letterRunes))] + } + return string(b) +} diff --git a/tests/integration/terraform/azure/aks.tf b/tests/integration/terraform/azure/aks.tf new file mode 100644 index 00000000..122cafa3 --- /dev/null +++ b/tests/integration/terraform/azure/aks.tf @@ -0,0 +1,19 @@ +module "aks" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/aks" + + name = local.name + location = var.azure_location + tags = var.tags +} + +module "acr" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/acr" + + name = local.name + location = var.azure_location + aks_principal_id = [module.aks.principal_id] + resource_group = module.aks.resource_group + tags = var.tags + + depends_on = [module.aks] +} diff --git a/tests/integration/terraform/azure/azuredevops.tf b/tests/integration/terraform/azure/azuredevops.tf new file mode 100644 index 00000000..f8a1a9b8 --- /dev/null +++ b/tests/integration/terraform/azure/azuredevops.tf @@ -0,0 +1,26 @@ +resource "azuredevops_project" "e2e" { + name = local.name + visibility = "private" + version_control = "Git" + work_item_template = "Agile" + description = "Test Project for Flux E2E test - Managed by Terraform" +} + + +resource "azuredevops_git_repository" "fleet_infra" { + project_id = azuredevops_project.e2e.id + name = "fleet-infra-${local.name}" + default_branch = "refs/heads/main" + initialization { + init_type = "Clean" + } +} + +resource "azuredevops_git_repository" "application" { + project_id = azuredevops_project.e2e.id + name = "application-${local.name}" + default_branch = "refs/heads/main" + initialization { + init_type = "Clean" + } +} diff --git a/tests/integration/terraform/azure/event-hub.tf b/tests/integration/terraform/azure/event-hub.tf new file mode 100644 index 00000000..cda95002 --- /dev/null +++ b/tests/integration/terraform/azure/event-hub.tf @@ -0,0 +1,27 @@ +resource "azurerm_eventhub_namespace" "this" { + name = local.name + location = var.azure_location + resource_group_name = module.aks.resource_group + sku = "Basic" + capacity = 1 + tags = var.tags +} + + +resource "azurerm_eventhub" "this" { + name = local.name + namespace_name = azurerm_eventhub_namespace.this.name + resource_group_name = module.aks.resource_group + partition_count = 1 + message_retention = 1 +} + +resource "azurerm_eventhub_authorization_rule" "this" { + name = local.name + resource_group_name = module.aks.resource_group + namespace_name = azurerm_eventhub_namespace.this.name + eventhub_name = azurerm_eventhub.this.name + listen = true + send = true + manage = false +} diff --git a/tests/integration/terraform/azure/keyvault.tf b/tests/integration/terraform/azure/keyvault.tf new file mode 100644 index 00000000..b86adc8e --- /dev/null +++ b/tests/integration/terraform/azure/keyvault.tf @@ -0,0 +1,61 @@ +resource "azurerm_key_vault" "this" { + name = local.name + resource_group_name = module.aks.resource_group + location = var.azure_location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + tags = var.tags +} + +resource "azurerm_key_vault_access_policy" "admin" { + key_vault_id = azurerm_key_vault.this.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Update", + "Encrypt", + "Delete", + "Get", + "List", + "Purge", + "Recover", + "GetRotationPolicy", + "SetRotationPolicy" + ] + + secret_permissions = [ + "Get", + "Delete", + "Purge", + "Recover" + ] + +} + +resource "azurerm_key_vault_access_policy" "cluster_binding" { + key_vault_id = azurerm_key_vault.this.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = module.aks.principal_id + + key_permissions = [ + "Decrypt", + "Encrypt", + ] +} + +resource "azurerm_key_vault_key" "sops" { + depends_on = [azurerm_key_vault_access_policy.admin] + + name = "sops" + key_vault_id = azurerm_key_vault.this.id + key_type = "RSA" + key_size = 2048 + tags = var.tags + + key_opts = [ + "decrypt", + "encrypt", + ] +} diff --git a/tests/integration/terraform/azure/main.tf b/tests/integration/terraform/azure/main.tf new file mode 100644 index 00000000..e4d22b29 --- /dev/null +++ b/tests/integration/terraform/azure/main.tf @@ -0,0 +1,35 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">=3.20.0" + } + azuread = { + source = "hashicorp/azuread" + version = ">=2.28.0" + } + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.2.2" + } + } +} + +provider "azurerm" { + features {} +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/${var.azuredevops_org}" + personal_access_token = var.azuredevops_pat +} + +data "azurerm_client_config" "current" {} + +resource "random_pet" "suffix" { + separator = "o" +} + +locals { + name = "e2e${random_pet.suffix.id}" +} diff --git a/tests/integration/terraform/azure/outputs.tf b/tests/integration/terraform/azure/outputs.tf new file mode 100644 index 00000000..a7f08904 --- /dev/null +++ b/tests/integration/terraform/azure/outputs.tf @@ -0,0 +1,41 @@ +output "aks_kubeconfig" { + description = "kubeconfig of the created AKS cluster" + value = module.aks.kubeconfig + sensitive = true +} + +output "azure_devops_access_token" { + sensitive = true + value = var.azuredevops_pat +} + +output "fleet_infra_repository" { + value = { + http = azuredevops_git_repository.fleet_infra.remote_url + ssh = "ssh://git@ssh.dev.azure.com/v3/${var.azuredevops_org}/${azuredevops_git_repository.fleet_infra.project_id}/${azuredevops_git_repository.fleet_infra.name}" + } +} + +output "application_repository" { + value = { + http = azuredevops_git_repository.application.remote_url + ssh = "ssh://git@ssh.dev.azure.com/v3/${var.azuredevops_org}/${azuredevops_git_repository.application.project_id}/${azuredevops_git_repository.application.name}" + } +} + +output "aks_client_id" { + value = module.aks.kubelet_client_id +} + +output "event_hub_sas" { + value = azurerm_eventhub_authorization_rule.this.primary_connection_string + sensitive = true +} + +output "sops_id" { + value = azurerm_key_vault_key.sops.id +} + +output "acr_url" { + value = module.acr.registry_url +} diff --git a/tests/integration/terraform/azure/variables.tf b/tests/integration/terraform/azure/variables.tf new file mode 100644 index 00000000..8f3b9376 --- /dev/null +++ b/tests/integration/terraform/azure/variables.tf @@ -0,0 +1,21 @@ +variable "azuredevops_org" { + type = string + description = "Name of Azure DevOps organizations were the repositories will be created" +} + +variable "azure_location" { + type = string + description = "Location of the resource group" + default = "eastus" +} + +variable "tags" { + type = map(string) + default = {} + description = "Tags for created Azure resources" +} + +variable "azuredevops_pat" { + type = string + description = "Personal access token for Azure DevOps repository" +} diff --git a/tests/integration/util_test.go b/tests/integration/util_test.go new file mode 100644 index 00000000..017bf193 --- /dev/null +++ b/tests/integration/util_test.go @@ -0,0 +1,413 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "errors" + "fmt" + "io" + "net/url" + "os" + "strings" + "time" + + extgogit "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/google/go-containerregistry/pkg/crane" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kerrors "k8s.io/apimachinery/pkg/util/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/pkg/git/gogit" + "github.com/fluxcd/pkg/git/repository" + "github.com/fluxcd/pkg/runtime/conditions" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + "github.com/fluxcd/test-infra/tftestenv" +) + +// installFlux adds the core Flux components to the cluster specified in the kubeconfig file. +func installFlux(ctx context.Context, tmpDir string, kubeconfigPath string) error { + // Create flux-system namespace + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "flux-system", + }, + } + err := testEnv.Create(ctx, &namespace) + if err != nil { + return err + } + + repoURL := getTransportURL(cfg.fleetInfraRepository) + if cfg.kustomizationYaml != "" { + files := make(map[string]io.Reader) + files["clusters/e2e/flux-system/kustomization.yaml"] = strings.NewReader(cfg.kustomizationYaml) + files["clusters/e2e/flux-system/gotk-components.yaml"] = strings.NewReader("") + files["clusters/e2e/flux-system/gotk-sync.yaml"] = strings.NewReader("") + c, err := getRepository(ctx, tmpDir, repoURL, defaultBranch, cfg.defaultAuthOpts) + if err != nil { + return err + } + + err = commitAndPushAll(ctx, c, files, defaultBranch) + if err != nil { + return err + } + } + + var bootstrapArgs string + if cfg.defaultGitTransport == git.SSH { + f, err := os.CreateTemp("", "flux-e2e-ssh-key-*") + if err != nil { + return err + } + err = os.WriteFile(f.Name(), []byte(cfg.gitPrivateKey), 0o644) + if err != nil { + return err + } + bootstrapArgs = fmt.Sprintf("--private-key-file=%s -s", f.Name()) + } else { + bootstrapArgs = fmt.Sprintf("--token-auth --password=%s", cfg.gitPat) + } + + bootstrapCmd := fmt.Sprintf("./build/flux bootstrap git --url=%s %s --kubeconfig=%s --path=clusters/e2e "+ + " --components-extra image-reflector-controller,image-automation-controller", + repoURL, bootstrapArgs, kubeconfigPath) + + return tftestenv.RunCommand(ctx, "./", bootstrapCmd, tftestenv.RunCommandOptions{ + Timeout: 15 * time.Minute, + }) +} + +func uninstallFlux(ctx context.Context) error { + uninstallCmd := fmt.Sprintf("./build/flux uninstall --kubeconfig %s -s", kubeconfigPath) + if err := tftestenv.RunCommand(ctx, "./", uninstallCmd, tftestenv.RunCommandOptions{ + Timeout: 15 * time.Minute, + }); err != nil { + return err + } + return nil +} + +// verifyGitAndKustomization checks that the gitrespository and kustomization combination are working properly. +func verifyGitAndKustomization(ctx context.Context, kubeClient client.Client, namespace, name string) error { + nn := types.NamespacedName{ + Name: name, + Namespace: namespace, + } + source := &sourcev1.GitRepository{} + if err := kubeClient.Get(ctx, nn, source); err != nil { + return err + } + if err := checkReadyCondition(source); err != nil { + return err + } + + kustomization := &kustomizev1.Kustomization{} + if err := kubeClient.Get(ctx, nn, kustomization); err != nil { + return err + } + if err := checkReadyCondition(kustomization); err != nil { + return err + } + + return nil +} + +type nsConfig struct { + repoURL string + ref *sourcev1.GitRepositoryRef + protocol git.TransportType + objectName string + path string + modifyKsSpec func(spec *kustomizev1.KustomizationSpec) +} + +// setUpFluxConfigs creates the namespace, then creates the git secret, +// git repository and kustomization in that namespace +func setUpFluxConfig(ctx context.Context, name string, opts nsConfig) error { + transport := cfg.defaultGitTransport + if opts.protocol != "" { + transport = opts.protocol + } + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + if err := testEnv.Create(ctx, &namespace); err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "git-credentials", + Namespace: name, + }, + } + + secret.StringData = map[string]string{ + "username": cfg.gitUsername, + "password": cfg.gitPat, + } + + if transport == git.SSH { + secret.StringData = map[string]string{ + "identity": cfg.gitPrivateKey, + "identity.pub": cfg.gitPublicKey, + "known_hosts": cfg.knownHosts, + } + } + if err := testEnv.Create(ctx, &secret); err != nil { + return err + } + + ref := &sourcev1.GitRepositoryRef{ + Branch: name, + } + + if opts.ref != nil { + ref = opts.ref + } + + gitSpec := &sourcev1.GitRepositorySpec{ + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + Reference: ref, + SecretRef: &meta.LocalObjectReference{ + Name: secret.Name, + }, + URL: opts.repoURL, + } + + source := &sourcev1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}, + Spec: *gitSpec, + } + if err := testEnv.Create(ctx, source); err != nil { + return err + } + + ksSpec := &kustomizev1.KustomizationSpec{ + Path: opts.path, + TargetNamespace: name, + SourceRef: kustomizev1.CrossNamespaceSourceReference{ + Kind: sourcev1.GitRepositoryKind, + Name: source.Name, + Namespace: source.Namespace, + }, + Interval: metav1.Duration{ + Duration: 1 * time.Minute, + }, + Prune: true, + } + if opts.modifyKsSpec != nil { + opts.modifyKsSpec(ksSpec) + } + kustomization := &kustomizev1.Kustomization{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace.Name}, + Spec: *ksSpec, + } + + return testEnv.Create(ctx, kustomization) +} + +func tearDownFluxConfig(ctx context.Context, name string) error { + var allErr []error + + source := &sourcev1.GitRepository{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} + if err := testEnv.Delete(ctx, source); err != nil { + allErr = append(allErr, err) + } + + kustomization := &kustomizev1.Kustomization{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: name}} + if err := testEnv.Delete(ctx, kustomization); err != nil { + allErr = append(allErr, err) + } + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + if err := testEnv.Delete(ctx, &namespace); err != nil { + allErr = append(allErr, err) + } + + return kerrors.NewAggregate(allErr) +} + +// getRepository and clones the git repository to the directory. +func getRepository(ctx context.Context, dir, repoURL, branchName string, authOpts *git.AuthOptions) (*gogit.Client, error) { + c, err := gogit.NewClient(dir, authOpts, gogit.WithSingleBranch(false), gogit.WithDiskStorage()) + if err != nil { + return nil, err + } + + _, err = c.Clone(ctx, repoURL, repository.CloneConfig{ + CheckoutStrategy: repository.CheckoutStrategy{ + Branch: branchName, + }, + }) + if err != nil { + return nil, err + } + + return c, nil +} + +// commitAndPushAll checks out to the specified branch, creates the files, commits and then pushes them to +// the remote git repository. +func commitAndPushAll(ctx context.Context, client *gogit.Client, files map[string]io.Reader, branchName string) error { + err := client.SwitchBranch(ctx, branchName) + if err != nil && !errors.Is(err, plumbing.ErrReferenceNotFound) { + return err + } + + _, err = client.Commit(git.Commit{ + Author: git.Signature{ + Name: git.DefaultPublicKeyAuthUser, + Email: "test@example.com", + When: time.Now(), + }, + }, repository.WithFiles(files)) + if err != nil { + if errors.Is(err, git.ErrNoStagedFiles) { + return nil + } + + return err + } + + err = client.Push(ctx, repository.PushConfig{}) + if err != nil { + return fmt.Errorf("unable to push: %s", err) + } + + return nil +} + +func createTagAndPush(ctx context.Context, client *gogit.Client, branchName, newTag string) error { + repo, err := extgogit.PlainOpen(client.Path()) + if err != nil { + return err + } + + ref, err := repo.Reference(plumbing.NewBranchReferenceName(branchName), false) + if err != nil { + return err + } + + tags, err := repo.TagObjects() + if err != nil { + return err + } + + err = tags.ForEach(func(tag *object.Tag) error { + if tag.Name == newTag { + err = repo.DeleteTag(tag.Name) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return fmt.Errorf("error deleting local tag: %w", err) + } + + // Delete remote tag + if err := client.Push(ctx, repository.PushConfig{ + Refspecs: []string{fmt.Sprintf(":refs/tags/%s", newTag)}, + Force: true, + }); err != nil && !errors.Is(err, extgogit.NoErrAlreadyUpToDate) { + return fmt.Errorf("unable to delete existing tag: %w", err) + } + + sig := &object.Signature{ + Name: git.DefaultPublicKeyAuthUser, + Email: "test@example.com", + When: time.Now(), + } + if _, err = repo.CreateTag(newTag, ref.Hash(), &extgogit.CreateTagOptions{ + Tagger: sig, + Message: "create tag", + }); err != nil { + return fmt.Errorf("unable to create tag: %w", err) + } + + return client.Push(ctx, repository.PushConfig{ + Refspecs: []string{"refs/tags/*:refs/tags/*"}, + }) +} + +func pushImagesFromURL(repoURL, imgURL string, tags []string) error { + img, err := crane.Pull(imgURL) + if err != nil { + return err + } + + for _, tag := range tags { + if err := crane.Push(img, fmt.Sprintf("%s:%s", repoURL, tag)); err != nil { + return err + } + } + + return nil +} + +func getTransportURL(repoCfg repoConfig) string { + if cfg.defaultGitTransport == git.SSH { + return repoCfg.ssh + } + + return repoCfg.http +} + +func authOpts(repoURL string, authData map[string][]byte) (*git.AuthOptions, error) { + u, err := url.Parse(repoURL) + if err != nil { + return nil, err + } + + return git.NewAuthOptions(*u, authData) +} + +// checkReadyCondition checks for a Ready condition, it returns nil if the condition is true +// or an error (with the message if the Ready condition is present). +func checkReadyCondition(from conditions.Getter) error { + if conditions.IsReady(from) { + return nil + } + errMsg := fmt.Sprintf("object not ready") + readyMsg := conditions.GetMessage(from, meta.ReadyCondition) + if readyMsg != "" { + errMsg += ": " + readyMsg + } + return errors.New(errMsg) +} From 7c1b8979198daeef50ed232d5023bccf768ff15d Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Wed, 21 Jun 2023 20:43:07 +0100 Subject: [PATCH 2/9] Add terraform files and config for GCP Signed-off-by: Somtochi Onyekwere --- tests/.gitignore | 1 + tests/integration/.env.sample | 9 ++ tests/integration/README.md | 46 +++++++ tests/integration/gcp_test.go | 129 +++++++++++++++++++ tests/integration/suite_test.go | 13 +- tests/integration/terraform/gcp/main.tf | 49 +++++++ tests/integration/terraform/gcp/outputs.tf | 28 ++++ tests/integration/terraform/gcp/variables.tf | 37 ++++++ 8 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 tests/integration/gcp_test.go create mode 100644 tests/integration/terraform/gcp/main.tf create mode 100644 tests/integration/terraform/gcp/outputs.tf create mode 100644 tests/integration/terraform/gcp/variables.tf diff --git a/tests/.gitignore b/tests/.gitignore index 0acf4705..fda47f9d 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -13,6 +13,7 @@ build/ # .tfstate files *.tfstate *.tfstate.* +*.terraform.lock.hcl # Crash log files crash.log diff --git a/tests/integration/.env.sample b/tests/integration/.env.sample index c77e3ea4..767e9b7e 100644 --- a/tests/integration/.env.sample +++ b/tests/integration/.env.sample @@ -13,5 +13,14 @@ export AZUREDEVOPS_SSH_PUB= # export ARM_SUBSCRIPTION_ID= # export ARM_TENANT_ID= +## GCP +export TF_VAR_gcp_project_id= +export TF_VAR_gcp_zone= +export TF_VAR_gcp_keyring= +export TF_VAR_gcp_crypto_key= +## Set the following only when using service account. +## Provide absolute path to the service account JSON key file. +# export GOOGLE_APPLICATION_CREDENTIALS= + ## Common variables # export TF_VAR_tags='{"environment"="dev", "createdat"='"\"$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)\""'}' diff --git a/tests/integration/README.md b/tests/integration/README.md index 35484802..f4232f1d 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -55,6 +55,52 @@ the tests: - `Microsoft.KeyVault/*` - `Microsoft.EventHub/*` +## GCP + +### Architecture + +The [gcp](./terraform/gcp) terraform files create the GKE cluster and related resources to run the tests. It creates: +- An Google Container Registry and Artifact Registry +- An Google Kubernetes Cluster +- Two Google Cloud Source Repositories + +Note: It doesn't create Google KMS keyrings and crypto keys because these cannot be destroyed. Instead, you have +to pass in the crypto key and keyring that would be used to test the sops encryption in Flux. Please see `.env.sample` +for the terraform variables + +### Requirements + +- GCP account with an active project to be able to create GKE and GCR, and permission to assign roles. +- Existing GCP KMS keyring and crypto key. +- gcloud CLI, need to be logged in using `gcloud auth login` as a User (not a + Service Account), configure application default credentials with `gcloud auth + application-default login` and docker credential helper with `gcloud auth configure-docker`. + + **NOTE:** To use Service Account (for example in CI environment), set + `GOOGLE_APPLICATION_CREDENTIALS` variable in `.env` with the path to the JSON + key file, source it and authenticate gcloud CLI with: + ```console + $ gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS + ``` + Depending on the Container/Artifact Registry host used in the test, authenticate + docker accordingly + ```console + $ gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev + $ gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://gcr.io + ``` + In this case, the GCP client in terraform uses the Service Account to + authenticate and the gcloud CLI is used only to authenticate with Google + Container Registry and Google Artifact Registry. + + **NOTE FOR CI USAGE:** When saving the JSON key file as a CI secret, compress + the file content with + ```console + $ cat key.json | jq -r tostring + ``` + to prevent aggressive masking in the logs. Refer + [aggressive replacement in logs](https://github.com/google-github-actions/auth/blob/v1.1.0/docs/TROUBLESHOOTING.md#aggressive--replacement-in-logs) + for more details. +- Register SSH Keys with Google Cloud. # add docs ## Tests diff --git a/tests/integration/gcp_test.go b/tests/integration/gcp_test.go new file mode 100644 index 00000000..02bc6ab4 --- /dev/null +++ b/tests/integration/gcp_test.go @@ -0,0 +1,129 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "fmt" + "io" + "os" + "strings" + + "github.com/fluxcd/pkg/git" + "github.com/fluxcd/test-infra/tftestenv" + tfjson "github.com/hashicorp/terraform-json" +) + +const ( + gkeDevOpsKnownHosts = "[source.developers.google.com]:2022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB5Iy4/cq/gt/fPqe3uyMy4jwv1Alc94yVPxmnwNhBzJqEV5gRPiRk5u4/JJMbbu9QUVAguBABxL7sBZa5PH/xY=" +) + +// createKubeConfigGKE constructs kubeconfig for a GKE cluster from the +// terraform state output at the given kubeconfig path. +func createKubeConfigGKE(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error { + kubeconfigYaml, ok := state["kubeconfig"].Value.(string) + if !ok || kubeconfigYaml == "" { + return fmt.Errorf("failed to obtain kubeconfig from tf output") + } + return tftestenv.CreateKubeconfigGKE(ctx, kubeconfigYaml, kcPath) +} + +// registryLoginGCR logs into the container/artifact registries using the +// provider's CLI tools and returns a list of test repositories. +func registryLoginGCR(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) { + // NOTE: ACR registry accept dynamic repository creation by just pushing a + // new image with a new repository name. + project := output["gcp_project"].Value.(string) + region := output["gcp_region"].Value.(string) + repositoryID := output["artifact_registry_id"].Value.(string) + artifactRegistryURL, artifactRepoURL := tftestenv.GetGoogleArtifactRegistryAndRepository(project, region, repositoryID) + if err := tftestenv.RegistryLoginGCR(ctx, artifactRegistryURL); err != nil { + return "", err + } + + return artifactRepoURL, nil +} + +func getTestConfigGKE(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) { + sharedSopsId := outputs["sops_id"].Value.(string) + + privateKeyFile, ok := os.LookupEnv("GCP_SOURCEREPO_SSH") + if !ok { + return nil, fmt.Errorf("GCP_SOURCEREPO_SSH env variable isn't set") + } + privateKeyData, err := os.ReadFile(privateKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting gcp source repositories private key, '%s': %w", privateKeyFile, err) + } + + pubKeyFile, ok := os.LookupEnv("GCP_SOURCEREPO_SSH_PUB") + if !ok { + return nil, fmt.Errorf("GCP_SOURCEREPO_SSH_PUB env variable isn't set") + } + pubKeyData, err := os.ReadFile(pubKeyFile) + if err != nil { + return nil, fmt.Errorf("error getting ssh pubkey '%s', %w", pubKeyFile, err) + } + + config := &testConfig{ + defaultGitTransport: git.SSH, + gitUsername: "git", + gitPrivateKey: string(privateKeyData), + gitPublicKey: string(pubKeyData), + knownHosts: gkeDevOpsKnownHosts, + fleetInfraRepository: repoConfig{ + ssh: outputs["fleet_infra_url"].Value.(string), + }, + applicationRepository: repoConfig{ + ssh: outputs["application_url"].Value.(string), + }, + sopsArgs: fmt.Sprintf("--gcp-kms %s", sharedSopsId), + } + + opts, err := authOpts(config.fleetInfraRepository.ssh, map[string][]byte{ + "identity": []byte(config.gitPrivateKey), + "known_hosts": []byte(config.knownHosts), + }) + if err != nil { + return nil, err + } + + config.defaultAuthOpts = opts + + // In Azure, the repository is initialized with a default branch through + // terraform. We have to do it manually here for GCP to prevent errors + // when trying to clone later. We only need to do it for the application repository + // since flux bootstrap pushes to the main branch. + files := make(map[string]io.Reader) + files["README.md"] = strings.NewReader("# Flux test repo") + tmpDir, err := os.MkdirTemp("", "*-flux-test") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpDir) + + client, err := getRepository(context.Background(), tmpDir, config.applicationRepository.ssh, defaultBranch, config.defaultAuthOpts) + if err != nil { + return nil, err + } + err = commitAndPushAll(context.Background(), client, files, defaultBranch) + if err != nil { + return nil, err + } + + return config, nil +} diff --git a/tests/integration/suite_test.go b/tests/integration/suite_test.go index f8696caa..9272c84f 100644 --- a/tests/integration/suite_test.go +++ b/tests/integration/suite_test.go @@ -45,6 +45,9 @@ const ( // azureTerraformPath is the path to the folder containing the // terraform files for azure infra azureTerraformPath = "./terraform/azure" + // gcpTerraformPath is the path to the folder containing the + // terraform files for gcp infra + gcpTerraformPath = "./terraform/gcp" // kubeconfigPath is the path of the file containing the kubeconfig kubeconfigPath = "./build/kubeconfig" @@ -55,7 +58,7 @@ const ( var ( // supportedProviders are the providers supported by the test. - supportedProviders = []string{"azure"} + supportedProviders = []string{"azure", "gcp"} // cfg is a struct containing different variables needed for the test. cfg *testConfig @@ -269,8 +272,14 @@ func getProviderConfig(provider string) *providerConfig { getTestConfig: getTestConfigAKS, registryLogin: registryLoginACR, } + case "gcp": + return &providerConfig{ + terraformPath: gcpTerraformPath, + createKubeconfig: createKubeConfigGKE, + getTestConfig: getTestConfigGKE, + registryLogin: registryLoginGCR, + } } - return nil } diff --git a/tests/integration/terraform/gcp/main.tf b/tests/integration/terraform/gcp/main.tf new file mode 100644 index 00000000..e8d1691c --- /dev/null +++ b/tests/integration/terraform/gcp/main.tf @@ -0,0 +1,49 @@ +provider "google" { + project = var.gcp_project_id + region = var.gcp_region + zone = var.gcp_zone +} + +resource "random_pet" "suffix" {} + +data "google_kms_key_ring" "keyring" { + name = var.gcp_keyring + location = "global" +} + +data "google_kms_crypto_key" "my_crypto_key" { + name = var.gcp_crypto_key + key_ring = data.google_kms_key_ring.keyring.id +} + +data "google_project" "project" { +} + +module "gke" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gke" + name = "flux-e2e-${random_pet.suffix.id}" + tags = var.tags +} + +module "gcr" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gcr" + name = "flux-e2e-${random_pet.suffix.id}" + tags = var.tags +} + +resource "google_sourcerepo_repository" "fleet-infra" { + name = "fleet-infra-${random_pet.suffix.id}" +} + +resource "google_sourcerepo_repository" "application" { + name = "application-${random_pet.suffix.id}" +} + +resource "google_kms_key_ring_iam_binding" "key_ring" { + key_ring_id = data.google_kms_key_ring.keyring.id + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + + members = [ + "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com", + ] +} diff --git a/tests/integration/terraform/gcp/outputs.tf b/tests/integration/terraform/gcp/outputs.tf new file mode 100644 index 00000000..cdf6fbd3 --- /dev/null +++ b/tests/integration/terraform/gcp/outputs.tf @@ -0,0 +1,28 @@ +output "kubeconfig" { + value = module.gke.kubeconfig + sensitive = true +} + +output "gcp_project" { + value = var.gcp_project_id +} + +output "gcp_region" { + value = var.gcp_region +} + +output "artifact_registry_id" { + value = module.gcr.artifact_repository_id +} + +output "sops_id" { + value = data.google_kms_crypto_key.my_crypto_key.id +} + +output "fleet_infra_url" { + value = "ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.fleet-infra.name}" +} + +output "application_url" { + value = "ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.application.name}" +} diff --git a/tests/integration/terraform/gcp/variables.tf b/tests/integration/terraform/gcp/variables.tf new file mode 100644 index 00000000..5996ddcc --- /dev/null +++ b/tests/integration/terraform/gcp/variables.tf @@ -0,0 +1,37 @@ +variable "gcp_project_id" { + type = string + description = "GCP project to create the resources in" +} + +variable "gcp_email" { + type = string + description = "GCP email" +} + +variable "gcp_region" { + type = string + default = "us-central1" + description = "GCP region" +} + +variable "gcp_zone" { + type = string + default = "us-central1" + description = "GCP region" +} + + +variable "gcp_keyring" { + type = string + description = "GCP keyring that contains crypto key for encrypting secrets" +} + +variable "gcp_crypto_key" { + type = string + description = "GCP crypto key for encrypting secrets" +} + +variable "tags" { + type = map(string) + default = {} +} From e63ddb99de1f170ddd2b15006f1378d16f5cdb5c Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Tue, 27 Jun 2023 10:34:07 +0100 Subject: [PATCH 3/9] make tests for notifications provider agnostic Signed-off-by: Somtochi Onyekwere --- tests/.gitignore | 1 - tests/integration/.env.sample | 25 ++- tests/integration/README.md | 41 +++- tests/integration/azure_specific_test.go | 173 --------------- tests/integration/azure_test.go | 55 ++++- tests/integration/gcp_test.go | 85 ++++++-- tests/integration/go.mod | 14 +- tests/integration/go.sum | 42 +++- tests/integration/notification_test.go | 204 ++++++++++++++++++ tests/integration/suite_test.go | 43 ++-- tests/integration/terraform/gcp/gke.tf | 13 ++ tests/integration/terraform/gcp/kms.tf | 18 ++ tests/integration/terraform/gcp/main.tf | 42 +--- tests/integration/terraform/gcp/outputs.tf | 12 +- tests/integration/terraform/gcp/pubsub.tf | 11 + tests/integration/terraform/gcp/sourcerepo.tf | 7 + tests/integration/terraform/gcp/variables.tf | 10 +- tests/integration/util_test.go | 6 +- 18 files changed, 515 insertions(+), 287 deletions(-) create mode 100644 tests/integration/notification_test.go create mode 100644 tests/integration/terraform/gcp/gke.tf create mode 100644 tests/integration/terraform/gcp/kms.tf create mode 100644 tests/integration/terraform/gcp/pubsub.tf create mode 100644 tests/integration/terraform/gcp/sourcerepo.tf diff --git a/tests/.gitignore b/tests/.gitignore index fda47f9d..0acf4705 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -13,7 +13,6 @@ build/ # .tfstate files *.tfstate *.tfstate.* -*.terraform.lock.hcl # Crash log files crash.log diff --git a/tests/integration/.env.sample b/tests/integration/.env.sample index 767e9b7e..ad9e1fe7 100644 --- a/tests/integration/.env.sample +++ b/tests/integration/.env.sample @@ -1,11 +1,7 @@ ## Azure -export TF_VAR_azuredevops_org= -export TF_VAR_azuredevops_pat= -export TF_VAR_azure_location= -## These are not terraform variables -## but they are needed for the azure tests -export AZUREDEVOPS_SSH= -export AZUREDEVOPS_SSH_PUB= +# export TF_VAR_azuredevops_org= +# export TF_VAR_azuredevops_pat= +# export TF_VAR_azure_location= ## Set the following only when authenticating using Service Principal (suited ## for CI environment). # export ARM_CLIENT_ID= @@ -14,13 +10,20 @@ export AZUREDEVOPS_SSH_PUB= # export ARM_TENANT_ID= ## GCP -export TF_VAR_gcp_project_id= -export TF_VAR_gcp_zone= -export TF_VAR_gcp_keyring= -export TF_VAR_gcp_crypto_key= +# export TF_VAR_gcp_project_id= +# export TF_VAR_gcp_zone= +# export TF_VAR_gcp_region= +# export TF_VAR_gcp_keyring= +# export TF_VAR_gcp_crypto_key= +## Email address of a GCP user used for git repository cloning over ssh. +# export TF_VAR_gcp_email= ## Set the following only when using service account. ## Provide absolute path to the service account JSON key file. # export GOOGLE_APPLICATION_CREDENTIALS= ## Common variables # export TF_VAR_tags='{"environment"="dev", "createdat"='"\"$(date -u +x%Y-%m-%d_%Hh%Mm%Ss)\""'}' +## These are not terraform variables +## but they are needed for the bootstrap tests +# export GITREPO_SSH_PATH= +# export GITREPO_SSH_PUB_PATH= diff --git a/tests/integration/README.md b/tests/integration/README.md index f4232f1d..5477628d 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -1,7 +1,7 @@ # E2E Tests The goal is to verify that Flux integration with cloud providers are actually working now and in the future. -Currently, we only have tests for Azure. +Currently, we only have tests for Azure and GCP. ## General requirements @@ -60,9 +60,10 @@ the tests: ### Architecture The [gcp](./terraform/gcp) terraform files create the GKE cluster and related resources to run the tests. It creates: -- An Google Container Registry and Artifact Registry -- An Google Kubernetes Cluster +- A Google Container Registry and Artifact Registry +- A Google Kubernetes Cluster - Two Google Cloud Source Repositories +- A Google Pub/Sub Topic and a subscription to the service that would be used in the tests Note: It doesn't create Google KMS keyrings and crypto keys because these cannot be destroyed. Instead, you have to pass in the crypto key and keyring that would be used to test the sops encryption in Flux. Please see `.env.sample` @@ -72,6 +73,8 @@ for the terraform variables - GCP account with an active project to be able to create GKE and GCR, and permission to assign roles. - Existing GCP KMS keyring and crypto key. + - [Create a Keyring](https://cloud.google.com/kms/docs/create-key-ring) + - [Create a Crypto Key](https://cloud.google.com/kms/docs/create-key) - gcloud CLI, need to be logged in using `gcloud auth login` as a User (not a Service Account), configure application default credentials with `gcloud auth application-default login` and docker credential helper with `gcloud auth configure-docker`. @@ -86,7 +89,6 @@ for the terraform variables docker accordingly ```console $ gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev - $ gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://gcr.io ``` In this case, the GCP client in terraform uses the Service Account to authenticate and the gcloud CLI is used only to authenticate with Google @@ -100,15 +102,33 @@ for the terraform variables to prevent aggressive masking in the logs. Refer [aggressive replacement in logs](https://github.com/google-github-actions/auth/blob/v1.1.0/docs/TROUBLESHOOTING.md#aggressive--replacement-in-logs) for more details. -- Register SSH Keys with Google Cloud. # add docs +- Register [SSH Keys with Google Cloud](https://cloud.google.com/source-repositories/docs/authentication#ssh) + - Google Cloud supports these three SSH key types: RSA (only for keys with more than 2048 bits), ECDSA and ED25519 + - **Note:** Google doesn't allow an SSH key to be associated with a service account email address. Therefore, there has to be an actual + user that the SSH keys are registered to, and the email of this user will be passed to terraform through the `TF_VAR_gcp_email` + variable. + +### Permissions + +Following roles are needed for provisioning the infrastructure and running the tests: + +- Compute Instance Admin (v1) +- Kubernetes Engine Admin +- Service Account User +- Artifact Registry Administrator +- Artifact Registry Repository Administrator +- Cloud KMS Admin +- Cloud KMS CryptoKey Encrypter +- Source Repository Administrator +- Pub/Sub Admin ## Tests Each test run is initiated by running `terraform apply` in the provider's terraform directory e.g terraform apply, it does this by using the [tftestenv package](https://github.com/fluxcd/test-infra/blob/main/tftestenv/testenv.go) within the `fluxcd/test-infra` repository. It then reads the output of the Terraform to get information needed -for the tests like the kubernetes client ID, the azure DevOps repository urls, the key vault ID etc. This means that -a lot of the communication with the Azure API is offset to Terraform instead of requiring it to be implemented in the test. +for the tests like the kubernetes client ID, the cloud repository urls, the key vault ID etc. This means that +a lot of the communication with the cloud provider API is offset to Terraform instead of requiring it to be implemented in the test. The following tests are currently implemented: @@ -118,11 +138,11 @@ The following tests are currently implemented: - kustomize-controller can decrypt secrets using SOPS and provider key vault - image-automation-controller can create branches and push to cloud repositories (https+ssh) - source-controller can pull charts from cloud provider container registry Helm repositories +- notification-controller can forward events to cloud Events Service(EventHub for Azure and Google Pub/Sub) The following tests are run only for Azure since it is supported in the notification-controller: - notification-controller can send commit status to Azure DevOps -- notification-controller can forward events to Azure Event Hub ### Running tests locally @@ -165,8 +185,9 @@ ok github.com/fluxcd/flux2/tests/integration 947.341s In the above, the test created a build directory build/ and the flux cli binary is copied build/flux. It would be used to bootstrap Flux on the cluster. You can configure the location of the Flux CLI binary by setting the FLUX_BINARY variable. -We also pull two version of `ghcr.io/stefanprodan/podinfo` image. These images are pushed to the Azure Container Registry -and used to test `ImageRepository` and `ImageUpdateAutomation`. The terraform resources get created and the tests are run. +We also pull two version of `ghcr.io/stefanprodan/podinfo` image. These images are pushed to the cloud provider's +Container Registry and used to test `ImageRepository` and `ImageUpdateAutomation`. The terraform resources get created +and the tests are run. **IMPORTANT:** In case the terraform infrastructure results in a bad state, maybe due to a crash during the apply, the whole infrastructure can be destroyed by running terraform destroy in terraform/ directory. diff --git a/tests/integration/azure_specific_test.go b/tests/integration/azure_specific_test.go index 63f78825..536de394 100644 --- a/tests/integration/azure_specific_test.go +++ b/tests/integration/azure_specific_test.go @@ -21,7 +21,6 @@ package integration import ( "context" - "encoding/json" "fmt" "io" "log" @@ -29,7 +28,6 @@ import ( "testing" "time" - eventhub "github.com/Azure/azure-event-hubs-go/v3" "github.com/microsoft/azure-devops-go-api/azuredevops" "github.com/microsoft/azure-devops-go-api/azuredevops/git" . "github.com/onsi/gomega" @@ -37,185 +35,14 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" notiv1 "github.com/fluxcd/notification-controller/api/v1" notiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2" - events "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/apis/meta" sourcev1 "github.com/fluxcd/source-controller/api/v1" ) -func TestEventHubNotification(t *testing.T) { - g := NewWithT(t) - - ctx := context.TODO() - branchName := "test-notification" - testID := branchName + "-" + randStringRunes(5) - - // Start listening to eventhub with latest offset - // TODO(somtochiama): Make here provider agnostic - hub, err := eventhub.NewHubFromConnectionString(cfg.notificationURL) - g.Expect(err).ToNot(HaveOccurred()) - c := make(chan string, 10) - handler := func(ctx context.Context, event *eventhub.Event) error { - c <- string(event.Data) - return nil - } - runtimeInfo, err := hub.GetRuntimeInformation(ctx) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(len(runtimeInfo.PartitionIDs)).To(Equal(1)) - listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset()) - g.Expect(err).ToNot(HaveOccurred()) - - // Setup Flux resources - manifest := `apiVersion: v1 -kind: ConfigMap -metadata: - name: foobar` - repoUrl := getTransportURL(cfg.applicationRepository) - client, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts) - g.Expect(err).ToNot(HaveOccurred()) - files := make(map[string]io.Reader) - files["configmap.yaml"] = strings.NewReader(manifest) - err = commitAndPushAll(ctx, client, files, branchName) - g.Expect(err).ToNot(HaveOccurred()) - - namespace := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: testID, - }, - } - g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed()) - defer testEnv.Delete(ctx, &namespace) - - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testID, - Namespace: testID, - }, - StringData: map[string]string{ - "address": cfg.notificationURL, - }, - } - g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) - defer testEnv.Delete(ctx, &secret) - - provider := notiv1beta2.Provider{ - ObjectMeta: metav1.ObjectMeta{ - Name: testID, - Namespace: testID, - }, - Spec: notiv1beta2.ProviderSpec{ - Type: "azureeventhub", - Address: repoUrl, - SecretRef: &meta.LocalObjectReference{ - Name: testID, - }, - }, - } - g.Expect(testEnv.Create(ctx, &provider)).To(Succeed()) - defer testEnv.Delete(ctx, &provider) - - alert := notiv1beta2.Alert{ - ObjectMeta: metav1.ObjectMeta{ - Name: testID, - Namespace: testID, - }, - Spec: notiv1beta2.AlertSpec{ - ProviderRef: meta.LocalObjectReference{ - Name: provider.Name, - }, - EventSources: []notiv1.CrossNamespaceObjectReference{ - { - Kind: "Kustomization", - Name: testID, - Namespace: testID, - }, - }, - }, - } - g.Expect(testEnv.Create(ctx, &alert)).ToNot(HaveOccurred()) - defer testEnv.Delete(ctx, &alert) - - g.Eventually(func() bool { - nn := types.NamespacedName{Name: alert.Name, Namespace: alert.Namespace} - alertObj := ¬iv1beta2.Alert{} - err := testEnv.Get(ctx, nn, alertObj) - if err != nil { - return false - } - if err := checkReadyCondition(alertObj); err != nil { - t.Log(err) - return false - } - - return true - }, testTimeout, testInterval).Should(BeTrue()) - - modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { - spec.Interval = metav1.Duration{Duration: 30 * time.Second} - spec.HealthChecks = []meta.NamespacedObjectKindReference{ - { - APIVersion: "v1", - Kind: "ConfigMap", - Name: "foobar", - Namespace: testID, - }, - } - } - g.Expect(setUpFluxConfig(ctx, testID, nsConfig{ - repoURL: repoUrl, - ref: &sourcev1.GitRepositoryRef{ - Branch: branchName, - }, - path: "./", - modifyKsSpec: modifyKsSpec, - })).To(Succeed()) - t.Cleanup(func() { - err := tearDownFluxConfig(ctx, testID) - if err != nil { - t.Logf("failed to delete resources in '%s' namespace: %s", testID, err) - } - }) - - g.Eventually(func() bool { - err := verifyGitAndKustomization(ctx, testEnv, testID, testID) - if err != nil { - t.Log(err) - return false - } - return true - }, testTimeout, testInterval).Should(BeTrue()) - - // Wait to read even from event hub - g.Eventually(func() bool { - select { - case eventJson := <-c: - event := &events.Event{} - err := json.Unmarshal([]byte(eventJson), event) - if err != nil { - t.Logf("the received event type does not match Flux format, error: %v", err) - return false - } - - if event.InvolvedObject.Kind == kustomizev1.KustomizationKind && - event.InvolvedObject.Name == testID && event.InvolvedObject.Namespace == testID { - return true - } - - return false - default: - return false - } - }, testTimeout, 1*time.Second).Should(BeTrue()) - err = listenerHandler.Close(ctx) - g.Expect(err).ToNot(HaveOccurred()) - err = hub.Close(ctx) - g.Expect(err).ToNot(HaveOccurred()) -} - func TestAzureDevOpsCommitStatus(t *testing.T) { g := NewWithT(t) diff --git a/tests/integration/azure_test.go b/tests/integration/azure_test.go index e88fc49d..6c7cab14 100644 --- a/tests/integration/azure_test.go +++ b/tests/integration/azure_test.go @@ -21,6 +21,7 @@ import ( "fmt" "os" + eventhub "github.com/Azure/azure-event-hubs-go/v3" "github.com/fluxcd/pkg/git" "github.com/fluxcd/test-infra/tftestenv" tfjson "github.com/hashicorp/terraform-json" @@ -68,24 +69,36 @@ patchesStrategicMerge: value: msi ` - privateKeyFile, ok := os.LookupEnv("AZUREDEVOPS_SSH") + privateKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPath) if !ok { - return nil, fmt.Errorf("AZUREDEVOPS_SSH env variable isn't set") + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPath) } privateKeyData, err := os.ReadFile(privateKeyFile) if err != nil { return nil, fmt.Errorf("error getting azure devops private key, '%s': %w", privateKeyFile, err) } - pubKeyFile, ok := os.LookupEnv("AZUREDEVOPS_SSH_PUB") + pubKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPubPath) if !ok { - return nil, fmt.Errorf("AZUREDEVOPS_SSH_PUB env variable isn't set") + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPubPath) } pubKeyData, err := os.ReadFile(pubKeyFile) if err != nil { return nil, fmt.Errorf("error getting ssh pubkey '%s', %w", pubKeyFile, err) } + c := make(chan []byte, 10) + closefn, err := setupEventHubHandler(ctx, c, eventHubSas) + + var notificationCfg = notificationConfig{ + notificationChan: c, + providerType: "azureeventhub", + closeChan: closefn, + secret: map[string]string{ + "address": eventHubSas, + }, + } + config := &testConfig{ defaultGitTransport: git.HTTP, gitUsername: git.DefaultPublicKeyAuthUser, @@ -93,15 +106,15 @@ patchesStrategicMerge: gitPrivateKey: string(privateKeyData), gitPublicKey: string(pubKeyData), knownHosts: azureDevOpsKnownHosts, - fleetInfraRepository: repoConfig{ + fleetInfraRepository: gitUrl{ http: fleetInfraRepository["http"].(string), ssh: fleetInfraRepository["ssh"].(string), }, - applicationRepository: repoConfig{ + applicationRepository: gitUrl{ http: applicationRepository["http"].(string), ssh: applicationRepository["ssh"].(string), }, - notificationURL: eventHubSas, + notificationCfg: notificationCfg, sopsArgs: fmt.Sprintf("--azure-kv %s", sharedSopsId), sopsSecretData: map[string]string{ "sops.azure-kv": fmt.Sprintf(`clientId: %s`, outputs["aks_client_id"].Value.(string)), @@ -116,7 +129,6 @@ patchesStrategicMerge: if err != nil { return nil, err } - config.defaultAuthOpts = opts return config, nil @@ -134,3 +146,30 @@ func registryLoginACR(ctx context.Context, output map[string]*tfjson.StateOutput return registryURL, nil } + +func setupEventHubHandler(ctx context.Context, c chan []byte, eventHubSas string) (func(), error) { + hub, err := eventhub.NewHubFromConnectionString(eventHubSas) + if err != nil { + return nil, err + } + + handler := func(ctx context.Context, event *eventhub.Event) error { + c <- event.Data + return nil + } + runtimeInfo, err := hub.GetRuntimeInformation(ctx) + if err != nil { + return nil, err + } + listenerHandler, err := hub.Receive(ctx, runtimeInfo.PartitionIDs[0], handler, eventhub.ReceiveWithLatestOffset()) + if err != nil { + return nil, err + } + + closefn := func() { + listenerHandler.Close(ctx) + hub.Close(ctx) + } + + return closefn, nil +} diff --git a/tests/integration/gcp_test.go b/tests/integration/gcp_test.go index 02bc6ab4..8d901dcb 100644 --- a/tests/integration/gcp_test.go +++ b/tests/integration/gcp_test.go @@ -20,34 +20,37 @@ import ( "context" "fmt" "io" + "log" "os" "strings" + "cloud.google.com/go/pubsub" + tfjson "github.com/hashicorp/terraform-json" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/fluxcd/pkg/git" "github.com/fluxcd/test-infra/tftestenv" - tfjson "github.com/hashicorp/terraform-json" ) const ( - gkeDevOpsKnownHosts = "[source.developers.google.com]:2022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB5Iy4/cq/gt/fPqe3uyMy4jwv1Alc94yVPxmnwNhBzJqEV5gRPiRk5u4/JJMbbu9QUVAguBABxL7sBZa5PH/xY=" + gcpSourceRepoKnownHosts = "[source.developers.google.com]:2022 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB5Iy4/cq/gt/fPqe3uyMy4jwv1Alc94yVPxmnwNhBzJqEV5gRPiRk5u4/JJMbbu9QUVAguBABxL7sBZa5PH/xY=" ) // createKubeConfigGKE constructs kubeconfig for a GKE cluster from the // terraform state output at the given kubeconfig path. func createKubeConfigGKE(ctx context.Context, state map[string]*tfjson.StateOutput, kcPath string) error { - kubeconfigYaml, ok := state["kubeconfig"].Value.(string) + kubeconfigYaml, ok := state["gke_kubeconfig"].Value.(string) if !ok || kubeconfigYaml == "" { return fmt.Errorf("failed to obtain kubeconfig from tf output") } return tftestenv.CreateKubeconfigGKE(ctx, kubeconfigYaml, kcPath) } -// registryLoginGCR logs into the container/artifact registries using the -// provider's CLI tools and returns a list of test repositories. +// registryLoginGCR logs into the Artifact registries using the gcloud +// and returns a list of test repositories. func registryLoginGCR(ctx context.Context, output map[string]*tfjson.StateOutput) (string, error) { - // NOTE: ACR registry accept dynamic repository creation by just pushing a - // new image with a new repository name. - project := output["gcp_project"].Value.(string) + project := output["gcp_project_id"].Value.(string) region := output["gcp_region"].Value.(string) repositoryID := output["artifact_registry_id"].Value.(string) artifactRegistryURL, artifactRepoURL := tftestenv.GetGoogleArtifactRegistryAndRepository(project, region, repositoryID) @@ -61,37 +64,57 @@ func registryLoginGCR(ctx context.Context, output map[string]*tfjson.StateOutput func getTestConfigGKE(ctx context.Context, outputs map[string]*tfjson.StateOutput) (*testConfig, error) { sharedSopsId := outputs["sops_id"].Value.(string) - privateKeyFile, ok := os.LookupEnv("GCP_SOURCEREPO_SSH") + privateKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPath) if !ok { - return nil, fmt.Errorf("GCP_SOURCEREPO_SSH env variable isn't set") + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPath) } privateKeyData, err := os.ReadFile(privateKeyFile) if err != nil { return nil, fmt.Errorf("error getting gcp source repositories private key, '%s': %w", privateKeyFile, err) } - pubKeyFile, ok := os.LookupEnv("GCP_SOURCEREPO_SSH_PUB") + pubKeyFile, ok := os.LookupEnv(envVarGitRepoSSHPubPath) if !ok { - return nil, fmt.Errorf("GCP_SOURCEREPO_SSH_PUB env variable isn't set") + return nil, fmt.Errorf("%s env variable isn't set", envVarGitRepoSSHPubPath) } pubKeyData, err := os.ReadFile(pubKeyFile) if err != nil { return nil, fmt.Errorf("error getting ssh pubkey '%s', %w", pubKeyFile, err) } + c := make(chan []byte, 10) + projectID := outputs["gcp_project_id"].Value.(string) + topicID := outputs["pubsub_topic"].Value.(string) + + fn, err := setupPubSubReceiver(ctx, c, projectID, topicID) + if err != nil { + return nil, err + } + + var notificationCfg = notificationConfig{ + providerType: "googlepubsub", + providerChannel: topicID, + notificationChan: c, + closeChan: fn, + secret: map[string]string{ + "address": projectID, + }, + } + config := &testConfig{ defaultGitTransport: git.SSH, gitUsername: "git", gitPrivateKey: string(privateKeyData), gitPublicKey: string(pubKeyData), - knownHosts: gkeDevOpsKnownHosts, - fleetInfraRepository: repoConfig{ - ssh: outputs["fleet_infra_url"].Value.(string), + knownHosts: gcpSourceRepoKnownHosts, + fleetInfraRepository: gitUrl{ + ssh: outputs["fleet_infra_repository"].Value.(string), }, - applicationRepository: repoConfig{ - ssh: outputs["application_url"].Value.(string), + applicationRepository: gitUrl{ + ssh: outputs["application_repository"].Value.(string), }, - sopsArgs: fmt.Sprintf("--gcp-kms %s", sharedSopsId), + notificationCfg: notificationCfg, + sopsArgs: fmt.Sprintf("--gcp-kms %s", sharedSopsId), } opts, err := authOpts(config.fleetInfraRepository.ssh, map[string][]byte{ @@ -127,3 +150,29 @@ func getTestConfigGKE(ctx context.Context, outputs map[string]*tfjson.StateOutpu return config, nil } + +func setupPubSubReceiver(ctx context.Context, c chan []byte, projectID string, topicID string) (func(), error) { + newCtx, cancel := context.WithCancel(ctx) + pubsubClient, err := pubsub.NewClient(newCtx, projectID) + if err != nil { + cancel() + return nil, fmt.Errorf("error creating pubsub client: %s", err) + } + + sub := pubsubClient.Subscription(topicID) + go func() { + err = sub.Receive(ctx, func(ctx context.Context, message *pubsub.Message) { + c <- message.Data + message.Ack() + }) + if err != nil && status.Code(err) != codes.Canceled { + log.Printf("error receiving message in subscription: %s\n", err) + return + } + }() + + return func() { + cancel() + pubsubClient.Close() + }, nil +} diff --git a/tests/integration/go.mod b/tests/integration/go.mod index ab27dbd2..6a405660 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -3,6 +3,7 @@ module github.com/fluxcd/flux2/tests/integration go 1.18 require ( + cloud.google.com/go/pubsub v1.31.0 github.com/Azure/azure-event-hubs-go/v3 v3.6.0 github.com/fluxcd/helm-controller/api v0.34.1 github.com/fluxcd/image-automation-controller/api v0.34.1 @@ -22,6 +23,7 @@ require ( github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5 github.com/onsi/gomega v1.27.8 github.com/whilp/git-urls v1.0.0 + google.golang.org/grpc v1.55.0 k8s.io/api v0.27.3 k8s.io/apimachinery v0.27.3 k8s.io/client-go v0.27.3 @@ -29,6 +31,10 @@ require ( ) require ( + cloud.google.com/go v0.110.2 // indirect + cloud.google.com/go/compute v1.19.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.0.1 // indirect github.com/Azure/azure-amqp-common-go/v4 v4.2.0 // indirect github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect github.com/Azure/go-amqp v1.0.0 // indirect @@ -74,7 +80,10 @@ require ( github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect + github.com/googleapis/gax-go/v2 v2.9.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -105,17 +114,20 @@ require ( github.com/vbatts/tar-split v0.11.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/zclconf/go-cty v1.13.0 // indirect + go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.6.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.8.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 + google.golang.org/api v0.124.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/tests/integration/go.sum b/tests/integration/go.sum index 65f998c6..09f99d0c 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -1,5 +1,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v1.0.1 h1:lyeCAU6jpnVNrE9zGQkTl3WgNgK/X+uWwaw0kynZJMU= +cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8= +cloud.google.com/go/kms v1.10.2 h1:8UePKEypK3SQ6g+4mn/s/VgE5L7XOh+FwGGRUqvY3Hw= +cloud.google.com/go/pubsub v1.31.0 h1:aXdyyJz90kA+bor9+6+xHAciMD5mj8v15WqFZ5E0sek= +cloud.google.com/go/pubsub v1.31.0/go.mod h1:dYmJ3K97NCQ/e4OwZ20rD4Ym3Bu8Gu9m/aJdWQjdcks= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= github.com/Azure/azure-amqp-common-go/v4 v4.2.0 h1:q/jLx1KJ8xeI8XGfkOWMN9XrXzAfVTkyvCxPvHCjd2I= github.com/Azure/azure-amqp-common-go/v4 v4.2.0/go.mod h1:GD3m/WPPma+621UaU6KNjKEo5Hl09z86viKwQjTpV0Q= @@ -64,6 +75,7 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= @@ -71,7 +83,11 @@ github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEM github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -104,6 +120,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 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/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= @@ -180,6 +197,7 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -206,6 +224,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -215,10 +234,16 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.9.1 h1:DpTpJqzZ3NvX9zqjhIuI1oVzYZMvboZe+3LoeEIJjHM= +github.com/googleapis/gax-go/v2 v2.9.1/go.mod h1:4FG3gMrVZlyMp5itSYKMU9z/lBE7+SbnUOvzH2HqbEY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -365,6 +390,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= @@ -378,6 +405,7 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= @@ -406,6 +434,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -419,8 +448,8 @@ 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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 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= @@ -473,6 +502,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -497,6 +527,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +google.golang.org/api v0.124.0 h1:dP6Ef1VgOGqQ8eiv4GiY8RhmeyqzovcXBYPDUYG8Syo= +google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -506,13 +538,19 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 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.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/tests/integration/notification_test.go b/tests/integration/notification_test.go new file mode 100644 index 00000000..4ea01692 --- /dev/null +++ b/tests/integration/notification_test.go @@ -0,0 +1,204 @@ +/* +Copyright 2023 The Flux authors + +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 integration + +import ( + "context" + "encoding/json" + "io" + "strings" + "testing" + "time" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + notiv1 "github.com/fluxcd/notification-controller/api/v1" + notiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2" + events "github.com/fluxcd/pkg/apis/event/v1beta1" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +func TestNotification(t *testing.T) { + g := NewWithT(t) + + ctx := context.TODO() + branchName := "test-notification" + testID := branchName + "-" + randStringRunes(5) + defer cfg.notificationCfg.closeChan() + + // Setup Flux resources + manifest := `apiVersion: v1 +kind: ConfigMap +metadata: + name: foobar` + repoUrl := getTransportURL(cfg.applicationRepository) + client, err := getRepository(ctx, t.TempDir(), repoUrl, defaultBranch, cfg.defaultAuthOpts) + g.Expect(err).ToNot(HaveOccurred()) + files := make(map[string]io.Reader) + files["configmap.yaml"] = strings.NewReader(manifest) + err = commitAndPushAll(ctx, client, files, branchName) + g.Expect(err).ToNot(HaveOccurred()) + + namespace := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + }, + } + g.Expect(testEnv.Create(ctx, &namespace)).To(Succeed()) + defer testEnv.Delete(ctx, &namespace) + + provider := notiv1beta2.Provider{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + Spec: notiv1beta2.ProviderSpec{ + Type: cfg.notificationCfg.providerType, + Address: cfg.notificationCfg.providerAddress, + Channel: cfg.notificationCfg.providerChannel, + }, + } + + if cfg.notificationCfg.secret != nil { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + StringData: cfg.notificationCfg.secret, + } + + g.Expect(testEnv.Create(ctx, &secret)).To(Succeed()) + defer testEnv.Delete(ctx, &secret) + + provider.Spec.SecretRef = &meta.LocalObjectReference{ + Name: testID, + } + } + + g.Expect(testEnv.Create(ctx, &provider)).To(Succeed()) + defer testEnv.Delete(ctx, &provider) + + alert := notiv1beta2.Alert{ + ObjectMeta: metav1.ObjectMeta{ + Name: testID, + Namespace: testID, + }, + Spec: notiv1beta2.AlertSpec{ + ProviderRef: meta.LocalObjectReference{ + Name: provider.Name, + }, + EventSources: []notiv1.CrossNamespaceObjectReference{ + { + Kind: "Kustomization", + Name: testID, + Namespace: testID, + }, + }, + }, + } + g.Expect(testEnv.Create(ctx, &alert)).ToNot(HaveOccurred()) + defer testEnv.Delete(ctx, &alert) + + g.Eventually(func() bool { + nn := types.NamespacedName{Name: provider.Name, Namespace: provider.Namespace} + obj := ¬iv1beta2.Provider{} + err := testEnv.Get(ctx, nn, obj) + if err != nil { + return false + } + if err := checkReadyCondition(obj); err != nil { + t.Log(err) + return false + } + + nn = types.NamespacedName{Name: alert.Name, Namespace: alert.Namespace} + alertObj := ¬iv1beta2.Alert{} + err = testEnv.Get(ctx, nn, alertObj) + if err != nil { + return false + } + if err := checkReadyCondition(alertObj); err != nil { + t.Log(err) + return false + } + + return true + }, testTimeout, testInterval).Should(BeTrue()) + + modifyKsSpec := func(spec *kustomizev1.KustomizationSpec) { + spec.Interval = metav1.Duration{Duration: 30 * time.Second} + spec.HealthChecks = []meta.NamespacedObjectKindReference{ + { + APIVersion: "v1", + Kind: "ConfigMap", + Name: "foobar", + Namespace: testID, + }, + } + } + g.Expect(setUpFluxConfig(ctx, testID, nsConfig{ + repoURL: repoUrl, + ref: &sourcev1.GitRepositoryRef{ + Branch: branchName, + }, + path: "./", + modifyKsSpec: modifyKsSpec, + })).To(Succeed()) + t.Cleanup(func() { + err := tearDownFluxConfig(ctx, testID) + if err != nil { + t.Logf("failed to delete resources in '%s' namespace: %s", testID, err) + } + }) + + g.Eventually(func() bool { + err := verifyGitAndKustomization(ctx, testEnv, testID, testID) + if err != nil { + t.Log(err) + return false + } + return true + }, testTimeout, testInterval).Should(BeTrue()) + + // Wait to read event from notification channel. + g.Eventually(func() bool { + select { + case eventJson := <-cfg.notificationCfg.notificationChan: + event := &events.Event{} + err := json.Unmarshal([]byte(eventJson), event) + if err != nil { + t.Logf("the received event type does not match Flux format, error: %v", err) + return false + } + + if event.InvolvedObject.Kind == kustomizev1.KustomizationKind && + event.InvolvedObject.Name == testID && event.InvolvedObject.Namespace == testID { + return true + } + + return false + default: + return false + } + }, testTimeout, 1*time.Second).Should(BeTrue()) +} diff --git a/tests/integration/suite_test.go b/tests/integration/suite_test.go index 9272c84f..be2ca0cf 100644 --- a/tests/integration/suite_test.go +++ b/tests/integration/suite_test.go @@ -54,6 +54,13 @@ const ( // default branch to be used when cloning git repositories defaultBranch = "main" + + // envVarGitRepoSSHPath is the environment variable that contains the path + // to the ssh key for the git repository + envVarGitRepoSSHPath = "GITREPO_SSH_PATH" + // envVarGitRepoSSHPubPath is the environment variable that contains the path + // to the ssh public key for the git repository + envVarGitRepoSSHPubPath = "GITREPO_SSH_PUB_PATH" ) var ( @@ -97,18 +104,18 @@ type testConfig struct { defaultGitTransport git.TransportType defaultAuthOpts *git.AuthOptions knownHosts string - fleetInfraRepository repoConfig - applicationRepository repoConfig - - notificationURL string + fleetInfraRepository gitUrl + applicationRepository gitUrl - // cloud provider dependent argument to pass to the sops cli + // sopsArgs is the cloud provider dependent argument to pass to the sops cli sopsArgs string - // secret data for sops + + // notificationCfg contains the values needed to properly set up notification on the + // cluster. + notificationCfg notificationConfig + + // sopsSecretData is the secret's data for the sops decryption sopsSecretData map[string]string - // envCredsData are data field for a secret containing environment variables that the Flux deployments - // may need - envCredsData map[string]string // kustomizationYaml is the content of the kustomization.yaml for customizing the Flux manifests kustomizationYaml string @@ -116,9 +123,21 @@ type testConfig struct { testRegistry string } -// repoConfig contains the http/ssh urls for the created git repositories +// notificationConfig contains various fields for configuring +// providers and testing notifications for the different +// cloud providers. +type notificationConfig struct { + providerChannel string + providerType string + providerAddress string + secret map[string]string + notificationChan chan []byte + closeChan func() +} + +// gitUrl contains the http/ssh urls for the created git repositories // on the various cloud providers. -type repoConfig struct { +type gitUrl struct { http string ssh string } @@ -222,7 +241,7 @@ func TestMain(m *testing.M) { // get provider specific test configuration cfg, err = providerCfg.getTestConfig(ctx, outputs) if err != nil { - panic(fmt.Sprintf("Failed to get provider config for %v", err)) + panic(fmt.Sprintf("Failed to get test config: %v", err)) } regUrl, err := providerCfg.registryLogin(ctx, outputs) diff --git a/tests/integration/terraform/gcp/gke.tf b/tests/integration/terraform/gcp/gke.tf new file mode 100644 index 00000000..5de455db --- /dev/null +++ b/tests/integration/terraform/gcp/gke.tf @@ -0,0 +1,13 @@ +module "gke" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gke" + + name = local.name + tags = var.tags +} + +module "gcr" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gcr" + + name = local.name + tags = var.tags +} diff --git a/tests/integration/terraform/gcp/kms.tf b/tests/integration/terraform/gcp/kms.tf new file mode 100644 index 00000000..6aed72a3 --- /dev/null +++ b/tests/integration/terraform/gcp/kms.tf @@ -0,0 +1,18 @@ +data "google_kms_key_ring" "keyring" { + name = var.gcp_keyring + location = "global" +} + +data "google_kms_crypto_key" "my_crypto_key" { + name = var.gcp_crypto_key + key_ring = data.google_kms_key_ring.keyring.id +} + +resource "google_kms_key_ring_iam_binding" "key_ring" { + key_ring_id = data.google_kms_key_ring.keyring.id + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + + members = [ + "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com", + ] +} diff --git a/tests/integration/terraform/gcp/main.tf b/tests/integration/terraform/gcp/main.tf index e8d1691c..cc917453 100644 --- a/tests/integration/terraform/gcp/main.tf +++ b/tests/integration/terraform/gcp/main.tf @@ -6,44 +6,8 @@ provider "google" { resource "random_pet" "suffix" {} -data "google_kms_key_ring" "keyring" { - name = var.gcp_keyring - location = "global" +locals { + name = "e2e-${random_pet.suffix.id}" } -data "google_kms_crypto_key" "my_crypto_key" { - name = var.gcp_crypto_key - key_ring = data.google_kms_key_ring.keyring.id -} - -data "google_project" "project" { -} - -module "gke" { - source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gke" - name = "flux-e2e-${random_pet.suffix.id}" - tags = var.tags -} - -module "gcr" { - source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/gcr" - name = "flux-e2e-${random_pet.suffix.id}" - tags = var.tags -} - -resource "google_sourcerepo_repository" "fleet-infra" { - name = "fleet-infra-${random_pet.suffix.id}" -} - -resource "google_sourcerepo_repository" "application" { - name = "application-${random_pet.suffix.id}" -} - -resource "google_kms_key_ring_iam_binding" "key_ring" { - key_ring_id = data.google_kms_key_ring.keyring.id - role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" - - members = [ - "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com", - ] -} +data "google_project" "project" {} diff --git a/tests/integration/terraform/gcp/outputs.tf b/tests/integration/terraform/gcp/outputs.tf index cdf6fbd3..d356b188 100644 --- a/tests/integration/terraform/gcp/outputs.tf +++ b/tests/integration/terraform/gcp/outputs.tf @@ -1,9 +1,9 @@ -output "kubeconfig" { +output "gke_kubeconfig" { value = module.gke.kubeconfig sensitive = true } -output "gcp_project" { +output "gcp_project_id" { value = var.gcp_project_id } @@ -19,10 +19,14 @@ output "sops_id" { value = data.google_kms_crypto_key.my_crypto_key.id } -output "fleet_infra_url" { +output "fleet_infra_repository" { value = "ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.fleet-infra.name}" } -output "application_url" { +output "application_repository" { value = "ssh://${var.gcp_email}@source.developers.google.com:2022/p/${var.gcp_project_id}/r/${google_sourcerepo_repository.application.name}" } + +output "pubsub_topic" { + value = google_pubsub_topic.pubsub.name +} diff --git a/tests/integration/terraform/gcp/pubsub.tf b/tests/integration/terraform/gcp/pubsub.tf new file mode 100644 index 00000000..c59c0e99 --- /dev/null +++ b/tests/integration/terraform/gcp/pubsub.tf @@ -0,0 +1,11 @@ +resource "google_pubsub_topic" "pubsub" { + name = local.name + labels = var.tags + message_retention_duration = "7200s" +} + +resource "google_pubsub_subscription" "sub" { + project = var.gcp_project_id + name = local.name + topic = google_pubsub_topic.pubsub.name +} diff --git a/tests/integration/terraform/gcp/sourcerepo.tf b/tests/integration/terraform/gcp/sourcerepo.tf new file mode 100644 index 00000000..02a14928 --- /dev/null +++ b/tests/integration/terraform/gcp/sourcerepo.tf @@ -0,0 +1,7 @@ +resource "google_sourcerepo_repository" "fleet-infra" { + name = "fleet-infra-${random_pet.suffix.id}" +} + +resource "google_sourcerepo_repository" "application" { + name = "application-${random_pet.suffix.id}" +} diff --git a/tests/integration/terraform/gcp/variables.tf b/tests/integration/terraform/gcp/variables.tf index 5996ddcc..6af9e7d6 100644 --- a/tests/integration/terraform/gcp/variables.tf +++ b/tests/integration/terraform/gcp/variables.tf @@ -5,7 +5,7 @@ variable "gcp_project_id" { variable "gcp_email" { type = string - description = "GCP email" + description = "GCP user email" } variable "gcp_region" { @@ -17,10 +17,9 @@ variable "gcp_region" { variable "gcp_zone" { type = string default = "us-central1" - description = "GCP region" + description = "GCP zone" } - variable "gcp_keyring" { type = string description = "GCP keyring that contains crypto key for encrypting secrets" @@ -32,6 +31,7 @@ variable "gcp_crypto_key" { } variable "tags" { - type = map(string) - default = {} + type = map(string) + default = {} + description = "tags for created resources" } diff --git a/tests/integration/util_test.go b/tests/integration/util_test.go index 017bf193..674c3b68 100644 --- a/tests/integration/util_test.go +++ b/tests/integration/util_test.go @@ -381,12 +381,12 @@ func pushImagesFromURL(repoURL, imgURL string, tags []string) error { return nil } -func getTransportURL(repoCfg repoConfig) string { +func getTransportURL(urls gitUrl) string { if cfg.defaultGitTransport == git.SSH { - return repoCfg.ssh + return urls.ssh } - return repoCfg.http + return urls.http } func authOpts(repoURL string, authData map[string][]byte) (*git.AuthOptions, error) { From ab94c8064c63135935a1f8d518e8325d53c7bcff Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 19 Jul 2023 17:40:01 +0000 Subject: [PATCH 4/9] tests/int: Add IAM setup automation docs and misc fixes Add instructions about how to create service accounts with IAM permissions and populate the secrets and variables required in the CI. Update the panic recovery code to ensure that the exit status surfaces on panic along with a log message. Signed-off-by: Sunny --- .github/workflows/e2e-azure.yaml | 33 +++--- tests/integration/README.md | 138 +++++++++++++++++++++-- tests/integration/suite_test.go | 9 +- tests/integration/terraform/azure/aks.tf | 1 + 4 files changed, 148 insertions(+), 33 deletions(-) diff --git a/.github/workflows/e2e-azure.yaml b/.github/workflows/e2e-azure.yaml index 3236c73a..def40dab 100644 --- a/.github/workflows/e2e-azure.yaml +++ b/.github/workflows/e2e-azure.yaml @@ -3,7 +3,7 @@ name: e2e-azure on: workflow_dispatch: schedule: - - cron: '0 6 * * *' + - cron: '0 6 * * *' push: branches: - main @@ -48,7 +48,7 @@ jobs: wget https://github.com/mozilla/sops/releases/download/v3.7.1/sops-v3.7.1.linux -O $HOME/.local/bin/sops chmod +x $HOME/.local/bin/sops - name: Setup Terraform - uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2 + uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2 with: terraform_version: 1.2.8 terraform_wrapper: false @@ -71,6 +71,7 @@ jobs: defaults: run: working-directory: ./tests/integration + if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' steps: - name: CheckoutD uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 @@ -92,7 +93,7 @@ jobs: - name: Authenticate to Azure uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6 with: - creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}' + creds: '{"clientId":"${{ secrets.AZ_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.AZ_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZ_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZ_ARM_TENANT_ID }}"}' - name: Set dynamic variables in .env run: | cat > .env < build/ssh/key - export AZUREDEVOPS_SSH=build/ssh/key + echo $GITREPO_SSH_CONTENTS | base64 -d > build/ssh/key + export GITREPO_SSH_PATH=build/ssh/key touch ./build/ssh/key.pub - echo $AZUREDEVOPS_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub - export AZUREDEVOPS_SSH_PUB=build/ssh/key.pub + echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub + export GITREPO_SSH_PUB_PATH=build/ssh/key.pub make test-azure diff --git a/tests/integration/README.md b/tests/integration/README.md index 5477628d..aadfe49b 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -55,6 +55,63 @@ the tests: - `Microsoft.KeyVault/*` - `Microsoft.EventHub/*` +### IAM and CI setup + +To create the necessary IAM role with all the permissions, set up CI secrets and +variables using +[azure-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/azure/github-actions) +use: + +```hcl +resource "tls_private_key" "privatekey" { + algorithm = "RSA" + rsa_bits = 4096 +} + +module "azure_gh_actions" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/azure/github-actions" + + azure_owners = ["owner-id-1", "owner-id-2"] + azure_app_name = "flux2-e2e" + azure_app_description = "flux2 e2e" + azure_app_secret_name = "flux2-e2e" + azure_permissions = [ + "Microsoft.Kubernetes/*", + "Microsoft.Resources/*", + "Microsoft.Authorization/roleAssignments/Read", + "Microsoft.Authorization/roleAssignments/Write", + "Microsoft.Authorization/roleAssignments/Delete", + "Microsoft.ContainerRegistry/*", + "Microsoft.ContainerService/*", + "Microsoft.KeyVault/*", + "Microsoft.EventHub/*" + ] + azure_location = "eastus" + + github_project = "flux2" + + github_secret_client_id_name = "AZ_ARM_CLIENT_ID" + github_secret_client_secret_name = "AZ_ARM_CLIENT_SECRET" + github_secret_subscription_id_name = "AZ_ARM_SUBSCRIPTION_ID" + github_secret_tenant_id_name = "AZ_ARM_TENANT_ID" + + github_secret_custom = { + "TF_VAR_azuredevops_org" = "", + "TF_VAR_azuredevops_pat" = "", + "GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), + "GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) + } +} + +output "publickey" { + value = tls_private_key.privatekey.public_key_openssh +} +``` + +Copy the `publickey` output printed after applying, or run `terraform output` to +print it again, and add it in the Azure DevOps SSH public keys under the user +account that'll be used by flux in the tests. + ## GCP ### Architecture @@ -73,8 +130,11 @@ for the terraform variables - GCP account with an active project to be able to create GKE and GCR, and permission to assign roles. - Existing GCP KMS keyring and crypto key. - - [Create a Keyring](https://cloud.google.com/kms/docs/create-key-ring) - - [Create a Crypto Key](https://cloud.google.com/kms/docs/create-key) + - [Create a Keyring](https://cloud.google.com/kms/docs/create-key-ring) in + `global` location. + - [Create a Crypto Key](https://cloud.google.com/kms/docs/create-key) with + symmetric algorithm for encryption and decryption, and software based + protection level. - gcloud CLI, need to be logged in using `gcloud auth login` as a User (not a Service Account), configure application default credentials with `gcloud auth application-default login` and docker credential helper with `gcloud auth configure-docker`. @@ -112,15 +172,71 @@ for the terraform variables Following roles are needed for provisioning the infrastructure and running the tests: -- Compute Instance Admin (v1) -- Kubernetes Engine Admin -- Service Account User -- Artifact Registry Administrator -- Artifact Registry Repository Administrator -- Cloud KMS Admin -- Cloud KMS CryptoKey Encrypter -- Source Repository Administrator -- Pub/Sub Admin +- Compute Instance Admin (v1) - `roles/compute.instanceAdmin.v1` +- Kubernetes Engine Admin - `roles/container.admin` +- Service Account User - `roles/iam.serviceAccountUser` +- Service Account Token Creator - `roles/iam.serviceAccountTokenCreator` +- Artifact Registry Administrator - `roles/artifactregistry.admin` +- Artifact Registry Repository Administrator - `roles/artifactregistry.repoAdmin` +- Cloud KMS Admin - `roles/cloudkms.admin` +- Cloud KMS CryptoKey Encrypter - `roles/cloudkms.cryptoKeyEncrypt` +- Source Repository Administrator - `roles/source.admin` +- Pub/Sub Admin - `roles/pubsub.admin` + +### IAM and CI setup + +To create the necessary IAM role with all the permissions, set up CI secrets and +variables using +[gcp-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/gcp/github-actions) +use: + +```hcl +provider "google" {} + +resource "tls_private_key" "privatekey" { + algorithm = "RSA" + rsa_bits = 4096 +} + +module "gcp_gh_actions" { + source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/github-actions" + + gcp_service_account_id = "flux2-e2e-test" + gcp_service_account_name = "flux2-e2e-test" + gcp_roles = [ + "roles/compute.instanceAdmin.v1", + "roles/container.admin", + "roles/iam.serviceAccountUser", + "roles/iam.serviceAccountTokenCreator", + "roles/artifactregistry.admin", + "roles/artifactregistry.repoAdmin", + "roles/cloudkms.admin", + "roles/cloudkms.cryptoKeyEncrypter", + "roles/source.admin", + "roles/pubsub.admin" + ] + + github_project = "flux2" + + github_secret_credentials_name = "FLUX2_E2E_GOOGLE_CREDENTIALS" + + github_secret_custom = { + "TF_VAR_gcp_keyring" = "", + "TF_VAR_gcp_crypto_key" = "", + "TF_VAR_gcp_email" = "", + "GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), + "GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) + } +} + +output "publickey" { + value = tls_private_key.privatekey.public_key_openssh +} +``` + +Copy the `publickey` output printed after applying, or run `terraform output` to +print it again, and add it in the Google Source Repository SSH public keys under +the user account with email address referred in `TF_VAR_gcp_email` above. ## Tests diff --git a/tests/integration/suite_test.go b/tests/integration/suite_test.go index be2ca0cf..b6ee3589 100644 --- a/tests/integration/suite_test.go +++ b/tests/integration/suite_test.go @@ -222,14 +222,11 @@ func TestMain(m *testing.M) { log.Printf("Failed to stop environment: %v", err) } - // Calling exit on panic prevents logging of panic error. - // Exit only on normal return. Explicitly detect panic and log the error - // on panic. - if err := recover(); err == nil { - os.Exit(exitCode) - } else { + // Log the panic error before exit to surface the cause of panic. + if err := recover(); err != nil { log.Printf("panic: %v", err) } + os.Exit(exitCode) }() // get terrraform infrastructure diff --git a/tests/integration/terraform/azure/aks.tf b/tests/integration/terraform/azure/aks.tf index 122cafa3..8ffadfce 100644 --- a/tests/integration/terraform/azure/aks.tf +++ b/tests/integration/terraform/azure/aks.tf @@ -13,6 +13,7 @@ module "acr" { location = var.azure_location aks_principal_id = [module.aks.principal_id] resource_group = module.aks.resource_group + admin_enabled = true tags = var.tags depends_on = [module.aks] From cdc1c98a115b1b797931fb87e199fe18d2891172 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Wed, 19 Jul 2023 17:58:28 +0000 Subject: [PATCH 5/9] add workflow for gcp Signed-off-by: Somtochi Onyekwere --- .github/workflows/e2e-azure.yaml | 2 +- .github/workflows/e2e-gcp.yaml | 92 ++++++++++++++++++++++++++++++++ tests/integration/README.md | 2 +- 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/e2e-gcp.yaml diff --git a/.github/workflows/e2e-azure.yaml b/.github/workflows/e2e-azure.yaml index def40dab..e34cb93c 100644 --- a/.github/workflows/e2e-azure.yaml +++ b/.github/workflows/e2e-azure.yaml @@ -97,7 +97,7 @@ jobs: - name: Set dynamic variables in .env run: | cat > .env < .env < build/ssh/key + export GITREPO_SSH_PATH=build/ssh/key + touch ./build/ssh/key.pub + echo $GITREPO_SSH_PUB_CONTENTS | base64 -d > ./build/ssh/key.pub + export GITREPO_SSH_PUB_PATH=build/ssh/key.pub + make test-gcp diff --git a/tests/integration/README.md b/tests/integration/README.md index aadfe49b..6e4c0349 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -179,7 +179,7 @@ Following roles are needed for provisioning the infrastructure and running the t - Artifact Registry Administrator - `roles/artifactregistry.admin` - Artifact Registry Repository Administrator - `roles/artifactregistry.repoAdmin` - Cloud KMS Admin - `roles/cloudkms.admin` -- Cloud KMS CryptoKey Encrypter - `roles/cloudkms.cryptoKeyEncrypt` +- Cloud KMS CryptoKey Encrypter - `roles/cloudkms.cryptoKeyEncrypter` - Source Repository Administrator - `roles/source.admin` - Pub/Sub Admin - `roles/pubsub.admin` From 3b637a512521de9414eab0850020b98ca6baa77b Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 21 Jul 2023 14:50:51 +0000 Subject: [PATCH 6/9] tests/int: Separate ssh key names for Azure & GCP - Also update IAM setup docs to include github terraform provider configuration with github owner so that it can be used with a repository under an organization. Signed-off-by: Sunny --- .github/workflows/e2e-azure.yaml | 4 ++-- .github/workflows/e2e-gcp.yaml | 4 ++-- tests/integration/README.md | 38 +++++++++++++++++++++++--------- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/.github/workflows/e2e-azure.yaml b/.github/workflows/e2e-azure.yaml index e34cb93c..bd8573be 100644 --- a/.github/workflows/e2e-azure.yaml +++ b/.github/workflows/e2e-azure.yaml @@ -110,8 +110,8 @@ jobs: TF_VAR_azuredevops_org: ${{ secrets.TF_VAR_azuredevops_org }} TF_VAR_azuredevops_pat: ${{ secrets.TF_VAR_azuredevops_pat }} TF_VAR_location: ${{ vars.TF_VAR_azure_location }} - GITREPO_SSH_CONTENTS: ${{ secrets.GITREPO_SSH_CONTENTS }} - GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GITREPO_SSH_PUB_CONTENTS }} + GITREPO_SSH_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_CONTENTS }} + GITREPO_SSH_PUB_CONTENTS: ${{ secrets.AZURE_GITREPO_SSH_PUB_CONTENTS }} run: | source .env mkdir -p ./build/ssh diff --git a/.github/workflows/e2e-gcp.yaml b/.github/workflows/e2e-gcp.yaml index 91634d4c..99d7be26 100644 --- a/.github/workflows/e2e-gcp.yaml +++ b/.github/workflows/e2e-gcp.yaml @@ -78,8 +78,8 @@ jobs: TF_VAR_gcp_email: ${{ secrets.TF_VAR_gcp_email }} TF_VAR_gcp_keyring: ${{ secret.TF_VAR_gcp_keyring }} TF_VAR_gcp_crypto_key: ${{ secret.TF_VAR_gcp_crypto_key }} - GITREPO_SSH_CONTENTS: ${{ secrets.GITREPO_SSH_CONTENTS }} - GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GITREPO_SSH_PUB_CONTENTS }} + GITREPO_SSH_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_CONTENTS }} + GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_PUB_CONTENTS }} run: | source .env mkdir -p ./build/ssh diff --git a/tests/integration/README.md b/tests/integration/README.md index 6e4c0349..afd6be64 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -60,9 +60,14 @@ the tests: To create the necessary IAM role with all the permissions, set up CI secrets and variables using [azure-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/azure/github-actions) -use: +use the terraform configuration below. Please make sure all the requirements of +azure-gh-actions are followed before running it. ```hcl +provider "github" { + owner = "fluxcd" +} + resource "tls_private_key" "privatekey" { algorithm = "RSA" rsa_bits = 4096 @@ -96,10 +101,10 @@ module "azure_gh_actions" { github_secret_tenant_id_name = "AZ_ARM_TENANT_ID" github_secret_custom = { - "TF_VAR_azuredevops_org" = "", - "TF_VAR_azuredevops_pat" = "", - "GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), - "GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) + "TF_VAR_azuredevops_org" = "", + "TF_VAR_azuredevops_pat" = "", + "AZURE_GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), + "AZURE_GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) } } @@ -112,6 +117,9 @@ Copy the `publickey` output printed after applying, or run `terraform output` to print it again, and add it in the Azure DevOps SSH public keys under the user account that'll be used by flux in the tests. +**NOTE:** The environment variables used above are for the GitHub workflow that +runs the tests. Change the variable names if needed accordingly. + ## GCP ### Architecture @@ -188,11 +196,16 @@ Following roles are needed for provisioning the infrastructure and running the t To create the necessary IAM role with all the permissions, set up CI secrets and variables using [gcp-gh-actions](https://github.com/fluxcd/test-infra/tree/main/tf-modules/gcp/github-actions) -use: +use the terraform configuration below. Please make sure all the requirements of +gcp-gh-actions are followed before running it. ```hcl provider "google" {} +provider "github" { + owner = "fluxcd" +} + resource "tls_private_key" "privatekey" { algorithm = "RSA" rsa_bits = 4096 @@ -221,11 +234,11 @@ module "gcp_gh_actions" { github_secret_credentials_name = "FLUX2_E2E_GOOGLE_CREDENTIALS" github_secret_custom = { - "TF_VAR_gcp_keyring" = "", - "TF_VAR_gcp_crypto_key" = "", - "TF_VAR_gcp_email" = "", - "GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), - "GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) + "TF_VAR_gcp_keyring" = "", + "TF_VAR_gcp_crypto_key" = "", + "TF_VAR_gcp_email" = "", + "GCP_GITREPO_SSH_CONTENTS" = base64encode(tls_private_key.privatekey.private_key_openssh), + "GCP_GITREPO_SSH_PUB_CONTENTS" = base64encode(tls_private_key.privatekey.public_key_openssh) } } @@ -238,6 +251,9 @@ Copy the `publickey` output printed after applying, or run `terraform output` to print it again, and add it in the Google Source Repository SSH public keys under the user account with email address referred in `TF_VAR_gcp_email` above. +**NOTE:** The environment variables used above are for the GitHub workflow that +runs the tests. Change the variable names if needed accordingly. + ## Tests Each test run is initiated by running `terraform apply` in the provider's terraform directory e.g terraform apply, From 7141271bae96a9c8dc17f7cc1ada808d295c0222 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Wed, 16 Aug 2023 22:14:13 +0100 Subject: [PATCH 7/9] instructions for test user in Azure DevOps and GCP source repo Signed-off-by: Somtochi Onyekwere --- tests/integration/README.md | 27 ++++++++++++++++--- tests/integration/terraform/gcp/sourcerepo.tf | 19 +++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/tests/integration/README.md b/tests/integration/README.md index afd6be64..01c331b8 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -36,6 +36,17 @@ The [azure](./terraform/azure) Terraform creates the AKS cluster and related res - Azure DevOps only supports RSA keys. Please see [documentation](https://learn.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops#set-up-ssh-key-authentication) for how to set up SSH key authentication. + - When using in CI, create a test user and use the test user's PAT and SSH key + for all Azure DevOps interactions. To grant the test user access in Azure + DevOps: + - Go to `Organization Settings` on the sidebar of the organization page. + - Under `General` > `Users`, click on `Add User` and input the user's email, + select `Access Level` of `Basic`. + - Go to `Security` > `Permissions`, click on the `User` tab. + - For the invited user, set the following permissions to `Allow`: + - `General: Create new project`. + - The user will get an email invitation and would need to create a Microsoft + account if they don't have one yet. **NOTE:** To use Service Principal (for example in CI environment), set the `ARM-*` variables in `.env`, source it and authenticate Azure CLI with: @@ -171,10 +182,18 @@ for the terraform variables [aggressive replacement in logs](https://github.com/google-github-actions/auth/blob/v1.1.0/docs/TROUBLESHOOTING.md#aggressive--replacement-in-logs) for more details. - Register [SSH Keys with Google Cloud](https://cloud.google.com/source-repositories/docs/authentication#ssh) - - Google Cloud supports these three SSH key types: RSA (only for keys with more than 2048 bits), ECDSA and ED25519 - - **Note:** Google doesn't allow an SSH key to be associated with a service account email address. Therefore, there has to be an actual - user that the SSH keys are registered to, and the email of this user will be passed to terraform through the `TF_VAR_gcp_email` - variable. + - Google Cloud supports these three SSH key types: RSA (only for keys with + more than 2048 bits), ECDSA and ED25519. + - The SSH user doesn't have to be a member of the GCP project. The terraform + setup will grant the user permissions to the repository. Visit + https://source.cloud.google.com, login or create a GCP account with the SSH + user's email address and add SSH keys in the account. Set this email as the + value for the environment variable `TF_VAR_gcp_email` in `.env` file to be + used as a terraform variable. + + **Note:** Google doesn't allow a SSH key to be associated with a service + account email address. Therefore, there has to be an actual user that the SSH + key is registered to. ### Permissions diff --git a/tests/integration/terraform/gcp/sourcerepo.tf b/tests/integration/terraform/gcp/sourcerepo.tf index 02a14928..4d7f5ee9 100644 --- a/tests/integration/terraform/gcp/sourcerepo.tf +++ b/tests/integration/terraform/gcp/sourcerepo.tf @@ -5,3 +5,22 @@ resource "google_sourcerepo_repository" "fleet-infra" { resource "google_sourcerepo_repository" "application" { name = "application-${random_pet.suffix.id}" } + +resource "google_sourcerepo_repository_iam_binding" "application_binding" { + project = google_sourcerepo_repository.application.project + repository = google_sourcerepo_repository.application.name + role = "roles/source.admin" + members = [ + "user:${var.gcp_email}", + ] +} + +resource "google_sourcerepo_repository_iam_binding" "fleet-infra_binding" { + project = google_sourcerepo_repository.fleet-infra.project + repository = google_sourcerepo_repository.fleet-infra.name + role = "roles/source.admin" + members = [ + "user:${var.gcp_email}", + ] +} + From c2e526ca57bce323537f64c682bf9982502abd44 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 31 Aug 2023 14:50:47 +0000 Subject: [PATCH 8/9] workflows/e2e-gcp: Fix secret variable typo Also update terraform config to add a description to the service account and a note about github organization repo. Signed-off-by: Sunny --- .github/workflows/e2e-gcp.yaml | 4 ++-- tests/integration/README.md | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-gcp.yaml b/.github/workflows/e2e-gcp.yaml index 99d7be26..5f68c639 100644 --- a/.github/workflows/e2e-gcp.yaml +++ b/.github/workflows/e2e-gcp.yaml @@ -76,8 +76,8 @@ jobs: TF_VAR_gcp_region: ${{ vars.TF_VAR_gcp_region }} TF_VAR_gcp_zone: ${{ vars.TF_VAR_gcp_zone }} TF_VAR_gcp_email: ${{ secrets.TF_VAR_gcp_email }} - TF_VAR_gcp_keyring: ${{ secret.TF_VAR_gcp_keyring }} - TF_VAR_gcp_crypto_key: ${{ secret.TF_VAR_gcp_crypto_key }} + TF_VAR_gcp_keyring: ${{ secrets.TF_VAR_gcp_keyring }} + TF_VAR_gcp_crypto_key: ${{ secrets.TF_VAR_gcp_crypto_key }} GITREPO_SSH_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_CONTENTS }} GITREPO_SSH_PUB_CONTENTS: ${{ secrets.GCP_GITREPO_SSH_PUB_CONTENTS }} run: | diff --git a/tests/integration/README.md b/tests/integration/README.md index 01c331b8..db6f8d14 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -218,6 +218,10 @@ variables using use the terraform configuration below. Please make sure all the requirements of gcp-gh-actions are followed before running it. +**NOTE:** When running the following for a repo under and organization, set the +environment variable `GITHUB_ORGANIZATION` if setting the `owner` in the +`github` provider doesn't work. + ```hcl provider "google" {} @@ -233,8 +237,9 @@ resource "tls_private_key" "privatekey" { module "gcp_gh_actions" { source = "git::https://github.com/fluxcd/test-infra.git//tf-modules/gcp/github-actions" - gcp_service_account_id = "flux2-e2e-test" - gcp_service_account_name = "flux2-e2e-test" + gcp_service_account_id = "flux2-e2e-test" + gcp_service_account_name = "flux2-e2e-test" + gcp_service_account_description = "For running fluxcd/flux2 e2e tests." gcp_roles = [ "roles/compute.instanceAdmin.v1", "roles/container.admin", From a9a67a27e7a2637c56db048f5d22fa616e327384 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 31 Aug 2023 16:00:53 +0000 Subject: [PATCH 9/9] workflows/e2e-azure: Disable new azure job There's no azure subscription to run the tests against at present. Signed-off-by: Sunny --- .github/workflows/e2e-azure.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-azure.yaml b/.github/workflows/e2e-azure.yaml index bd8573be..80e70dc5 100644 --- a/.github/workflows/e2e-azure.yaml +++ b/.github/workflows/e2e-azure.yaml @@ -71,7 +71,8 @@ jobs: defaults: run: working-directory: ./tests/integration - if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' + # This job is currently disabled. Remove the false check when Azure subscription is enabled. + if: false && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' steps: - name: CheckoutD uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0