Browse Source

Merge branch 'main' into feature/docs-versioning

Gustavo Fernandes de Carvalho 4 years ago
parent
commit
47a7425a2a
47 changed files with 1944 additions and 326 deletions
  1. 0 5
      .github/codecov.yml
  2. 8 10
      .github/workflows/ci.yml
  3. 45 2
      .github/workflows/e2e-managed.yml
  4. 6 4
      .github/workflows/e2e.yml
  5. 2 2
      .github/workflows/helm.yml
  6. 27 0
      apis/externalsecrets/v1alpha1/secretstore_fake_types.go
  7. 4 0
      apis/externalsecrets/v1alpha1/secretstore_types.go
  8. 49 0
      apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go
  9. 23 0
      deploy/crds/external-secrets.io_clustersecretstores.yaml
  10. 23 0
      deploy/crds/external-secrets.io_secretstores.yaml
  11. 76 0
      docs/contributing-process.md
  12. 26 0
      docs/provider-fake.md
  13. 18 0
      docs/snippets/fake-provider-es.yaml
  14. 9 0
      docs/snippets/fake-provider-secret.yaml
  15. 20 0
      docs/snippets/fake-provider-store.yaml
  16. 336 0
      docs/spec.md
  17. 1 1
      e2e/Makefile
  18. 97 0
      e2e/suite/aws/common.go
  19. 45 0
      e2e/suite/aws/parameterstore/parameterstore.go
  20. 85 0
      e2e/suite/aws/parameterstore/parameterstore_managed.go
  21. 165 0
      e2e/suite/aws/parameterstore/provider.go
  22. 14 76
      e2e/suite/aws/provider.go
  23. 0 0
      e2e/suite/aws/secretsmanager/secretsmanager.go
  24. 85 0
      e2e/suite/aws/secretsmanager/secretsmanager_managed.go
  25. 0 102
      e2e/suite/aws/secretsmanager_managed.go
  26. 66 0
      e2e/suite/azure/azure_cert.go
  27. 69 0
      e2e/suite/azure/azure_key.go
  28. 2 1
      e2e/suite/azure/azure.go
  29. 80 4
      e2e/suite/azure/provider.go
  30. 2 1
      e2e/suite/import.go
  31. 11 18
      go.mod
  32. 27 32
      go.sum
  33. 1 0
      hack/api-docs/mkdocs.yml
  34. 1 3
      pkg/controllers/externalsecret/externalsecret_controller_template.go
  35. 82 1
      pkg/controllers/externalsecret/externalsecret_controller_test.go
  36. 57 0
      pkg/controllers/externalsecret/metrics.go
  37. 7 1
      pkg/controllers/externalsecret/suite_test.go
  38. 51 58
      pkg/provider/fake/fake.go
  39. 194 0
      pkg/provider/fake/fake_test.go
  40. 1 1
      pkg/provider/gcp/secretmanager/fake/fake.go
  41. 1 1
      pkg/provider/gcp/secretmanager/secretsmanager.go
  42. 1 1
      pkg/provider/gcp/secretmanager/secretsmanager_workload_identity.go
  43. 1 1
      pkg/provider/gcp/secretmanager/secretsmanager_workload_identity_test.go
  44. 1 0
      pkg/provider/register/register.go
  45. 103 0
      pkg/provider/testing/fake/fake.go
  46. 3 1
      pkg/provider/webhook/webhook_test.go
  47. 19 0
      terraform/aws/modules/cluster/irsa.tf

+ 0 - 5
.github/codecov.yml

@@ -1,5 +0,0 @@
-ignore:
-- pkg/provider/**/fake
-coverage:
-  round: down
-  precision: 2

+ 8 - 10
.github/workflows/ci.yml

@@ -12,9 +12,7 @@ env:
   # Common versions
   GO_VERSION: '1.17'
   GOLANGCI_VERSION: 'v1.42.1'
-  # list of available versions: https://storage.googleapis.com/kubebuilder-tools
-  # TODO: 1.21.2 does not shut down properly with controller-runtime 0.9.2
-  KUBEBUILDER_TOOLS_VERSION: '1.20.2'
+  KUBERNETES_VERSION: '1.23.x'
   DOCKER_BUILDX_VERSION: 'v0.4.2'
 
   # Common users. We can't run a step 'if secrets.GHCR_USERNAME != ""' but we can run
@@ -164,22 +162,22 @@ jobs:
           key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
           restore-keys: ${{ runner.os }}-pkg-
 
-      - name: Add envtest binaries
-        run:  |
-          curl -sSLo envtest-bins.tar.gz "https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-${{env.KUBEBUILDER_TOOLS_VERSION}}-linux-amd64.tar.gz"
-          sudo mkdir -p /usr/local/kubebuilder
-          sudo tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz
+      - name: Add setup-envtest
+        run: |
+          go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
+          setup-envtest use ${{env.KUBERNETES_VERSION}} -p env --os $(go env GOOS) --arch $(go env GOARCH)
 
       - name: Cache envtest binaries
         uses: actions/cache@v2.1.7
         with:
-          path: /usr/local/kubebuilder
-          key: ${{ runner.os }}-kubebuilder-${{env.KUBEBUILDER_TOOLS_VERSION}}
+          path: /home/runner/.local/share/kubebuilder-envtest/
+          key: ${{ runner.os }}-kubebuilder-${{env.KUBERNETES_VERSION}}
           restore-keys: ${{ runner.os }}-kubebuilder-
 
       - name: Run Unit Tests
         run: |
           export KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT=true
+          source <(setup-envtest use ${{env.KUBERNETES_VERSION}} -p env --os $(go env GOOS) --arch $(go env GOARCH))
           make test
 
 

+ 45 - 2
.github/workflows/e2e-managed.yml

@@ -1,6 +1,5 @@
 # Run secret-dependent e2e tests only after /ok-to-test-managed approval
 on:
-  pull_request:
   repository_dispatch:
     types: [ok-to-test-managed-command]
 
@@ -49,6 +48,48 @@ jobs:
 
     steps:
 
+    # create new status check for this specific provider
+    - uses: actions/github-script@v1
+      if: ${{ always() }}
+      env:
+        number: ${{ github.event.client_payload.pull_request.number }}
+        provider: ${{ github.event.client_payload.slash_command.args.named.provider }}
+        job: ${{ github.job }}
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        script: |
+          const { data: pull } = await github.pulls.get({
+            ...context.repo,
+            pull_number: process.env.number
+          });
+          const ref = pull.head.sha;
+          console.log("\n\nPR sha: " + ref)
+          const { data: checks } = await github.checks.listForRef({
+            ...context.repo,
+            ref
+          });
+          const job_name = process.env.job + "-" + process.env.provider
+          console.log("\n\nPR CHECKS: " + checks)
+          const check = checks.check_runs.filter(c => c.name === job_name);
+          console.log("\n\nPR Filtered CHECK: " + check)
+          console.log(check)
+          if(check && check.length > 0){
+            const { data: result } = await github.checks.update({
+              ...context.repo,
+              check_run_id: check[0].id,
+              status: 'in_progress',
+            });
+            return result;
+          }
+          const { data: result } = await github.checks.create({
+            ...context.repo,
+            name: job_name,
+            head_sha: pull.head.sha,
+            status: 'in_progress',
+          });
+          return result;
+
+
     # Check out merge commit
     - name: Fork based /ok-to-test-managed checkout
       uses: actions/checkout@v2
@@ -164,6 +205,7 @@ jobs:
       if: ${{ always() }}
       env:
         number: ${{ github.event.client_payload.pull_request.number }}
+        provider: ${{ github.event.client_payload.slash_command.args.named.provider }}
         job: ${{ github.job }}
         # Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
         conclusion: ${{ job.status }}
@@ -180,8 +222,9 @@ jobs:
             ...context.repo,
             ref
           });
+          const job_name = process.env.job + "-" + process.env.provider
           console.log("\n\nPR CHECKS: " + checks)
-          const check = checks.check_runs.filter(c => c.name === process.env.job);
+          const check = checks.check_runs.filter(c => c.name === job_name);
           console.log("\n\nPR Filtered CHECK: " + check)
           console.log(check)
           const { data: result } = await github.checks.update({

+ 6 - 4
.github/workflows/e2e.yml

@@ -9,6 +9,8 @@ env:
   GO_VERSION: '1.17'
   GOLANGCI_VERSION: 'v1.33'
   DOCKER_BUILDX_VERSION: 'v0.4.2'
+  KIND_VERSION: 'v0.11.1'
+  KIND_IMAGE: 'kindest/node:v1.23.3'
 
   # Common users. We can't run a step 'if secrets.GHCR_USERNAME != ""' but we can run
   # a step 'if env.GHCR_USERNAME' != ""', so we copy these to succinctly test whether
@@ -71,9 +73,9 @@ jobs:
     - name: Setup kind
       uses: engineerd/setup-kind@v0.5.0
       with:
-        version: "v0.11.1"
+        version: ${{env.KIND_VERSION}}
         wait: 10m
-        node_image: kindest/node:v1.20.7
+        node_image: ${{env.KIND_IMAGE}}
         name: external-secrets
 
     - name: Setup Docker Buildx
@@ -134,9 +136,9 @@ jobs:
     - name: Setup kind
       uses: engineerd/setup-kind@v0.5.0
       with:
-        version: "v0.11.1"
+        version: ${{env.KIND_VERSION}}
         wait: 10m
-        node_image: kindest/node:v1.20.7
+        node_image: ${{env.KIND_IMAGE}}
         name: external-secrets
 
     - name: Setup Docker Buildx

+ 2 - 2
.github/workflows/helm.yml

@@ -26,7 +26,7 @@ jobs:
           make helm.generate
 
       - name: Set up Helm
-        uses: azure/setup-helm@v1.1
+        uses: azure/setup-helm@v2.0
         with:
           version: v3.4.2
 
@@ -62,7 +62,7 @@ jobs:
           git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
 
       - name: Set up Helm
-        uses: azure/setup-helm@v1.1
+        uses: azure/setup-helm@v2.0
         with:
           version: v3.4.2
 

+ 27 - 0
apis/externalsecrets/v1alpha1/secretstore_fake_types.go

@@ -0,0 +1,27 @@
+/*
+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 v1alpha1
+
+// FakeProvider configures a fake provider that returns static values.
+type FakeProvider struct {
+	Data []FakeProviderData `json:"data"`
+}
+
+type FakeProviderData struct {
+	Key      string            `json:"key"`
+	Value    string            `json:"value,omitempty"`
+	ValueMap map[string]string `json:"valueMap,omitempty"`
+	Version  string            `json:"version,omitempty"`
+}

+ 4 - 0
apis/externalsecrets/v1alpha1/secretstore_types.go

@@ -81,6 +81,10 @@ type SecretStoreProvider struct {
 	// Webhook configures this store to sync secrets using a generic templated webhook
 	// +optional
 	Webhook *WebhookProvider `json:"webhook,omitempty"`
+
+	// Fake configures a store with static key/value pairs
+	// +optional
+	Fake *FakeProvider `json:"fake,omitempty"`
 }
 
 type SecretStoreRetrySettings struct {

+ 49 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -600,6 +600,50 @@ func (in *ExternalSecretTemplateMetadata) DeepCopy() *ExternalSecretTemplateMeta
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FakeProvider) DeepCopyInto(out *FakeProvider) {
+	*out = *in
+	if in.Data != nil {
+		in, out := &in.Data, &out.Data
+		*out = make([]FakeProviderData, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeProvider.
+func (in *FakeProvider) DeepCopy() *FakeProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(FakeProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FakeProviderData) DeepCopyInto(out *FakeProviderData) {
+	*out = *in
+	if in.ValueMap != nil {
+		in, out := &in.ValueMap, &out.ValueMap
+		*out = make(map[string]string, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeProviderData.
+func (in *FakeProviderData) DeepCopy() *FakeProviderData {
+	if in == nil {
+		return nil
+	}
+	out := new(FakeProviderData)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *GCPSMAuth) DeepCopyInto(out *GCPSMAuth) {
 	*out = *in
 	if in.SecretRef != nil {
@@ -939,6 +983,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(WebhookProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Fake != nil {
+		in, out := &in.Fake, &out.Fake
+		*out = new(FakeProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.

+ 23 - 0
deploy/crds/external-secrets.io_clustersecretstores.yaml

@@ -380,6 +380,29 @@ spec:
                     required:
                     - vaultUrl
                     type: object
+                  fake:
+                    description: Fake configures a store with static key/value pairs
+                    properties:
+                      data:
+                        items:
+                          properties:
+                            key:
+                              type: string
+                            value:
+                              type: string
+                            valueMap:
+                              additionalProperties:
+                                type: string
+                              type: object
+                            version:
+                              type: string
+                          required:
+                          - key
+                          type: object
+                        type: array
+                    required:
+                    - data
+                    type: object
                   gcpsm:
                     description: GCPSM configures this store to sync secrets using
                       Google Cloud Platform Secret Manager provider

+ 23 - 0
deploy/crds/external-secrets.io_secretstores.yaml

@@ -380,6 +380,29 @@ spec:
                     required:
                     - vaultUrl
                     type: object
+                  fake:
+                    description: Fake configures a store with static key/value pairs
+                    properties:
+                      data:
+                        items:
+                          properties:
+                            key:
+                              type: string
+                            value:
+                              type: string
+                            valueMap:
+                              additionalProperties:
+                                type: string
+                              type: object
+                            version:
+                              type: string
+                          required:
+                          - key
+                          type: object
+                        type: array
+                    required:
+                    - data
+                    type: object
                   gcpsm:
                     description: GCPSM configures this store to sync secrets using
                       Google Cloud Platform Secret Manager provider

+ 76 - 0
docs/contributing-process.md

@@ -31,6 +31,82 @@ for the lifecycle of the PR: review, merging, ping on inactivity, close.
 We close pull requests or issues if there is no response from the author for
 a period of time. Feel free to reopen if you want to get back on it.
 
+### Triggering e2e tests
+
+We have an extensive set of e2e tests that test the integration with *real* cloud provider APIs.
+Maintainers must trigger these kind of tests manually for PRs that come from forked repositories. These tests run inside a `kind` cluster in the GitHub Actions runner:
+
+```
+/ok-to-test sha=xxxxxx
+```
+
+#### Executing e2e tests locally
+
+You have to prepare your shell environment with the necessary variables so the e2e test
+runner knows what credentials to use. See `e2e/run.sh` for the variables that are passed in.
+If you e.g. want to test AWS integration make sure set all `AWS_*` variables mentioned
+in that file.
+
+Use [ginkgo labels](https://onsi.github.io/ginkgo/#spec-labels) to select the tests
+you want to execute. You have to specify `!managed` to ensure that you do not
+run managed tests.
+
+```
+make test.e2e GINKGO_LABELS='gcp&&!managed'
+```
+
+#### Managed Kubernetes e2e tests
+
+There's another suite of e2e tests that integrate with managed Kuberentes offerings.
+They create real infrastructure at a cloud provider and deploy the controller
+into that environment.
+This is necessary to test the authentication integration
+([GCP Worklaod Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity),
+[EKS IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)...).
+
+These tests are time intensive (~20-45min) and must be triggered manually by
+a maintainer when a particular provider or authentication mechanism was changed:
+
+```
+/ok-to-test-managed sha=xxxxxx provider=aws
+# or
+/ok-to-test-managed sha=xxxxxx provider=gcp
+```
+
+Both tests can run in parallel. Once started they add a dynamic GitHub check `integration-managed-(gcp|aws)` to the PR that triggered the test.
+
+
+### Executing Managed Kubernetes e2e tests locally
+
+You have to prepare your shell environment with the necessary variables so the e2e
+test runner knows what credentials to use. See `.github/workflows/e2e-managed.yml`
+for the variables that are passed in. If you e.g. want to test AWS integration make
+sure set all variables containing `AWS_*` and `TF_VAR_AWS_*` mentioned in that file.
+
+Then execute `tf.apply.aws` or `tf.apply.gcp` to create the infrastructure.
+
+```
+make tf.apply.aws
+```
+
+Then run the `managed` testsuite. You will need push permissions to the external-secrets ghcr repository. You can set `IMAGE_REGISTRY` to control which image registry is used to store the controller and e2e test images in.
+
+You also have to setup a proper Kubeconfig so the e2e test pod gets deployed into the managed cluster.
+
+```
+aws eks update-kubeconfig --name ${AWS_CLUSTER_NAME}
+or
+gcloud container clusters get-credentials ${GCP_GKE_CLUSTER} --region europe-west1-b
+```
+
+Use [ginkgo labels](https://onsi.github.io/ginkgo/#spec-labels) to select the tests
+you want to execute.
+
+```
+# you may have to set IMAGE_REGISTRY=docker.io/your-user/external-secrets
+make test.e2e.managed GINKGO_LABELS='gcp'
+```
+
 ## Proposal Process
 Before we introduce significant changes to the project we want to gather feedback
 from the community to ensure that we progress in the right direction before we

+ 26 - 0
docs/provider-fake.md

@@ -0,0 +1,26 @@
+We provide a `fake` implementation to help with testing. This provider returns static key/value pairs and nothing else.
+To use the `fake` provider simply create a `SecretStore` or `ClusterSecretStore` and configure it like in the following example:
+
+!!! note inline end
+    The provider returns static data configured in `value` or `valueMap`. You can define a `version`, too. If set the `remoteRef` from an ExternalSecret must match otherwise no value is returned.
+
+```yaml
+{% include 'fake-provider-store.yaml' %}
+```
+
+Please note that `value` is intended for exclusive use with `data` and `valueMap` for `dataFrom`.
+Here is an example `ExternalSecret` that displays this behavior:
+
+!!! warning inline end
+    This provider supports specifying different `data[].version` configurations. However, `data[].property` is ignored.
+
+```yaml
+{% include 'fake-provider-es.yaml' %}
+```
+
+This results in the following secret:
+
+
+```yaml
+{% include 'fake-provider-secret.yaml' %}
+```

+ 18 - 0
docs/snippets/fake-provider-es.yaml

@@ -0,0 +1,18 @@
+apiVersion: external-secrets.io/v1alpha1
+kind: ExternalSecret
+metadata:
+  name: example
+spec:
+  refreshInterval: 1h
+  secretStoreRef:
+    name: fake
+    kind: ClusterSecretStore
+  target:
+    name: secret-to-be-created
+  data:
+  - secretKey: foo_bar
+    remoteRef:
+      key: /foo/bar
+      version: v1
+  dataFrom:
+  - key: /foo/baz

+ 9 - 0
docs/snippets/fake-provider-secret.yaml

@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: secret-to-be-created
+  namespace: default
+data:
+  foo_bar: SEVMTE8x # HELLO1  (via data)
+  foo: ZXhhbXBsZQ== # example (via dataFrom)
+  other: dGhpbmc=   # thing   (via dataFrom)

+ 20 - 0
docs/snippets/fake-provider-store.yaml

@@ -0,0 +1,20 @@
+apiVersion: external-secrets.io/v1alpha1
+kind: ClusterSecretStore
+metadata:
+  name: fake
+spec:
+  provider:
+    fake:
+      data:
+      - key: "/foo/bar"
+        value: "HELLO1"
+        version: "v1"
+      - key: "/foo/bar"
+        value: "HELLO2"
+        version: "v2"
+      - key: "/foo/baz"
+        valueMap:
+          foo: example
+          other: thing
+
+

+ 336 - 0
docs/spec.md

@@ -714,6 +714,237 @@ string
 <td></td>
 </tr></tbody>
 </table>
+<h3 id="external-secrets.io/v1alpha1.ClusterExternalSecret">ClusterExternalSecret
+</h3>
+<p>
+<p>ClusterExternalSecret is the Schema for the clusterexternalsecrets API.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>metadata</code></br>
+<em>
+<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta">
+Kubernetes meta/v1.ObjectMeta
+</a>
+</em>
+</td>
+<td>
+Refer to the Kubernetes API documentation for the fields of the
+<code>metadata</code> field.
+</td>
+</tr>
+<tr>
+<td>
+<code>spec</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecretSpec">
+ClusterExternalSecretSpec
+</a>
+</em>
+</td>
+<td>
+<br/>
+<br/>
+<table>
+<tr>
+<td>
+<code>externalSecretSpec</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.ExternalSecretSpec">
+ExternalSecretSpec
+</a>
+</em>
+</td>
+<td>
+<p>The spec for the ExternalSecrets to be created</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>externalSecretName</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>The name of the external secrets to be created defaults to the name of the ClusterExternalSecret</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>namespaceSelector</code></br>
+<em>
+<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
+Kubernetes meta/v1.LabelSelector
+</a>
+</em>
+</td>
+<td>
+<p>The labels to select by to find the Namespaces to create the ExternalSecrets in.</p>
+</td>
+</tr>
+</table>
+</td>
+</tr>
+<tr>
+<td>
+<code>status</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecretStatus">
+ClusterExternalSecretStatus
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1alpha1.ClusterExternalSecretConditionType">ClusterExternalSecretConditionType
+(<code>string</code> alias)</p></h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecretStatus">ClusterExternalSecretStatus</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Value</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody><tr><td><p>&#34;NotReady&#34;</p></td>
+<td></td>
+</tr><tr><td><p>&#34;PartiallyReady&#34;</p></td>
+<td></td>
+</tr><tr><td><p>&#34;Ready&#34;</p></td>
+<td></td>
+</tr></tbody>
+</table>
+<h3 id="external-secrets.io/v1alpha1.ClusterExternalSecretSpec">ClusterExternalSecretSpec
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecret">ClusterExternalSecret</a>)
+</p>
+<p>
+<p>ClusterExternalSecretSpec defines the desired state of ClusterExternalSecret.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>externalSecretSpec</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.ExternalSecretSpec">
+ExternalSecretSpec
+</a>
+</em>
+</td>
+<td>
+<p>The spec for the ExternalSecrets to be created</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>externalSecretName</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>The name of the external secrets to be created defaults to the name of the ClusterExternalSecret</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>namespaceSelector</code></br>
+<em>
+<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselector-v1-meta">
+Kubernetes meta/v1.LabelSelector
+</a>
+</em>
+</td>
+<td>
+<p>The labels to select by to find the Namespaces to create the ExternalSecrets in.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1alpha1.ClusterExternalSecretStatus">ClusterExternalSecretStatus
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecret">ClusterExternalSecret</a>)
+</p>
+<p>
+<p>ClusterExternalSecretStatus defines the observed state of ClusterExternalSecret.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>type</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecretConditionType">
+ClusterExternalSecretConditionType
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>status</code></br>
+<em>
+<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#conditionstatus-v1-core">
+Kubernetes core/v1.ConditionStatus
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>externalSecretStatuses</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.ExternalSecretStatus">
+[]ExternalSecretStatus
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1alpha1.ClusterSecretStore">ClusterSecretStore
 </h3>
 <p>
@@ -1084,6 +1315,7 @@ string
 </h3>
 <p>
 (<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecretSpec">ClusterExternalSecretSpec</a>, 
 <a href="#external-secrets.io/v1alpha1.ExternalSecret">ExternalSecret</a>)
 </p>
 <p>
@@ -1171,6 +1403,7 @@ If multiple entries are specified, the Secret keys are merged in the specified o
 </h3>
 <p>
 (<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.ClusterExternalSecretStatus">ClusterExternalSecretStatus</a>, 
 <a href="#external-secrets.io/v1alpha1.ExternalSecret">ExternalSecret</a>)
 </p>
 <p>
@@ -1486,6 +1719,95 @@ map[string]string
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1alpha1.FakeProvider">FakeProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>FakeProvider configures a fake provider that returns static values</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>data</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.FakeProviderData">
+[]FakeProviderData
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1alpha1.FakeProviderData">FakeProviderData
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1alpha1.FakeProvider">FakeProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>key</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>value</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>valueMap</code></br>
+<em>
+map[string]string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>version</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1alpha1.GCPSMAuth">GCPSMAuth
 </h3>
 <p>
@@ -2310,6 +2632,20 @@ WebhookProvider
 <p>Webhook configures this store to sync secrets using a generic templated webhook</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>fake</code></br>
+<em>
+<a href="#external-secrets.io/v1alpha1.FakeProvider">
+FakeProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Fake configures a store with static key/value pairs</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1alpha1.SecretStoreRef">SecretStoreRef

+ 1 - 1
e2e/Makefile

@@ -2,7 +2,7 @@ MAKEFLAGS   += --warn-undefined-variables
 SHELL       := /bin/bash
 .SHELLFLAGS := -euo pipefail -c
 
-KIND_IMG       = "kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6"
+KIND_IMG       = "kindest/node:v1.23.3@sha256:0df8215895129c0d3221cda19847d1296c4f29ec93487339149333bd9d899e5a"
 BUILD_ARGS     ?=
 
 export E2E_IMAGE_REGISTRY ?= ghcr.io/external-secrets/external-secrets-e2e

+ 97 - 0
e2e/suite/aws/common.go

@@ -0,0 +1,97 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package common
+
+import (
+	"context"
+
+	// nolint
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+)
+
+const (
+	WithReferencedIRSA          = "with referenced IRSA"
+	WithMountedIRSA             = "with mounted IRSA"
+	StaticCredentialsSecretName = "provider-secret"
+)
+
+func ReferencedIRSAStoreName(f *framework.Framework) string {
+	return "irsa-ref-" + f.Namespace.Name
+}
+
+func MountedIRSAStoreName(f *framework.Framework) string {
+	return "irsa-mounted-" + f.Namespace.Name
+}
+
+func UseClusterSecretStore(tc *framework.TestCase) {
+	tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.ClusterSecretStoreKind
+	tc.ExternalSecret.Spec.SecretStoreRef.Name = ReferencedIRSAStoreName(tc.Framework)
+}
+
+func UseMountedIRSAStore(tc *framework.TestCase) {
+	tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.SecretStoreKind
+	tc.ExternalSecret.Spec.SecretStoreRef.Name = MountedIRSAStoreName(tc.Framework)
+}
+
+// StaticStore is namespaced and references
+// static credentials from a secret.
+func SetupStaticStore(f *framework.Framework, kid, sak, region string, serviceType esv1alpha1.AWSServiceType) {
+	awsCreds := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      StaticCredentialsSecretName,
+			Namespace: f.Namespace.Name,
+		},
+		StringData: map[string]string{
+			"kid": kid,
+			"sak": sak,
+		},
+	}
+	err := f.CRClient.Create(context.Background(), awsCreds)
+	Expect(err).ToNot(HaveOccurred())
+
+	secretStore := &esv1alpha1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      f.Namespace.Name,
+			Namespace: f.Namespace.Name,
+		},
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				AWS: &esv1alpha1.AWSProvider{
+					Service: serviceType,
+					Region:  region,
+					Auth: esv1alpha1.AWSAuth{
+						SecretRef: &esv1alpha1.AWSAuthSecretRef{
+							AccessKeyID: esmetav1.SecretKeySelector{
+								Name: StaticCredentialsSecretName,
+								Key:  "kid",
+							},
+							SecretAccessKey: esmetav1.SecretKeySelector{
+								Name: StaticCredentialsSecretName,
+								Key:  "sak",
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	err = f.CRClient.Create(context.Background(), secretStore)
+	Expect(err).ToNot(HaveOccurred())
+}

+ 45 - 0
e2e/suite/aws/parameterstore/parameterstore.go

@@ -0,0 +1,45 @@
+/*
+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 aws
+
+import (
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/suite/common"
+)
+
+var _ = Describe("[aws] ", Label("aws", "parameterstore"), func() {
+	f := framework.New("eso-aws-ps")
+	prov := NewFromEnv(f)
+
+	DescribeTable("sync secrets",
+		framework.TableFunc(f,
+			prov),
+		Entry(common.SimpleDataSync(f)),
+		Entry(common.NestedJSONWithGJSON(f)),
+		Entry(common.JSONDataFromSync(f)),
+		Entry(common.JSONDataWithProperty(f)),
+		Entry(common.JSONDataWithTemplate(f)),
+		Entry(common.DockerJSONConfig(f)),
+		Entry(common.DataPropertyDockerconfigJSON(f)),
+		Entry(common.SSHKeySync(f)),
+		Entry(common.SSHKeySyncDataProperty(f)),
+		Entry(common.SyncWithoutTargetName(f)),
+		Entry(common.JSONDataWithoutTargetName(f)),
+	)
+})

+ 85 - 0
e2e/suite/aws/parameterstore/parameterstore_managed.go

@@ -0,0 +1,85 @@
+/*
+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 aws
+
+import (
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/framework/addon"
+	awscommon "github.com/external-secrets/external-secrets/e2e/suite/aws"
+	"github.com/external-secrets/external-secrets/e2e/suite/common"
+)
+
+// here we use the global eso instance
+// that uses the service account in the default namespace
+// which was created by terraform.
+var _ = Describe("[awsmanaged] IRSA via referenced service account", Label("aws", "parameterstore", "managed"), func() {
+	f := framework.New("eso-aws-ps-managed")
+	prov := NewFromEnv(f)
+
+	// nolint
+	DescribeTable("sync secrets",
+		framework.TableFunc(f,
+			prov),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SimpleDataSync, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.NestedJSONWithGJSON, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataFromSync, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataWithProperty, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataWithTemplate, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.DockerJSONConfig, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.DataPropertyDockerconfigJSON, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SSHKeySync, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SSHKeySyncDataProperty, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SyncWithoutTargetName, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataWithoutTargetName, awscommon.UseClusterSecretStore),
+	)
+})
+
+// here we create a central eso instance in the default namespace
+// that mounts the service account which was created by terraform.
+var _ = Describe("[awsmanaged] with mounted IRSA", Label("aws", "parameterstore", "managed"), func() {
+	f := framework.New("eso-aws-ps-irsa-managed")
+	prov := NewFromEnv(f)
+
+	// each test case gets its own ESO instance
+	BeforeEach(func() {
+		f.Install(addon.NewESO(
+			addon.WithControllerClass(f.BaseName),
+			addon.WithServiceAccount(prov.ServiceAccountName),
+			addon.WithReleaseName(f.Namespace.Name),
+			addon.WithNamespace("default"),
+		))
+	})
+
+	// nolint
+	DescribeTable("sync secrets",
+		framework.TableFunc(f,
+			prov),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SimpleDataSync, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.NestedJSONWithGJSON, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataFromSync, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataWithProperty, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataWithTemplate, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.DockerJSONConfig, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.DataPropertyDockerconfigJSON, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SSHKeySync, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SSHKeySyncDataProperty, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SyncWithoutTargetName, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataWithoutTargetName, awscommon.UseMountedIRSAStore),
+	)
+})

+ 165 - 0
e2e/suite/aws/parameterstore/provider.go

@@ -0,0 +1,165 @@
+/*
+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 aws
+
+import (
+	"context"
+	"os"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/credentials"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/ssm"
+
+	//nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	// nolint
+	. "github.com/onsi/gomega"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/framework/log"
+	common "github.com/external-secrets/external-secrets/e2e/suite/aws"
+)
+
+type Provider struct {
+	ServiceAccountName      string
+	ServiceAccountNamespace string
+
+	region    string
+	client    *ssm.SSM
+	framework *framework.Framework
+}
+
+func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace string) *Provider {
+	sess, err := session.NewSessionWithOptions(session.Options{
+		Config: aws.Config{
+			Credentials: credentials.NewStaticCredentials(kid, sak, ""),
+			Region:      aws.String(region),
+		},
+	})
+	if err != nil {
+		Fail(err.Error())
+	}
+	sm := ssm.New(sess)
+	prov := &Provider{
+		ServiceAccountName:      saName,
+		ServiceAccountNamespace: saNamespace,
+		region:                  region,
+		client:                  sm,
+		framework:               f,
+	}
+
+	BeforeEach(func() {
+		common.SetupStaticStore(f, kid, sak, region, esv1alpha1.AWSServiceParameterStore)
+		prov.SetupReferencedIRSAStore()
+		prov.SetupMountedIRSAStore()
+	})
+
+	AfterEach(func() {
+		// Cleanup ClusterSecretStore
+		err := prov.framework.CRClient.Delete(context.Background(), &esv1alpha1.ClusterSecretStore{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: common.ReferencedIRSAStoreName(f),
+			},
+		})
+		Expect(err).ToNot(HaveOccurred())
+	})
+
+	return prov
+}
+
+func NewFromEnv(f *framework.Framework) *Provider {
+	kid := os.Getenv("AWS_ACCESS_KEY_ID")
+	sak := os.Getenv("AWS_SECRET_ACCESS_KEY")
+	region := "eu-west-1"
+	saName := os.Getenv("AWS_SA_NAME")
+	saNamespace := os.Getenv("AWS_SA_NAMESPACE")
+	return NewProvider(f, kid, sak, region, saName, saNamespace)
+}
+
+// CreateSecret creates a secret at the provider.
+func (s *Provider) CreateSecret(key, val string) {
+	_, err := s.client.PutParameter(&ssm.PutParameterInput{
+		Name:     aws.String(key),
+		Value:    aws.String(val),
+		DataType: aws.String("text"),
+		Type:     aws.String("String"),
+	})
+	Expect(err).ToNot(HaveOccurred())
+}
+
+// DeleteSecret deletes a secret at the provider.
+func (s *Provider) DeleteSecret(key string) {
+	_, err := s.client.DeleteParameter(&ssm.DeleteParameterInput{
+		Name: aws.String(key),
+	})
+	Expect(err).ToNot(HaveOccurred())
+}
+
+// MountedIRSAStore is a SecretStore without auth config
+// ESO relies on the pod-mounted ServiceAccount when using this store.
+func (s *Provider) SetupMountedIRSAStore() {
+	secretStore := &esv1alpha1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      common.MountedIRSAStoreName(s.framework),
+			Namespace: s.framework.Namespace.Name,
+		},
+		Spec: esv1alpha1.SecretStoreSpec{
+			Provider: &esv1alpha1.SecretStoreProvider{
+				AWS: &esv1alpha1.AWSProvider{
+					Service: esv1alpha1.AWSServiceParameterStore,
+					Region:  s.region,
+					Auth:    esv1alpha1.AWSAuth{},
+				},
+			},
+		},
+	}
+	err := s.framework.CRClient.Create(context.Background(), secretStore)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+// ReferncedIRSAStore is a ClusterStore
+// that references a (IRSA-) ServiceAccount in the default namespace.
+func (s *Provider) SetupReferencedIRSAStore() {
+	log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name)
+	secretStore := &esv1alpha1.ClusterSecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: common.ReferencedIRSAStoreName(s.framework),
+		},
+	}
+	_, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error {
+		secretStore.Spec.Provider = &esv1alpha1.SecretStoreProvider{
+			AWS: &esv1alpha1.AWSProvider{
+				Service: esv1alpha1.AWSServiceParameterStore,
+				Region:  s.region,
+				Auth: esv1alpha1.AWSAuth{
+					JWTAuth: &esv1alpha1.AWSJWTAuth{
+						ServiceAccountRef: &esmetav1.ServiceAccountSelector{
+							Name:      s.ServiceAccountName,
+							Namespace: &s.ServiceAccountNamespace,
+						},
+					},
+				},
+			},
+		}
+		return nil
+	})
+	Expect(err).ToNot(HaveOccurred())
+}

+ 14 - 76
e2e/suite/aws/provider.go

@@ -29,7 +29,6 @@ import (
 
 	// nolint
 	. "github.com/onsi/gomega"
-	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 
@@ -37,24 +36,19 @@ import (
 	esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
 	"github.com/external-secrets/external-secrets/e2e/framework/log"
+	common "github.com/external-secrets/external-secrets/e2e/suite/aws"
 )
 
-type SMProvider struct {
+type Provider struct {
 	ServiceAccountName      string
 	ServiceAccountNamespace string
 
-	kid       string
-	sak       string
 	region    string
 	client    *secretsmanager.SecretsManager
 	framework *framework.Framework
 }
 
-const (
-	staticCredentialsSecretName = "provider-secret"
-)
-
-func NewSMProvider(f *framework.Framework, kid, sak, region, saName, saNamespace string) *SMProvider {
+func NewProvider(f *framework.Framework, kid, sak, region, saName, saNamespace string) *Provider {
 	sess, err := session.NewSessionWithOptions(session.Options{
 		Config: aws.Config{
 			Credentials: credentials.NewStaticCredentials(kid, sak, ""),
@@ -65,18 +59,16 @@ func NewSMProvider(f *framework.Framework, kid, sak, region, saName, saNamespace
 		Fail(err.Error())
 	}
 	sm := secretsmanager.New(sess)
-	prov := &SMProvider{
+	prov := &Provider{
 		ServiceAccountName:      saName,
 		ServiceAccountNamespace: saNamespace,
-		kid:                     kid,
-		sak:                     sak,
 		region:                  region,
 		client:                  sm,
 		framework:               f,
 	}
 
 	BeforeEach(func() {
-		prov.SetupStaticStore()
+		common.SetupStaticStore(f, kid, sak, region, esv1alpha1.AWSServiceSecretsManager)
 		prov.SetupReferencedIRSAStore()
 		prov.SetupMountedIRSAStore()
 	})
@@ -85,7 +77,7 @@ func NewSMProvider(f *framework.Framework, kid, sak, region, saName, saNamespace
 		// Cleanup ClusterSecretStore
 		err := prov.framework.CRClient.Delete(context.Background(), &esv1alpha1.ClusterSecretStore{
 			ObjectMeta: metav1.ObjectMeta{
-				Name: prov.ReferencedIRSAStoreName(),
+				Name: common.ReferencedIRSAStoreName(f),
 			},
 		})
 		Expect(err).ToNot(HaveOccurred())
@@ -94,17 +86,17 @@ func NewSMProvider(f *framework.Framework, kid, sak, region, saName, saNamespace
 	return prov
 }
 
-func NewFromEnv(f *framework.Framework) *SMProvider {
+func NewFromEnv(f *framework.Framework) *Provider {
 	kid := os.Getenv("AWS_ACCESS_KEY_ID")
 	sak := os.Getenv("AWS_SECRET_ACCESS_KEY")
 	region := "eu-west-1"
 	saName := os.Getenv("AWS_SA_NAME")
 	saNamespace := os.Getenv("AWS_SA_NAMESPACE")
-	return NewSMProvider(f, kid, sak, region, saName, saNamespace)
+	return NewProvider(f, kid, sak, region, saName, saNamespace)
 }
 
 // CreateSecret creates a secret at the provider.
-func (s *SMProvider) CreateSecret(key, val string) {
+func (s *Provider) CreateSecret(key, val string) {
 	// we re-use some secret names throughout our test suite
 	// due to the fact that there is a short delay before the secret is actually deleted
 	// we have to retry creating the secret
@@ -129,7 +121,7 @@ func (s *SMProvider) CreateSecret(key, val string) {
 // DeleteSecret deletes a secret at the provider.
 // There may be a short delay between calling this function
 // and the removal of the secret on the provider side.
-func (s *SMProvider) DeleteSecret(key string) {
+func (s *Provider) DeleteSecret(key string) {
 	log.Logf("deleting secret %s", key)
 	_, err := s.client.DeleteSecret(&secretsmanager.DeleteSecretInput{
 		SecretId:                   aws.String(key),
@@ -140,10 +132,10 @@ func (s *SMProvider) DeleteSecret(key string) {
 
 // MountedIRSAStore is a SecretStore without auth config
 // ESO relies on the pod-mounted ServiceAccount when using this store.
-func (s *SMProvider) SetupMountedIRSAStore() {
+func (s *Provider) SetupMountedIRSAStore() {
 	secretStore := &esv1alpha1.SecretStore{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      s.MountedIRSAStoreName(),
+			Name:      common.MountedIRSAStoreName(s.framework),
 			Namespace: s.framework.Namespace.Name,
 		},
 		Spec: esv1alpha1.SecretStoreSpec{
@@ -160,17 +152,13 @@ func (s *SMProvider) SetupMountedIRSAStore() {
 	Expect(err).ToNot(HaveOccurred())
 }
 
-func (s *SMProvider) MountedIRSAStoreName() string {
-	return "irsa-mounted-" + s.framework.Namespace.Name
-}
-
 // ReferncedIRSAStore is a ClusterStore
 // that references a (IRSA-) ServiceAccount in the default namespace.
-func (s *SMProvider) SetupReferencedIRSAStore() {
+func (s *Provider) SetupReferencedIRSAStore() {
 	log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name)
 	secretStore := &esv1alpha1.ClusterSecretStore{
 		ObjectMeta: metav1.ObjectMeta{
-			Name: s.ReferencedIRSAStoreName(),
+			Name: common.ReferencedIRSAStoreName(s.framework),
 		},
 	}
 	_, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error {
@@ -192,53 +180,3 @@ func (s *SMProvider) SetupReferencedIRSAStore() {
 	})
 	Expect(err).ToNot(HaveOccurred())
 }
-
-func (s *SMProvider) ReferencedIRSAStoreName() string {
-	return "irsa-ref-" + s.framework.Namespace.Name
-}
-
-// StaticStore is namespaced and references
-// static credentials from a secret.
-func (s *SMProvider) SetupStaticStore() {
-	awsCreds := &v1.Secret{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      staticCredentialsSecretName,
-			Namespace: s.framework.Namespace.Name,
-		},
-		StringData: map[string]string{
-			"kid": s.kid,
-			"sak": s.sak,
-		},
-	}
-	err := s.framework.CRClient.Create(context.Background(), awsCreds)
-	Expect(err).ToNot(HaveOccurred())
-
-	secretStore := &esv1alpha1.SecretStore{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      s.framework.Namespace.Name,
-			Namespace: s.framework.Namespace.Name,
-		},
-		Spec: esv1alpha1.SecretStoreSpec{
-			Provider: &esv1alpha1.SecretStoreProvider{
-				AWS: &esv1alpha1.AWSProvider{
-					Service: esv1alpha1.AWSServiceSecretsManager,
-					Region:  s.region,
-					Auth: esv1alpha1.AWSAuth{
-						SecretRef: &esv1alpha1.AWSAuthSecretRef{
-							AccessKeyID: esmetav1.SecretKeySelector{
-								Name: staticCredentialsSecretName,
-								Key:  "kid",
-							},
-							SecretAccessKey: esmetav1.SecretKeySelector{
-								Name: staticCredentialsSecretName,
-								Key:  "sak",
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	err = s.framework.CRClient.Create(context.Background(), secretStore)
-	Expect(err).ToNot(HaveOccurred())
-}

e2e/suite/aws/secretsmanager.go → e2e/suite/aws/secretsmanager/secretsmanager.go


+ 85 - 0
e2e/suite/aws/secretsmanager/secretsmanager_managed.go

@@ -0,0 +1,85 @@
+/*
+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 aws
+
+import (
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/framework/addon"
+	awscommon "github.com/external-secrets/external-secrets/e2e/suite/aws"
+	"github.com/external-secrets/external-secrets/e2e/suite/common"
+)
+
+// here we use the global eso instance
+// that uses the service account in the default namespace
+// which was created by terraform.
+var _ = Describe("[awsmanaged] IRSA via referenced service account", Label("aws", "secretsmanager", "managed"), func() {
+	f := framework.New("eso-aws-managed")
+	prov := NewFromEnv(f)
+
+	// nolint
+	DescribeTable("sync secretsmanager secrets",
+		framework.TableFunc(f,
+			prov),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SimpleDataSync, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.NestedJSONWithGJSON, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataFromSync, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataWithProperty, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataWithTemplate, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.DockerJSONConfig, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.DataPropertyDockerconfigJSON, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SSHKeySync, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SSHKeySyncDataProperty, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.SyncWithoutTargetName, awscommon.UseClusterSecretStore),
+		framework.Compose(awscommon.WithReferencedIRSA, f, common.JSONDataWithoutTargetName, awscommon.UseClusterSecretStore),
+	)
+})
+
+// here we create a central eso instance in the default namespace
+// that mounts the service account which was created by terraform.
+var _ = Describe("[awsmanaged] with mounted IRSA", Label("aws", "secretsmanager", "managed"), func() {
+	f := framework.New("eso-aws-managed")
+	prov := NewFromEnv(f)
+
+	// each test case gets its own ESO instance
+	BeforeEach(func() {
+		f.Install(addon.NewESO(
+			addon.WithControllerClass(f.BaseName),
+			addon.WithServiceAccount(prov.ServiceAccountName),
+			addon.WithReleaseName(f.Namespace.Name),
+			addon.WithNamespace("default"),
+		))
+	})
+
+	// nolint
+	DescribeTable("sync secretsmanager secrets",
+		framework.TableFunc(f,
+			prov),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SimpleDataSync, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.NestedJSONWithGJSON, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataFromSync, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataWithProperty, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataWithTemplate, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.DockerJSONConfig, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.DataPropertyDockerconfigJSON, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SSHKeySync, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SSHKeySyncDataProperty, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.SyncWithoutTargetName, awscommon.UseMountedIRSAStore),
+		framework.Compose(awscommon.WithMountedIRSA, f, common.JSONDataWithoutTargetName, awscommon.UseMountedIRSAStore),
+	)
+})

+ 0 - 102
e2e/suite/aws/secretsmanager_managed.go

@@ -1,102 +0,0 @@
-/*
-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 aws
-
-import (
-
-	// nolint
-	. "github.com/onsi/ginkgo/v2"
-
-	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
-	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/framework/addon"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
-)
-
-const (
-	withReferencedIRSA = "with referenced IRSA"
-	withMountedIRSA    = "with mounted IRSA"
-)
-
-// here we use the global eso instance
-// that uses the service account in the default namespace
-// which was created by terraform.
-var _ = Describe("[awsmanaged] IRSA via referenced service account", Label("aws", "secretsmanager", "managed"), func() {
-	f := framework.New("eso-aws-managed")
-	prov := NewFromEnv(f)
-
-	DescribeTable("sync secrets",
-		framework.TableFunc(f,
-			prov),
-		framework.Compose(withReferencedIRSA, f, common.SimpleDataSync, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.NestedJSONWithGJSON, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.JSONDataFromSync, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.JSONDataWithProperty, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.JSONDataWithTemplate, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.DockerJSONConfig, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.DataPropertyDockerconfigJSON, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.SSHKeySync, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.SSHKeySyncDataProperty, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.SyncWithoutTargetName, useClusterSecretStore(prov)),
-		framework.Compose(withReferencedIRSA, f, common.JSONDataWithoutTargetName, useClusterSecretStore(prov)),
-	)
-})
-
-// here we create a central eso instance in the default namespace
-// that mounts the service account which was created by terraform.
-var _ = Describe("[awsmanaged] with mounted IRSA", Label("aws", "secretsmanager", "managed"), func() {
-	f := framework.New("eso-aws-managed")
-	prov := NewFromEnv(f)
-
-	// each test case gets its own ESO instance
-	BeforeEach(func() {
-		f.Install(addon.NewESO(
-			addon.WithControllerClass(f.BaseName),
-			addon.WithServiceAccount(prov.ServiceAccountName),
-			addon.WithReleaseName(f.Namespace.Name),
-			addon.WithNamespace("default"),
-		))
-	})
-
-	DescribeTable("sync secrets",
-		framework.TableFunc(f,
-			prov),
-		framework.Compose(withMountedIRSA, f, common.SimpleDataSync, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.NestedJSONWithGJSON, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.JSONDataFromSync, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.JSONDataWithProperty, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.JSONDataWithTemplate, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.DockerJSONConfig, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.DataPropertyDockerconfigJSON, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.SSHKeySync, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.SSHKeySyncDataProperty, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.SyncWithoutTargetName, useMountedIRSAStore(prov)),
-		framework.Compose(withMountedIRSA, f, common.JSONDataWithoutTargetName, useMountedIRSAStore(prov)),
-	)
-})
-
-func useClusterSecretStore(prov *SMProvider) func(*framework.TestCase) {
-	return func(tc *framework.TestCase) {
-		tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.ClusterSecretStoreKind
-		tc.ExternalSecret.Spec.SecretStoreRef.Name = prov.ReferencedIRSAStoreName()
-	}
-}
-
-func useMountedIRSAStore(prov *SMProvider) func(*framework.TestCase) {
-	return func(tc *framework.TestCase) {
-		tc.ExternalSecret.Spec.SecretStoreRef.Kind = esv1alpha1.SecretStoreKind
-		tc.ExternalSecret.Spec.SecretStoreRef.Name = prov.MountedIRSAStoreName()
-	}
-}

+ 66 - 0
e2e/suite/azure/azure_cert.go

@@ -0,0 +1,66 @@
+/*
+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.
+limitations under the License.
+*/
+package azure
+
+import (
+	"fmt"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+	v1 "k8s.io/api/core/v1"
+
+	// nolint
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+)
+
+// azure keyvault type=cert should get a certificate from the api.
+var _ = Describe("[azure]", Label("azure", "keyvault", "cert"), func() {
+	f := framework.New("eso-azure-certtype")
+	prov := newFromEnv(f)
+	var certBytes []byte
+	var certName string
+
+	BeforeEach(func() {
+		certName = fmt.Sprintf("%s-%s", f.Namespace.Name, "certtest")
+		prov.CreateCertificate(certName)
+		certBytes = prov.GetCertificate(certName)
+	})
+
+	AfterEach(func() {
+		prov.DeleteCertificate(certName)
+	})
+
+	ff := framework.TableFunc(f, prov)
+	It("should sync keyvault objects with type=cert", func() {
+		ff(func(tc *framework.TestCase) {
+			secretKey := "azkv-cert"
+
+			tc.ExpectedSecret = &v1.Secret{
+				Type: v1.SecretTypeOpaque,
+				Data: map[string][]byte{
+					secretKey: certBytes,
+				},
+			}
+			tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
+				{
+					SecretKey: secretKey,
+					RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+						Key: "cert/" + certName,
+					},
+				},
+			}
+		})
+	})
+
+})

+ 69 - 0
e2e/suite/azure/azure_key.go

@@ -0,0 +1,69 @@
+/*
+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.
+limitations under the License.
+*/
+package azure
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+	v1 "k8s.io/api/core/v1"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+)
+
+// azure keyvault type=key should retrieve a jwk from the api.
+var _ = Describe("[azure]", Label("azure", "keyvault", "key"), func() {
+	f := framework.New("eso-azure-keytype")
+	prov := newFromEnv(f)
+	var jwk *keyvault.JSONWebKey
+	var keyName string
+
+	BeforeEach(func() {
+		keyName = fmt.Sprintf("%s-%s", f.Namespace.Name, "keytest")
+		jwk = prov.CreateKey(keyName)
+	})
+
+	AfterEach(func() {
+		prov.DeleteKey(keyName)
+	})
+
+	ff := framework.TableFunc(f, prov)
+
+	It("should sync keyvault objects with type=key", func() {
+		ff(func(tc *framework.TestCase) {
+			secretKey := "azkv-key"
+			keyBytes, _ := json.Marshal(jwk)
+
+			tc.ExpectedSecret = &v1.Secret{
+				Type: v1.SecretTypeOpaque,
+				Data: map[string][]byte{
+					secretKey: keyBytes,
+				},
+			}
+			tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
+				{
+					SecretKey: secretKey,
+					RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+						Key: "key/" + keyName,
+					},
+				},
+			}
+		})
+	})
+
+})

+ 2 - 1
e2e/suite/azure/azure.go

@@ -21,7 +21,8 @@ import (
 	"github.com/external-secrets/external-secrets/e2e/suite/common"
 )
 
-var _ = Describe("[azure]", Label("azure", "keyvault"), func() {
+// keyvault type=secret should behave just like any other secret store.
+var _ = Describe("[azure]", Label("azure", "keyvault", "secret"), func() {
 	f := framework.New("eso-azure")
 	prov := newFromEnv(f)
 

+ 80 - 4
e2e/suite/azure/provider.go

@@ -15,16 +15,15 @@ package azure
 import (
 	"context"
 	"os"
+	"time"
 
 	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
 	kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
 
 	// nolint
-	. "github.com/onsi/gomega"
-
-	// nolint
 	. "github.com/onsi/ginkgo/v2"
-
+	// nolint
+	. "github.com/onsi/gomega"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	utilpointer "k8s.io/utils/pointer"
@@ -100,6 +99,83 @@ func (s *azureProvider) DeleteSecret(key string) {
 	Expect(err).ToNot(HaveOccurred())
 }
 
+func (s *azureProvider) CreateKey(key string) *keyvault.JSONWebKey {
+	out, err := s.client.CreateKey(
+		context.Background(),
+		s.vaultURL,
+		key,
+		keyvault.KeyCreateParameters{
+			Kty: keyvault.RSA,
+			KeyAttributes: &keyvault.KeyAttributes{
+				RecoveryLevel: keyvault.Purgeable,
+				Enabled:       utilpointer.BoolPtr(true),
+			},
+		},
+	)
+	Expect(err).ToNot(HaveOccurred())
+	return out.Key
+}
+
+func (s *azureProvider) DeleteKey(key string) {
+	_, err := s.client.DeleteKey(context.Background(), s.vaultURL, key)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *azureProvider) CreateCertificate(key string) {
+	_, err := s.client.CreateCertificate(
+		context.Background(),
+		s.vaultURL,
+		key,
+		keyvault.CertificateCreateParameters{
+			CertificatePolicy: &keyvault.CertificatePolicy{
+				X509CertificateProperties: &keyvault.X509CertificateProperties{
+					Subject:          utilpointer.String("CN=e2e.test"),
+					ValidityInMonths: utilpointer.Int32(42),
+				},
+				IssuerParameters: &keyvault.IssuerParameters{
+					Name: utilpointer.String("Self"),
+				},
+				Attributes: &keyvault.CertificateAttributes{
+					RecoveryLevel: keyvault.Purgeable,
+					Enabled:       utilpointer.BoolPtr(true),
+				},
+			},
+			CertificateAttributes: &keyvault.CertificateAttributes{
+				RecoveryLevel: keyvault.Purgeable,
+				Enabled:       utilpointer.BoolPtr(true),
+			},
+		},
+	)
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *azureProvider) GetCertificate(key string) []byte {
+	attempts := 20
+	for {
+		out, err := s.client.GetCertificate(
+			context.Background(),
+			s.vaultURL,
+			key,
+			"",
+		)
+		Expect(err).ToNot(HaveOccurred())
+		if out.Cer != nil {
+			return *out.Cer
+		}
+
+		attempts--
+		if attempts <= 0 {
+			Fail("failed fetching azkv certificate")
+		}
+		<-time.After(time.Second * 5)
+	}
+}
+
+func (s *azureProvider) DeleteCertificate(key string) {
+	_, err := s.client.DeleteCertificate(context.Background(), s.vaultURL, key)
+	Expect(err).ToNot(HaveOccurred())
+}
+
 func (s *azureProvider) CreateSecretStore() {
 	azureCreds := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{

+ 2 - 1
e2e/suite/import.go

@@ -16,7 +16,8 @@ package suite
 import (
 
 	// import different e2e test suites.
-	_ "github.com/external-secrets/external-secrets/e2e/suite/aws"
+	_ "github.com/external-secrets/external-secrets/e2e/suite/aws/parameterstore"
+	_ "github.com/external-secrets/external-secrets/e2e/suite/aws/secretsmanager"
 	_ "github.com/external-secrets/external-secrets/e2e/suite/azure"
 	_ "github.com/external-secrets/external-secrets/e2e/suite/gcp"
 	_ "github.com/external-secrets/external-secrets/e2e/suite/vault"

+ 11 - 18
go.mod

@@ -33,7 +33,7 @@ replace (
 )
 
 require (
-	cloud.google.com/go v0.99.0
+	cloud.google.com/go v0.100.2 // indirect
 	cloud.google.com/go/secretmanager v1.0.0
 	github.com/Azure/azure-sdk-for-go v61.1.0+incompatible
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
@@ -45,33 +45,33 @@ require (
 	github.com/PaesslerAG/jsonpath v0.1.1
 	github.com/ahmetb/gen-crd-api-reference-docs v0.3.0
 	github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2
-	github.com/akeylesslabs/akeyless-go/v2 v2.15.24
-	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1458
+	github.com/akeylesslabs/akeyless-go/v2 v2.15.25
+	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1473
 	github.com/aws/aws-sdk-go v1.38.6
 	github.com/crossplane/crossplane-runtime v0.15.1
 	github.com/go-logr/logr v1.2.2
 	github.com/golang-jwt/jwt/v4 v4.2.0
 	github.com/google/go-cmp v0.5.7
 	github.com/google/uuid v1.2.0
-	github.com/googleapis/gax-go v1.0.3
+	github.com/googleapis/gax-go/v2 v2.1.1
 	github.com/hashicorp/vault/api v1.3.1
 	github.com/huandu/xstrings v1.3.2 // indirect
 	github.com/lestrrat-go/jwx v1.2.1
-	github.com/onsi/ginkgo/v2 v2.0.0
+	github.com/onsi/ginkgo/v2 v2.1.1
 	github.com/onsi/gomega v1.17.0
 	github.com/oracle/oci-go-sdk/v45 v45.2.0
 	github.com/prometheus/client_golang v1.11.0
 	github.com/prometheus/client_model v0.2.0
 	github.com/stretchr/testify v1.7.0
 	github.com/tidwall/gjson v1.12.1
-	github.com/xanzy/go-gitlab v0.50.1
+	github.com/xanzy/go-gitlab v0.54.3
 	github.com/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588
 	github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa
 	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
 	go.uber.org/zap v1.20.0
 	golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
 	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
-	google.golang.org/api v0.61.0
+	google.golang.org/api v0.64.0
 	google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5
 	google.golang.org/grpc v1.43.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
@@ -85,7 +85,10 @@ require (
 	software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
 )
 
+require cloud.google.com/go/iam v0.1.1
+
 require (
+	cloud.google.com/go/compute v0.1.0 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest v0.11.18 // indirect
 	github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
@@ -95,7 +98,6 @@ require (
 	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/BurntSushi/toml v0.3.1 // indirect
 	github.com/PaesslerAG/gval v1.0.0 // indirect
 	github.com/armon/go-metrics v0.3.10 // indirect
 	github.com/armon/go-radix v1.0.0 // indirect
@@ -103,16 +105,11 @@ require (
 	github.com/aws/aws-sdk-go-v2 v0.23.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cenkalti/backoff/v3 v3.2.2 // indirect
-	github.com/census-instrumentation/opencensus-proto v0.2.1 // indirect
 	github.com/cespare/xxhash/v2 v2.1.1 // indirect
-	github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
-	github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 // indirect
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
-	github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 // indirect
-	github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect
 	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/fatih/color v1.13.0 // indirect
 	github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
@@ -131,10 +128,9 @@ require (
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
-	github.com/google/go-querystring v1.0.0 // indirect
+	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
-	github.com/googleapis/gax-go/v2 v2.1.1 // indirect
 	github.com/googleapis/gnostic v0.5.5 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -194,8 +190,6 @@ require (
 	go.opencensus.io v0.23.0 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
-	golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect
-	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
 	golang.org/x/mod v0.4.2 // indirect
 	golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect
 	golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
@@ -212,7 +206,6 @@ require (
 	gopkg.in/ini.v1 v1.66.2 // indirect
 	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	honnef.co/go/tools v0.1.4 // indirect
 	k8s.io/apiextensions-apiserver v0.23.0 // indirect
 	k8s.io/component-base v0.23.0 // indirect
 	k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect

+ 27 - 32
go.sum

@@ -25,17 +25,23 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
 cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
 cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
-cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY=
 cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/compute v0.1.0 h1:rSUBvAyVwNJ5uQCKNJFMwPtTvJkfN38b6Pvb9zZoqJ8=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/iam v0.1.1 h1:4CapQyNFjiksks1/x7jsvsygFPhihslYk5GptIrlX68=
+cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -78,7 +84,6 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
 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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@@ -107,15 +112,15 @@ github.com/ahmetb/gen-crd-api-reference-docs v0.3.0 h1:+XfOU14S4bGuwyvCijJwhhBIj
 github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8=
 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2 h1:1h4udX3Y5KgSG0m4Th2bHfaYxZB9fbngiij9PrKEp6c=
 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2/go.mod h1:ionnWiARf5TYoFGuHS7syh51/7lYosZejpZbnECFJcU=
-github.com/akeylesslabs/akeyless-go/v2 v2.15.24 h1:q++f8n4l66kSptlo9cxrYq8fGHg25GdjqOsw0vbKUAE=
-github.com/akeylesslabs/akeyless-go/v2 v2.15.24/go.mod h1:uOdXD49NCCe4rexeSc2aBU5Qv4KZgJE6YlbtYalvb+I=
+github.com/akeylesslabs/akeyless-go/v2 v2.15.25 h1:9iztd2fXVfj4b3LRsyvGNRheFoz8IFs/+K4GaAshZc4=
+github.com/akeylesslabs/akeyless-go/v2 v2.15.25/go.mod h1:uOdXD49NCCe4rexeSc2aBU5Qv4KZgJE6YlbtYalvb+I=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
-github.com/aliyun/alibaba-cloud-sdk-go v1.61.1458 h1:pMdm+s6k9yeAYJNqgZIpZcDBuh2SNR3Q137G9rpxDZc=
-github.com/aliyun/alibaba-cloud-sdk-go v1.61.1458/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1473 h1:rUoiu7Duq0hr4mjlQWZMORKaCbNXaYvYN2HFJQt228E=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1473/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -152,7 +157,6 @@ github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9cop
 github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
 github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
 github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
-github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
@@ -170,12 +174,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI=
 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 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
 github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
@@ -222,9 +224,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 h1:fP+fF0up6oPY49OrjPrhIJ8yQfdIM85NXMLkMg1EXVs=
 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 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
 github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -340,7 +340,6 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4er
 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/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -391,8 +390,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -422,9 +421,6 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
 github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go v1.0.3 h1:9dMLqhaibYONnDRcnHdUs9P8Mw64jLlZTYlDe3leBtQ=
-github.com/googleapis/gax-go v1.0.3/go.mod h1:QyXYajJFdARxGzjwUfbDFIse7Spkw81SJ4LrBJXtlQ8=
-github.com/googleapis/gax-go/v2 v2.0.2/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
@@ -682,8 +678,8 @@ github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
-github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.1 h1:LCnPB85AvFNr91s0B2aDzEiiIg6MUwLYbryC1NSlWi8=
+github.com/onsi/ginkgo/v2 v2.1.1/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -814,8 +810,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
-github.com/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ=
-github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE=
+github.com/xanzy/go-gitlab v0.54.3 h1:fPfZ3Jcu5dPc3xyIYtAALZsEgoyKNFNuULD+TdJ7Zvk=
+github.com/xanzy/go-gitlab v0.54.3/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
@@ -904,7 +900,6 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
 golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190221220918-438050ddec5e/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
@@ -914,11 +909,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
-golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -930,7 +922,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -1109,6 +1100,8 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -1135,7 +1128,6 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1243,8 +1235,10 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6
 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
-google.golang.org/api v0.61.0 h1:TXXKS1slM3b2bZNJwD5DV/Tp6/M2cLzLOLh9PjDhrw8=
 google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.64.0 h1:l3pi8ncrQgB9+ncFw3A716L8lWujnXniBYbxWqqy6tE=
+google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1319,10 +1313,13 @@ google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ6
 google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q=
 google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
-google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1348,6 +1345,7 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
 google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
 google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
@@ -1413,7 +1411,6 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
 grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY=
 grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
-honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1421,8 +1418,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ=
-honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
 k8s.io/api v0.23.0 h1:WrL1gb73VSC8obi8cuYETJGXEoFNEh3LU0Pt+Sokgro=
 k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg=
 k8s.io/apiextensions-apiserver v0.23.0 h1:uii8BYmHYiT2ZTAJxmvc3X8UhNYMxl2A0z0Xq3Pm+WY=

+ 1 - 0
hack/api-docs/mkdocs.yml

@@ -61,6 +61,7 @@ nav:
     - Oracle:
       - Oracle Vault: provider-oracle-vault.md
     - Webhook: provider-webhook.md
+    - Fake: provider-fake.md
   - References:
     - API specification: spec.md
   - Contributing:

+ 1 - 3
pkg/controllers/externalsecret/externalsecret_controller_template.go

@@ -64,9 +64,7 @@ func (r *Reconciler) applyTemplate(ctx context.Context, es *esv1alpha1.ExternalS
 	// if no data was provided by template fallback
 	// to value from the provider
 	if len(es.Spec.Target.Template.Data) == 0 {
-		for k, v := range dataMap {
-			secret.Data[k] = v
-		}
+		secret.Data = dataMap
 	}
 	secret.Annotations[esv1alpha1.AnnotationDataHash] = utils.ObjectHash(secret.Data)
 

+ 82 - 1
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -31,8 +31,8 @@ import (
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	"github.com/external-secrets/external-secrets/pkg/provider"
-	"github.com/external-secrets/external-secrets/pkg/provider/fake"
 	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+	"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
 )
 
 var (
@@ -654,6 +654,85 @@ var _ = Describe("ExternalSecret controller", func() {
 		}
 	}
 
+	// when a provider secret was deleted it must be deleted from
+	// the secret aswell
+	refreshSecretValueMap := func(tc *testCase) {
+		fakeProvider.WithGetSecretMap(map[string][]byte{
+			"foo": []byte("1111"),
+			"bar": []byte("2222"),
+		}, nil)
+		tc.externalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{}
+		tc.externalSecret.Spec.DataFrom = []esv1alpha1.ExternalSecretDataRemoteRef{
+			{
+				Key: remoteKey,
+			},
+		}
+		tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second}
+		tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
+			// check values
+			Expect(string(secret.Data["foo"])).To(Equal("1111"))
+			Expect(string(secret.Data["bar"])).To(Equal("2222"))
+
+			// update provider secret
+			sec := &v1.Secret{}
+			fakeProvider.WithGetSecretMap(map[string][]byte{
+				"foo": []byte("1111"),
+			}, nil)
+			secretLookupKey := types.NamespacedName{
+				Name:      ExternalSecretTargetSecretName,
+				Namespace: ExternalSecretNamespace,
+			}
+			Eventually(func() bool {
+				err := k8sClient.Get(context.Background(), secretLookupKey, sec)
+				if err != nil {
+					return false
+				}
+				return string(sec.Data["foo"]) == "1111" &&
+					sec.Data["bar"] == nil // must not be defined, it was deleted
+			}, timeout, interval).Should(BeTrue())
+		}
+	}
+
+	// when a provider secret was deleted it must be deleted from
+	// the secret aswell when using a template
+	refreshSecretValueMapTemplate := func(tc *testCase) {
+		fakeProvider.WithGetSecretMap(map[string][]byte{
+			"foo": []byte("1111"),
+			"bar": []byte("2222"),
+		}, nil)
+		tc.externalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{}
+		tc.externalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{}
+		tc.externalSecret.Spec.DataFrom = []esv1alpha1.ExternalSecretDataRemoteRef{
+			{
+				Key: remoteKey,
+			},
+		}
+		tc.externalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second}
+		tc.checkSecret = func(es *esv1alpha1.ExternalSecret, secret *v1.Secret) {
+			// check values
+			Expect(string(secret.Data["foo"])).To(Equal("1111"))
+			Expect(string(secret.Data["bar"])).To(Equal("2222"))
+
+			// update provider secret
+			sec := &v1.Secret{}
+			fakeProvider.WithGetSecretMap(map[string][]byte{
+				"foo": []byte("1111"),
+			}, nil)
+			secretLookupKey := types.NamespacedName{
+				Name:      ExternalSecretTargetSecretName,
+				Namespace: ExternalSecretNamespace,
+			}
+			Eventually(func() bool {
+				err := k8sClient.Get(context.Background(), secretLookupKey, sec)
+				if err != nil {
+					return false
+				}
+				return string(sec.Data["foo"]) == "1111" &&
+					sec.Data["bar"] == nil // must not be defined, it was deleted
+			}, timeout, interval).Should(BeTrue())
+		}
+	}
+
 	refreshintervalZero := func(tc *testCase) {
 		const targetProp = "targetProperty"
 		const secretVal = "someValue"
@@ -971,6 +1050,8 @@ var _ = Describe("ExternalSecret controller", func() {
 		Entry("should refresh secret from template", refreshWithTemplate),
 		Entry("should be able to use only metadata from template", onlyMetadataFromTemplate),
 		Entry("should refresh secret value when provider secret changes", refreshSecretValue),
+		Entry("should refresh secret map when provider secret changes", refreshSecretValueMap),
+		Entry("should refresh secret map when provider secret changes when using a template", refreshSecretValueMapTemplate),
 		Entry("should not refresh secret value when provider secret changes but refreshInterval is zero", refreshintervalZero),
 		Entry("should fetch secret using dataFrom", syncWithDataFrom),
 		Entry("should fetch secret using dataFrom and a template", syncWithDataFromTemplate),

+ 57 - 0
pkg/controllers/externalsecret/metrics.go

@@ -16,6 +16,7 @@ package externalsecret
 
 import (
 	"github.com/prometheus/client_golang/prometheus"
+	v1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
@@ -50,6 +51,62 @@ var (
 
 // updateExternalSecretCondition updates the ExternalSecret conditions.
 func updateExternalSecretCondition(es *esv1alpha1.ExternalSecret, condition *esv1alpha1.ExternalSecretStatusCondition, value float64) {
+	switch condition.Type {
+	case esv1alpha1.ExternalSecretDeleted:
+		// Remove condition=Ready metrics when the object gets deleted.
+		externalSecretCondition.Delete(prometheus.Labels{
+			"name":      es.Name,
+			"namespace": es.Namespace,
+			"condition": string(esv1alpha1.ExternalSecretReady),
+			"status":    string(v1.ConditionFalse),
+		})
+		externalSecretCondition.Delete(prometheus.Labels{
+			"name":      es.Name,
+			"namespace": es.Namespace,
+			"condition": string(esv1alpha1.ExternalSecretReady),
+			"status":    string(v1.ConditionTrue),
+		})
+
+	case esv1alpha1.ExternalSecretReady:
+		// Remove condition=Deleted metrics when the object gets ready.
+		externalSecretCondition.Delete(prometheus.Labels{
+			"name":      es.Name,
+			"namespace": es.Namespace,
+			"condition": string(esv1alpha1.ExternalSecretDeleted),
+			"status":    string(v1.ConditionFalse),
+		})
+		externalSecretCondition.Delete(prometheus.Labels{
+			"name":      es.Name,
+			"namespace": es.Namespace,
+			"condition": string(esv1alpha1.ExternalSecretDeleted),
+			"status":    string(v1.ConditionTrue),
+		})
+		// Toggle opposite Status to 0
+		switch condition.Status {
+		case v1.ConditionFalse:
+			externalSecretCondition.With(prometheus.Labels{
+				"name":      es.Name,
+				"namespace": es.Namespace,
+				"condition": string(esv1alpha1.ExternalSecretReady),
+				"status":    string(v1.ConditionTrue),
+			}).Set(0)
+		case v1.ConditionTrue:
+			externalSecretCondition.With(prometheus.Labels{
+				"name":      es.Name,
+				"namespace": es.Namespace,
+				"condition": string(esv1alpha1.ExternalSecretReady),
+				"status":    string(v1.ConditionFalse),
+			}).Set(0)
+		case v1.ConditionUnknown:
+			break
+		default:
+			break
+		}
+
+	default:
+		break
+	}
+
 	externalSecretCondition.With(prometheus.Labels{
 		"name":      es.Name,
 		"namespace": es.Namespace,

+ 7 - 1
pkg/controllers/externalsecret/suite_test.go

@@ -15,6 +15,7 @@ limitations under the License.
 package externalsecret
 
 import (
+	"context"
 	"path/filepath"
 	"testing"
 	"time"
@@ -40,6 +41,7 @@ import (
 var cfg *rest.Config
 var k8sClient client.Client
 var testEnv *envtest.Environment
+var cancel context.CancelFunc
 
 func TestAPIs(t *testing.T) {
 	RegisterFailHandler(Fail)
@@ -56,6 +58,9 @@ var _ = BeforeSuite(func() {
 		CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "deploy", "crds")},
 	}
 
+	var ctx context.Context
+	ctx, cancel = context.WithCancel(context.Background())
+
 	var err error
 	cfg, err = testEnv.Start()
 	Expect(err).ToNot(HaveOccurred())
@@ -87,12 +92,13 @@ var _ = BeforeSuite(func() {
 
 	go func() {
 		defer GinkgoRecover()
-		Expect(k8sManager.Start(ctrl.SetupSignalHandler())).ToNot(HaveOccurred())
+		Expect(k8sManager.Start(ctx)).ToNot(HaveOccurred())
 	}()
 })
 
 var _ = AfterSuite(func() {
 	By("tearing down the test environment")
+	cancel() // stop manager
 	err := testEnv.Stop()
 	Expect(err).ToNot(HaveOccurred())
 })

+ 51 - 58
pkg/provider/fake/fake.go

@@ -16,6 +16,7 @@ package fake
 
 import (
 	"context"
+	"fmt"
 
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -24,80 +25,72 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
 
-var _ provider.Provider = &Client{}
+var (
+	errNotFound            = fmt.Errorf("secret value not found")
+	errMissingStore        = fmt.Errorf("missing store provider")
+	errMissingFakeProvider = fmt.Errorf("missing store provider fake")
+)
 
-// Client is a fake client for testing.
-type Client struct {
-	NewFn func(context.Context, esv1alpha1.GenericStore, client.Client,
-		string) (provider.SecretsClient, error)
-	GetSecretFn    func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error)
-	GetSecretMapFn func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
+type Provider struct {
+	config *esv1alpha1.FakeProvider
 }
 
-// New returns a fake provider/client.
-func New() *Client {
-	v := &Client{
-		GetSecretFn: func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
-			return nil, nil
-		},
-		GetSecretMapFn: func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-			return nil, nil
-		},
-	}
-
-	v.NewFn = func(context.Context, esv1alpha1.GenericStore, client.Client, string) (provider.SecretsClient, error) {
-		return v, nil
+func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+	cfg, err := getProvider(store)
+	if err != nil {
+		return nil, err
 	}
-
-	return v
-}
-
-// RegisterAs registers the fake client in the schema.
-func (v *Client) RegisterAs(provider *esv1alpha1.SecretStoreProvider) {
-	schema.ForceRegister(v, provider)
+	return &Provider{
+		config: cfg,
+	}, nil
 }
 
-// GetSecret implements the provider.Provider interface.
-func (v *Client) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	return v.GetSecretFn(ctx, ref)
+func getProvider(store esv1alpha1.GenericStore) (*esv1alpha1.FakeProvider, error) {
+	if store == nil {
+		return nil, errMissingStore
+	}
+	spc := store.GetSpec()
+	if spc == nil || spc.Provider == nil || spc.Provider.Fake == nil {
+		return nil, errMissingFakeProvider
+	}
+	return spc.Provider.Fake, nil
 }
 
-// WithGetSecret wraps secret data returned by this provider.
-func (v *Client) WithGetSecret(secData []byte, err error) *Client {
-	v.GetSecretFn = func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
-		return secData, err
+// GetSecret returns a single secret from the provider.
+func (p *Provider) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	for _, data := range p.config.Data {
+		if data.Key == ref.Key && data.Version == ref.Version {
+			return []byte(data.Value), nil
+		}
 	}
-	return v
+	return nil, errNotFound
 }
 
-// GetSecretMap imeplements the provider.Provider interface.
-func (v *Client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	return v.GetSecretMapFn(ctx, ref)
-}
-func (v *Client) Close(ctx context.Context) error {
-	return nil
+// GetSecretMap returns multiple k/v pairs from the provider.
+func (p *Provider) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	for _, data := range p.config.Data {
+		if data.Key != ref.Key || data.Version != ref.Version || data.ValueMap == nil {
+			continue
+		}
+		return convertMap(data.ValueMap), nil
+	}
+	return nil, errNotFound
 }
 
-// WithGetSecretMap wraps the secret data map returned by this fake provider.
-func (v *Client) WithGetSecretMap(secData map[string][]byte, err error) *Client {
-	v.GetSecretMapFn = func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-		return secData, err
+func convertMap(in map[string]string) map[string][]byte {
+	m := make(map[string][]byte)
+	for k, v := range in {
+		m[k] = []byte(v)
 	}
-	return v
+	return m
 }
 
-// WithNew wraps the fake provider factory function.
-func (v *Client) WithNew(f func(context.Context, esv1alpha1.GenericStore, client.Client,
-	string) (provider.SecretsClient, error)) *Client {
-	v.NewFn = f
-	return v
+func (p *Provider) Close(ctx context.Context) error {
+	return nil
 }
 
-// NewClient returns a new fake provider.
-func (v *Client) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
-	c, err := v.NewFn(ctx, store, kube, namespace)
-	if err != nil {
-		return nil, err
-	}
-	return c, nil
+func init() {
+	schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
+		Fake: &esv1alpha1.FakeProvider{},
+	})
 }

+ 194 - 0
pkg/provider/fake/fake_test.go

@@ -0,0 +1,194 @@
+/*
+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 fake
+
+import (
+	"context"
+	"testing"
+
+	"github.com/onsi/gomega"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+)
+
+func TestNewClient(t *testing.T) {
+	p := &Provider{}
+	gomega.RegisterTestingT(t)
+
+	// nil store
+	_, err := p.NewClient(context.Background(), nil, nil, "")
+	gomega.Expect(err).To(gomega.HaveOccurred())
+
+	// missing provider
+	_, err = p.NewClient(context.Background(), &esv1alpha1.SecretStore{}, nil, "")
+	gomega.Expect(err).To(gomega.HaveOccurred())
+}
+
+func TestClose(t *testing.T) {
+	p := &Provider{}
+	gomega.RegisterTestingT(t)
+	err := p.Close(context.TODO())
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+}
+
+type testCase struct {
+	name     string
+	input    []esv1alpha1.FakeProviderData
+	request  esv1alpha1.ExternalSecretDataRemoteRef
+	expValue string
+	expErr   string
+}
+
+func TestGetSecret(t *testing.T) {
+	gomega.RegisterTestingT(t)
+	p := &Provider{}
+	tbl := []testCase{
+		{
+			name:  "return err when not found",
+			input: []esv1alpha1.FakeProviderData{},
+			request: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:     "/foo",
+				Version: "v2",
+			},
+			expErr: "secret value not found",
+		},
+		{
+			name: "get correct value from multiple versions",
+			input: []esv1alpha1.FakeProviderData{
+				{
+					Key:     "/foo",
+					Value:   "bar2",
+					Version: "v2",
+				},
+				{
+					Key:   "junk",
+					Value: "xxxxx",
+				},
+				{
+					Key:     "/foo",
+					Value:   "bar1",
+					Version: "v1",
+				},
+			},
+			request: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:     "/foo",
+				Version: "v2",
+			},
+			expValue: "bar2",
+		},
+	}
+
+	for _, row := range tbl {
+		t.Run(row.name, func(t *testing.T) {
+			cl, err := p.NewClient(context.Background(), &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						Fake: &esv1alpha1.FakeProvider{
+							Data: row.input,
+						},
+					},
+				},
+			}, nil, "")
+			gomega.Expect(err).ToNot(gomega.HaveOccurred())
+			out, err := cl.GetSecret(context.Background(), row.request)
+			if row.expErr != "" {
+				gomega.Expect(err).To(gomega.MatchError(row.expErr))
+			} else {
+				gomega.Expect(err).ToNot(gomega.HaveOccurred())
+			}
+			gomega.Expect(string(out)).To(gomega.Equal(row.expValue))
+		})
+	}
+}
+
+type testMapCase struct {
+	name     string
+	input    []esv1alpha1.FakeProviderData
+	request  esv1alpha1.ExternalSecretDataRemoteRef
+	expValue map[string][]byte
+	expErr   string
+}
+
+func TestGetSecretMap(t *testing.T) {
+	gomega.RegisterTestingT(t)
+	p := &Provider{}
+	tbl := []testMapCase{
+		{
+			name:  "return err when not found",
+			input: []esv1alpha1.FakeProviderData{},
+			request: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:     "/foo",
+				Version: "v2",
+			},
+			expErr: "secret value not found",
+		},
+		{
+			name: "get correct value from multiple versions",
+			input: []esv1alpha1.FakeProviderData{
+				{
+					Key: "junk",
+					ValueMap: map[string]string{
+						"junk": "ok",
+					},
+				},
+				{
+					Key: "/foo",
+					ValueMap: map[string]string{
+						"foo": "bar",
+						"baz": "bang",
+					},
+					Version: "v1",
+				},
+				{
+					Key: "/foo",
+					ValueMap: map[string]string{
+						"foo": "bar",
+						"baz": "bang",
+					},
+					Version: "v2",
+				},
+			},
+			request: esv1alpha1.ExternalSecretDataRemoteRef{
+				Key:     "/foo",
+				Version: "v2",
+			},
+			expValue: map[string][]byte{
+				"foo": []byte("bar"),
+				"baz": []byte("bang"),
+			},
+		},
+	}
+
+	for _, row := range tbl {
+		t.Run(row.name, func(t *testing.T) {
+			cl, err := p.NewClient(context.Background(), &esv1alpha1.SecretStore{
+				Spec: esv1alpha1.SecretStoreSpec{
+					Provider: &esv1alpha1.SecretStoreProvider{
+						Fake: &esv1alpha1.FakeProvider{
+							Data: row.input,
+						},
+					},
+				},
+			}, nil, "")
+			gomega.Expect(err).ToNot(gomega.HaveOccurred())
+			out, err := cl.GetSecretMap(context.Background(), row.request)
+			if row.expErr != "" {
+				gomega.Expect(err).To(gomega.MatchError(row.expErr))
+			} else {
+				gomega.Expect(err).ToNot(gomega.HaveOccurred())
+			}
+			gomega.Expect(out).To(gomega.Equal(row.expValue))
+		})
+	}
+}

+ 1 - 1
pkg/provider/gcp/secretmanager/fake/fake.go

@@ -19,7 +19,7 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
-	grpc "github.com/googleapis/gax-go"
+	grpc "github.com/googleapis/gax-go/v2"
 	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
 )
 

+ 1 - 1
pkg/provider/gcp/secretmanager/secretsmanager.go

@@ -19,7 +19,7 @@ import (
 	"fmt"
 
 	secretmanager "cloud.google.com/go/secretmanager/apiv1"
-	"github.com/googleapis/gax-go"
+	"github.com/googleapis/gax-go/v2"
 	"github.com/tidwall/gjson"
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/google"

+ 1 - 1
pkg/provider/gcp/secretmanager/secretsmanager_workload_identity.go

@@ -24,7 +24,7 @@ import (
 
 	iam "cloud.google.com/go/iam/credentials/apiv1"
 	secretmanager "cloud.google.com/go/secretmanager/apiv1"
-	"github.com/googleapis/gax-go"
+	"github.com/googleapis/gax-go/v2"
 	"golang.org/x/oauth2"
 	"google.golang.org/api/option"
 	credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"

+ 1 - 1
pkg/provider/gcp/secretmanager/secretsmanager_workload_identity_test.go

@@ -21,7 +21,7 @@ import (
 	"net/http/httptest"
 	"testing"
 
-	"github.com/googleapis/gax-go"
+	"github.com/googleapis/gax-go/v2"
 	"github.com/stretchr/testify/assert"
 	"golang.org/x/oauth2"
 	credentialspb "google.golang.org/genproto/googleapis/iam/credentials/v1"

+ 1 - 0
pkg/provider/register/register.go

@@ -21,6 +21,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/pkg/provider/alibaba"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/aws"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/fake"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gitlab"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/ibm"

+ 103 - 0
pkg/provider/testing/fake/fake.go

@@ -0,0 +1,103 @@
+/*
+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 fake
+
+import (
+	"context"
+
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	"github.com/external-secrets/external-secrets/pkg/provider"
+	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+)
+
+var _ provider.Provider = &Client{}
+
+// Client is a fake client for testing.
+type Client struct {
+	NewFn func(context.Context, esv1alpha1.GenericStore, client.Client,
+		string) (provider.SecretsClient, error)
+	GetSecretFn    func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error)
+	GetSecretMapFn func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
+}
+
+// New returns a fake provider/client.
+func New() *Client {
+	v := &Client{
+		GetSecretFn: func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
+			return nil, nil
+		},
+		GetSecretMapFn: func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+			return nil, nil
+		},
+	}
+
+	v.NewFn = func(context.Context, esv1alpha1.GenericStore, client.Client, string) (provider.SecretsClient, error) {
+		return v, nil
+	}
+
+	return v
+}
+
+// RegisterAs registers the fake client in the schema.
+func (v *Client) RegisterAs(provider *esv1alpha1.SecretStoreProvider) {
+	schema.ForceRegister(v, provider)
+}
+
+// GetSecret implements the provider.Provider interface.
+func (v *Client) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	return v.GetSecretFn(ctx, ref)
+}
+
+// WithGetSecret wraps secret data returned by this provider.
+func (v *Client) WithGetSecret(secData []byte, err error) *Client {
+	v.GetSecretFn = func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
+		return secData, err
+	}
+	return v
+}
+
+// GetSecretMap imeplements the provider.Provider interface.
+func (v *Client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	return v.GetSecretMapFn(ctx, ref)
+}
+func (v *Client) Close(ctx context.Context) error {
+	return nil
+}
+
+// WithGetSecretMap wraps the secret data map returned by this fake provider.
+func (v *Client) WithGetSecretMap(secData map[string][]byte, err error) *Client {
+	v.GetSecretMapFn = func(context.Context, esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+		return secData, err
+	}
+	return v
+}
+
+// WithNew wraps the fake provider factory function.
+func (v *Client) WithNew(f func(context.Context, esv1alpha1.GenericStore, client.Client,
+	string) (provider.SecretsClient, error)) *Client {
+	v.NewFn = f
+	return v
+}
+
+// NewClient returns a new fake provider.
+func (v *Client) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+	c, err := v.NewFn(ctx, store, kube, namespace)
+	if err != nil {
+		return nil, err
+	}
+	return c, nil
+}

+ 3 - 1
pkg/provider/webhook/webhook_test.go

@@ -298,7 +298,9 @@ func testGetSecret(tc testCase, t *testing.T, client provider.SecretsClient) {
 		Key:     tc.Args.Key,
 		Version: tc.Args.Version,
 	}
-	secret, err := client.GetSecret(context.Background(), testRef)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	secret, err := client.GetSecret(ctx, testRef)
 	errStr := ""
 	if err != nil {
 		errStr = err.Error()

+ 19 - 0
terraform/aws/modules/cluster/irsa.tf

@@ -37,6 +37,25 @@ resource "aws_iam_role" "eso-e2e-irsa" {
     "arn:aws:iam::aws:policy/SecretsManagerReadWrite"
   ]
 
+  inline_policy {
+    name = "aws_ssm_parameterstore"
+
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Action = [
+            "ssm:GetParameter",
+            "ssm:PutParameter",
+          ]
+          Effect   = "Allow"
+          Resource = "*"
+        },
+      ]
+    })
+  }
+
+
 }
 
 resource "null_resource" "apply_sa" {