ソースを参照

Merge branch 'main' into chore/bump-0.9.2-branch

Signed-off-by: Gustavo Carvalho <gusfcarvalho@gmail.com>
Gustavo Carvalho 2 年 前
コミット
1918ac2745
100 ファイル変更4317 行追加1151 行削除
  1. 1 0
      .github/PAUL.yaml
  2. 2 10
      .github/workflows/ci.yml
  3. 5 1
      .github/workflows/e2e.yml
  4. 2 2
      .github/workflows/helm.yml
  5. 2 2
      .github/workflows/update-deps.yml
  6. 1 1
      Dockerfile.standalone
  7. 1 1
      GOVERNANCE.md
  8. 34 21
      Makefile
  9. 1 1
      README.md
  10. 2 3
      apis/externalsecrets/v1beta1/clusterexternalsecret_types.go
  11. 51 0
      apis/externalsecrets/v1beta1/secretsstore_delinea_types.go
  12. 5 0
      apis/externalsecrets/v1beta1/secretstore_types.go
  13. 23 1
      apis/externalsecrets/v1beta1/secretstore_vault_types.go
  14. 71 0
      apis/externalsecrets/v1beta1/zz_generated.deepcopy.go
  15. 3 6
      config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml
  16. 112 1
      config/crds/bases/external-secrets.io_clustersecretstores.yaml
  17. 1 1
      config/crds/bases/external-secrets.io_externalsecrets.yaml
  18. 1 1
      config/crds/bases/external-secrets.io_pushsecrets.yaml
  19. 112 1
      config/crds/bases/external-secrets.io_secretstores.yaml
  20. 1 1
      config/crds/bases/generators.external-secrets.io_acraccesstokens.yaml
  21. 1 1
      config/crds/bases/generators.external-secrets.io_ecrauthorizationtokens.yaml
  22. 1 1
      config/crds/bases/generators.external-secrets.io_fakes.yaml
  23. 1 1
      config/crds/bases/generators.external-secrets.io_gcraccesstokens.yaml
  24. 1 1
      config/crds/bases/generators.external-secrets.io_passwords.yaml
  25. 38 1
      config/crds/bases/generators.external-secrets.io_vaultdynamicsecrets.yaml
  26. 2 2
      deploy/charts/external-secrets/Chart.yaml
  27. 4 9
      deploy/charts/external-secrets/README.md
  28. 2 0
      deploy/charts/external-secrets/templates/cert-controller-deployment.yaml
  29. 3 0
      deploy/charts/external-secrets/templates/deployment.yaml
  30. 1 1
      deploy/charts/external-secrets/templates/webhook-poddisruptionbudget.yaml
  31. 5 3
      deploy/charts/external-secrets/tests/__snapshot__/cert_controller_test.yaml.snap
  32. 3 3
      deploy/charts/external-secrets/tests/__snapshot__/controller_test.yaml.snap
  33. 85 1
      deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap
  34. 5 5
      deploy/charts/external-secrets/tests/__snapshot__/webhook_test.yaml.snap
  35. 14 0
      deploy/charts/external-secrets/tests/cert_controller_test.yaml
  36. 9 26
      deploy/charts/external-secrets/values.yaml
  37. 208 16
      deploy/crds/bundle.yaml
  38. 30 16
      docs/api/metrics.md
  39. 215 1
      docs/api/spec.md
  40. 8 3
      docs/contributing/release.md
  41. 4 4
      docs/guides/common-k8s-secret-types.md
  42. 1 3
      docs/guides/ownership-deletion-policy.md
  43. 1 1
      docs/index.md
  44. 3 1
      docs/introduction/stability-support.md
  45. BIN
      docs/pictures/cloak-provider-header.png
  46. 47 0
      docs/provider/cloak.md
  47. 2 2
      docs/provider/conjur.md
  48. 59 0
      docs/provider/delinea.md
  49. 13 0
      docs/provider/hashicorp-vault.md
  50. 44 0
      docs/provider/ibm-secrets-manager.md
  51. 19 0
      docs/snippets/cloak-external-secret.yaml
  52. 28 0
      docs/snippets/cloak-proxy-deployment.yaml
  53. 12 0
      docs/snippets/cloak-proxy-service.yaml
  54. 15 0
      docs/snippets/cloak-secret-store.yaml
  55. 27 0
      docs/snippets/ibm-external-secret-with-metadata.yaml
  56. 21 0
      docs/snippets/vault-userpass-store.yaml
  57. 73 1
      docs/spec.md
  58. 31 28
      e2e/go.mod
  59. 57 50
      e2e/go.sum
  60. 5 0
      e2e/run.sh
  61. 1 1
      e2e/suites/provider/cases/common/common.go
  62. 47 0
      e2e/suites/provider/cases/delinea/config.go
  63. 115 0
      e2e/suites/provider/cases/delinea/delinea.go
  64. 47 0
      e2e/suites/provider/cases/delinea/provider.go
  65. 1 0
      e2e/suites/provider/cases/import.go
  66. 50 46
      go.mod
  67. 100 84
      go.sum
  68. 5 1
      hack/api-docs/Dockerfile
  69. 3 3
      hack/api-docs/Makefile
  70. 2 0
      hack/api-docs/mkdocs.yml
  71. 1 1
      hack/api-docs/requirements.txt
  72. 1 1
      hack/crd.generate.sh
  73. 1 0
      pkg/constants/constants.go
  74. 165 97
      pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller.go
  75. 478 309
      pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller_test.go
  76. 1 3
      pkg/controllers/clusterexternalsecret/suite_test.go
  77. 2 23
      pkg/controllers/clusterexternalsecret/util.go
  78. 1 2
      pkg/controllers/externalsecret/externalsecret_controller.go
  79. 5 5
      pkg/controllers/webhookconfig/webhookconfig_test.go
  80. 3 3
      pkg/generator/ecr/ecr_test.go
  81. 5 5
      pkg/provider/aws/parameterstore/parameterstore.go
  82. 4 4
      pkg/provider/aws/provider_test.go
  83. 8 8
      pkg/provider/aws/secretsmanager/secretsmanager.go
  84. 10 5
      pkg/provider/aws/util/errors.go
  85. 4 0
      pkg/provider/aws/util/errors_test.go
  86. 5 5
      pkg/provider/azure/keyvault/keyvault.go
  87. 10 10
      pkg/provider/azure/keyvault/keyvault_auth_test.go
  88. 42 42
      pkg/provider/azure/keyvault/keyvault_test.go
  89. 148 0
      pkg/provider/delinea/client.go
  90. 117 0
      pkg/provider/delinea/client_test.go
  91. 207 0
      pkg/provider/delinea/provider.go
  92. 369 0
      pkg/provider/delinea/provider_test.go
  93. 25 0
      pkg/provider/delinea/secret_api.go
  94. 101 71
      pkg/provider/gcp/secretmanager/client.go
  95. 230 32
      pkg/provider/gcp/secretmanager/client_test.go
  96. 18 12
      pkg/provider/gcp/secretmanager/fake/fake.go
  97. 39 16
      pkg/provider/ibm/provider.go
  98. 411 121
      pkg/provider/ibm/provider_test.go
  99. 3 3
      pkg/provider/kubernetes/auth_test.go
  100. 3 3
      pkg/provider/kubernetes/provider_test.go

+ 1 - 0
.github/PAUL.yaml

@@ -5,6 +5,7 @@ maintainers:
 - sebagomez
 - rodrmartinez
 - IdanAdar
+- shuheiktgw
 # Emeritus Approvers
 - Flydiverny
 - silasbw

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

@@ -106,11 +106,8 @@ jobs:
           path: ${{ steps.go.outputs.mod-cache }}
           key: ${{ runner.os }}-mod-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
 
-      # Check DIff also runs Reviewable which needs golangci-lint installed
       - name: Check Diff
         run: |
-          wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s ${{ env.GOLANGCI_VERSION }}
-          export PATH=$PATH:./bin
           make check-diff
 
   unit-tests:
@@ -148,16 +145,11 @@ jobs:
           path: ${{ steps.go.outputs.mod-cache }}
           key: ${{ runner.os }}-mod-${{ github.sha }}-${{ hashFiles('**/go.sum') }}
 
-      - 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@v3
         with:
-          path: /home/runner/.local/share/kubebuilder-envtest/
-          key: ${{ runner.os }}-kubebuilder-${{env.KUBERNETES_VERSION}}
+          path: bin/k8s
+          key: ${{ runner.os }}-envtest-${{env.KUBERNETES_VERSION}}
 
       - name: Run Unit Tests
         run: |

+ 5 - 1
.github/workflows/e2e.yml

@@ -41,7 +41,11 @@ env:
   SCALEWAY_PROJECT_ID: ${{ secrets.SCALEWAY_PROJECT_ID }}
   SCALEWAY_ACCESS_KEY: ${{ secrets.SCALEWAY_ACCESS_KEY }}
   SCALEWAY_SECRET_KEY: ${{ secrets.SCALEWAY_SECRET_KEY }}
-
+  DELINEA_TLD: ${{ secrets.DELINEA_TLD }}
+  DELINEA_URL_TEMPLATE: ${{ secrets.DELINEA_URL_TEMPLATE }}
+  DELINEA_TENANT: ${{ secrets.DELINEA_TENANT }}
+  DELINEA_CLIENT_ID: ${{ secrets.DELINEA_CLIENT_ID }}
+  DELINEA_CLIENT_SECRET: ${{ secrets.DELINEA_CLIENT_SECRET }}
 jobs:
 
   integration-trusted:

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

@@ -31,7 +31,7 @@ jobs:
         with:
           version: v3.4.2
 
-      - uses: actions/setup-python@v4.6.1
+      - uses: actions/setup-python@v4.7.0
         with:
           python-version: 3.7
 
@@ -53,7 +53,7 @@ jobs:
         run: ct lint --config=.github/ci/ct.yaml
 
       - name: Create kind cluster
-        uses: helm/kind-action@v1.7.0
+        uses: helm/kind-action@v1.8.0
         if: steps.list-changed.outputs.changed == 'true'
 
       - name: Run chart-testing (install)

+ 2 - 2
.github/workflows/update-deps.yml

@@ -23,9 +23,9 @@ jobs:
           ref: ${{ github.event.inputs.ref }}
       - name: set branches output
         id: branches
-        # outputs the two most recent `release-x.y` branches plus `main` as JSON
+        # outputs the second to most recent `release-x.y` branches plus `main` as JSON
         run: |
-          echo "branches=$(git branch -a | grep -E "remotes/origin/(main|release-)" | sed 's/  remotes\/origin\///' | sort -V | tail -2 | jq -R -s -c 'split("\n") | map(select(length > 0)) | . + ["main"]')" >> $GITHUB_OUTPUT
+          echo "branches=$(git branch -a | grep -E "remotes/origin/(main|release-)" | sed 's/  remotes\/origin\///' | sort -V | tail -2 | head -1 | jq -R -s -c 'split("\n") | map(select(length > 0)) | . + ["main"]')" >> $GITHUB_OUTPUT
 
   update-dependencies:
     runs-on: ubuntu-latest

+ 1 - 1
Dockerfile.standalone

@@ -1,6 +1,6 @@
 # This version of Dockerfile is for building without external dependencies.
 # Build a multi-platform image e.g. `docker buildx build --push --platform linux/arm64,linux/amd64 --tag external-secrets:dev --file Dockerfile.standalone .`
-FROM golang:1.20.5-alpine AS builder
+FROM golang:1.20.6-alpine AS builder
 ARG TARGETOS
 ARG TARGETARCH
 ENV CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH}

+ 1 - 1
GOVERNANCE.md

@@ -9,7 +9,7 @@ secret management systems like [AWS Secrets
 Manager](https://aws.amazon.com/secrets-manager/), [HashiCorp
 Vault](https://www.vaultproject.io/), [Google Secrets
 Manager](https://cloud.google.com/secret-manager), [Azure Key
-Vault](https://azure.microsoft.com/en-us/services/key-vault/) and many more. The
+Vault](https://azure.microsoft.com/en-us/services/key-vault/), [CyberArk Conjur](https://www.conjur.org) and many more. The
 operator reads information from external APIs and automatically injects the
 values into a [Kubernetes
 Secret](https://kubernetes.io/docs/concepts/configuration/secret/).

+ 34 - 21
Makefile

@@ -35,8 +35,6 @@ else
 GOBIN=$(shell go env GOBIN)
 endif
 
-KUBERNETES_VERSION := '1.24.x'
-
 # check if there are any existing `git tag` values
 ifeq ($(shell git tag),)
 # no tags found - default to initial tag `v0.0.0`
@@ -93,10 +91,9 @@ update-deps:
 # Golang
 
 .PHONY: test
-test: export KUBEBUILDER_ASSETS := $(shell setup-envtest use $(KUBERNETES_VERSION) -p path --os $(shell go env GOOS) --arch $(shell go env GOARCH))
-test: generate ## Run tests
+test: generate envtest ## Run tests
 	@$(INFO) go test unit-tests
-	go test -race -v $(shell go list ./... | grep -v e2e) -coverprofile cover.out
+	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(KUBERNETES_VERSION) -p path --bin-dir $(LOCALBIN))" go test -race -v $(shell go list ./... | grep -v e2e) -coverprofile cover.out
 	@$(OK) go test unit-tests
 
 .PHONY: test.e2e
@@ -121,30 +118,18 @@ build-%: generate ## Build binary for the specified arch
 		go build -o '$(OUTPUT_DIR)/external-secrets-linux-$*' main.go
 	@$(OK) go build $*
 
-lint.check: ## Check install of golanci-lint
-	@if ! golangci-lint --version > /dev/null 2>&1; then \
-		echo -e "\033[0;33mgolangci-lint is not installed: run \`\033[0;32mmake lint.install\033[0m\033[0;33m\` or install it from https://golangci-lint.run\033[0m"; \
-		exit 1; \
-	fi
-
-lint.install: ## Install golangci-lint to the go bin dir
-	@if ! golangci-lint --version > /dev/null 2>&1; then \
-		echo "Installing golangci-lint"; \
-		curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.49.0; \
-	fi
-
-lint: lint.check ## Run golangci-lint
-	@if ! golangci-lint run; then \
+lint: golangci-lint ## Run golangci-lint
+	@if ! $(GOLANGCI_LINT) run; then \
 		echo -e "\033[0;33mgolangci-lint failed: some checks can be fixed with \`\033[0;32mmake fmt\033[0m\033[0;33m\`\033[0m"; \
 		exit 1; \
 	fi
 	@$(OK) Finished linting
 
-fmt: lint.check ## Ensure consistent code style
+fmt: golangci-lint ## Ensure consistent code style
 	@go mod tidy
 	@cd e2e/ && go mod tidy
 	@go fmt ./...
-	@golangci-lint run --fix
+	@$(GOLANGCI_LINT) run --fix
 	@$(OK) Ensured consistent code style
 
 generate: ## Generate code and crds
@@ -304,3 +289,31 @@ clean:  ## Clean bins
 	@$(INFO) clean
 	@rm -f $(OUTPUT_DIR)/external-secrets-linux-*
 	@$(OK) go build $*
+
+# ====================================================================================
+# Build Dependencies
+
+## Location to install dependencies to
+LOCALBIN ?= $(shell pwd)/bin
+$(LOCALBIN):
+	mkdir -p $(LOCALBIN)
+
+## Tool Binaries
+ENVTEST ?= $(LOCALBIN)/setup-envtest
+GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
+
+## Tool Versions
+GOLANGCI_VERSION := 1.52.2
+KUBERNETES_VERSION := 1.24.x
+
+.PHONY: envtest
+envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
+$(ENVTEST): $(LOCALBIN)
+	test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
+
+.PHONY: golangci-lint
+.PHONY: $(GOLANGCI_LINT)
+golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
+$(GOLANGCI_LINT): $(LOCALBIN)
+	test -s $(LOCALBIN)/golangci-lint && $(LOCALBIN)/golangci-lint version --format short | grep -q $(GOLANGCI_VERSION) || \
+	curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCALBIN) v$(GOLANGCI_VERSION)

+ 1 - 1
README.md

@@ -16,7 +16,7 @@ secret management systems like [AWS Secrets
 Manager](https://aws.amazon.com/secrets-manager/), [HashiCorp
 Vault](https://www.vaultproject.io/), [Google Secrets
 Manager](https://cloud.google.com/secret-manager), [Azure Key
-Vault](https://azure.microsoft.com/en-us/services/key-vault/), [IBM Cloud Secrets Manager](https://www.ibm.com/cloud/secrets-manager), [Akeyless](https://akeyless.io) and many more. The
+Vault](https://azure.microsoft.com/en-us/services/key-vault/), [IBM Cloud Secrets Manager](https://www.ibm.com/cloud/secrets-manager), [Akeyless](https://akeyless.io), [CyberArk Conjur](https://www.conjur.org) and many more. The
 operator reads information from external APIs and automatically injects the
 values into a [Kubernetes
 Secret](https://kubernetes.io/docs/concepts/configuration/secret/).

+ 2 - 3
apis/externalsecrets/v1beta1/clusterexternalsecret_types.go

@@ -93,9 +93,8 @@ type ClusterExternalSecretStatus struct {
 // +kubebuilder:storageversion
 // +kubebuilder:resource:scope=Cluster,categories={externalsecrets},shortName=ces
 // +kubebuilder:subresource:status
-// +kubebuilder:printcolumn:name="Store",type=string,JSONPath=`.spec.secretStoreRef.name`
-// +kubebuilder:printcolumn:name="Refresh Interval",type=string,JSONPath=`.spec.refreshInterval`
-// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
+// +kubebuilder:printcolumn:name="Store",type=string,JSONPath=`.spec.externalSecretSpec.secretStoreRef.name`
+// +kubebuilder:printcolumn:name="Refresh Interval",type=string,JSONPath=`.spec.refreshTime`
 // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
 // ClusterExternalSecret is the Schema for the clusterexternalsecrets API.
 type ClusterExternalSecret struct {

+ 51 - 0
apis/externalsecrets/v1beta1/secretsstore_delinea_types.go

@@ -0,0 +1,51 @@
+/*
+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 v1beta1
+
+import esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+
+type DelineaProviderSecretRef struct {
+
+	// Value can be specified directly to set a value without using a secret.
+	// +optional
+	Value string `json:"value,omitempty"`
+
+	// SecretRef references a key in a secret that will be used as value.
+	// +optional
+	SecretRef *esmeta.SecretKeySelector `json:"secretRef,omitempty"`
+}
+
+// See https://github.com/DelineaXPM/dsv-sdk-go/blob/main/vault/vault.go.
+type DelineaProvider struct {
+
+	// ClientID is the non-secret part of the credential.
+	ClientID *DelineaProviderSecretRef `json:"clientId"`
+
+	// ClientSecret is the secret part of the credential.
+	ClientSecret *DelineaProviderSecretRef `json:"clientSecret"`
+
+	// Tenant is the chosen hostname / site name.
+	Tenant string `json:"tenant"`
+
+	// URLTemplate
+	// If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
+	// +optional
+	URLTemplate string `json:"urlTemplate,omitempty"`
+
+	// TLD is based on the server location that was chosen during provisioning.
+	// If unset, defaults to "com".
+	// +optional
+	TLD string `json:"tld,omitempty"`
+}

+ 5 - 0
apis/externalsecrets/v1beta1/secretstore_types.go

@@ -136,6 +136,11 @@ type SecretStoreProvider struct {
 	// Conjur configures this store to sync secrets using conjur provider
 	// +optional
 	Conjur *ConjurProvider `json:"conjur,omitempty"`
+
+	// Delinea DevOps Secrets Vault
+	// https://docs.delinea.com/online-help/products/devops-secrets-vault/current
+	// +optional
+	Delinea *DelineaProvider `json:"delinea,omitempty"`
 }
 
 type CAProviderType string

+ 23 - 1
apis/externalsecrets/v1beta1/secretstore_vault_types.go

@@ -81,7 +81,7 @@ type VaultProvider struct {
 }
 
 // VaultAuth is the configuration used to authenticate with a Vault server.
-// Only one of `tokenSecretRef`, `appRole`,  `kubernetes`, `ldap`, `jwt` or `cert`
+// Only one of `tokenSecretRef`, `appRole`,  `kubernetes`, `ldap`, `userPass`, `jwt` or `cert`
 // can be specified.
 type VaultAuth struct {
 	// TokenSecretRef authenticates with Vault by presenting a token.
@@ -117,6 +117,10 @@ type VaultAuth struct {
 	// AWS IAM authentication method
 	// +optional
 	Iam *VaultIamAuth `json:"iam,omitempty"`
+
+	// UserPass authenticates with Vault by passing username/password pair
+	// +optional
+	UserPass *VaultUserPassAuth `json:"userPass,omitempty"`
 }
 
 // VaultAppRole authenticates with Vault using the App Role auth mechanism,
@@ -304,3 +308,21 @@ type VaultIamAuth struct {
 	// +optional
 	JWTAuth *VaultAwsJWTAuth `json:"jwt,omitempty"`
 }
+
+// VaultUserPassAuth authenticates with Vault using UserPass authentication method,
+// with the username and password stored in a Kubernetes Secret resource.
+type VaultUserPassAuth struct {
+	// Path where the UserPassword authentication backend is mounted
+	// in Vault, e.g: "user"
+	// +kubebuilder:default=user
+	Path string `json:"path"`
+
+	// Username is a user name used to authenticate using the UserPass Vault
+	// authentication method
+	Username string `json:"username"`
+
+	// SecretRef to a key in a Secret resource containing password for the
+	// user used to authenticate with Vault using the UserPass authentication
+	// method
+	SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
+}

+ 71 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -701,6 +701,51 @@ func (in *ConjurProvider) DeepCopy() *ConjurProvider {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DelineaProvider) DeepCopyInto(out *DelineaProvider) {
+	*out = *in
+	if in.ClientID != nil {
+		in, out := &in.ClientID, &out.ClientID
+		*out = new(DelineaProviderSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ClientSecret != nil {
+		in, out := &in.ClientSecret, &out.ClientSecret
+		*out = new(DelineaProviderSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DelineaProvider.
+func (in *DelineaProvider) DeepCopy() *DelineaProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(DelineaProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DelineaProviderSecretRef) DeepCopyInto(out *DelineaProviderSecretRef) {
+	*out = *in
+	if in.SecretRef != nil {
+		in, out := &in.SecretRef, &out.SecretRef
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DelineaProviderSecretRef.
+func (in *DelineaProviderSecretRef) DeepCopy() *DelineaProviderSecretRef {
+	if in == nil {
+		return nil
+	}
+	out := new(DelineaProviderSecretRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *DopplerAuth) DeepCopyInto(out *DopplerAuth) {
 	*out = *in
@@ -1861,6 +1906,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(ConjurProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Delinea != nil {
+		in, out := &in.Delinea, &out.Delinea
+		*out = new(DelineaProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
@@ -2195,6 +2245,11 @@ func (in *VaultAuth) DeepCopyInto(out *VaultAuth) {
 		*out = new(VaultIamAuth)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.UserPass != nil {
+		in, out := &in.UserPass, &out.UserPass
+		*out = new(VaultUserPassAuth)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth.
@@ -2448,6 +2503,22 @@ func (in *VaultProvider) DeepCopy() *VaultProvider {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VaultUserPassAuth) DeepCopyInto(out *VaultUserPassAuth) {
+	*out = *in
+	in.SecretRef.DeepCopyInto(&out.SecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultUserPassAuth.
+func (in *VaultUserPassAuth) DeepCopy() *VaultUserPassAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(VaultUserPassAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *WebhookCAProvider) DeepCopyInto(out *WebhookCAProvider) {
 	*out = *in

+ 3 - 6
config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: clusterexternalsecrets.external-secrets.io
 spec:
   group: external-secrets.io
@@ -18,15 +18,12 @@ spec:
   scope: Cluster
   versions:
   - additionalPrinterColumns:
-    - jsonPath: .spec.secretStoreRef.name
+    - jsonPath: .spec.externalSecretSpec.secretStoreRef.name
       name: Store
       type: string
-    - jsonPath: .spec.refreshInterval
+    - jsonPath: .spec.refreshTime
       name: Refresh Interval
       type: string
-    - jsonPath: .status.conditions[?(@.type=="Ready")].reason
-      name: Status
-      type: string
     - jsonPath: .status.conditions[?(@.type=="Ready")].status
       name: Ready
       type: string

+ 112 - 1
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: clustersecretstores.external-secrets.io
 spec:
   group: external-secrets.io
@@ -2262,6 +2262,78 @@ spec:
                     - auth
                     - url
                     type: object
+                  delinea:
+                    description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
+                    properties:
+                      clientId:
+                        description: ClientID is the non-secret part of the credential.
+                        properties:
+                          secretRef:
+                            description: SecretRef references a key in a secret that
+                              will be used as value.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            type: object
+                          value:
+                            description: Value can be specified directly to set a
+                              value without using a secret.
+                            type: string
+                        type: object
+                      clientSecret:
+                        description: ClientSecret is the secret part of the credential.
+                        properties:
+                          secretRef:
+                            description: SecretRef references a key in a secret that
+                              will be used as value.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            type: object
+                          value:
+                            description: Value can be specified directly to set a
+                              value without using a secret.
+                            type: string
+                        type: object
+                      tenant:
+                        description: Tenant is the chosen hostname / site name.
+                        type: string
+                      tld:
+                        description: TLD is based on the server location that was
+                          chosen during provisioning. If unset, defaults to "com".
+                        type: string
+                      urlTemplate:
+                        description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
+                        type: string
+                    required:
+                    - clientId
+                    - clientSecret
+                    - tenant
+                    type: object
                   doppler:
                     description: Doppler configures this store to sync secrets using
                       the Doppler provider
@@ -3468,6 +3540,45 @@ spec:
                                   defaults to the namespace of the referent.
                                 type: string
                             type: object
+                          userPass:
+                            description: UserPass authenticates with Vault by passing
+                              username/password pair
+                            properties:
+                              path:
+                                default: user
+                                description: 'Path where the UserPassword authentication
+                                  backend is mounted in Vault, e.g: "user"'
+                                type: string
+                              secretRef:
+                                description: SecretRef to a key in a Secret resource
+                                  containing password for the user used to authenticate
+                                  with Vault using the UserPass authentication method
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                type: object
+                              username:
+                                description: Username is a user name used to authenticate
+                                  using the UserPass Vault authentication method
+                                type: string
+                            required:
+                            - path
+                            - username
+                            type: object
                         type: object
                       caBundle:
                         description: PEM encoded CA bundle used to validate Vault

+ 1 - 1
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: externalsecrets.external-secrets.io
 spec:
   group: external-secrets.io

+ 1 - 1
config/crds/bases/external-secrets.io_pushsecrets.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: pushsecrets.external-secrets.io
 spec:
   group: external-secrets.io

+ 112 - 1
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: secretstores.external-secrets.io
 spec:
   group: external-secrets.io
@@ -2262,6 +2262,78 @@ spec:
                     - auth
                     - url
                     type: object
+                  delinea:
+                    description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
+                    properties:
+                      clientId:
+                        description: ClientID is the non-secret part of the credential.
+                        properties:
+                          secretRef:
+                            description: SecretRef references a key in a secret that
+                              will be used as value.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            type: object
+                          value:
+                            description: Value can be specified directly to set a
+                              value without using a secret.
+                            type: string
+                        type: object
+                      clientSecret:
+                        description: ClientSecret is the secret part of the credential.
+                        properties:
+                          secretRef:
+                            description: SecretRef references a key in a secret that
+                              will be used as value.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            type: object
+                          value:
+                            description: Value can be specified directly to set a
+                              value without using a secret.
+                            type: string
+                        type: object
+                      tenant:
+                        description: Tenant is the chosen hostname / site name.
+                        type: string
+                      tld:
+                        description: TLD is based on the server location that was
+                          chosen during provisioning. If unset, defaults to "com".
+                        type: string
+                      urlTemplate:
+                        description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
+                        type: string
+                    required:
+                    - clientId
+                    - clientSecret
+                    - tenant
+                    type: object
                   doppler:
                     description: Doppler configures this store to sync secrets using
                       the Doppler provider
@@ -3468,6 +3540,45 @@ spec:
                                   defaults to the namespace of the referent.
                                 type: string
                             type: object
+                          userPass:
+                            description: UserPass authenticates with Vault by passing
+                              username/password pair
+                            properties:
+                              path:
+                                default: user
+                                description: 'Path where the UserPassword authentication
+                                  backend is mounted in Vault, e.g: "user"'
+                                type: string
+                              secretRef:
+                                description: SecretRef to a key in a Secret resource
+                                  containing password for the user used to authenticate
+                                  with Vault using the UserPass authentication method
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                type: object
+                              username:
+                                description: Username is a user name used to authenticate
+                                  using the UserPass Vault authentication method
+                                type: string
+                            required:
+                            - path
+                            - username
+                            type: object
                         type: object
                       caBundle:
                         description: PEM encoded CA bundle used to validate Vault

+ 1 - 1
config/crds/bases/generators.external-secrets.io_acraccesstokens.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: acraccesstokens.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io

+ 1 - 1
config/crds/bases/generators.external-secrets.io_ecrauthorizationtokens.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: ecrauthorizationtokens.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io

+ 1 - 1
config/crds/bases/generators.external-secrets.io_fakes.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: fakes.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io

+ 1 - 1
config/crds/bases/generators.external-secrets.io_gcraccesstokens.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: gcraccesstokens.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io

+ 1 - 1
config/crds/bases/generators.external-secrets.io_passwords.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: passwords.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io

+ 38 - 1
config/crds/bases/generators.external-secrets.io_vaultdynamicsecrets.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: vaultdynamicsecrets.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io
@@ -504,6 +504,43 @@ spec:
                               defaults to the namespace of the referent.
                             type: string
                         type: object
+                      userPass:
+                        description: UserPass authenticates with Vault by passing
+                          username/password pair
+                        properties:
+                          path:
+                            default: user
+                            description: 'Path where the UserPassword authentication
+                              backend is mounted in Vault, e.g: "user"'
+                            type: string
+                          secretRef:
+                            description: SecretRef to a key in a Secret resource containing
+                              password for the user used to authenticate with Vault
+                              using the UserPass authentication method
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            type: object
+                          username:
+                            description: Username is a user name used to authenticate
+                              using the UserPass Vault authentication method
+                            type: string
+                        required:
+                        - path
+                        - username
+                        type: object
                     type: object
                   caBundle:
                     description: PEM encoded CA bundle used to validate Vault server

+ 2 - 2
deploy/charts/external-secrets/Chart.yaml

@@ -2,8 +2,8 @@ apiVersion: v2
 name: external-secrets
 description: External secret management for Kubernetes
 type: application
-version: "0.9.0"
-appVersion: "v0.9.0"
+version: "0.9.2"
+appVersion: "v0.9.2"
 kubeVersion: ">= 1.19.0-0"
 keywords:
   - kubernetes-external-secrets

+ 4 - 9
deploy/charts/external-secrets/README.md

@@ -4,7 +4,7 @@
 
 [//]: # (README.md generated by gotmpl. DO NOT EDIT.)
 
-![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.9.0](https://img.shields.io/badge/Version-0.9.0-informational?style=flat-square)
+![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.9.2](https://img.shields.io/badge/Version-0.9.2-informational?style=flat-square)
 
 External secret management for Kubernetes
 
@@ -61,6 +61,8 @@ The command removes all the Kubernetes components associated with the chart and
 | certController.prometheus.enabled | bool | `false` | deprecated. will be removed with 0.7.0, use serviceMonitor instead |
 | certController.prometheus.service.port | int | `8080` | deprecated. will be removed with 0.7.0, use serviceMonitor instead |
 | certController.rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. |
+| certController.readinessProbe.address | string | `""` | Address for readiness probe |
+| certController.readinessProbe.port | int | `8081` | ReadinessProbe port for kubelet |
 | certController.replicaCount | int | `1` |  |
 | certController.requeueInterval | string | `"5m"` |  |
 | certController.resources | object | `{}` |  |
@@ -76,10 +78,6 @@ The command removes all the Kubernetes components associated with the chart and
 | certController.serviceAccount.create | bool | `true` | Specifies whether a service account should be created. |
 | certController.serviceAccount.extraLabels | object | `{}` | Extra Labels to add to the service account. |
 | certController.serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. |
-| certController.serviceMonitor.additionalLabels | object | `{}` | Additional labels |
-| certController.serviceMonitor.enabled | bool | `false` | Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics |
-| certController.serviceMonitor.interval | string | `"30s"` | Interval to scrape metrics |
-| certController.serviceMonitor.scrapeTimeout | string | `"25s"` | Timeout if metrics can't be retrieved in given time interval |
 | certController.tolerations | list | `[]` |  |
 | certController.topologySpreadConstraints | list | `[]` |  |
 | commonLabels | object | `{}` | Additional labels added to all helm chart resources. |
@@ -119,6 +117,7 @@ The command removes all the Kubernetes components associated with the chart and
 | priorityClassName | string | `""` | Pod priority class name. |
 | processClusterExternalSecret | bool | `true` | if true, the operator will process cluster external secret. Else, it will ignore them. |
 | processClusterStore | bool | `true` | if true, the operator will process cluster store. Else, it will ignore them. |
+| processPushSecret | bool | `true` | if true, the operator will process push secret. Else, it will ignore them. |
 | prometheus.enabled | bool | `false` | deprecated. will be removed with 0.7.0, use serviceMonitor instead. |
 | prometheus.service.port | int | `8080` | deprecated. will be removed with 0.7.0, use serviceMonitor instead. |
 | rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. |
@@ -204,9 +203,5 @@ The command removes all the Kubernetes components associated with the chart and
 | webhook.serviceAccount.create | bool | `true` | Specifies whether a service account should be created. |
 | webhook.serviceAccount.extraLabels | object | `{}` | Extra Labels to add to the service account. |
 | webhook.serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. |
-| webhook.serviceMonitor.additionalLabels | object | `{}` | Additional labels |
-| webhook.serviceMonitor.enabled | bool | `false` | Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics |
-| webhook.serviceMonitor.interval | string | `"30s"` | Interval to scrape metrics |
-| webhook.serviceMonitor.scrapeTimeout | string | `"25s"` | Timeout if metrics can't be retrieved in given time interval |
 | webhook.tolerations | list | `[]` |  |
 | webhook.topologySpreadConstraints | list | `[]` |  |

+ 2 - 0
deploy/charts/external-secrets/templates/cert-controller-deployment.yaml

@@ -54,6 +54,8 @@ spec:
           - --service-namespace={{ .Release.Namespace }}
           - --secret-name={{ include "external-secrets.fullname" . }}-webhook
           - --secret-namespace={{ .Release.Namespace }}
+          - --metrics-addr=:{{ .Values.certController.prometheus.service.port }}
+          - --healthz-addr={{ .Values.certController.readinessProbe.address }}:{{ .Values.certController.readinessProbe.port }}
           {{ if not .Values.crds.createClusterSecretStore -}}
           - --crd-names=externalsecrets.external-secrets.io
           - --crd-names=secretstores.external-secrets.io

+ 3 - 0
deploy/charts/external-secrets/templates/deployment.yaml

@@ -65,6 +65,9 @@ spec:
             {{- if not .Values.processClusterExternalSecret }}
           - --enable-cluster-external-secret-reconciler=false
             {{- end }}
+            {{- if not .Values.processPushSecret }}
+          - --enable-push-secret-reconciler=false
+            {{- end }}
           {{- end }}
           {{- if .Values.controllerClass }}
           - --controller-class={{ .Values.controllerClass }}

+ 1 - 1
deploy/charts/external-secrets/templates/webhook-poddisruptionbudget.yaml

@@ -6,7 +6,7 @@ metadata:
   namespace: {{ .Release.Namespace | quote }}
   labels:
     {{- include "external-secrets-webhook.labels" . | nindent 4 }}
-    external-secrets.io/component : webhook
+    external-secrets.io/component: webhook
 spec:
   {{- if .Values.webhook.podDisruptionBudget.minAvailable }}
   minAvailable: {{ .Values.webhook.podDisruptionBudget.minAvailable }}

+ 5 - 3
deploy/charts/external-secrets/tests/__snapshot__/cert_controller_test.yaml.snap

@@ -7,8 +7,8 @@ should match snapshot of default values:
         app.kubernetes.io/instance: RELEASE-NAME
         app.kubernetes.io/managed-by: Helm
         app.kubernetes.io/name: external-secrets-cert-controller
-        app.kubernetes.io/version: v0.9.0
-        helm.sh/chart: external-secrets-0.9.0
+        app.kubernetes.io/version: v0.9.2
+        helm.sh/chart: external-secrets-0.9.2
       name: RELEASE-NAME-external-secrets-cert-controller
       namespace: NAMESPACE
     spec:
@@ -33,7 +33,9 @@ should match snapshot of default values:
                 - --service-namespace=NAMESPACE
                 - --secret-name=RELEASE-NAME-external-secrets-webhook
                 - --secret-namespace=NAMESPACE
-              image: ghcr.io/external-secrets/external-secrets:v0.9.0
+                - --metrics-addr=:8080
+                - --healthz-addr=:8081
+              image: ghcr.io/external-secrets/external-secrets:v0.9.2
               imagePullPolicy: IfNotPresent
               name: cert-controller
               ports:

+ 3 - 3
deploy/charts/external-secrets/tests/__snapshot__/controller_test.yaml.snap

@@ -7,8 +7,8 @@ should match snapshot of default values:
         app.kubernetes.io/instance: RELEASE-NAME
         app.kubernetes.io/managed-by: Helm
         app.kubernetes.io/name: external-secrets
-        app.kubernetes.io/version: v0.9.0
-        helm.sh/chart: external-secrets-0.9.0
+        app.kubernetes.io/version: v0.9.2
+        helm.sh/chart: external-secrets-0.9.2
       name: RELEASE-NAME-external-secrets
       namespace: NAMESPACE
     spec:
@@ -28,7 +28,7 @@ should match snapshot of default values:
           containers:
             - args:
                 - --concurrent=1
-              image: ghcr.io/external-secrets/external-secrets:v0.9.0
+              image: ghcr.io/external-secrets/external-secrets:v0.9.2
               imagePullPolicy: IfNotPresent
               name: external-secrets
               ports:

+ 85 - 1
deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap

@@ -4,7 +4,7 @@ should match snapshot of default values:
     kind: CustomResourceDefinition
     metadata:
       annotations:
-        controller-gen.kubebuilder.io/version: v0.12.0
+        controller-gen.kubebuilder.io/version: v0.12.1
       name: secretstores.external-secrets.io
     spec:
       conversion:
@@ -1656,6 +1656,63 @@ should match snapshot of default values:
                             - auth
                             - url
                           type: object
+                        delinea:
+                          description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
+                          properties:
+                            clientId:
+                              description: ClientID is the non-secret part of the credential.
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            clientSecret:
+                              description: ClientSecret is the secret part of the credential.
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            tenant:
+                              description: Tenant is the chosen hostname / site name.
+                              type: string
+                            tld:
+                              description: TLD is based on the server location that was chosen during provisioning. If unset, defaults to "com".
+                              type: string
+                            urlTemplate:
+                              description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
+                              type: string
+                          required:
+                            - clientId
+                            - clientSecret
+                            - tenant
+                          type: object
                         doppler:
                           description: Doppler configures this store to sync secrets using the Doppler provider
                           properties:
@@ -2522,6 +2579,33 @@ should match snapshot of default values:
                                       description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
                                       type: string
                                   type: object
+                                userPass:
+                                  description: UserPass authenticates with Vault by passing username/password pair
+                                  properties:
+                                    path:
+                                      default: user
+                                      description: 'Path where the UserPassword authentication backend is mounted in Vault, e.g: "user"'
+                                      type: string
+                                    secretRef:
+                                      description: SecretRef to a key in a Secret resource containing password for the user used to authenticate with Vault using the UserPass authentication method
+                                      properties:
+                                        key:
+                                          description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                          type: string
+                                        name:
+                                          description: The name of the Secret resource being referred to.
+                                          type: string
+                                        namespace:
+                                          description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                          type: string
+                                      type: object
+                                    username:
+                                      description: Username is a user name used to authenticate using the UserPass Vault authentication method
+                                      type: string
+                                  required:
+                                    - path
+                                    - username
+                                  type: object
                               type: object
                             caBundle:
                               description: PEM encoded CA bundle used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection.

+ 5 - 5
deploy/charts/external-secrets/tests/__snapshot__/webhook_test.yaml.snap

@@ -7,8 +7,8 @@ should match snapshot of default values:
         app.kubernetes.io/instance: RELEASE-NAME
         app.kubernetes.io/managed-by: Helm
         app.kubernetes.io/name: external-secrets-webhook
-        app.kubernetes.io/version: v0.9.0
-        helm.sh/chart: external-secrets-0.9.0
+        app.kubernetes.io/version: v0.9.2
+        helm.sh/chart: external-secrets-0.9.2
       name: RELEASE-NAME-external-secrets-webhook
       namespace: NAMESPACE
     spec:
@@ -34,7 +34,7 @@ should match snapshot of default values:
                 - --check-interval=5m
                 - --metrics-addr=:8080
                 - --healthz-addr=:8081
-              image: ghcr.io/external-secrets/external-secrets:v0.9.0
+              image: ghcr.io/external-secrets/external-secrets:v0.9.2
               imagePullPolicy: IfNotPresent
               name: webhook
               ports:
@@ -78,8 +78,8 @@ should match snapshot of default values:
         app.kubernetes.io/instance: RELEASE-NAME
         app.kubernetes.io/managed-by: Helm
         app.kubernetes.io/name: external-secrets-webhook
-        app.kubernetes.io/version: v0.9.0
+        app.kubernetes.io/version: v0.9.2
         external-secrets.io/component: webhook
-        helm.sh/chart: external-secrets-0.9.0
+        helm.sh/chart: external-secrets-0.9.2
       name: RELEASE-NAME-external-secrets-webhook
       namespace: NAMESPACE

+ 14 - 0
deploy/charts/external-secrets/tests/cert_controller_test.yaml

@@ -47,3 +47,17 @@ tests:
       - equal:
           path: spec.template.spec.hostNetwork
           value: true
+  - it: should override readinessProbe port
+    set:
+      certController.readinessProbe.port: 8082
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].args[7]
+          value: "--healthz-addr=:8082"
+  - it: should override metrics port
+    set:
+      certController.prometheus.service.port: 8888
+    asserts:
+      - equal:
+          path: spec.template.spec.containers[0].args[6]
+          value: "--metrics-addr=:8888"

+ 9 - 26
deploy/charts/external-secrets/values.yaml

@@ -59,6 +59,9 @@ processClusterExternalSecret: true
 # -- if true, the operator will process cluster store. Else, it will ignore them.
 processClusterStore: true
 
+# -- if true, the operator will process push secret. Else, it will ignore them.
+processPushSecret: true
+
 # -- Specifies whether an external secret operator deployment be created.
 createOperator: true
 
@@ -303,19 +306,6 @@ webhook:
       # -- deprecated. will be removed with 0.7.0, use serviceMonitor instead
       port: 8080
 
-  serviceMonitor:
-    # -- Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics
-    enabled: false
-
-    # -- Additional labels
-    additionalLabels: {}
-
-    # --  Interval to scrape metrics
-    interval: 30s
-
-    # -- Timeout if metrics can't be retrieved in given time interval
-    scrapeTimeout: 25s
-
   metrics:
     service:
       # -- Enable if you use another monitoring tool than Prometheus to scrape the metrics
@@ -435,19 +425,6 @@ certController:
       # -- deprecated. will be removed with 0.7.0, use serviceMonitor instead
       port: 8080
 
-  serviceMonitor:
-    # -- Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics
-    enabled: false
-
-    # -- Additional labels
-    additionalLabels: {}
-
-    # --  Interval to scrape metrics
-    interval: 30s
-
-    # -- Timeout if metrics can't be retrieved in given time interval
-    scrapeTimeout: 25s
-
   metrics:
     service:
       # -- Enable if you use another monitoring tool than Prometheus to scrape the metrics
@@ -459,6 +436,12 @@ certController:
       # -- Additional service annotations
       annotations: {}
 
+  readinessProbe:
+    # -- Address for readiness probe
+    address: ""
+    # -- ReadinessProbe port for kubelet
+    port: 8081
+
     ## -- Extra environment variables to add to container.
   extraEnv: []
 

+ 208 - 16
deploy/crds/bundle.yaml

@@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: clusterexternalsecrets.external-secrets.io
 spec:
   group: external-secrets.io
@@ -18,15 +18,12 @@ spec:
   scope: Cluster
   versions:
     - additionalPrinterColumns:
-        - jsonPath: .spec.secretStoreRef.name
+        - jsonPath: .spec.externalSecretSpec.secretStoreRef.name
           name: Store
           type: string
-        - jsonPath: .spec.refreshInterval
+        - jsonPath: .spec.refreshTime
           name: Refresh Interval
           type: string
-        - jsonPath: .status.conditions[?(@.type=="Ready")].reason
-          name: Status
-          type: string
         - jsonPath: .status.conditions[?(@.type=="Ready")].status
           name: Ready
           type: string
@@ -470,7 +467,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: clustersecretstores.external-secrets.io
 spec:
   group: external-secrets.io
@@ -2112,6 +2109,63 @@ spec:
                         - auth
                         - url
                       type: object
+                    delinea:
+                      description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
+                      properties:
+                        clientId:
+                          description: ClientID is the non-secret part of the credential.
+                          properties:
+                            secretRef:
+                              description: SecretRef references a key in a secret that will be used as value.
+                              properties:
+                                key:
+                                  description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                  type: string
+                              type: object
+                            value:
+                              description: Value can be specified directly to set a value without using a secret.
+                              type: string
+                          type: object
+                        clientSecret:
+                          description: ClientSecret is the secret part of the credential.
+                          properties:
+                            secretRef:
+                              description: SecretRef references a key in a secret that will be used as value.
+                              properties:
+                                key:
+                                  description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                  type: string
+                              type: object
+                            value:
+                              description: Value can be specified directly to set a value without using a secret.
+                              type: string
+                          type: object
+                        tenant:
+                          description: Tenant is the chosen hostname / site name.
+                          type: string
+                        tld:
+                          description: TLD is based on the server location that was chosen during provisioning. If unset, defaults to "com".
+                          type: string
+                        urlTemplate:
+                          description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
+                          type: string
+                      required:
+                        - clientId
+                        - clientSecret
+                        - tenant
+                      type: object
                     doppler:
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       properties:
@@ -2978,6 +3032,33 @@ spec:
                                   description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
                                   type: string
                               type: object
+                            userPass:
+                              description: UserPass authenticates with Vault by passing username/password pair
+                              properties:
+                                path:
+                                  default: user
+                                  description: 'Path where the UserPassword authentication backend is mounted in Vault, e.g: "user"'
+                                  type: string
+                                secretRef:
+                                  description: SecretRef to a key in a Secret resource containing password for the user used to authenticate with Vault using the UserPass authentication method
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                username:
+                                  description: Username is a user name used to authenticate using the UserPass Vault authentication method
+                                  type: string
+                              required:
+                                - path
+                                - username
+                              type: object
                           type: object
                         caBundle:
                           description: PEM encoded CA bundle used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection.
@@ -3261,7 +3342,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: externalsecrets.external-secrets.io
 spec:
   group: external-secrets.io
@@ -3897,7 +3978,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: pushsecrets.external-secrets.io
 spec:
   group: external-secrets.io
@@ -4116,7 +4197,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: secretstores.external-secrets.io
 spec:
   group: external-secrets.io
@@ -5758,6 +5839,63 @@ spec:
                         - auth
                         - url
                       type: object
+                    delinea:
+                      description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
+                      properties:
+                        clientId:
+                          description: ClientID is the non-secret part of the credential.
+                          properties:
+                            secretRef:
+                              description: SecretRef references a key in a secret that will be used as value.
+                              properties:
+                                key:
+                                  description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                  type: string
+                              type: object
+                            value:
+                              description: Value can be specified directly to set a value without using a secret.
+                              type: string
+                          type: object
+                        clientSecret:
+                          description: ClientSecret is the secret part of the credential.
+                          properties:
+                            secretRef:
+                              description: SecretRef references a key in a secret that will be used as value.
+                              properties:
+                                key:
+                                  description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                  type: string
+                              type: object
+                            value:
+                              description: Value can be specified directly to set a value without using a secret.
+                              type: string
+                          type: object
+                        tenant:
+                          description: Tenant is the chosen hostname / site name.
+                          type: string
+                        tld:
+                          description: TLD is based on the server location that was chosen during provisioning. If unset, defaults to "com".
+                          type: string
+                        urlTemplate:
+                          description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
+                          type: string
+                      required:
+                        - clientId
+                        - clientSecret
+                        - tenant
+                      type: object
                     doppler:
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       properties:
@@ -6624,6 +6762,33 @@ spec:
                                   description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
                                   type: string
                               type: object
+                            userPass:
+                              description: UserPass authenticates with Vault by passing username/password pair
+                              properties:
+                                path:
+                                  default: user
+                                  description: 'Path where the UserPassword authentication backend is mounted in Vault, e.g: "user"'
+                                  type: string
+                                secretRef:
+                                  description: SecretRef to a key in a Secret resource containing password for the user used to authenticate with Vault using the UserPass authentication method
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                username:
+                                  description: Username is a user name used to authenticate using the UserPass Vault authentication method
+                                  type: string
+                              required:
+                                - path
+                                - username
+                              type: object
                           type: object
                         caBundle:
                           description: PEM encoded CA bundle used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection.
@@ -6907,7 +7072,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: acraccesstokens.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io
@@ -7047,7 +7212,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: ecrauthorizationtokens.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io
@@ -7174,7 +7339,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: fakes.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io
@@ -7234,7 +7399,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: gcraccesstokens.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io
@@ -7341,7 +7506,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: passwords.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io
@@ -7418,7 +7583,7 @@ apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.12.0
+    controller-gen.kubebuilder.io/version: v0.12.1
   name: vaultdynamicsecrets.generators.external-secrets.io
 spec:
   group: generators.external-secrets.io
@@ -7765,6 +7930,33 @@ spec:
                               description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
                               type: string
                           type: object
+                        userPass:
+                          description: UserPass authenticates with Vault by passing username/password pair
+                          properties:
+                            path:
+                              default: user
+                              description: 'Path where the UserPassword authentication backend is mounted in Vault, e.g: "user"'
+                              type: string
+                            secretRef:
+                              description: SecretRef to a key in a Secret resource containing password for the user used to authenticate with Vault using the UserPass authentication method
+                              properties:
+                                key:
+                                  description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                  type: string
+                                name:
+                                  description: The name of the Secret resource being referred to.
+                                  type: string
+                                namespace:
+                                  description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                  type: string
+                              type: object
+                            username:
+                              description: Username is a user name used to authenticate using the UserPass Vault authentication method
+                              type: string
+                          required:
+                            - path
+                            - username
+                          type: object
                       type: object
                     caBundle:
                       description: PEM encoded CA bundle used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection.

+ 30 - 16
docs/api/metrics.md

@@ -5,27 +5,41 @@ hide:
 
 # Metrics
 
-The External Secrets Operator exposes its Prometheus metrics in the `/metrics` path. To enable it, set the `serviceMonitor.enabled` Helm flag to `true`. In addition you can also set `webhook.serviceMonitor.enabled=true` and `certController.serviceMonitor.enabled=true` to create `ServiceMonitor` resources for the other components.
+The External Secrets Operator exposes its Prometheus metrics in the `/metrics` path. To enable it, set the `serviceMonitor.enabled` Helm flag to `true`.
 
 If you are using a different monitoring tool that also needs a `/metrics` endpoint, you can set the `metrics.service.enabled` Helm flag to `true`. In addition you can also set `webhook.metrics.service.enabled` and `certController.metrics.service.enabled` to scrape the other components.
 
-The Operator has the metrics inherited from Kubebuilder plus some custom metrics with the `externalsecret` prefix.
+The Operator has [the controller-runtime metrics inherited from kubebuilder](https://book.kubebuilder.io/reference/metrics-reference.html) plus some custom metrics with a resource name prefix, such as `externalsecret_`.
 
-## External Secret Metrics
+## Cluster External Secret Metrics
+| Name                                       | Type  | Description                                                |
+|--------------------------------------------|-------|------------------------------------------------------------|
+| `clusterexternalsecret_status_condition`   | Gauge | The status condition of a specific Cluster External Secret |
+| `clusterexternalsecret_reconcile_duration` | Gauge | The duration time to reconcile the Cluster External Secret |
 
-| Name                                           | Type      | Description                                                                                                                                                                                                            |
-| ---------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `externalsecret_provider_api_calls_count`      | Counter   | Number of API calls made to an upstream secret provider API. The metric provides a `provider`, `call` and `status` labels.                                                                                             |
-| `externalsecret_sync_calls_total`              | Counter   | Total number of the External Secret sync calls                                                                                                                                                                         |
-| `externalsecret_sync_calls_error`              | Counter   | Total number of the External Secret sync errors                                                                                                                                                                        |
-| `externalsecret_status_condition`              | Gauge     | The status condition of a specific External Secret                                                                                                                                                                     |
-| `externalsecret_reconcile_duration`            | Gauge     | The duration time to reconcile the External Secret                                                                                                                                                                     |
-| `controller_runtime_reconcile_total`           | Counter   | Holds the totalnumber of reconciliations per controller. It has two labels. controller label refers to the controller name and result label refers to the reconcile result i.e success, error, requeue, requeue_after. |
-| `controller_runtime_reconcile_errors_total`    | Counter   | Total number of reconcile errors per controller                                                                                                                                                                        |
-| `controller_runtime_reconcile_time_seconds`    | Histogram | Length of time per reconcile per controller                                                                                                                                                                            |
-| `controller_runtime_reconcile_queue_length`    | Gauge     | Length of reconcile queue per controller                                                                                                                                                                               |
-| `controller_runtime_max_concurrent_reconciles` | Gauge     | Maximum number of concurrent reconciles per controller                                                                                                                                                                 |
-| `controller_runtime_active_workers`            | Gauge     | Number of currently used workers per controller                                                                                                                                                                        |
+## External Secret Metrics
+| Name                                           | Type      | Description                                                                                                                                                                                                             |
+|------------------------------------------------|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `externalsecret_provider_api_calls_count`      | Counter   | Number of API calls made to an upstream secret provider API. The metric provides a `provider`, `call` and `status` labels.                                                                                              |
+| `externalsecret_sync_calls_total`              | Counter   | Total number of the External Secret sync calls                                                                                                                                                                          |
+| `externalsecret_sync_calls_error`              | Counter   | Total number of the External Secret sync errors                                                                                                                                                                         |
+| `externalsecret_status_condition`              | Gauge     | The status condition of a specific External Secret                                                                                                                                                                      |
+| `externalsecret_reconcile_duration`            | Gauge     | The duration time to reconcile the External Secret                                                                                                                                                                      |
+
+## Cluster Secret Store Metrics
+| Name                                    | Type  | Description                                             |
+|-----------------------------------------|-------|---------------------------------------------------------|
+| `clustersecretstore_status_condition`   | Gauge | The status condition of a specific Cluster Secret Store |
+| `clustersecretstore_reconcile_duration` | Gauge | The duration time to reconcile the Cluster Secret Store |
+
+# Secret Store Metrics
+| Name                             | Type  | Description                                     |
+|----------------------------------|-------|-------------------------------------------------|
+| `secretstore_status_condition`   | Gauge | The status condition of a specific Secret Store |
+| `secretstore_reconcile_duration` | Gauge | The duration time to reconcile the Secret Store |
+
+## Controller Runtime Metrics
+See [the kubebuilder documentation](https://book.kubebuilder.io/reference/metrics-reference.html) on the default exported metrics by controller-runtime.
 
 ## Dashboard
 

+ 215 - 1
docs/api/spec.md

@@ -1775,6 +1775,132 @@ ConjurAuth
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1beta1.DelineaProvider">DelineaProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>See <a href="https://github.com/DelineaXPM/dsv-sdk-go/blob/main/vault/vault.go">https://github.com/DelineaXPM/dsv-sdk-go/blob/main/vault/vault.go</a>.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>clientId</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.DelineaProviderSecretRef">
+DelineaProviderSecretRef
+</a>
+</em>
+</td>
+<td>
+<p>ClientID is the non-secret part of the credential.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>clientSecret</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.DelineaProviderSecretRef">
+DelineaProviderSecretRef
+</a>
+</em>
+</td>
+<td>
+<p>ClientSecret is the secret part of the credential.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>tenant</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Tenant is the chosen hostname / site name.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>urlTemplate</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>URLTemplate
+If unset, defaults to &ldquo;https://%s.secretsvaultcloud.%s/v1/%s%s&rdquo;.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>tld</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>TLD is based on the server location that was chosen during provisioning.
+If unset, defaults to &ldquo;com&rdquo;.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.DelineaProviderSecretRef">DelineaProviderSecretRef
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.DelineaProvider">DelineaProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>value</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Value can be specified directly to set a value without using a secret.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>secretRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>SecretRef references a key in a secret that will be used as value.</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.DopplerAuth">DopplerAuth
 </h3>
 <p>
@@ -4865,6 +4991,21 @@ ConjurProvider
 <p>Conjur configures this store to sync secrets using conjur provider</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>delinea</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.DelineaProvider">
+DelineaProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Delinea DevOps Secrets Vault
+<a href="https://docs.delinea.com/online-help/products/devops-secrets-vault/current">https://docs.delinea.com/online-help/products/devops-secrets-vault/current</a></p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
@@ -5749,7 +5890,7 @@ resource is used as the app role secret.</p>
 </p>
 <p>
 <p>VaultAuth is the configuration used to authenticate with a Vault server.
-Only one of <code>tokenSecretRef</code>, <code>appRole</code>,  <code>kubernetes</code>, <code>ldap</code>, <code>jwt</code> or <code>cert</code>
+Only one of <code>tokenSecretRef</code>, <code>appRole</code>,  <code>kubernetes</code>, <code>ldap</code>, <code>userPass</code>, <code>jwt</code> or <code>cert</code>
 can be specified.</p>
 </p>
 <table>
@@ -5864,6 +6005,20 @@ VaultIamAuth
 AWS IAM authentication method</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>userPass</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.VaultUserPassAuth">
+VaultUserPassAuth
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>UserPass authenticates with Vault by passing username/password pair</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1beta1.VaultAwsAuth">VaultAwsAuth
@@ -6609,6 +6764,65 @@ the option is enabled serverside.
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1beta1.VaultUserPassAuth">VaultUserPassAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.VaultAuth">VaultAuth</a>)
+</p>
+<p>
+<p>VaultUserPassAuth authenticates with Vault using UserPass authentication method,
+with the username and password stored in a Kubernetes Secret resource.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>path</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Path where the UserPassword authentication backend is mounted
+in Vault, e.g: &ldquo;user&rdquo;</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>username</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Username is a user name used to authenticate using the UserPass Vault
+authentication method</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>secretRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<p>SecretRef to a key in a Secret resource containing password for the
+user used to authenticate with Vault using the UserPass authentication
+method</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.WebhookCAProvider">WebhookCAProvider
 </h3>
 <p>

+ 8 - 3
docs/contributing/release.md

@@ -4,16 +4,21 @@ The external-secrets project is released on a as-needed basis. Feel free to open
 
 ## Release ESO
 
+When doing a release it's best to start with  with the ["Create Release" issue template](https://github.com/external-secrets/external-secrets/issues/new?assignees=&labels=area%2Frelease&projects=&template=create_release.md&title=Release+x.y), it has a checklist to go over.
+
+⚠️ Note: when releasing multiple versions, make sure to first release the "old" version, then the newer version.
+Otherwise the `latest` documentation will point to the older version. Also avoid to release both versions at the same time to avoid race conditions in the CI pipeline (updating docs, GitHub Release, helm chart release).
+
 1. Run `Create Release` Action to create a new release, pass in the desired version number to release.
-    1. note: choose the right `branch` to execute the action: use `main` when creating a new release. Use `release-x.y` when you want to bump a LTS release.
+    1. choose the right `branch` to execute the action: use `main` when creating a new release. Use `release-x.y` when you want to bump a LTS release.
+    1. ⚠️ make sure that CI on the relevant branch has completed the docker build/push jobs. Otherwise an old image will be promoted.
 1. GitHub Release, Changelog will be created by the `release.yml` workflow which also promotes the container image.
 1. update Helm Chart, see below
 1. update OLM bundle, see [helm-operator docs](https://github.com/external-secrets/external-secrets-helm-operator/blob/main/docs/release.md#operatorhubio)
 
 ## Release Helm Chart
 
-1. Update `version` and/or `appVersion` in `Chart.yaml` and run `make helm.docs helm.update.appversion`
-1. If there is any CRD change, run `make helm.test.update` and `make helm.test`
+1. Update `version` and/or `appVersion` in `Chart.yaml` and run `make helm.docs helm.update.appversion helm.test.update helm.test`
 1. push to branch and open pr
 1. run `/ok-to-test-managed` commands for all cloud providers
 1. merge PR if everyhing is green

+ 4 - 4
docs/guides/common-k8s-secret-types.md

@@ -32,7 +32,7 @@ This will generate a valid dockerconfigjson secret for you to use!
 You can get the final value with:
 
 ```bash
-kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data\.dockerconfigjson}" | base64 -d
+kubectl get secret secret-to-be-created -n <namespace> -o jsonpath="{.data\.dockerconfigjson}" | base64 -d
 ```
 
 ## TLS Cert example
@@ -56,8 +56,8 @@ And now you can create an ExternalSecret that gets it. You will end up with a k8
 You can get their values with:
 
 ```bash
-kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data.tls\.crt}" | base64 -d
-kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data.tls\.key}" | base64 -d
+kubectl get secret secret-to-be-created -n <namespace> -o jsonpath="{.data.tls\.crt}" | base64 -d
+kubectl get secret secret-to-be-created -n <namespace> -o jsonpath="{.data.tls\.key}" | base64 -d
 ```
 
 
@@ -76,7 +76,7 @@ And now you can create an ExternalSecret that gets it. You will end up with a k8
 You can get the privkey value with:
 
 ```bash
-kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath="{.data.ssh-privatekey}" | base64 -d
+kubectl get secret secret-to-be-created -n <namespace> -o jsonpath="{.data.ssh-privatekey}" | base64 -d
 ```
 
 ## More examples

+ 1 - 3
docs/guides/ownership-deletion-policy.md

@@ -25,9 +25,7 @@ The operator does not create or update the secret, this is basically a no-op.
 ## Deletion Policy
 DeletionPolicy defines what should happen if a given secret gets deleted **from the provider**.
 
-DeletionPolicy is only supported on the following providers. Please feel free to contribute more:
-* AWS Secrets Manager
-* AWS Parameter Store
+DeletionPolicy is only supported on the specific providers, please refer to our [stability/support table](../introduction/stability-support.md).
 
 ### Retain (default)
 Retain will retain the secret if all provider secrets have been deleted.

+ 1 - 1
docs/index.md

@@ -7,7 +7,7 @@ secret management systems like [AWS Secrets
 Manager](https://aws.amazon.com/secrets-manager/), [HashiCorp
 Vault](https://www.vaultproject.io/), [Google Secrets
 Manager](https://cloud.google.com/secret-manager), [Azure Key
-Vault](https://azure.microsoft.com/en-us/services/key-vault/), [IBM Cloud Secrets Manager](https://www.ibm.com/cloud/secrets-manager), and many more. The
+Vault](https://azure.microsoft.com/en-us/services/key-vault/), [IBM Cloud Secrets Manager](https://www.ibm.com/cloud/secrets-manager), [CyberArk Conjur](https://www.conjur.org) and many more. The
 operator reads information from external APIs and automatically injects the
 values into a [Kubernetes
 Secret](https://kubernetes.io/docs/concepts/configuration/secret/).

+ 3 - 1
docs/introduction/stability-support.md

@@ -52,6 +52,7 @@ The following table describes the stability level of each provider and who's res
 | [Keeper Security](https://www.keepersecurity.com/)                                                         |   alpha   |                                                                                                                                              [@ppodevlab](https://github.com/ppodevlab) |
 | [Scaleway](https://external-secrets.io/latest/provider/scaleway)                                           |   alpha   |                                                                                                                                                   [@azert9](https://github.com/azert9/) |
 | [Conjur](https://external-secrets.io/latest/provider/conjur)                                               |   alpha   |                                                                                                                                 [@davidh-cyberark](https://github.com/davidh-cyberark/) |
+| [Delinea](https://external-secrets.io/latest/provider/delinea)                                             |   alpha   |                                                                                                                                     [@michaelsauter](https://github.com/michaelsauter/) |
 
 ## Provider Feature Support
 
@@ -65,7 +66,7 @@ The following table show the support for features across different providers.
 | GCP Secret Manager        |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
 | Azure Keyvault            |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
 | Kubernetes                |      x       |      x       |          x           |            x            |        x         |      x      |              x              |
-| IBM Cloud Secrets Manager |      x       |              |                      |                         |        x         |             |                             |
+| IBM Cloud Secrets Manager |      x       |              |          x           |                         |        x         |             |                             |
 | Yandex Lockbox            |              |              |                      |                         |        x         |             |                             |
 | GitLab Variables          |      x       |      x       |                      |                         |        x         |             |                             |
 | Alibaba Cloud KMS         |              |              |                      |                         |        x         |             |                             |
@@ -78,6 +79,7 @@ The following table show the support for features across different providers.
 | Keeper Security           |      x       |              |                      |                         |        x         |      x      |                             |
 | Scaleway                  |      x       |      x       |                      |                         |        x         |      x      |              x              |
 | Conjur                    |              |              |                      |                         |        x         |             |                             |
+| Delinea                   |      x       |              |                      |                         |        x         |             |                             |
 
 ## Support Policy
 

BIN
docs/pictures/cloak-provider-header.png


+ 47 - 0
docs/provider/cloak.md

@@ -0,0 +1,47 @@
+![Cloak End 2 End Encrypted Secrets](../pictures/cloak-provider-header.png)
+
+## Cloak
+
+Sync secrets from the [Cloak Encrypted Secrets Platform](https://cloak.software) to Kubernetes using the External Secrets Operator.
+
+Cloak uses the webhook provider built into the External Secrets Operator but also required a proxy service to handle decrypting secrets when they arrive into your cluster.
+
+## Key Setup
+
+From the Cloak user interface [create a service account](https://cloak.software/docs/getting-started/03-cli/) and store the private key on your file system.
+
+Now create a kubernetes secret in the same namespace as the External Secrets Operator.
+
+```sh
+HISTIGNORE='*kubectl*' kubectl --namespace=external-secrets \
+    create secret generic cloak-key \
+    --from-file=ecdh_private_key=$LOCATION_OF_YOUR_PEM_FILE
+```
+
+## Deploy the decryption proxy
+
+```yaml
+{% include 'cloak-proxy-deployment.yaml' %}
+```
+
+And a Kubernetes Service so External Secrets Operator can access the proxy.
+
+```yaml
+{% include 'cloak-proxy-service.yaml' %}
+```
+
+## Create a secret store
+
+You can now place the configuration in any Kubernetes Namespace.
+
+```yaml
+{% include 'cloak-secret-store.yaml' %}
+```
+
+## Connect a secret to the provider
+
+Each `secretKey` reference in the yaml should point to the name of the secret as it is stored in Cloak.
+
+```yaml
+{% include 'cloak-external-secret.yaml' %}
+```

+ 2 - 2
docs/provider/conjur.md

@@ -29,7 +29,7 @@ Important note: **Creds must live in the same namespace as a SecretStore  - the
 Recommend to save as filename: `conjur-external-secret.yaml`
 
 ```yaml
-{% include 'conjur-external-secret.yaml' %}}
+{% include 'conjur-external-secret.yaml' %}
 ```
 
 ### Create Kubernetes Secrets
@@ -75,7 +75,7 @@ kubectl apply -n external-secrets -f conjur-external-secret.yaml
 ### Getting the K8S Secret
 
 * Login to your Conjur server and verify that your secret exists
-* Review the value of your kubernetes secret to see that it contains the same value from Conjur
+* Review the value of your Kubernetes secret to see that it contains the same value from Conjur
 
 ```shell
 # WARNING: this command will reveal the stored secret in plain text

+ 59 - 0
docs/provider/delinea.md

@@ -0,0 +1,59 @@
+## Delinea DevOps Secrets Vault
+
+External Secrets Operator integrates with [Delinea DevOps Secrets Vault](https://docs.delinea.com/online-help/products/devops-secrets-vault/current).
+
+Please note that the [Delinea Secret Server](https://delinea.com/products/secret-server) product is NOT in scope of this integration.
+
+### Creating a SecretStore
+
+You need client ID, client secret and tenant to authenticate with DSV.
+Both client ID and client secret can be specified either directly in the config, or by referencing a kubernetes secret.
+
+To acquire client ID and client secret, refer to the  [policy management](https://docs.delinea.com/dsv/current/tutorials/policy.md) and [client management](https://docs.delinea.com/dsv/current/usage/cli-ref/client.md) documentation.
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: secret-store
+spec:
+  provider:
+    delinea:
+      tenant: <TENANT>
+      tld: <TLD>
+      clientId:
+        value: <CLIENT_ID>
+      clientSecret:
+        secretRef:
+          name: <NAME_OF_KUBE_SECRET>
+          key: <KEY_IN_KUBE_SECRET>
+```
+
+Both `clientId` and `clientSecret` can either be specified directly via the `value` field or can reference a kubernetes secret.
+
+The `tenant` field must correspond to the host name / site name of your DevOps vault. If you selected a region other than the US you must also specify the TLD, e.g. `tld: eu`.
+
+If required, the URL template (`urlTemplate`) can be customized as well.
+
+### Referencing Secrets
+
+Secrets can be referenced by path. Getting a specific version of a secret is not yet supported.
+
+Note that because all DSV secrets are JSON objects, you must specify `remoteRef.property`. You can access nested values or arrays using [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md).
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+    name: secret
+spec:
+    refreshInterval: 20s
+    secretStoreRef:
+        kind: SecretStore
+        name: secret-store
+    data:
+      - secretKey: <KEY_IN_KUBE_SECRET>
+        remoteRef:
+          key: <SECRET_PATH>
+          property: <JSON_PROPERTY>
+```

+ 13 - 0
docs/provider/hashicorp-vault.md

@@ -272,6 +272,7 @@ We support five different modes for authentication:
 [appRole](https://www.vaultproject.io/docs/auth/approle),
 [kubernetes-native](https://www.vaultproject.io/docs/auth/kubernetes),
 [ldap](https://www.vaultproject.io/docs/auth/ldap),
+[userPass](https://www.vaultproject.io/docs/auth/userpass),
 [jwt/oidc](https://www.vaultproject.io/docs/auth/jwt) and
 [awsAuth](https://developer.hashicorp.com/vault/docs/auth/aws), each one comes with it's own
 trade-offs. Depending on the authentication method you need to adapt your environment.
@@ -322,6 +323,18 @@ in a `Kind=Secret` referenced by the `secretRef`.
 ```
 **NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` in `secretRef` with the namespace where the secret resides.
 
+#### UserPass authentication
+
+[UserPass authentication](https://www.vaultproject.io/docs/auth/userpass) uses
+username/password pair to get an access token. Username is stored directly in
+a `Kind=SecretStore` or `Kind=ClusterSecretStore` resource, password is stored
+in a `Kind=Secret` referenced by the `secretRef`.
+
+```yaml
+{% include 'vault-userpass-store.yaml' %}
+```
+**NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` in `secretRef` with the namespace where the secret resides.
+
 #### JWT/OIDC authentication
 
 [JWT/OIDC](https://www.vaultproject.io/docs/auth/jwt) uses either a

+ 44 - 0
docs/provider/ibm-secrets-manager.md

@@ -208,3 +208,47 @@ The operator will fetch the IBM Secret Manager secret and inject it as a `Kind=S
 ```
 kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.test}' | base64 -d
 ```
+
+### Populating the Kubernetes secret with metadata from IBM Secrets Manager Provider
+ESO can add metadata while creating or updating a Kubernetes secret to be reflected in its labels or annotations. The metadata could be any of the fields that are supported and returned in the response by IBM Secrets Manager.
+
+In order for the user to opt-in to adding metadata to secret, an existing optional field `spec.dataFrom.extract.metadataPolicy` can be be set to `Fetch`, its default value being `None`. In addition to this, templating provided be ESO can be leveraged to specify the key-value pairs of the resultant secrets' labels and annotation.
+
+In order for the required metadata to be populated in the Kubernetes secret, combination of below should be provided in the External Secrets resource:
+1. The required metadata should be specified under `template.metadata.labels` or `template.metadata.annotations`.
+2. The required secret data should be specified under `template.data`.
+3. The spec.dataFrom.extract should be specified with details of the Secrets Manager secret with `spec.dataFrom.extract.metadataPolicy` set to `Fetch`.
+Below is an example, where `secret_id` and `updated_at` are the metadata of a secret in IBM Secrets Manager:
+
+```yaml
+{% include 'ibm-external-secret-with-metadata.yaml' %}
+```
+
+While the secret is being reconciled, it will have the secret data along with the required annotations. Below is the example of the secret after reconciliation:
+
+```yaml
+apiVersion: v1
+data:
+  secret: OHE0MFV5MGhQb2FmRjZTOGVva3dPQjRMeVZXeXpWSDlrSWgyR1BiVDZTMyc=
+immutable: false
+kind: Secret
+metadata:
+  annotations:
+    reconcile.external-secrets.io/data-hash: 02217008d13ed228e75cf6d26fe74324
+  creationTimestamp: "2023-05-04T08:41:24Z"
+  annotations:
+    secret_id: 1234
+    updated_at: 2023-05-04T08:57:19Z
+  name: database-credentials
+  namespace: external-secrets
+  ownerReferences:
+  - apiVersion: external-secrets.io/v1beta1
+    blockOwnerDeletion: true
+    controller: true
+    kind: ExternalSecret
+    name: database-credentials
+    uid: c2a018e7-1ac3-421b-bd3b-d9497204f843
+  resourceVersion: "1803567"
+  uid: f5dff604-611b-4d41-9d65-b860c61a0b8d
+type: Opaque
+```

+ 19 - 0
docs/snippets/cloak-external-secret.yaml

@@ -0,0 +1,19 @@
+# Access a secret
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: cloak-example
+spec:
+  refreshInterval: "15m"
+  secretStoreRef:
+    name: cloak-backend
+    kind: SecretStore
+  target:
+    name: example-sync
+  data:
+  - secretKey: access-token
+    remoteRef:
+      key: PULUMI_ACCESS_TOKEN
+  - secretKey: do-access-token
+    remoteRef:
+      key: DIGITALOCEAN_ACCESS_TOKEN

+ 28 - 0
docs/snippets/cloak-proxy-deployment.yaml

@@ -0,0 +1,28 @@
+# The cloak external secrets proxy
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: cloak-external-secrets
+  namespace: external-secrets
+spec:
+  selector:
+    matchLabels:
+      app: cloak-external-secrets
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app: cloak-external-secrets
+    spec:
+      containers:
+      - name: cloak-external-secrets
+        image: purtontech/cloak-external-secrets:latest
+        imagePullPolicy: IfNotPresent
+        env: 
+          - name: ECDH_PRIVATE_KEY 
+            valueFrom: 
+              secretKeyRef: 
+                name: cloak-key 
+                key: ecdh_private_key 
+        ports:
+        - containerPort: 7105

+ 12 - 0
docs/snippets/cloak-proxy-service.yaml

@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: cloak-external-secrets-service
+  namespace: external-secrets
+spec:
+  selector:
+    app: cloak-external-secrets
+  ports:
+    - protocol: TCP
+      port: 7105
+      targetPort: 7105

+ 15 - 0
docs/snippets/cloak-secret-store.yaml

@@ -0,0 +1,15 @@
+{% raw %}
+# An External secrets webhookl
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: cloak-backend
+spec:
+  provider:
+    webhook:
+      url: "http://cloak-external-secrets-service:7105/{{ .remoteRef.key }}"
+      result:
+        jsonPath: "$.value"
+      headers:
+        Content-Type: application/json
+{%- endraw %}

+ 27 - 0
docs/snippets/ibm-external-secret-with-metadata.yaml

@@ -0,0 +1,27 @@
+{% raw %}
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: database-credentials
+  namespace: external-secrets
+spec:
+  dataFrom:
+  - extract:
+      key: username_password/<SECRET_ID>
+      metadataPolicy: Fetch           # leveraging optional parameter, defaults to None
+    secretKey: username
+  secretStoreRef:
+    kind: SecretStore
+    name: ibm-store
+  target:
+    name: database-credentials
+    template:
+      engineVersion: v2
+      data:
+        secret: "{{ .password }}"
+      metadata:
+        annotations:
+          secret_id: "{{ .id }}"     # adding metadata key whose value would be added to the secret as a label
+          updated_at: "{{ .updated_at }}"
+
+{% endraw %}

+ 21 - 0
docs/snippets/vault-userpass-store.yaml

@@ -0,0 +1,21 @@
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: vault-backend
+  namespace: example
+spec:
+  provider:
+    vault:
+      server: "https://vault.acme.org"
+      path: "secret"
+      version: "v2"
+      auth:
+        # VaultUserPass authenticates with Vault using the UserPass auth mechanism
+        # https://www.vaultproject.io/docs/auth/userpass
+        userPass:
+          # Path where the UserPass authentication backend is mounted
+          path: "userpass"
+          username: "username"
+          secretRef:
+            name: "my-secret"
+            key: "password"

+ 73 - 1
docs/spec.md

@@ -5010,7 +5010,7 @@ resource is used as the app role secret.</p>
 </p>
 <p>
 <p>VaultAuth is the configuration used to authenticate with a Vault server.
-Only one of <code>tokenSecretRef</code>, <code>appRole</code>,  <code>kubernetes</code>, <code>ldap</code>, <code>jwt</code> or <code>cert</code>
+Only one of <code>tokenSecretRef</code>, <code>appRole</code>,  <code>kubernetes</code>, <code>ldap</code>, <code>userPass</code>, <code>jwt</code> or <code>cert</code>
 can be specified.</p>
 </p>
 <table>
@@ -5080,6 +5080,21 @@ the LDAP authentication method</p>
 </tr>
 <tr>
 <td>
+<code>userPass</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.VaultUserPassAuth">
+VaultUserPassAuth
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>UserPass authenticates with Vault by passing username/password pair using
+the userPass authentication method</p>
+</td>
+</tr>
+<tr>
+<td>
 <code>jwt</code></br>
 <em>
 <a href="#external-secrets.io/v1beta1.VaultJwtAuth">
@@ -5443,6 +5458,63 @@ method</p>
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1beta1.VaultUserPassAuth">VaultUserPassAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.VaultAuth">VaultAuth</a>)
+</p>
+<p>
+<p>VaultUserPassAuth authenticates with Vault using the UserPass authentication method,
+with the username and password stored in a Kubernetes Secret resource.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>path</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Path where the UserPass authentication backend is mounted
+in Vault, e.g: &ldquo;userpass&rdquo;</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>username</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Username is a user name used to authenticate using the UserPass Vault
+authentication method</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>secretRef</code></br>
+<em>
+github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
+</em>
+</td>
+<td>
+<p>SecretRef to a key in a Secret resource containing password for the
+user used to authenticate with Vault using the UserPass authentication
+method</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.VaultProvider">VaultProvider
 </h3>
 <p>

+ 31 - 28
e2e/go.mod

@@ -39,10 +39,11 @@ require (
 	cloud.google.com/go/secretmanager v1.11.1
 	github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
+	github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0
 	github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4
-	github.com/akeylesslabs/akeyless-go/v3 v3.3.12
+	github.com/akeylesslabs/akeyless-go/v3 v3.3.16
 	github.com/aliyun/alibaba-cloud-sdk-go v1.62.271
-	github.com/aws/aws-sdk-go v1.44.294
+	github.com/aws/aws-sdk-go v1.44.314
 	github.com/external-secrets/external-secrets v0.0.0
 	github.com/fluxcd/helm-controller/api v0.22.2
 	github.com/fluxcd/pkg/apis/meta v0.14.2
@@ -52,22 +53,22 @@ require (
 	github.com/onsi/ginkgo/v2 v2.11.0
 	github.com/onsi/gomega v1.27.8
 	github.com/oracle/oci-go-sdk/v56 v56.1.0
-	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17
-	github.com/xanzy/go-gitlab v0.86.0
-	golang.org/x/oauth2 v0.9.0
-	google.golang.org/api v0.129.0
-	k8s.io/api v0.27.3
-	k8s.io/apiextensions-apiserver v0.27.3
-	k8s.io/apimachinery v0.27.3
+	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20
+	github.com/xanzy/go-gitlab v0.89.0
+	golang.org/x/oauth2 v0.10.0
+	google.golang.org/api v0.134.0
+	k8s.io/api v0.27.4
+	k8s.io/apiextensions-apiserver v0.27.4
+	k8s.io/apimachinery v0.27.4
 	k8s.io/client-go v1.5.2
-	k8s.io/utils v0.0.0-20230505201702-9f6742963106
+	k8s.io/utils v0.0.0-20230726121419-3b25d923346b
 	sigs.k8s.io/controller-runtime v0.15.0
 	sigs.k8s.io/yaml v1.3.0
 	software.sslmate.com/src/go-pkcs12 v0.2.0
 )
 
 require (
-	cloud.google.com/go/compute v1.20.1 // indirect
+	cloud.google.com/go/compute v1.23.0 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/iam v1.1.1 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@@ -91,7 +92,7 @@ require (
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/go-logr/logr v1.2.4 // indirect
-	github.com/go-openapi/jsonpointer v0.19.6 // indirect
+	github.com/go-openapi/jsonpointer v0.20.0 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
 	github.com/go-openapi/swag v0.22.4 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
@@ -102,11 +103,11 @@ require (
 	github.com/google/go-cmp v0.5.9 // 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-20230602150820-91b7bce49751 // indirect
+	github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 // indirect
 	github.com/google/s2a-go v0.1.4 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
-	github.com/googleapis/gax-go/v2 v2.11.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.12.0 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -133,36 +134,38 @@ require (
 	github.com/prometheus/client_golang v1.16.0 // indirect
 	github.com/prometheus/client_model v0.4.0 // indirect
 	github.com/prometheus/common v0.44.0 // indirect
-	github.com/prometheus/procfs v0.11.0 // indirect
+	github.com/prometheus/procfs v0.11.1 // indirect
 	github.com/ryanuber/go-glob v1.0.0 // indirect
 	github.com/sony/gobreaker v0.5.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/tidwall/gjson v1.14.4 // indirect
+	github.com/tidwall/gjson v1.15.0 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.1 // indirect
+	github.com/tidwall/sjson v1.2.5 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	golang.org/x/crypto v0.10.0 // indirect
-	golang.org/x/net v0.11.0 // indirect
-	golang.org/x/sys v0.9.0 // indirect
-	golang.org/x/term v0.9.0 // indirect
-	golang.org/x/text v0.10.0 // indirect
+	golang.org/x/crypto v0.11.0 // indirect
+	golang.org/x/net v0.13.0 // indirect
+	golang.org/x/sync v0.3.0 // indirect
+	golang.org/x/sys v0.10.0 // indirect
+	golang.org/x/term v0.10.0 // indirect
+	golang.org/x/text v0.11.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
-	golang.org/x/tools v0.10.0 // indirect
+	golang.org/x/tools v0.11.1 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 // indirect
-	google.golang.org/grpc v1.56.1 // indirect
+	google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect
+	google.golang.org/grpc v1.57.0 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 // indirect
-	k8s.io/component-base v0.27.3 // indirect
+	k8s.io/component-base v0.27.4 // indirect
 	k8s.io/klog/v2 v2.100.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20230523194449-df37dd07aa00 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
 )

+ 57 - 50
e2e/go.sum

@@ -18,15 +18,15 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
+cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q=
 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 v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
-cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
+cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
+cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
 cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
 cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
@@ -77,19 +77,21 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0 h1:+XXJ43iH4js8LIBr4MUGq1J09ycivNkTNhtn4mFyhY8=
+github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0/go.mod h1:NTdQaRBIRZ/8gIzs010CS/u69aVSmqD1zbESW25y2cE=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4 h1:vTckjyBhHOBiOWSC/oaEU2Oo4OH5eAlQiwKu2RMxsFg=
 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4/go.mod h1:As/RomC2w/fa3y+yHRlVHPmkbP+zrKBFRow41y5dk+E=
-github.com/akeylesslabs/akeyless-go/v3 v3.3.12 h1:LJWtTFQv7bcHhtk9AARywVUoaJtw9A/xcZWV5WkI4iw=
-github.com/akeylesslabs/akeyless-go/v3 v3.3.12/go.mod h1:xcSXQWFRzKupIPCFRd9/mFYW0lHnDnWVvMD/pQ0x7sU=
+github.com/akeylesslabs/akeyless-go/v3 v3.3.16 h1:JiGvbNynzSygzRJBGqUfMeditDmEz03NucdY6JOSAHY=
+github.com/akeylesslabs/akeyless-go/v3 v3.3.16/go.mod h1:xcSXQWFRzKupIPCFRd9/mFYW0lHnDnWVvMD/pQ0x7sU=
 github.com/aliyun/alibaba-cloud-sdk-go v1.62.271 h1:0QmSDMovuCyUbYp70MZHoTi/GYnHb/wYEIIBqoVsCjs=
 github.com/aliyun/alibaba-cloud-sdk-go v1.62.271/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/aws/aws-sdk-go v1.41.13/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
-github.com/aws/aws-sdk-go v1.44.294 h1:3x7GaEth+pDU9HwFcAU0awZlEix5CEdyIZvV08SlHa8=
-github.com/aws/aws-sdk-go v1.44.294/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.314 h1:d/5Jyk/Fb+PBd/4nzQg0JuC2W4A0knrDIzBgK/ggAow=
+github.com/aws/aws-sdk-go v1.44.314/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -159,8 +161,9 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
 github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
 github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
-github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
+github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
 github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
 github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
 github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
@@ -246,8 +249,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
+github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 h1:ZgoomqkdjGbQ3+qQXCkvYMCDvGDNg2k5JJDjjdTB6jY=
+github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
 github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
@@ -258,8 +261,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvki
 github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
 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.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
-github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
+github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
+github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -362,16 +365,16 @@ github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUo
 github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
 github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
 github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
-github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk=
-github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
+github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 h1:1WuWJu7/e8SqK+uQl7lfk/N/oMZTL2NE/TJsNKRNMc4=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20 h1:a9hSJdJcd16e0HoMsnFvaHvxB3pxSD+SC7+CISp7xY0=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
 github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
 github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
@@ -394,19 +397,22 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
-github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw=
+github.com/tidwall/gjson v1.15.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
 github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/xanzy/go-gitlab v0.86.0 h1:jR8V9cK9jXRQDb46KOB20NCF3ksY09luaG0IfXE6p7w=
-github.com/xanzy/go-gitlab v0.86.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
+github.com/xanzy/go-gitlab v0.89.0 h1:yJuy1Pw+to/NqHzVIiopt/VApoHvGDB5SEGuRs3EJpI=
+github.com/xanzy/go-gitlab v0.89.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@@ -429,7 +435,7 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -442,8 +448,8 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
-golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -479,7 +485,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -521,8 +527,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
-golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
+golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -536,8 +542,8 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
-golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
-golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
+golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
+golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -552,6 +558,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -599,15 +606,15 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
-golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -621,8 +628,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
-golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -678,8 +685,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
-golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
+golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
+golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -708,8 +715,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
-google.golang.org/api v0.129.0 h1:2XbdjjNfFPXQyufzQVwPf1RRnHH8Den2pfNE2jw7L8w=
-google.golang.org/api v0.129.0/go.mod h1:dFjiXlanKwWE3612X97llhsoI36FAoIiRj3aTl5b/zE=
+google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw=
+google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -760,12 +767,12 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
-google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
-google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk=
-google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
+google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf h1:v5Cf4E9+6tawYrs/grq1q1hFpGtzlGFzgWHqwt6NFiU=
+google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=
+google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf h1:xkVZ5FdZJF4U82Q/JS+DcZA83s/GRVL+QrFMlexk9Yo=
+google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf h1:guOdSPaeFgN+jEJwTo1dQ71hdBm+yKSCCKuTRkJzcVo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I=
 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=
@@ -788,8 +795,8 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
 google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
-google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
-google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
+google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
+google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -850,8 +857,8 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
 k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/kube-openapi v0.0.0-20230523194449-df37dd07aa00 h1:Sb9XaC5itZdDOVp7CTFhW8cxWIuRryoU4Oq6U8eEeOk=
 k8s.io/kube-openapi v0.0.0-20230523194449-df37dd07aa00/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ=
-k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU=
-k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
@@ -859,8 +866,8 @@ sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0
 sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
-sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
-sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
+sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
 sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
 sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
 software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE=

+ 5 - 0
e2e/run.sh

@@ -79,6 +79,11 @@ kubectl run --rm \
   --env="SCALEWAY_PROJECT_ID=${SCALEWAY_PROJECT_ID:-}" \
   --env="SCALEWAY_ACCESS_KEY=${SCALEWAY_ACCESS_KEY:-}" \
   --env="SCALEWAY_SECRET_KEY=${SCALEWAY_SECRET_KEY:-}" \
+  --env="DELINEA_TLD=${DELINEA_TLD:-}" \
+  --env="DELINEA_URL_TEMPLATE=${DELINEA_URL_TEMPLATE:-}" \
+  --env="DELINEA_TENANT=${DELINEA_TENANT:-}" \
+  --env="DELINEA_CLIENT_ID=${DELINEA_CLIENT_ID:-}" \
+  --env="DELINEA_CLIENT_SECRET=${DELINEA_CLIENT_SECRET:-}" \
   --env="VERSION=${VERSION}" \
   --env="TEST_SUITES=${TEST_SUITES}" \
   --overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \

+ 1 - 1
e2e/suites/provider/cases/common/common.go

@@ -512,7 +512,7 @@ func DockerJSONConfig(f *framework.Framework) (string, func(*framework.TestCase)
 	return "[common] should sync docker configurated json secrets with template simple", func(tc *framework.TestCase) {
 		cloudSecretName := fmt.Sprintf("%s-%s", f.Namespace.Name, dockerConfigExampleName)
 		cloudRemoteRefKey := f.MakeRemoteRefKey(cloudSecretName)
-		dockerconfig := `{"auths":{"https://index.docker.io/v1/": {"auth": "c3R...zE2"}}}`
+		dockerconfig := `{"auths":{"https://index.docker.io/v1/":{"auth":"c3R...zE2"}}}`
 		cloudSecretValue := fmt.Sprintf(`{"dockerconfig": %s}`, dockerconfig)
 		tc.Secrets = map[string]framework.SecretEntry{
 			cloudRemoteRefKey: {Value: cloudSecretValue},

+ 47 - 0
e2e/suites/provider/cases/delinea/config.go

@@ -0,0 +1,47 @@
+package delinea
+
+import (
+	"fmt"
+	"os"
+)
+
+type config struct {
+	tld          string
+	urlTemplate  string
+	tenant       string
+	clientID     string
+	clientSecret string
+}
+
+func loadConfigFromEnv() (*config, error) {
+	var cfg config
+	var err error
+
+	// Optional settings
+	cfg.tld, _ = getEnv("DELINEA_TLD")
+	cfg.urlTemplate, _ = getEnv("DELINEA_URL_TEMPLATE")
+
+	// Required settings
+	cfg.tenant, err = getEnv("DELINEA_TENANT")
+	if err != nil {
+		return nil, err
+	}
+	cfg.clientID, err = getEnv("DELINEA_CLIENT_ID")
+	if err != nil {
+		return nil, err
+	}
+	cfg.clientSecret, err = getEnv("DELINEA_CLIENT_SECRET")
+	if err != nil {
+		return nil, err
+	}
+
+	return &cfg, nil
+}
+
+func getEnv(name string) (string, error) {
+	value, ok := os.LookupEnv(name)
+	if !ok {
+		return "", fmt.Errorf("environment variable %q is not set", name)
+	}
+	return value, nil
+}

+ 115 - 0
e2e/suites/provider/cases/delinea/delinea.go

@@ -0,0 +1,115 @@
+package delinea
+
+import (
+	"context"
+
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	"github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common"
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/onsi/ginkgo/v2"
+	"github.com/onsi/gomega"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var _ = ginkgo.Describe("[delinea]", ginkgo.Label("delinea"), func() {
+
+	f := framework.New("eso-delinea")
+
+	// Initialization is deferred so that assertions work.
+	provider := &secretStoreProvider{}
+
+	ginkgo.BeforeEach(func() {
+
+		cfg, err := loadConfigFromEnv()
+		gomega.Expect(err).ToNot(gomega.HaveOccurred())
+
+		provider.init(cfg)
+
+		createResources(context.Background(), f, cfg)
+	})
+
+	ginkgo.DescribeTable("sync secrets", framework.TableFunc(f, provider),
+
+		ginkgo.Entry(common.JSONDataWithProperty(f)),
+		ginkgo.Entry(common.JSONDataWithoutTargetName(f)),
+		ginkgo.Entry(common.JSONDataWithTemplate(f)),
+		ginkgo.Entry(common.JSONDataWithTemplateFromLiteral(f)),
+		ginkgo.Entry(common.TemplateFromConfigmaps(f)),
+		ginkgo.Entry(common.JSONDataFromSync(f)),
+		ginkgo.Entry(common.JSONDataFromRewrite(f)),
+		ginkgo.Entry(common.NestedJSONWithGJSON(f)),
+		ginkgo.Entry(common.DockerJSONConfig(f)),
+		ginkgo.Entry(common.DataPropertyDockerconfigJSON(f)),
+		ginkgo.Entry(common.SSHKeySyncDataProperty(f)),
+		ginkgo.Entry(common.DecodingPolicySync(f)),
+
+		// V1Alpha1 is not supported.
+		// ginkgo.Entry(common.SyncV1Alpha1(f)),
+
+		// Non-JSON values are not supported by DSV.
+		// ginkgo.Entry(common.SimpleDataSync(f)),
+		// ginkgo.Entry(common.SyncWithoutTargetName(f)),
+		// ginkgo.Entry(common.SSHKeySync(f)),
+		// ginkgo.Entry(common.DeletionPolicyDelete(f)),
+
+		// FindByName is not supported.
+		// ginkgo.Entry(common.FindByName(f)),
+		// ginkgo.Entry(common.FindByNameAndRewrite(f)),
+		// ginkgo.Entry(common.FindByNameWithPath(f)),
+
+		// FindByTag is not supported.
+		// ginkgo.Entry(common.FindByTag(f)),
+		// ginkgo.Entry(common.FindByTagWithPath(f)),
+	)
+})
+
+func createResources(ctx context.Context, f *framework.Framework, cfg *config) {
+
+	secretName := "delinea-credential"
+	secretKey := "client-secret"
+
+	// Creating a secret to hold the Delinea client secret.
+	secretSpec := v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: f.Namespace.Name,
+		},
+		StringData: map[string]string{
+			secretKey: cfg.clientSecret,
+		},
+	}
+
+	err := f.CRClient.Create(ctx, &secretSpec)
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+
+	// Creating SecretStore.
+	secretStoreSpec := esv1beta1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      f.Namespace.Name,
+			Namespace: f.Namespace.Name,
+		},
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				Delinea: &esv1beta1.DelineaProvider{
+					Tenant:      cfg.tenant,
+					TLD:         cfg.tld,
+					URLTemplate: cfg.urlTemplate,
+					ClientID: &esv1beta1.DelineaProviderSecretRef{
+						Value: cfg.clientID,
+					},
+					ClientSecret: &esv1beta1.DelineaProviderSecretRef{
+						SecretRef: &esmeta.SecretKeySelector{
+							Name: secretName,
+							Key:  secretKey,
+						},
+					},
+				},
+			},
+		},
+	}
+
+	err = f.CRClient.Create(ctx, &secretStoreSpec)
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+}

+ 47 - 0
e2e/suites/provider/cases/delinea/provider.go

@@ -0,0 +1,47 @@
+package delinea
+
+import (
+	"encoding/json"
+
+	"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	"github.com/onsi/gomega"
+)
+
+type secretStoreProvider struct {
+	api *vault.Vault
+	cfg *config
+}
+
+func (p *secretStoreProvider) init(cfg *config) {
+
+	p.cfg = cfg
+
+	dsvClient, err := vault.New(vault.Configuration{
+		Credentials: vault.ClientCredential{
+			ClientID:     cfg.clientID,
+			ClientSecret: cfg.clientSecret,
+		},
+		Tenant:      cfg.tenant,
+		URLTemplate: cfg.urlTemplate,
+		TLD:         cfg.tld,
+	})
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+
+	p.api = dsvClient
+}
+
+func (p *secretStoreProvider) CreateSecret(key string, val framework.SecretEntry) {
+	var data map[string]interface{}
+	err := json.Unmarshal([]byte(val.Value), &data)
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+	_, err = p.api.CreateSecret(key, &vault.SecretCreateRequest{
+		Data: data,
+	})
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+}
+
+func (p *secretStoreProvider) DeleteSecret(key string) {
+	err := p.api.DeleteSecret(key)
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+}

+ 1 - 0
e2e/suites/provider/cases/import.go

@@ -19,6 +19,7 @@ import (
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws/parameterstore"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws/secretsmanager"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/azure"
+	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/delinea"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/gcp"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/kubernetes"
 	_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/scaleway"

+ 50 - 46
go.mod

@@ -9,7 +9,7 @@ require (
 	github.com/Azure/go-autorest/autorest v0.11.29
 	github.com/Azure/go-autorest/autorest/adal v0.9.23
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
-	github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0
+	github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0
 	github.com/IBM/go-sdk-core/v5 v5.13.4
 	github.com/IBM/secrets-manager-go-sdk/v2 v2.0.0
 	github.com/Masterminds/goutils v1.1.1 // indirect
@@ -17,12 +17,12 @@ 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.4
-	github.com/aws/aws-sdk-go v1.44.294
+	github.com/aws/aws-sdk-go v1.44.314
 	github.com/go-logr/logr v1.2.4
 	github.com/go-test/deep v1.0.4 // indirect
 	github.com/google/go-cmp v0.5.9
 	github.com/google/uuid v1.3.0
-	github.com/googleapis/gax-go/v2 v2.11.0
+	github.com/googleapis/gax-go/v2 v2.12.0
 	github.com/hashicorp/vault/api v1.9.2
 	github.com/hashicorp/vault/api/auth/approle v0.4.1
 	github.com/hashicorp/vault/api/auth/kubernetes v0.4.1
@@ -36,80 +36,85 @@ require (
 	github.com/prometheus/client_model v0.4.0
 	github.com/spf13/cobra v1.7.0
 	github.com/stretchr/testify v1.8.4
-	github.com/tidwall/gjson v1.14.4
-	github.com/xanzy/go-gitlab v0.86.0
-	github.com/yandex-cloud/go-genproto v0.0.0-20230628143002-ac2343960883
-	github.com/yandex-cloud/go-sdk v0.0.0-20230628143705-2a8cf9425a6f
+	github.com/tidwall/gjson v1.15.0
+	github.com/xanzy/go-gitlab v0.89.0
+	github.com/yandex-cloud/go-genproto v0.0.0-20230802072036-e115ab7dc00c
+	github.com/yandex-cloud/go-sdk v0.0.0-20230725174633-36e62072536f
 	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
-	go.uber.org/zap v1.24.0
-	golang.org/x/crypto v0.10.0
-	golang.org/x/oauth2 v0.9.0
-	google.golang.org/api v0.129.0
-	google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect
-	google.golang.org/grpc v1.56.1
+	go.uber.org/zap v1.25.0
+	golang.org/x/crypto v0.11.0
+	golang.org/x/oauth2 v0.10.0
+	google.golang.org/api v0.134.0
+	google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf
+	google.golang.org/grpc v1.57.0
 	gopkg.in/yaml.v3 v3.0.1
 	grpc.go4.org v0.0.0-20170609214715-11d0a25b4919
-	k8s.io/api v0.27.3
-	k8s.io/apiextensions-apiserver v0.27.3
-	k8s.io/apimachinery v0.27.3
-	k8s.io/client-go v0.27.3
-	k8s.io/utils v0.0.0-20230505201702-9f6742963106
+	k8s.io/api v0.27.4
+	k8s.io/apiextensions-apiserver v0.27.4
+	k8s.io/apimachinery v0.27.4
+	k8s.io/client-go v0.27.4
+	k8s.io/utils v0.0.0-20230726121419-3b25d923346b
 	sigs.k8s.io/controller-runtime v0.15.0
-	sigs.k8s.io/controller-tools v0.12.0
+	sigs.k8s.io/controller-tools v0.12.1
 )
 
-require github.com/1Password/connect-sdk-go v1.5.1
+require github.com/1Password/connect-sdk-go v1.5.3
 
 require (
-	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
-	github.com/akeylesslabs/akeyless-go/v3 v3.3.12
+	github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0
+	github.com/akeylesslabs/akeyless-go/v3 v3.3.16
 	github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
 	github.com/alibabacloud-go/kms-20160120/v3 v3.0.2
 	github.com/alibabacloud-go/openapi-util v0.1.0
 	github.com/alibabacloud-go/tea v1.2.1
-	github.com/alibabacloud-go/tea-utils/v2 v2.0.3
-	github.com/aliyun/credentials-go v1.3.0
+	github.com/alibabacloud-go/tea-utils/v2 v2.0.4
+	github.com/aliyun/credentials-go v1.3.1
 	github.com/avast/retry-go/v4 v4.3.4
 	github.com/cyberark/conjur-api-go v0.11.1
+	github.com/go-openapi/strfmt v0.21.7
 	github.com/golang-jwt/jwt/v5 v5.0.0
 	github.com/hashicorp/golang-lru v0.5.4
 	github.com/hashicorp/vault/api/auth/aws v0.4.1
-	github.com/keeper-security/secrets-manager-go/core v1.5.2
+	github.com/hashicorp/vault/api/auth/userpass v0.4.1
+	github.com/keeper-security/secrets-manager-go/core v1.6.1
 	github.com/maxbrunsfeld/counterfeiter/v6 v6.6.2
-	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17
+	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20
 	github.com/sethvargo/go-password v0.2.0
 	github.com/spf13/pflag v1.0.5
+	github.com/tidwall/sjson v1.2.5
 	sigs.k8s.io/yaml v1.3.0
 )
 
 require (
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
-	github.com/alessio/shellescape v1.4.1 // indirect
+	github.com/alessio/shellescape v1.4.2 // indirect
 	github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
 	github.com/alibabacloud-go/debug v1.0.0 // indirect
 	github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect
 	github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
 	github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
 	github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
-	github.com/clbanning/mxj/v2 v2.5.7 // indirect
+	github.com/clbanning/mxj/v2 v2.7.0 // indirect
 	github.com/danieljoos/wincred v1.2.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/go-playground/validator/v10 v10.14.1 // indirect
 	github.com/godbus/dbus/v5 v5.1.0 // indirect
 	github.com/google/s2a-go v0.1.4 // indirect
-	github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 // indirect
+	github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3 // indirect
 	github.com/hashicorp/go-uuid v1.0.3 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	github.com/zalando/go-keyring v0.2.3 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 // indirect
+	golang.org/x/sync v0.3.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf // indirect
 )
 
 require (
-	cloud.google.com/go/compute v1.20.1 // indirect
+	cloud.google.com/go/compute v1.23.0 // indirect
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
@@ -135,9 +140,8 @@ require (
 	github.com/ghodss/yaml v1.0.0 // indirect
 	github.com/go-logr/zapr v1.2.4 // indirect
 	github.com/go-openapi/errors v0.20.4 // indirect
-	github.com/go-openapi/jsonpointer v0.19.6 // indirect
-	github.com/go-openapi/jsonreference v0.20.2 // indirect
-	github.com/go-openapi/strfmt v0.21.7 // indirect
+	github.com/go-openapi/jsonpointer v0.20.0 // indirect
+	github.com/go-openapi/jsonreference v0.20.2 // indirect; indirectgithub.com/go-openapi/strfmt v0.21.7 // indirect
 	github.com/go-openapi/swag v0.22.4 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
@@ -151,7 +155,7 @@ require (
 	github.com/google/gnostic v0.6.9 // 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-20230602150820-91b7bce49751 // indirect
+	github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -193,7 +197,7 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/common v0.44.0 // indirect
-	github.com/prometheus/procfs v0.11.0 // indirect
+	github.com/prometheus/procfs v0.11.1 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/ryanuber/go-glob v1.0.0 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
@@ -207,25 +211,25 @@ require (
 	go.opencensus.io v0.24.0 // indirect
 	go.uber.org/atomic v1.11.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
-	golang.org/x/mod v0.11.0 // indirect
-	golang.org/x/net v0.11.0 // indirect
-	golang.org/x/sys v0.9.0 // indirect
-	golang.org/x/term v0.9.0 // indirect
-	golang.org/x/text v0.10.0 // indirect
+	golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
+	golang.org/x/mod v0.12.0 // indirect
+	golang.org/x/net v0.13.0 // indirect
+	golang.org/x/sys v0.10.0 // indirect
+	golang.org/x/term v0.10.0 // indirect
+	golang.org/x/text v0.11.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
-	golang.org/x/tools v0.10.0 // indirect
+	golang.org/x/tools v0.11.1 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
-	k8s.io/component-base v0.27.3 // indirect
+	k8s.io/component-base v0.27.4 // indirect
 	k8s.io/gengo v0.0.0-20230306165830-ab3349d207d4 // indirect
 	k8s.io/klog v1.0.0 // indirect
 	k8s.io/klog/v2 v2.100.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20230523194449-df37dd07aa00 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
 )

+ 100 - 84
go.sum

@@ -18,15 +18,15 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
 cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
+cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q=
 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 v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
-cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
+cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
+cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
 cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
 cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
@@ -46,12 +46,12 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/1Password/connect-sdk-go v1.5.1 h1:wb9niRg4BOa+lZJjj1TOX6093VJxuOYtzqUnRpwKnvs=
-github.com/1Password/connect-sdk-go v1.5.1/go.mod h1:lKGz6DFO6qMchEQ+lDx6f9MzORTxC1HkhUdHnJ24fKs=
+github.com/1Password/connect-sdk-go v1.5.3 h1:KyjJ+kCKj6BwB2Y8tPM1Ixg5uIS6HsB0uWA8U38p/Uk=
+github.com/1Password/connect-sdk-go v1.5.3/go.mod h1:5rSymY4oIYtS4G3t0oMkGAXBeoYiukV3vkqlnEjIDJs=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1 h1:SEy2xmstIphdPwNBUi7uhvjyjhVKISfwjfOJmuy7kg4=
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
@@ -83,10 +83,12 @@ 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/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
-github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 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/DelineaXPM/dsv-sdk-go/v2 v2.1.0 h1:+XXJ43iH4js8LIBr4MUGq1J09ycivNkTNhtn4mFyhY8=
+github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0/go.mod h1:NTdQaRBIRZ/8gIzs010CS/u69aVSmqD1zbESW25y2cE=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
 github.com/IBM/go-sdk-core/v5 v5.13.4 h1:kJvBNQOwhFRkXCPapjNvKVC7n7n2vd1Nr6uUtDZGcfo=
 github.com/IBM/go-sdk-core/v5 v5.13.4/go.mod h1:gKRSB+YyKsGlRQW7v5frlLbue5afulSvrRa4O26o4MM=
@@ -110,10 +112,10 @@ 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.4 h1:vTckjyBhHOBiOWSC/oaEU2Oo4OH5eAlQiwKu2RMxsFg=
 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4/go.mod h1:As/RomC2w/fa3y+yHRlVHPmkbP+zrKBFRow41y5dk+E=
-github.com/akeylesslabs/akeyless-go/v3 v3.3.12 h1:LJWtTFQv7bcHhtk9AARywVUoaJtw9A/xcZWV5WkI4iw=
-github.com/akeylesslabs/akeyless-go/v3 v3.3.12/go.mod h1:xcSXQWFRzKupIPCFRd9/mFYW0lHnDnWVvMD/pQ0x7sU=
-github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
-github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
+github.com/akeylesslabs/akeyless-go/v3 v3.3.16 h1:JiGvbNynzSygzRJBGqUfMeditDmEz03NucdY6JOSAHY=
+github.com/akeylesslabs/akeyless-go/v3 v3.3.16/go.mod h1:xcSXQWFRzKupIPCFRd9/mFYW0lHnDnWVvMD/pQ0x7sU=
+github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
+github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
 github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
@@ -142,14 +144,14 @@ github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOq
 github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
 github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
 github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
-github.com/alibabacloud-go/tea-utils/v2 v2.0.3 h1:6OM8vm/6pjQg1a7zc3QNMviaoumnhImRi5V84CnuFkc=
-github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.4 h1:SoFgjJuO7pze88j9RBJNbKb7AgTS52O+J5ITxc00lCs=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.4/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
 github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
 github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
 github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
 github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
-github.com/aliyun/credentials-go v1.3.0 h1:wfBNojfNJJyuHK3YUIIjRPwnlQIdmy/YMkia1XOnPtY=
-github.com/aliyun/credentials-go v1.3.0/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
+github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28=
+github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
@@ -159,10 +161,10 @@ github.com/avast/retry-go/v4 v4.3.4/go.mod h1:rv+Nla6Vk3/ilU0H51VHddWHiwimzX66yZ
 github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
 github.com/aws/aws-sdk-go v1.41.13/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
-github.com/aws/aws-sdk-go v1.44.294 h1:3x7GaEth+pDU9HwFcAU0awZlEix5CEdyIZvV08SlHa8=
-github.com/aws/aws-sdk-go v1.44.294/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
-github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/aws/aws-sdk-go v1.44.314 h1:d/5Jyk/Fb+PBd/4nzQg0JuC2W4A0knrDIzBgK/ggAow=
+github.com/aws/aws-sdk-go v1.44.314/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
@@ -182,8 +184,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
-github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0=
-github.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
+github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
+github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -250,8 +252,9 @@ github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
 github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
 github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
 github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
-github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
+github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
 github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
 github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
 github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
@@ -360,8 +363,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
-github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
+github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 h1:ZgoomqkdjGbQ3+qQXCkvYMCDvGDNg2k5JJDjjdTB6jY=
+github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
 github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
@@ -373,8 +376,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvki
 github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
 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.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
-github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
+github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
+github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
@@ -397,8 +400,8 @@ github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5
 github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
 github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
 github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
-github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 h1:kWg2vyKl7BRXrNxYziqDJ55n+vtOQ1QsGORjzoeB+uM=
-github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg=
+github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3 h1:AAQ6Vmo/ncfrZYtbpjhO+g0Qt+iNpYtl3UWT1NLmbYY=
+github.com/hashicorp/go-secure-stdlib/awsutil v0.2.3/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg=
 github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
 github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs=
 github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
@@ -427,6 +430,8 @@ github.com/hashicorp/vault/api/auth/kubernetes v0.4.1 h1:amFWL1ZhwMWdmqvT51J9phX
 github.com/hashicorp/vault/api/auth/kubernetes v0.4.1/go.mod h1:ikWDT8Adnfvm+8DzKez50vvLD9GWD/unZfJxeqP09sU=
 github.com/hashicorp/vault/api/auth/ldap v0.4.1 h1:5I9K7Tn/b4Sdv/7/ZU5Dvqxce7SnFeMH099bGuipkR8=
 github.com/hashicorp/vault/api/auth/ldap v0.4.1/go.mod h1:BrrVM2IXUII33MKKzdMLJQA4da9TFQoXIO73MVaHM5Y=
+github.com/hashicorp/vault/api/auth/userpass v0.4.1 h1:k/OJOeFqPWdp3H4buyjRF2nPMdgk2qLnEqAAUkDQikU=
+github.com/hashicorp/vault/api/auth/userpass v0.4.1/go.mod h1:PJJd/rurbT65M1nNu9LesRyCb9HI3ywfE10hVeO7gEA=
 github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
 github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
@@ -451,8 +456,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/keeper-security/secrets-manager-go/core v1.5.2 h1:Uo5JU3OMK2NJ+mmoPogZ7kNS0xpXXskUv0RDo0mtygs=
-github.com/keeper-security/secrets-manager-go/core v1.5.2/go.mod h1:dtlaeeds9+SZsbDAZnQRsDSqEAK9a62SYtqhNql+VgQ=
+github.com/keeper-security/secrets-manager-go/core v1.6.1 h1:L4X21yee+Zsai1rQpN1eWTcQCsHaRXwHXUmZajkPae4=
+github.com/keeper-security/secrets-manager-go/core v1.6.1/go.mod h1:dtlaeeds9+SZsbDAZnQRsDSqEAK9a62SYtqhNql+VgQ=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
@@ -557,20 +562,20 @@ github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUo
 github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
 github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
 github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
-github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk=
-github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
+github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 h1:1WuWJu7/e8SqK+uQl7lfk/N/oMZTL2NE/TJsNKRNMc4=
-github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20 h1:a9hSJdJcd16e0HoMsnFvaHvxB3pxSD+SC7+CISp7xY0=
+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
 github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
 github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
 github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
@@ -613,13 +618,16 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
-github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw=
+github.com/tidwall/gjson v1.15.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
 github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
 github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
 github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
@@ -627,18 +635,19 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO
 github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/xanzy/go-gitlab v0.86.0 h1:jR8V9cK9jXRQDb46KOB20NCF3ksY09luaG0IfXE6p7w=
-github.com/xanzy/go-gitlab v0.86.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
+github.com/xanzy/go-gitlab v0.89.0 h1:yJuy1Pw+to/NqHzVIiopt/VApoHvGDB5SEGuRs3EJpI=
+github.com/xanzy/go-gitlab v0.89.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
-github.com/yandex-cloud/go-genproto v0.0.0-20230628143002-ac2343960883 h1:SpNmg8Mg8g8ZEMw5+KRAzoS7YqBVtU3SAZpZyksHI6Y=
-github.com/yandex-cloud/go-genproto v0.0.0-20230628143002-ac2343960883/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
-github.com/yandex-cloud/go-sdk v0.0.0-20230628143705-2a8cf9425a6f h1:DgDp/VsBXaQIMjETqwvjV5gLKPkNi6Kx+YHrvZk2FVg=
-github.com/yandex-cloud/go-sdk v0.0.0-20230628143705-2a8cf9425a6f/go.mod h1:M517yqPsrvp315Pipjzg2Ghuw1Dm4rarRiqP9g9PBig=
+github.com/yandex-cloud/go-genproto v0.0.0-20230725160706-538fcd9b346d/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
+github.com/yandex-cloud/go-genproto v0.0.0-20230802072036-e115ab7dc00c h1:wqY+Hr4fyfiFovOJDDlAy9YsIyqx0LeScGGhZkP4Fq4=
+github.com/yandex-cloud/go-genproto v0.0.0-20230802072036-e115ab7dc00c/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
+github.com/yandex-cloud/go-sdk v0.0.0-20230725174633-36e62072536f h1:y1BXDDRgKEzzqXSkG/CQKhK34BEKYuenyn1aZdry1CU=
+github.com/yandex-cloud/go-sdk v0.0.0-20230725174633-36e62072536f/go.mod h1:dnSew0wuJbgOmAZ6lW+axR8t/H9sCwoMcxUP9bw1N3I=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
 github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
@@ -671,8 +680,9 @@ go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
 go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
+go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
+go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -691,8 +701,9 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
-golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
 golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -703,8 +714,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-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
-golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
+golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -731,8 +742,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
-golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -777,8 +788,9 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
 golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
+golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -792,8 +804,8 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
-golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
-golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
+golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
+golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -808,6 +820,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -868,16 +881,18 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
 golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
 golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -891,8 +906,9 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
 golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -953,8 +969,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
-golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
+golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
+golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -983,8 +999,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
 google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
 google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA=
-google.golang.org/api v0.129.0 h1:2XbdjjNfFPXQyufzQVwPf1RRnHH8Den2pfNE2jw7L8w=
-google.golang.org/api v0.129.0/go.mod h1:dFjiXlanKwWE3612X97llhsoI36FAoIiRj3aTl5b/zE=
+google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw=
+google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1036,12 +1052,12 @@ google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaE
 google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
-google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
-google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk=
-google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
+google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf h1:v5Cf4E9+6tawYrs/grq1q1hFpGtzlGFzgWHqwt6NFiU=
+google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=
+google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf h1:xkVZ5FdZJF4U82Q/JS+DcZA83s/GRVL+QrFMlexk9Yo=
+google.golang.org/genproto/googleapis/api v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf h1:guOdSPaeFgN+jEJwTo1dQ71hdBm+yKSCCKuTRkJzcVo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230731193218-e0aa005b6bdf/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I=
 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=
@@ -1065,8 +1081,8 @@ google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
-google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
-google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
+google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
+google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1115,16 +1131,16 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y=
-k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg=
-k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4=
-k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84=
-k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM=
-k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
-k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8=
-k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48=
-k8s.io/component-base v0.27.3 h1:g078YmdcdTfrCE4fFobt7qmVXwS8J/3cI1XxRi/2+6k=
-k8s.io/component-base v0.27.3/go.mod h1:JNiKYcGImpQ44iwSYs6dysxzR9SxIIgQalk4HaCNVUY=
+k8s.io/api v0.27.4 h1:0pCo/AN9hONazBKlNUdhQymmnfLRbSZjd5H5H3f0bSs=
+k8s.io/api v0.27.4/go.mod h1:O3smaaX15NfxjzILfiln1D8Z3+gEYpjEpiNA/1EVK1Y=
+k8s.io/apiextensions-apiserver v0.27.4 h1:ie1yZG4nY/wvFMIR2hXBeSVq+HfNzib60FjnBYtPGSs=
+k8s.io/apiextensions-apiserver v0.27.4/go.mod h1:KHZaDr5H9IbGEnSskEUp/DsdXe1hMQ7uzpQcYUFt2bM=
+k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs=
+k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
+k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk=
+k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc=
+k8s.io/component-base v0.27.4 h1:Wqc0jMKEDGjKXdae8hBXeskRP//vu1m6ypC+gwErj4c=
+k8s.io/component-base v0.27.4/go.mod h1:hoiEETnLc0ioLv6WPeDt8vD34DDeB35MfQnxCARq3kY=
 k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
 k8s.io/gengo v0.0.0-20230306165830-ab3349d207d4 h1:aClvVG6GbX10ISHcc24J+tqbr0S7fEe1MWkFJ7cWWCI=
 k8s.io/gengo v0.0.0-20230306165830-ab3349d207d4/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
@@ -1136,19 +1152,19 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
 k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/kube-openapi v0.0.0-20230523194449-df37dd07aa00 h1:Sb9XaC5itZdDOVp7CTFhW8cxWIuRryoU4Oq6U8eEeOk=
 k8s.io/kube-openapi v0.0.0-20230523194449-df37dd07aa00/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ=
-k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU=
-k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
+k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU=
 sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
-sigs.k8s.io/controller-tools v0.12.0 h1:TY6CGE6+6hzO7hhJFte65ud3cFmmZW947jajXkuDfBw=
-sigs.k8s.io/controller-tools v0.12.0/go.mod h1:rXlpTfFHZMpZA8aGq9ejArgZiieHd+fkk/fTatY8A2M=
+sigs.k8s.io/controller-tools v0.12.1 h1:GyQqxzH5wksa4n3YDIJdJJOopztR5VDM+7qsyg5yE4U=
+sigs.k8s.io/controller-tools v0.12.1/go.mod h1:rXlpTfFHZMpZA8aGq9ejArgZiieHd+fkk/fTatY8A2M=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
-sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
-sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk=
+sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
 sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

+ 5 - 1
hack/api-docs/Dockerfile

@@ -11,11 +11,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM alpine:3.11
+FROM alpine:3.18
 COPY requirements.txt /
 RUN apk add -U --no-cache \
     python3 \
     python3-dev \
+    py3-pip \
     musl-dev \
     git \
     openssh \
@@ -24,3 +25,6 @@ RUN apk add -U --no-cache \
     gcc \
     diffutils
 RUN pip3 install -r /requirements.txt
+# Disable the top-level directory owner check
+# https://github.com/git/git/commit/8959555cee7ec045958f9b6dd62e541affb7e7d9
+RUN git config --system --add safe.directory '*'

+ 3 - 3
hack/api-docs/Makefile

@@ -42,7 +42,7 @@ all: build
 
 .PHONY: image
 image:
-	$(DOCKER) build -t $(MKDOCS_IMAGE) --build-arg USER_ID=$(UID) -f Dockerfile .
+	$(DOCKER) build -t $(MKDOCS_IMAGE) -f Dockerfile .
 
 .PHONY: build
 build: image generate $(SOURCES)
@@ -52,8 +52,8 @@ build: image generate $(SOURCES)
 		--sig-proxy=true \
 		--rm \
 		--user $(UID):$(GID) \
-		--env GIT_COMMITTER_NAME=$(shell git config user.name) \
-		--env GIT_COMMITTER_EMAIL=$(shell git config user.email) \
+		--env "GIT_COMMITTER_NAME=$(shell git config user.name)" \
+		--env "GIT_COMMITTER_EMAIL=$(shell git config user.email)" \
 		$(MKDOCS_IMAGE) \
 		/bin/bash -c "cd /repo && $(MIKE) deploy --ignore --update-aliases -F hack/api-docs/mkdocs.yml $(DOCS_VERSION) $(DOCS_ALIAS);"
 .PHONY: build.publish

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

@@ -105,7 +105,9 @@ nav:
     - senhasegura DevOps Secrets Management (DSM): provider/senhasegura-dsm.md
     - Doppler: provider/doppler.md
     - Keeper Security: provider/keeper-security.md
+    - Cloak End 2 End Encrypted Secrets: provider/cloak.md
     - Scaleway: provider/scaleway.md
+    - Delinea: provider/delinea.md
   - Examples:
     - FluxCD: examples/gitops-using-fluxcd.md
     - Anchore Engine: examples/anchore-engine-credentials.md

+ 1 - 1
hack/api-docs/requirements.txt

@@ -12,7 +12,7 @@ mkdocs-minify-plugin==0.5.0
 pep562==1.1
 Pygments==2.15.1
 pymdown-extensions==9.11
-PyYAML==6.0
+PyYAML==5.3.1 # 6.0 is broken: https://github.com/yaml/pyyaml/issues/601
 six==1.16.0
 tornado==6.1
 mkdocs-macros-plugin==0.7.0

+ 1 - 1
hack/crd.generate.sh

@@ -8,7 +8,7 @@ BUNDLE_YAML="${BUNDLE_DIR}/bundle.yaml"
 
 cd "${SCRIPT_DIR}"/../
 
-go run sigs.k8s.io/controller-tools/cmd/controller-gen object \
+go run sigs.k8s.io/controller-tools/cmd/controller-gen \
   object:headerFile="hack/boilerplate.go.txt" \
   paths="./apis/..."
 go run sigs.k8s.io/controller-tools/cmd/controller-gen crd \

+ 1 - 0
pkg/constants/constants.go

@@ -44,6 +44,7 @@ const (
 	CallGCPSMGetSecret           = "GetSecret"
 	CallGCPSMDeleteSecret        = "DeleteSecret"
 	CallGCPSMCreateSecret        = "CreateSecret"
+	CallGCPSMUpdateSecret        = "UpdateSecret"
 	CallGCPSMAccessSecretVersion = "AccessSecretVersion"
 	CallGCPSMAddSecretVersion    = "AddSecretVersion"
 	CallGCPSMListSecrets         = "ListSecrets"

+ 165 - 97
pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller.go

@@ -16,6 +16,8 @@ package clusterexternalsecret
 
 import (
 	"context"
+	"fmt"
+	"reflect"
 	"sort"
 	"time"
 
@@ -23,13 +25,19 @@ import (
 	v1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
@@ -50,11 +58,7 @@ const (
 	errConvertLabelSelector = "unable to convert labelselector"
 	errNamespaces           = "could not get namespaces from selector"
 	errGetExistingES        = "could not get existing ExternalSecret"
-	errCreatingOrUpdating   = "could not create or update ExternalSecret"
-	errSetCtrlReference     = "could not set the controller owner reference"
-	errSecretAlreadyExists  = "external secret already exists in namespace"
 	errNamespacesFailed     = "one or more namespaces failed"
-	errFailedToDelete       = "external secret in non matching namespace could not be deleted"
 )
 
 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -67,13 +71,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 	defer func() { externalSecretReconcileDuration.With(resourceLabels).Set(float64(time.Since(start))) }()
 
 	var clusterExternalSecret esv1beta1.ClusterExternalSecret
-
 	err := r.Get(ctx, req.NamespacedName, &clusterExternalSecret)
-	if apierrors.IsNotFound(err) {
-		return ctrl.Result{}, nil
-	} else if err != nil {
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			return ctrl.Result{}, nil
+		}
+
 		log.Error(err, errGetCES)
-		return ctrl.Result{}, nil
+		return ctrl.Result{}, err
 	}
 
 	p := client.MergeFrom(clusterExternalSecret.DeepCopy())
@@ -102,99 +107,88 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		esName = clusterExternalSecret.ObjectMeta.Name
 	}
 
-	failedNamespaces := r.removeOldNamespaces(ctx, namespaceList, esName, clusterExternalSecret.Status.ProvisionedNamespaces)
-	provisionedNamespaces := []string{}
+	failedNamespaces := r.deleteOutdatedExternalSecrets(ctx, namespaceList, esName, clusterExternalSecret.Name, clusterExternalSecret.Status.ProvisionedNamespaces)
 
+	provisionedNamespaces := []string{}
 	for _, namespace := range namespaceList.Items {
-		var existingES esv1beta1.ExternalSecret
-		err = r.Get(ctx, types.NamespacedName{
-			Name:      esName,
-			Namespace: namespace.Name,
-		}, &existingES)
+		existingES, err := r.getExternalSecret(ctx, namespace.Name, esName)
+		if err != nil && !apierrors.IsNotFound(err) {
+			log.Error(err, errGetExistingES)
+			failedNamespaces[namespace.Name] = err
+			continue
+		}
 
-		if result := checkForError(err, &existingES); result != "" {
-			log.Error(err, result)
-			failedNamespaces[namespace.Name] = result
+		if err == nil && !isExternalSecretOwnedBy(existingES, clusterExternalSecret.Name) {
+			failedNamespaces[namespace.Name] = fmt.Errorf("external secret already exists in namespace")
 			continue
 		}
 
-		if result, err := r.resolveExternalSecret(ctx, &clusterExternalSecret, &existingES, namespace, esName, clusterExternalSecret.Spec.ExternalSecretMetadata); err != nil {
-			log.Error(err, result)
-			failedNamespaces[namespace.Name] = result
+		if err := r.createOrUpdateExternalSecret(ctx, &clusterExternalSecret, namespace, esName, clusterExternalSecret.Spec.ExternalSecretMetadata); err != nil {
+			log.Error(err, "failed to create or update external secret")
+			failedNamespaces[namespace.Name] = err
 			continue
 		}
 
-		provisionedNamespaces = append(provisionedNamespaces, namespace.ObjectMeta.Name)
+		provisionedNamespaces = append(provisionedNamespaces, namespace.Name)
 	}
 
 	condition := NewClusterExternalSecretCondition(failedNamespaces, &namespaceList)
 	SetClusterExternalSecretCondition(&clusterExternalSecret, *condition)
-	setFailedNamespaces(&clusterExternalSecret, failedNamespaces)
 
-	if len(provisionedNamespaces) > 0 {
-		sort.Strings(provisionedNamespaces)
-		clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
-	}
+	clusterExternalSecret.Status.FailedNamespaces = toNamespaceFailures(failedNamespaces)
+	sort.Strings(provisionedNamespaces)
+	clusterExternalSecret.Status.ProvisionedNamespaces = provisionedNamespaces
 
 	return ctrl.Result{RequeueAfter: refreshInt}, nil
 }
 
-func (r *Reconciler) resolveExternalSecret(ctx context.Context, clusterExternalSecret *esv1beta1.ClusterExternalSecret, existingES *esv1beta1.ExternalSecret, namespace v1.Namespace, esName string, esMetadata esv1beta1.ExternalSecretMetadata) (string, error) {
-	// this means the existing ES does not belong to us
-	if err := controllerutil.SetControllerReference(clusterExternalSecret, existingES, r.Scheme); err != nil {
-		return errSetCtrlReference, err
-	}
-
-	externalSecret := esv1beta1.ExternalSecret{
+func (r *Reconciler) createOrUpdateExternalSecret(ctx context.Context, clusterExternalSecret *esv1beta1.ClusterExternalSecret, namespace v1.Namespace, esName string, esMetadata esv1beta1.ExternalSecretMetadata) error {
+	externalSecret := &esv1beta1.ExternalSecret{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:        esName,
-			Namespace:   namespace.Name,
-			Labels:      esMetadata.Labels,
-			Annotations: esMetadata.Annotations,
+			Namespace: namespace.Name,
+			Name:      esName,
 		},
-		Spec: clusterExternalSecret.Spec.ExternalSecretSpec,
-	}
-
-	if err := controllerutil.SetControllerReference(clusterExternalSecret, &externalSecret, r.Scheme); err != nil {
-		return errSetCtrlReference, err
 	}
 
 	mutateFunc := func() error {
+		externalSecret.Labels = esMetadata.Labels
+		externalSecret.Annotations = esMetadata.Annotations
 		externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
+
+		if err := controllerutil.SetControllerReference(clusterExternalSecret, externalSecret, r.Scheme); err != nil {
+			return fmt.Errorf("could not set the controller owner reference %w", err)
+		}
+
 		return nil
 	}
 
-	// An empty mutate func as nothing needs to happen currently
-	if _, err := ctrl.CreateOrUpdate(ctx, r.Client, &externalSecret, mutateFunc); err != nil {
-		return errCreatingOrUpdating, err
+	if _, err := ctrl.CreateOrUpdate(ctx, r.Client, externalSecret, mutateFunc); err != nil {
+		return fmt.Errorf("could not create or update ExternalSecret: %w", err)
 	}
 
-	return "", nil
+	return nil
 }
 
-func (r *Reconciler) removeExternalSecret(ctx context.Context, esName, namespace string) (string, error) {
-	var existingES esv1beta1.ExternalSecret
-	err := r.Get(ctx, types.NamespacedName{
-		Name:      esName,
-		Namespace: namespace,
-	}, &existingES)
-
-	// If we can't find it then just leave
-	if err != nil && apierrors.IsNotFound(err) {
-		return "", nil
+func (r *Reconciler) deleteExternalSecret(ctx context.Context, esName, cesName, namespace string) error {
+	existingES, err := r.getExternalSecret(ctx, namespace, esName)
+	if err != nil {
+		// If we can't find it then just leave
+		if apierrors.IsNotFound(err) {
+			return nil
+		}
+		return err
 	}
 
-	if result := checkForError(err, &existingES); result != "" {
-		return result, err
+	if !isExternalSecretOwnedBy(existingES, cesName) {
+		return nil
 	}
 
-	err = r.Delete(ctx, &existingES, &client.DeleteOptions{})
-
+	err = r.Delete(ctx, existingES, &client.DeleteOptions{})
 	if err != nil {
-		return errFailedToDelete, err
+		return fmt.Errorf("external secret in non matching namespace could not be deleted: %w", err)
 	}
 
-	return "", nil
+	return nil
 }
 
 func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExternalSecret *esv1beta1.ClusterExternalSecret, p client.Patch) {
@@ -203,60 +197,66 @@ func (r *Reconciler) deferPatch(ctx context.Context, log logr.Logger, clusterExt
 	}
 }
 
-func (r *Reconciler) removeOldNamespaces(ctx context.Context, namespaceList v1.NamespaceList, esName string, provisionedNamespaces []string) map[string]string {
-	failedNamespaces := map[string]string{}
+func (r *Reconciler) deleteOutdatedExternalSecrets(ctx context.Context, namespaceList v1.NamespaceList, esName, cesName string, provisionedNamespaces []string) map[string]error {
+	failedNamespaces := map[string]error{}
 	// Loop through existing namespaces first to make sure they still have our labels
 	for _, namespace := range getRemovedNamespaces(namespaceList, provisionedNamespaces) {
-		result, err := r.removeExternalSecret(ctx, esName, namespace)
+		err := r.deleteExternalSecret(ctx, esName, cesName, namespace)
 		if err != nil {
-			r.Log.Error(err, "unable to delete external-secret")
-		}
-		if result != "" {
-			failedNamespaces[namespace] = result
+			r.Log.Error(err, "unable to delete external secret")
+			failedNamespaces[namespace] = err
 		}
 	}
 
 	return failedNamespaces
 }
 
-func checkForError(getError error, existingES *esv1beta1.ExternalSecret) string {
-	if getError != nil && !apierrors.IsNotFound(getError) {
-		return errGetExistingES
-	}
-
-	// No one owns this resource so error out
-	if !apierrors.IsNotFound(getError) && len(existingES.ObjectMeta.OwnerReferences) == 0 {
-		return errSecretAlreadyExists
-	}
+func (r *Reconciler) getExternalSecret(ctx context.Context, namespace, name string) (*metav1.PartialObjectMetadata, error) {
+	// Should not use esv1beta1.ExternalSecret since we specify builder.OnlyMetadata and cache only metadata
+	metadata := metav1.PartialObjectMetadata{}
+	metadata.SetGroupVersionKind(schema.GroupVersionKind{
+		Group:   esv1beta1.Group,
+		Version: esv1beta1.Version,
+		Kind:    esv1beta1.ExtSecretKind,
+	})
+	err := r.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, &metadata)
+	return &metadata, err
+}
 
-	return ""
+func isExternalSecretOwnedBy(es *metav1.PartialObjectMetadata, cesName string) bool {
+	owner := metav1.GetControllerOf(es)
+	return owner != nil && owner.APIVersion == esv1beta1.SchemeGroupVersion.String() && owner.Kind == esv1beta1.ClusterExtSecretKind && owner.Name == cesName
 }
 
-func getRemovedNamespaces(nsList v1.NamespaceList, provisionedNs []string) []string {
-	result := []string{}
+func getRemovedNamespaces(currentNSs v1.NamespaceList, provisionedNSs []string) []string {
+	currentNSSet := map[string]struct{}{}
+	for i := range currentNSs.Items {
+		currentNSSet[currentNSs.Items[i].Name] = struct{}{}
+	}
 
-	for _, ns := range provisionedNs {
-		if !ContainsNamespace(nsList, ns) {
-			result = append(result, ns)
+	var removedNSs []string
+	for _, ns := range provisionedNSs {
+		if _, ok := currentNSSet[ns]; !ok {
+			removedNSs = append(removedNSs, ns)
 		}
 	}
 
-	return result
+	return removedNSs
 }
 
-func setFailedNamespaces(ces *esv1beta1.ClusterExternalSecret, failedNamespaces map[string]string) {
-	if len(failedNamespaces) == 0 {
-		return
-	}
-
-	ces.Status.FailedNamespaces = []esv1beta1.ClusterExternalSecretNamespaceFailure{}
+func toNamespaceFailures(failedNamespaces map[string]error) []esv1beta1.ClusterExternalSecretNamespaceFailure {
+	namespaceFailures := make([]esv1beta1.ClusterExternalSecretNamespaceFailure, len(failedNamespaces))
 
-	for namespace, message := range failedNamespaces {
-		ces.Status.FailedNamespaces = append(ces.Status.FailedNamespaces, esv1beta1.ClusterExternalSecretNamespaceFailure{
+	i := 0
+	for namespace, err := range failedNamespaces {
+		namespaceFailures[i] = esv1beta1.ClusterExternalSecretNamespaceFailure{
 			Namespace: namespace,
-			Reason:    message,
-		})
+			Reason:    err.Error(),
+		}
+		i++
 	}
+	sort.Slice(namespaceFailures, func(i, j int) bool { return namespaceFailures[i].Namespace < namespaceFailures[j].Namespace })
+	return namespaceFailures
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -265,5 +265,73 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options)
 		WithOptions(opts).
 		For(&esv1beta1.ClusterExternalSecret{}).
 		Owns(&esv1beta1.ExternalSecret{}, builder.OnlyMetadata).
+		Watches(
+			&v1.Namespace{},
+			handler.EnqueueRequestsFromMapFunc(r.findObjectsForNamespace),
+			builder.WithPredicates(namespacePredicate()),
+		).
 		Complete(r)
 }
+
+func (r *Reconciler) findObjectsForNamespace(ctx context.Context, namespace client.Object) []reconcile.Request {
+	namespaceLabels := labels.Set(namespace.GetLabels())
+
+	// Avoid consuming too much memory
+	const limit = 100
+	var requests []reconcile.Request
+	options := &client.ListOptions{Limit: limit}
+
+	for {
+		var clusterExternalSecrets esv1beta1.ClusterExternalSecretList
+		if err := r.List(ctx, &clusterExternalSecrets, options); err != nil {
+			r.Log.Error(err, errGetCES)
+			return []reconcile.Request{}
+		}
+
+		for i := range clusterExternalSecrets.Items {
+			clusterExternalSecret := &clusterExternalSecrets.Items[i]
+			labelSelector, err := metav1.LabelSelectorAsSelector(&clusterExternalSecret.Spec.NamespaceSelector)
+			if err != nil {
+				r.Log.Error(err, errConvertLabelSelector)
+				return []reconcile.Request{}
+			}
+
+			if labelSelector.Matches(namespaceLabels) {
+				requests = append(requests, reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Name:      clusterExternalSecret.GetName(),
+						Namespace: clusterExternalSecret.GetNamespace(),
+					},
+				})
+			}
+		}
+
+		if clusterExternalSecrets.Continue == "" {
+			break
+		}
+
+		options = &client.ListOptions{
+			Limit:    limit,
+			Continue: clusterExternalSecrets.Continue,
+		}
+	}
+
+	return requests
+}
+
+func namespacePredicate() predicate.Predicate {
+	return predicate.Funcs{
+		CreateFunc: func(e event.CreateEvent) bool {
+			return true
+		},
+		UpdateFunc: func(e event.UpdateEvent) bool {
+			if e.ObjectOld == nil || e.ObjectNew == nil {
+				return false
+			}
+			return !reflect.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels())
+		},
+		DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
+			return true
+		},
+	}
+}

+ 478 - 309
pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller_test.go

@@ -16,381 +16,550 @@ package clusterexternalsecret
 
 import (
 	"context"
+	"fmt"
 	"math/rand"
+	"sort"
 	"time"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	v1 "k8s.io/api/core/v1"
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
+	crclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
-	ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
 	ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
 )
 
+func init() {
+	ctrlmetrics.SetUpLabelNames(false)
+	cesmetrics.SetUpMetrics()
+}
+
 var (
 	timeout  = time.Second * 10
 	interval = time.Millisecond * 250
 )
 
-var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")
+type testCase struct {
+	namespaces                    []v1.Namespace
+	clusterExternalSecret         func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret
+	beforeCheck                   func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret)
+	expectedClusterExternalSecret func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret
+	expectedExternalSecrets       func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret
+}
 
-func RandString(n int) string {
-	b := make([]rune, n)
-	for i := range b {
-		b[i] = letterRunes[rand.Intn(len(letterRunes))]
+var _ = Describe("ClusterExternalSecret controller", func() {
+	defaultClusterExternalSecret := func() *esv1beta1.ClusterExternalSecret {
+		return &esv1beta1.ClusterExternalSecret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: fmt.Sprintf("test-ces-%s", randString(10)),
+			},
+			Spec: esv1beta1.ClusterExternalSecretSpec{
+				ExternalSecretSpec: esv1beta1.ExternalSecretSpec{
+					SecretStoreRef: esv1beta1.SecretStoreRef{
+						Name: "test-store",
+					},
+					Target: esv1beta1.ExternalSecretTarget{
+						Name: "test-secret",
+					},
+					Data: []esv1beta1.ExternalSecretData{
+						{
+							SecretKey: "test-secret-key",
+							RemoteRef: esv1beta1.ExternalSecretDataRemoteRef{
+								Key: "test-remote-key",
+							},
+						},
+					},
+				},
+			},
+		}
 	}
-	return string(b)
-}
 
-type testNamespace struct {
-	namespace  v1.Namespace
-	containsES bool
-	deletedES  bool
-}
+	DescribeTable("When reconciling a ClusterExternal Secret",
+		func(tc testCase) {
+			ctx := context.Background()
+			By("creating namespaces")
+			var namespaces []v1.Namespace
+			for _, ns := range tc.namespaces {
+				err := k8sClient.Create(ctx, &ns)
+				Expect(err).ShouldNot(HaveOccurred())
+				namespaces = append(namespaces, ns)
+			}
 
-type testCase struct {
-	clusterExternalSecret *esv1beta1.ClusterExternalSecret
+			By("creating a cluster external secret")
+			ces := tc.clusterExternalSecret(tc.namespaces)
+			err := k8sClient.Create(ctx, &ces)
+			Expect(err).ShouldNot(HaveOccurred())
 
-	// These are the namespaces that are being tested
-	externalSecretNamespaces []testNamespace
+			By("running before check")
+			if tc.beforeCheck != nil {
+				tc.beforeCheck(ctx, namespaces, ces)
+			}
 
-	// The labels to be used for the namespaces
-	namespaceLabels map[string]string
+			// the before check above may have updated the namespaces, so refresh them
+			for i, ns := range namespaces {
+				err := k8sClient.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
+				Expect(err).ShouldNot(HaveOccurred())
+				namespaces[i] = ns
+			}
 
-	// This is a setup function called for each test much like BeforeEach but with knowledge of the test case
-	// This is used by default to create namespaces and random labels
-	setup func(*testCase)
+			By("checking the cluster external secret")
+			expectedCES := tc.expectedClusterExternalSecret(namespaces, ces)
 
-	// Is a method that's ran after everything has been created, but before the check methods are called
-	beforeCheck func(*testCase)
+			Eventually(func(g Gomega) {
+				key := types.NamespacedName{Name: expectedCES.Name}
+				var gotCes esv1beta1.ClusterExternalSecret
+				err = k8sClient.Get(ctx, key, &gotCes)
+				g.Expect(err).ShouldNot(HaveOccurred())
 
-	// A function to do any work needed before a test is ran
-	preTest func()
+				g.Expect(gotCes.Labels).To(Equal(expectedCES.Labels))
+				g.Expect(gotCes.Annotations).To(Equal(expectedCES.Annotations))
+				g.Expect(gotCes.Spec).To(Equal(expectedCES.Spec))
+				g.Expect(gotCes.Status).To(Equal(expectedCES.Status))
+			}).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
 
-	// checkCondition should return true if the externalSecret
-	// has the expected condition
-	checkCondition func(*esv1beta1.ClusterExternalSecret) bool
+			By("checking the external secrets")
+			expectedESs := tc.expectedExternalSecrets(namespaces, ces)
 
-	// checkExternalSecret is called after the condition has been verified
-	// use this to verify the externalSecret
-	checkClusterExternalSecret func(*esv1beta1.ClusterExternalSecret)
+			Eventually(func(g Gomega) {
+				var gotESs []esv1beta1.ExternalSecret
+				for _, ns := range namespaces {
+					var externalSecrets esv1beta1.ExternalSecretList
+					err := k8sClient.List(ctx, &externalSecrets, crclient.InNamespace(ns.Name))
+					g.Expect(err).ShouldNot(HaveOccurred())
 
-	// checkExternalSecret is called after the condition has been verified
-	// use this to verify the externalSecret
-	checkExternalSecret func(*esv1beta1.ClusterExternalSecret, *esv1beta1.ExternalSecret)
-}
+					gotESs = append(gotESs, externalSecrets.Items...)
+				}
 
-type testTweaks func(*testCase)
+				g.Expect(len(gotESs)).Should(Equal(len(expectedESs)))
+				for _, gotES := range gotESs {
+					found := false
+					for _, expectedES := range expectedESs {
+						if gotES.Namespace == expectedES.Namespace && gotES.Name == expectedES.Name {
+							found = true
+							g.Expect(gotES.Labels).To(Equal(expectedES.Labels))
+							g.Expect(gotES.Annotations).To(Equal(expectedES.Annotations))
+							g.Expect(gotES.Spec).To(Equal(expectedES.Spec))
+						}
+					}
+					g.Expect(found).To(Equal(true))
+				}
+			}).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
+		},
 
-var _ = Describe("ClusterExternalSecret controller", func() {
-	const (
-		ClusterExternalSecretName      = "test-ces"
-		ExternalSecretName             = "test-es"
-		ExternalSecretStore            = "test-store"
-		ExternalSecretTargetSecretName = "test-secret"
-	)
-
-	var ExternalSecretNamespaceTargets = []testNamespace{
-		{
-			namespace: v1.Namespace{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "test-ns-1",
-				},
+		Entry("Should use cluster external secret name if external secret name isn't defined", testCase{
+			namespaces: []v1.Namespace{
+				{ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
 			},
-			containsES: true,
-		},
-		{
-			namespace: v1.Namespace{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "test-ns-2",
-				},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
+				return *ces
 			},
-			containsES: true,
-		},
-		{
-			namespace: v1.Namespace{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "test-ns-5",
-				},
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
+				return esv1beta1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: created.Spec,
+					Status: esv1beta1.ClusterExternalSecretStatus{
+						ProvisionedNamespaces: []string{namespaces[0].Name},
+						Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
+							{
+								Type:   esv1beta1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
+				}
 			},
-			containsES: false,
-		},
-	}
-
-	const targetProp = "targetProperty"
-	const remoteKey = "barz"
-	const remoteProperty = "bang"
-
-	makeDefaultTestCase := func() *testCase {
-		return &testCase{
-			checkCondition: func(ces *esv1beta1.ClusterExternalSecret) bool {
-				cond := GetClusterExternalSecretCondition(ces.Status, esv1beta1.ClusterExternalSecretReady)
-				if cond == nil || cond.Status != v1.ConditionTrue {
-					return false
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
+				return []esv1beta1.ExternalSecret{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespaces[0].Name,
+							Name:      created.Name,
+						},
+						Spec: created.Spec.ExternalSecretSpec,
+					},
 				}
-				return true
 			},
-			checkClusterExternalSecret: func(es *esv1beta1.ClusterExternalSecret) {
-				// To be implemented by the tests
+		}),
+		Entry("Should set external secret name and metadata if the fields are set", testCase{
+			namespaces: []v1.Namespace{
+				{ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
 			},
-			checkExternalSecret: func(*esv1beta1.ClusterExternalSecret, *esv1beta1.ExternalSecret) {
-				// To be implemented by the tests
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
+				ces.Spec.ExternalSecretName = "test-es"
+				ces.Spec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
+					Labels:      map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
+					Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
+				}
+				return *ces
 			},
-			clusterExternalSecret: &esv1beta1.ClusterExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					GenerateName: ClusterExternalSecretName,
-				},
-				Spec: esv1beta1.ClusterExternalSecretSpec{
-					NamespaceSelector:  metav1.LabelSelector{},
-					ExternalSecretName: ExternalSecretName,
-					ExternalSecretSpec: esv1beta1.ExternalSecretSpec{
-						SecretStoreRef: esv1beta1.SecretStoreRef{
-							Name: ExternalSecretStore,
-						},
-						Target: esv1beta1.ExternalSecretTarget{
-							Name: ExternalSecretTargetSecretName,
-						},
-						Data: []esv1beta1.ExternalSecretData{
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
+				return esv1beta1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: created.Spec,
+					Status: esv1beta1.ClusterExternalSecretStatus{
+						ProvisionedNamespaces: []string{namespaces[0].Name},
+						Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
 							{
-								SecretKey: targetProp,
-								RemoteRef: esv1beta1.ExternalSecretDataRemoteRef{
-									Key:      remoteKey,
-									Property: remoteProperty,
-								},
+								Type:   esv1beta1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
 							},
 						},
 					},
-				},
+				}
 			},
-			setup: func(tc *testCase) {
-				// Generate a random label since we don't want to match previous ones.
-				tc.namespaceLabels = map[string]string{
-					RandString(5): RandString(5),
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
+				return []esv1beta1.ExternalSecret{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace:   namespaces[0].Name,
+							Name:        "test-es",
+							Labels:      map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
+							Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
+						},
+						Spec: created.Spec.ExternalSecretSpec,
+					},
 				}
-
-				namespaces := []testNamespace{}
-				for _, ns := range ExternalSecretNamespaceTargets {
-					name, err := ctest.CreateNamespaceWithLabels(ns.namespace.Name, k8sClient, tc.namespaceLabels)
-					Expect(err).ToNot(HaveOccurred())
-
-					newNs := ns
-					newNs.namespace.ObjectMeta.Name = name
-					namespaces = append(namespaces, newNs)
+			},
+		}),
+		Entry("Should update external secret if the fields change", testCase{
+			namespaces: []v1.Namespace{
+				{ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
+			},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
+				return *ces
+			},
+			beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
+				// Wait until the external secret is provisioned
+				var es esv1beta1.ExternalSecret
+				Eventually(func(g Gomega) {
+					key := types.NamespacedName{Namespace: namespaces[0].Name, Name: created.Name}
+					g.Expect(k8sClient.Get(ctx, key, &es)).ShouldNot(HaveOccurred())
+					g.Expect(len(es.Labels)).Should(Equal(0))
+					g.Expect(len(es.Annotations)).Should(Equal(0))
+					g.Expect(es.Spec).Should(Equal(created.Spec.ExternalSecretSpec))
+				}).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
+
+				copied := created.DeepCopy()
+				copied.Spec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
+					Labels:      map[string]string{"test-label-key": "test-label-value"},
+					Annotations: map[string]string{"test-annotation-key": "test-annotation-value"},
 				}
-
-				tc.externalSecretNamespaces = namespaces
-
-				tc.clusterExternalSecret.Spec.NamespaceSelector.MatchLabels = tc.namespaceLabels
+				copied.Spec.ExternalSecretSpec.SecretStoreRef.Name = "updated-test-store" //nolint:goconst
+				Expect(k8sClient.Patch(ctx, copied, crclient.MergeFrom(created.DeepCopy()))).ShouldNot(HaveOccurred())
 			},
-		}
-	}
-
-	// If the ES does noes not have a name specified then it should use the CES name
-	syncWithoutESName := func(tc *testCase) {
-		tc.clusterExternalSecret.Spec.ExternalSecretName = ""
-		tc.checkExternalSecret = func(ces *esv1beta1.ClusterExternalSecret, es *esv1beta1.ExternalSecret) {
-			Expect(es.ObjectMeta.Name).To(Equal(ces.ObjectMeta.Name))
-		}
-	}
-
-	syncWithESMetadata := func(tc *testCase) {
-		tc.clusterExternalSecret.Spec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
-			Labels:      map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
-			Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
-		}
-		tc.checkExternalSecret = func(ces *esv1beta1.ClusterExternalSecret, es *esv1beta1.ExternalSecret) {
-			Expect(es.ObjectMeta.Labels).To(Equal(map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"}))
-			Expect(es.ObjectMeta.Annotations).To(Equal(map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"}))
-		}
-	}
-
-	doNotOverwriteExistingES := func(tc *testCase) {
-		tc.preTest = func() {
-			es := &esv1beta1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      ExternalSecretName,
-					Namespace: tc.externalSecretNamespaces[0].namespace.Name,
-				},
-			}
-
-			err := k8sClient.Create(context.Background(), es, &client.CreateOptions{})
-			Expect(err).ShouldNot(HaveOccurred())
-		}
-		tc.checkCondition = func(ces *esv1beta1.ClusterExternalSecret) bool {
-			cond := GetClusterExternalSecretCondition(ces.Status, esv1beta1.ClusterExternalSecretPartiallyReady)
-			return cond != nil
-		}
-		tc.checkClusterExternalSecret = func(ces *esv1beta1.ClusterExternalSecret) {
-			Expect(len(ces.Status.FailedNamespaces)).Should(Equal(1))
-
-			failure := ces.Status.FailedNamespaces[0]
-
-			Expect(failure.Namespace).Should(Equal(tc.externalSecretNamespaces[0].namespace.Name))
-			Expect(failure.Reason).Should(Equal(errSecretAlreadyExists))
-		}
-	}
-
-	populatedProvisionedNamespaces := func(tc *testCase) {
-		tc.checkClusterExternalSecret = func(ces *esv1beta1.ClusterExternalSecret) {
-			for _, namespace := range tc.externalSecretNamespaces {
-				if !namespace.containsES {
-					continue
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
+				updatedSpec := created.Spec.DeepCopy()
+				updatedSpec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
+					Labels:      map[string]string{"test-label-key": "test-label-value"},
+					Annotations: map[string]string{"test-annotation-key": "test-annotation-value"},
 				}
+				updatedSpec.ExternalSecretSpec.SecretStoreRef.Name = "updated-test-store"
 
-				Expect(sliceContainsString(namespace.namespace.Name, ces.Status.ProvisionedNamespaces)).To(BeTrue())
-			}
-		}
-	}
+				return esv1beta1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: *updatedSpec,
+					Status: esv1beta1.ClusterExternalSecretStatus{
+						ProvisionedNamespaces: []string{namespaces[0].Name},
+						Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
+							{
+								Type:   esv1beta1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
+				}
+			},
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
+				updatedSpec := created.Spec.ExternalSecretSpec.DeepCopy()
+				updatedSpec.SecretStoreRef.Name = "updated-test-store"
 
-	deleteESInNonMatchingNS := func(tc *testCase) {
-		tc.beforeCheck = func(tc *testCase) {
-			ns := tc.externalSecretNamespaces[0]
+				return []esv1beta1.ExternalSecret{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace:   namespaces[0].Name,
+							Name:        created.Name,
+							Labels:      map[string]string{"test-label-key": "test-label-value"},
+							Annotations: map[string]string{"test-annotation-key": "test-annotation-value"},
+						},
+						Spec: *updatedSpec,
+					},
+				}
+			},
+		}),
+		Entry("Should not overwrite existing external secrets and error out if one is present", testCase{
+			namespaces: []v1.Namespace{
+				{ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
+			},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
+
+				es := &esv1beta1.ExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      ces.Name,
+						Namespace: namespaces[0].Name,
+					},
+				}
+				Expect(k8sClient.Create(context.Background(), es)).ShouldNot(HaveOccurred())
 
-			// Remove the labels, but leave the should contain ES so we can still check it
-			ns.namespace.ObjectMeta.Labels = map[string]string{}
-			tc.externalSecretNamespaces[0].deletedES = true
+				return *ces
+			},
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
+				return esv1beta1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: created.Spec,
+					Status: esv1beta1.ClusterExternalSecretStatus{
+						FailedNamespaces: []esv1beta1.ClusterExternalSecretNamespaceFailure{
+							{
+								Namespace: namespaces[0].Name,
+								Reason:    "external secret already exists in namespace",
+							},
+						},
+						Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
+							{
+								Type:    esv1beta1.ClusterExternalSecretNotReady,
+								Status:  v1.ConditionTrue,
+								Message: "one or more namespaces failed",
+							},
+						},
+					},
+				}
+			},
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
+				return []esv1beta1.ExternalSecret{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespaces[0].Name,
+							Name:      created.Name,
+						},
+						Spec: esv1beta1.ExternalSecretSpec{
+							Target: esv1beta1.ExternalSecretTarget{
+								CreationPolicy: "Owner",
+								DeletionPolicy: "Retain",
+							},
+							RefreshInterval: &metav1.Duration{Duration: time.Hour},
+						},
+					},
+				}
+			},
+		}),
+		Entry("Should crate an external secret and if one with the same name has been deleted", testCase{
+			namespaces: []v1.Namespace{
+				{ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
+			},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
+				return *ces
+			},
+			beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
+				es := &esv1beta1.ExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      created.Name,
+						Namespace: namespaces[0].Name,
+					},
+				}
+				Expect(k8sClient.Create(ctx, es)).ShouldNot(HaveOccurred())
 
-			err := k8sClient.Update(context.Background(), &ns.namespace, &client.UpdateOptions{})
-			Expect(err).ToNot(HaveOccurred())
-			time.Sleep(time.Second) // Sleep to make sure the controller gets it.
-		}
-	}
+				ces := esv1beta1.ClusterExternalSecret{}
+				Eventually(func(g Gomega) {
+					key := types.NamespacedName{Namespace: created.Namespace, Name: created.Name}
+					g.Expect(k8sClient.Get(ctx, key, &ces)).ShouldNot(HaveOccurred())
+					g.Expect(len(ces.Status.FailedNamespaces)).Should(Equal(1))
+				}).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
 
-	syncWithMatchExpressions := func(tc *testCase) {
-		tc.setup = func(tc *testCase) {
-			prefixes := []string{"foo", "bar", "baz"}
-			for _, prefix := range prefixes {
-				labels := map[string]string{
-					"e2e":    "with-label-selector",
-					"prefix": prefix,
+				Expect(k8sClient.Delete(ctx, es)).ShouldNot(HaveOccurred())
+			},
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
+				return esv1beta1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: created.Spec,
+					Status: esv1beta1.ClusterExternalSecretStatus{
+						ProvisionedNamespaces: []string{namespaces[0].Name},
+						Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
+							{
+								Type:    esv1beta1.ClusterExternalSecretNotReady,
+								Status:  v1.ConditionTrue,
+								Message: "one or more namespaces failed",
+							},
+							{
+								Type:   esv1beta1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
 				}
-				ns, err := ctest.CreateNamespaceWithLabels(prefix, k8sClient, labels)
-				Expect(err).ToNot(HaveOccurred())
-				tc.externalSecretNamespaces = append(tc.externalSecretNamespaces, testNamespace{
-					namespace: v1.Namespace{
+			},
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
+				return []esv1beta1.ExternalSecret{
+					{
 						ObjectMeta: metav1.ObjectMeta{
-							Name: ns,
+							Namespace: namespaces[0].Name,
+							Name:      created.Name,
 						},
+						Spec: created.Spec.ExternalSecretSpec,
 					},
-					containsES: true,
-				})
-			}
-			tc.clusterExternalSecret.Spec.NamespaceSelector.MatchExpressions = []metav1.LabelSelectorRequirement{
+				}
+			},
+		}),
+		Entry("Should delete external secrets when namespaces no longer match", testCase{
+			namespaces: []v1.Namespace{
 				{
-					Key:      "prefix",
-					Operator: metav1.LabelSelectorOpIn,
-					Values:   prefixes,
+					ObjectMeta: metav1.ObjectMeta{
+						Name:   randomNamespaceName(),
+						Labels: map[string]string{"no-longer-match-label-key": "no-longer-match-label-value"},
+					},
 				},
-			}
-		}
-		tc.checkClusterExternalSecret = func(ces *esv1beta1.ClusterExternalSecret) {
-			for _, namespace := range tc.externalSecretNamespaces {
-				var es esv1beta1.ExternalSecret
-				err := k8sClient.Get(context.Background(), types.NamespacedName{
-					Namespace: namespace.namespace.Name,
-					Name:      ExternalSecretName,
-				}, &es)
-				Expect(err).ToNot(HaveOccurred())
-				Expect(sliceContainsString(namespace.namespace.Name, ces.Status.ProvisionedNamespaces)).To(BeTrue())
-			}
-		}
-	}
-
-	DescribeTable("When reconciling a ClusterExternal Secret",
-		func(tweaks ...testTweaks) {
-			tc := makeDefaultTestCase()
-			for _, tweak := range tweaks {
-				tweak(tc)
-			}
-
-			// Run test setup
-			tc.setup(tc)
-
-			if tc.preTest != nil {
-				By("running pre-test")
-				tc.preTest()
-			}
-			ctx := context.Background()
-			By("creating namespaces and cluster external secret")
-			err := k8sClient.Create(ctx, tc.clusterExternalSecret)
-			Expect(err).ShouldNot(HaveOccurred())
-			cesKey := types.NamespacedName{Name: tc.clusterExternalSecret.Name}
-			createdCES := &esv1beta1.ClusterExternalSecret{}
-
-			By("checking the ces condition")
-			Eventually(func() bool {
-				err := k8sClient.Get(ctx, cesKey, createdCES)
-				if err != nil {
-					return false
-				}
-				return tc.checkCondition(createdCES)
-			}, timeout, interval).Should(BeTrue())
-
-			// Run before check
-			if tc.beforeCheck != nil {
-				tc.beforeCheck(tc)
-			}
-
-			tc.checkClusterExternalSecret(createdCES)
-
-			if tc.checkExternalSecret != nil {
-				for _, ns := range tc.externalSecretNamespaces {
-
-					if !ns.containsES {
-						continue
-					}
-
-					es := &esv1beta1.ExternalSecret{}
-
-					esName := createdCES.Spec.ExternalSecretName
-					if esName == "" {
-						esName = createdCES.ObjectMeta.Name
-					}
-
-					esLookupKey := types.NamespacedName{
-						Name:      esName,
-						Namespace: ns.namespace.Name,
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:   randomNamespaceName(),
+						Labels: map[string]string{"no-longer-match-label-key": "no-longer-match-label-value"},
+					},
+				},
+			},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Spec.RefreshInterval = &metav1.Duration{Duration: 100 * time.Millisecond}
+				ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"no-longer-match-label-key": "no-longer-match-label-value"}
+				return *ces
+			},
+			beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
+				// Wait until the target ESs have been created
+				Eventually(func(g Gomega) {
+					for _, ns := range namespaces {
+						key := types.NamespacedName{Namespace: ns.Name, Name: created.Name}
+						g.Expect(k8sClient.Get(ctx, key, &esv1beta1.ExternalSecret{})).ShouldNot(HaveOccurred())
 					}
+				}).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
 
-					Eventually(func() bool {
-						err := k8sClient.Get(ctx, esLookupKey, es)
-
-						if ns.deletedES && apierrors.IsNotFound(err) {
-							return true
-						}
-
-						return err == nil
-					}, timeout, interval).Should(BeTrue())
-					tc.checkExternalSecret(createdCES, es)
+				for _, ns := range namespaces {
+					ns.Labels = map[string]string{}
+					Expect(k8sClient.Update(ctx, &ns)).ShouldNot(HaveOccurred())
 				}
-			}
-		},
-
-		Entry("Should use cluster external secret name if external secret name isn't defined", syncWithoutESName),
-		Entry("Should set external secret metadata if the field is set", syncWithESMetadata),
-		Entry("Should not overwrite existing external secrets and error out if one is present", doNotOverwriteExistingES),
-		Entry("Should have list of all provisioned namespaces", populatedProvisionedNamespaces),
-		Entry("Should delete external secrets when namespaces no longer match", deleteESInNonMatchingNS),
-		Entry("Should sync with label selector", syncWithMatchExpressions))
+			},
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
+				return esv1beta1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: created.Spec,
+					Status: esv1beta1.ClusterExternalSecretStatus{
+						Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
+							{
+								Type:   esv1beta1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
+				}
+			},
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
+				return []esv1beta1.ExternalSecret{}
+			},
+		}),
+		Entry("Should sync with match expression", testCase{
+			namespaces: []v1.Namespace{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:   randomNamespaceName(),
+						Labels: map[string]string{"prefix": "foo"},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:   randomNamespaceName(),
+						Labels: map[string]string{"prefix": "bar"},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:   randomNamespaceName(),
+						Labels: map[string]string{"prefix": "baz"},
+					},
+				},
+			},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Spec.RefreshInterval = &metav1.Duration{Duration: 100 * time.Millisecond}
+				ces.Spec.NamespaceSelector.MatchExpressions = []metav1.LabelSelectorRequirement{
+					{
+						Key:      "prefix",
+						Operator: metav1.LabelSelectorOpIn,
+						Values:   []string{"foo", "bar"}, // "baz" is excluded
+					},
+				}
+				return *ces
+			},
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
+				provisionedNamespaces := []string{namespaces[0].Name, namespaces[1].Name}
+				sort.Strings(provisionedNamespaces)
+				return esv1beta1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: created.Spec,
+					Status: esv1beta1.ClusterExternalSecretStatus{
+						ProvisionedNamespaces: provisionedNamespaces,
+						Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
+							{
+								Type:   esv1beta1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
+				}
+			},
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
+				return []esv1beta1.ExternalSecret{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespaces[0].Name,
+							Name:      created.Name,
+						},
+						Spec: created.Spec.ExternalSecretSpec,
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespaces[1].Name,
+							Name:      created.Name,
+						},
+						Spec: created.Spec.ExternalSecretSpec,
+					},
+				}
+			},
+		}))
 })
 
-func sliceContainsString(toFind string, collection []string) bool {
-	for _, val := range collection {
-		if val == toFind {
-			return true
-		}
-	}
+var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")
 
-	return false
+func randString(n int) string {
+	b := make([]rune, n)
+	for i := range b {
+		b[i] = letterRunes[rand.Intn(len(letterRunes))]
+	}
+	return string(b)
 }
 
-func init() {
-	ctrlmetrics.SetUpLabelNames(false)
-	cesmetrics.SetUpMetrics()
+func randomNamespaceName() string {
+	return fmt.Sprintf("testns-%s", randString(10))
 }

+ 1 - 3
pkg/controllers/clusterexternalsecret/suite_test.go

@@ -24,7 +24,6 @@ import (
 	. "github.com/onsi/gomega"
 	"go.uber.org/zap/zapcore"
 	"k8s.io/client-go/kubernetes/scheme"
-	"k8s.io/client-go/rest"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -38,7 +37,6 @@ import (
 // These tests use Ginkgo (BDD-style Go testing framework). Refer to
 // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
 
-var cfg *rest.Config
 var k8sClient client.Client
 var testEnv *envtest.Environment
 var cancel context.CancelFunc
@@ -62,7 +60,7 @@ var _ = BeforeSuite(func() {
 	ctx, cancel = context.WithCancel(context.Background())
 
 	var err error
-	cfg, err = testEnv.Start()
+	cfg, err := testEnv.Start()
 	Expect(err).ToNot(HaveOccurred())
 	Expect(cfg).ToNot(BeNil())
 

+ 2 - 23
pkg/controllers/clusterexternalsecret/util.go

@@ -21,7 +21,7 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
 )
 
-func NewClusterExternalSecretCondition(failedNamespaces map[string]string, namespaceList *v1.NamespaceList) *esv1beta1.ClusterExternalSecretStatusCondition {
+func NewClusterExternalSecretCondition(failedNamespaces map[string]error, namespaceList *v1.NamespaceList) *esv1beta1.ClusterExternalSecretStatusCondition {
 	conditionType := getConditionType(failedNamespaces, namespaceList)
 	condition := &esv1beta1.ClusterExternalSecretStatusCondition{
 		Type:   conditionType,
@@ -35,17 +35,6 @@ func NewClusterExternalSecretCondition(failedNamespaces map[string]string, names
 	return condition
 }
 
-// GetClusterExternalSecretCondition returns the condition with the provided type.
-func GetClusterExternalSecretCondition(status esv1beta1.ClusterExternalSecretStatus, condType esv1beta1.ClusterExternalSecretConditionType) *esv1beta1.ClusterExternalSecretStatusCondition {
-	for i := range status.Conditions {
-		c := status.Conditions[i]
-		if c.Type == condType {
-			return &c
-		}
-	}
-	return nil
-}
-
 func SetClusterExternalSecretCondition(ces *esv1beta1.ClusterExternalSecret, condition esv1beta1.ClusterExternalSecretStatusCondition) {
 	ces.Status.Conditions = append(filterOutCondition(ces.Status.Conditions, condition.Type), condition)
 	cesmetrics.UpdateClusterExternalSecretCondition(ces, &condition)
@@ -63,17 +52,7 @@ func filterOutCondition(conditions []esv1beta1.ClusterExternalSecretStatusCondit
 	return newConditions
 }
 
-func ContainsNamespace(namespaces v1.NamespaceList, namespace string) bool {
-	for _, ns := range namespaces.Items {
-		if ns.ObjectMeta.Name == namespace {
-			return true
-		}
-	}
-
-	return false
-}
-
-func getConditionType(failedNamespaces map[string]string, namespaceList *v1.NamespaceList) esv1beta1.ClusterExternalSecretConditionType {
+func getConditionType(failedNamespaces map[string]error, namespaceList *v1.NamespaceList) esv1beta1.ClusterExternalSecretConditionType {
 	if len(failedNamespaces) == 0 {
 		return esv1beta1.ClusterExternalSecretReady
 	}

+ 1 - 2
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -31,7 +31,6 @@ import (
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/record"
 	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/builder"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -555,6 +554,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options)
 	return ctrl.NewControllerManagedBy(mgr).
 		WithOptions(opts).
 		For(&esv1beta1.ExternalSecret{}).
-		Owns(&v1.Secret{}, builder.OnlyMetadata).
+		Owns(&v1.Secret{}).
 		Complete(r)
 }

+ 5 - 5
pkg/controllers/webhookconfig/webhookconfig_test.go

@@ -24,7 +24,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 )
 
 const defaultCACert = `-----BEGIN CERTIFICATE-----
@@ -236,27 +236,27 @@ func makeValidatingWebhookConfig() *admissionregistration.ValidatingWebhookConfi
 		Webhooks: []admissionregistration.ValidatingWebhook{
 			{
 				Name:                    "secretstores.external-secrets.io",
-				SideEffects:             (*admissionregistration.SideEffectClass)(pointer.String(string(admissionregistration.SideEffectClassNone))),
+				SideEffects:             (*admissionregistration.SideEffectClass)(pointer.To(string(admissionregistration.SideEffectClassNone))),
 				AdmissionReviewVersions: []string{"v1"},
 				ClientConfig: admissionregistration.WebhookClientConfig{
 					CABundle: []byte("Cg=="),
 					Service: &admissionregistration.ServiceReference{
 						Name:      "noop",
 						Namespace: "noop",
-						Path:      pointer.String("/validate-secretstore"),
+						Path:      pointer.To("/validate-secretstore"),
 					},
 				},
 			},
 			{
 				Name:                    "clustersecretstores.external-secrets.io",
-				SideEffects:             (*admissionregistration.SideEffectClass)(pointer.String(string(admissionregistration.SideEffectClassNone))),
+				SideEffects:             (*admissionregistration.SideEffectClass)(pointer.To(string(admissionregistration.SideEffectClassNone))),
 				AdmissionReviewVersions: []string{"v1"},
 				ClientConfig: admissionregistration.WebhookClientConfig{
 					CABundle: []byte("Cg=="),
 					Service: &admissionregistration.ServiceReference{
 						Name:      "noop",
 						Namespace: "noop",
-						Path:      pointer.String("/validate-clustersecretstore"),
+						Path:      pointer.To("/validate-clustersecretstore"),
 					},
 				},
 			},

+ 3 - 3
pkg/generator/ecr/ecr_test.go

@@ -28,7 +28,7 @@ import (
 	v1 "k8s.io/api/core/v1"
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	utilpointer "k8s.io/utils/pointer"
+	utilpointer "k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
@@ -86,8 +86,8 @@ func TestGenerate(t *testing.T) {
 					return &ecr.GetAuthorizationTokenOutput{
 						AuthorizationData: []*ecr.AuthorizationData{
 							{
-								AuthorizationToken: utilpointer.String(base64.StdEncoding.EncodeToString([]byte("uuser:pass"))),
-								ProxyEndpoint:      utilpointer.String("foo"),
+								AuthorizationToken: utilpointer.To(base64.StdEncoding.EncodeToString([]byte("uuser:pass"))),
+								ProxyEndpoint:      utilpointer.To("foo"),
 								ExpiresAt:          &t,
 							},
 						},

+ 5 - 5
pkg/provider/aws/parameterstore/parameterstore.go

@@ -26,7 +26,7 @@ import (
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/tidwall/gjson"
-	utilpointer "k8s.io/utils/pointer"
+	utilpointer "k8s.io/utils/ptr"
 	ctrl "sigs.k8s.io/controller-runtime"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -324,9 +324,9 @@ func (pm *ParameterStore) findByTags(ctx context.Context, ref esv1beta1.External
 	filters := make([]*ssm.ParameterStringFilter, 0)
 	for k, v := range ref.Tags {
 		filters = append(filters, &ssm.ParameterStringFilter{
-			Key:    utilpointer.String(fmt.Sprintf("tag:%s", k)),
-			Values: []*string{utilpointer.String(v)},
-			Option: utilpointer.String("Equals"),
+			Key:    utilpointer.To(fmt.Sprintf("tag:%s", k)),
+			Values: []*string{utilpointer.To(v)},
+			Option: utilpointer.To("Equals"),
 		})
 	}
 
@@ -368,7 +368,7 @@ func (pm *ParameterStore) findByTags(ctx context.Context, ref esv1beta1.External
 
 func (pm *ParameterStore) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
 	out, err := pm.client.GetParameterWithContext(ctx, &ssm.GetParameterInput{
-		Name:           utilpointer.String(name),
+		Name:           utilpointer.To(name),
 		WithDecryption: aws.Bool(true),
 	})
 	metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSGetParameter, err)

+ 4 - 4
pkg/provider/aws/provider_test.go

@@ -26,7 +26,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -204,7 +204,7 @@ func TestValidateStore(t *testing.T) {
 									SecretRef: &esv1beta1.AWSAuthSecretRef{
 										AccessKeyID: esmeta.SecretKeySelector{
 											Name:      "foobar",
-											Namespace: pointer.String("unacceptable"),
+											Namespace: pointer.To("unacceptable"),
 										},
 									},
 								},
@@ -227,7 +227,7 @@ func TestValidateStore(t *testing.T) {
 									SecretRef: &esv1beta1.AWSAuthSecretRef{
 										SecretAccessKey: esmeta.SecretKeySelector{
 											Name:      "foobar",
-											Namespace: pointer.String("unacceptable"),
+											Namespace: pointer.To("unacceptable"),
 										},
 									},
 								},
@@ -325,7 +325,7 @@ func TestValidateStore(t *testing.T) {
 									JWTAuth: &esv1beta1.AWSJWTAuth{
 										ServiceAccountRef: &esmeta.ServiceAccountSelector{
 											Name:      "foobar",
-											Namespace: pointer.String("unacceptable"),
+											Namespace: pointer.To("unacceptable"),
 										},
 									},
 								},

+ 8 - 8
pkg/provider/aws/secretsmanager/secretsmanager.go

@@ -28,7 +28,7 @@ import (
 	"github.com/aws/aws-sdk-go/aws/session"
 	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
 	"github.com/tidwall/gjson"
-	utilpointer "k8s.io/utils/pointer"
+	utilpointer "k8s.io/utils/ptr"
 	ctrl "sigs.k8s.io/controller-runtime"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -276,7 +276,7 @@ func (sm *SecretsManager) findByName(ctx context.Context, ref esv1beta1.External
 	filters := make([]*awssm.Filter, 0)
 	if ref.Path != nil {
 		filters = append(filters, &awssm.Filter{
-			Key: utilpointer.String(awssm.FilterNameStringTypeName),
+			Key: utilpointer.To(awssm.FilterNameStringTypeName),
 			Values: []*string{
 				ref.Path,
 			},
@@ -318,21 +318,21 @@ func (sm *SecretsManager) findByTags(ctx context.Context, ref esv1beta1.External
 	filters := make([]*awssm.Filter, 0)
 	for k, v := range ref.Tags {
 		filters = append(filters, &awssm.Filter{
-			Key: utilpointer.String(awssm.FilterNameStringTypeTagKey),
+			Key: utilpointer.To(awssm.FilterNameStringTypeTagKey),
 			Values: []*string{
-				utilpointer.String(k),
+				utilpointer.To(k),
 			},
 		}, &awssm.Filter{
-			Key: utilpointer.String(awssm.FilterNameStringTypeTagValue),
+			Key: utilpointer.To(awssm.FilterNameStringTypeTagValue),
 			Values: []*string{
-				utilpointer.String(v),
+				utilpointer.To(v),
 			},
 		})
 	}
 
 	if ref.Path != nil {
 		filters = append(filters, &awssm.Filter{
-			Key: utilpointer.String(awssm.FilterNameStringTypeName),
+			Key: utilpointer.To(awssm.FilterNameStringTypeName),
 			Values: []*string{
 				ref.Path,
 			},
@@ -460,7 +460,7 @@ func (sm *SecretsManager) Validate() (esv1beta1.ValidationResult, error) {
 	}
 	_, err := sm.sess.Config.Credentials.Get()
 	if err != nil {
-		return esv1beta1.ValidationResultError, err
+		return esv1beta1.ValidationResultError, util.SanitizeErr(err)
 	}
 	return esv1beta1.ValidationResultReady, nil
 }

+ 10 - 5
pkg/provider/aws/util/errors.go

@@ -19,11 +19,16 @@ import (
 	"regexp"
 )
 
-var regexReqID = regexp.MustCompile(`request id: (\S+)`)
+var regexReqIDs = []*regexp.Regexp{
+	regexp.MustCompile(`request id: (\S+)`),
+	regexp.MustCompile(` Credential=.+`),
+}
 
-// SanitizeErr sanitizes the error string
-// because the requestID must not be included in the error.
-// otherwise the secrets keeps syncing.
+// SanitizeErr sanitizes the error string.
 func SanitizeErr(err error) error {
-	return errors.New(string(regexReqID.ReplaceAll([]byte(err.Error()), nil)))
+	msg := err.Error()
+	for _, regex := range regexReqIDs {
+		msg = string(regex.ReplaceAll([]byte(msg), nil))
+	}
+	return errors.New(msg)
 }

+ 4 - 0
pkg/provider/aws/util/errors_test.go

@@ -29,6 +29,10 @@ func TestSanitize(t *testing.T) {
 			err:      errors.New("some AccessDeniedException: User: arn:aws:sts::123123123123:assumed-role/foobar is not authorized to perform: secretsmanager:GetSecretValue on resource: example\n\tstatus code: 400, request id: df34-75f-0c5f-4b4c-a71a-f93d581d177c"),
 			expected: "some AccessDeniedException: User: arn:aws:sts::123123123123:assumed-role/foobar is not authorized to perform: secretsmanager:GetSecretValue on resource: example\n\tstatus code: 400, ",
 		},
+		{
+			err:      errors.New("IncompleteSignature: 'something' not a valid key=value pair (missing equal-sign) in Authorization header: 'AWS4-HMAC-SHA256 Credential=You,Can Get\"Almost{Anything}Here', SignedHeaders=content-length;content-type;host;x-amz-date, Signature=42ee80d90508ee472701f8fb7014f10c0ac16b6d6ac59379f0612ca2d35d7464'"),
+			expected: "IncompleteSignature: 'something' not a valid key=value pair (missing equal-sign) in Authorization header: 'AWS4-HMAC-SHA256",
+		},
 		{
 			err:      errors.New("some generic error"),
 			expected: "some generic error",

+ 5 - 5
pkg/provider/azure/keyvault/keyvault.go

@@ -43,7 +43,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/kubernetes"
 	kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
 
@@ -392,10 +392,10 @@ func (a *Azure) setKeyVaultSecret(ctx context.Context, secretName string, value
 	secretParams := keyvault.SecretSetParameters{
 		Value: &val,
 		Tags: map[string]*string{
-			"managed-by": pointer.String(managerLabel),
+			"managed-by": pointer.To(managerLabel),
 		},
 		SecretAttributes: &keyvault.SecretAttributes{
-			Enabled: pointer.Bool(true),
+			Enabled: pointer.To(true),
 		},
 	}
 	_, err = a.baseClient.SetSecret(ctx, *a.provider.VaultURL, secretName, secretParams)
@@ -428,7 +428,7 @@ func (a *Azure) setKeyVaultCertificate(ctx context.Context, secretName string, v
 	params := keyvault.CertificateImportParameters{
 		Base64EncodedCertificate: &val,
 		Tags: map[string]*string{
-			"managed-by": pointer.String(managerLabel),
+			"managed-by": pointer.To(managerLabel),
 		},
 	}
 	_, err = a.baseClient.ImportCertificate(ctx, *a.provider.VaultURL, secretName, params)
@@ -484,7 +484,7 @@ func (a *Azure) setKeyVaultKey(ctx context.Context, secretName string, value []b
 		Key:           &azkey,
 		KeyAttributes: &keyvault.KeyAttributes{},
 		Tags: map[string]*string{
-			"managed-by": pointer.String(managerLabel),
+			"managed-by": pointer.To(managerLabel),
 		},
 	}
 	_, err = a.baseClient.ImportKey(ctx, *a.provider.VaultURL, secretName, params)

+ 10 - 10
pkg/provider/azure/keyvault/keyvault_auth_test.go

@@ -26,7 +26,7 @@ import (
 	tassert "github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
@@ -228,7 +228,7 @@ func TestAuth(t *testing.T) {
 			provider: &esv1beta1.AzureKVProvider{
 				AuthType: &authType,
 				VaultURL: &vaultURL,
-				TenantID: pointer.String("mytenant"),
+				TenantID: pointer.To("mytenant"),
 			},
 		},
 		{
@@ -238,7 +238,7 @@ func TestAuth(t *testing.T) {
 			provider: &esv1beta1.AzureKVProvider{
 				AuthType:      &authType,
 				VaultURL:      &vaultURL,
-				TenantID:      pointer.String("mytenant"),
+				TenantID:      pointer.To("mytenant"),
 				AuthSecretRef: &esv1beta1.AzureKVAuth{},
 			},
 		},
@@ -249,7 +249,7 @@ func TestAuth(t *testing.T) {
 			provider: &esv1beta1.AzureKVProvider{
 				AuthType: &authType,
 				VaultURL: &vaultURL,
-				TenantID: pointer.String("mytenant"),
+				TenantID: pointer.To("mytenant"),
 				AuthSecretRef: &esv1beta1.AzureKVAuth{
 					ClientSecret: &v1.SecretKeySelector{Name: "password"},
 					ClientID:     &v1.SecretKeySelector{Name: "password"},
@@ -268,10 +268,10 @@ func TestAuth(t *testing.T) {
 			provider: &esv1beta1.AzureKVProvider{
 				AuthType: &authType,
 				VaultURL: &vaultURL,
-				TenantID: pointer.String("mytenant"),
+				TenantID: pointer.To("mytenant"),
 				AuthSecretRef: &esv1beta1.AzureKVAuth{
-					ClientSecret: &v1.SecretKeySelector{Name: "password", Namespace: pointer.String("foo")},
-					ClientID:     &v1.SecretKeySelector{Name: "password", Namespace: pointer.String("foo")},
+					ClientSecret: &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo")},
+					ClientID:     &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo")},
 				},
 			},
 		},
@@ -296,10 +296,10 @@ func TestAuth(t *testing.T) {
 			provider: &esv1beta1.AzureKVProvider{
 				AuthType: &authType,
 				VaultURL: &vaultURL,
-				TenantID: pointer.String("mytenant"),
+				TenantID: pointer.To("mytenant"),
 				AuthSecretRef: &esv1beta1.AzureKVAuth{
-					ClientSecret: &v1.SecretKeySelector{Name: "password", Namespace: pointer.String("foo"), Key: "secret"},
-					ClientID:     &v1.SecretKeySelector{Name: "password", Namespace: pointer.String("foo"), Key: "id"},
+					ClientSecret: &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "secret"},
+					ClientID:     &v1.SecretKeySelector{Name: "password", Namespace: pointer.To("foo"), Key: "id"},
 				},
 			},
 		},

+ 42 - 42
pkg/provider/azure/keyvault/keyvault_test.go

@@ -25,7 +25,7 @@ import (
 
 	"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault"
 	"github.com/Azure/go-autorest/autorest"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
@@ -176,9 +176,9 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 		}
 		smtc.secretOutput = keyvault.SecretBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
-			Value: pointer.String("foo"),
+			Value: pointer.To("foo"),
 		}
 		smtc.deleteSecretOutput = keyvault.DeletedSecretBundle{}
 	}
@@ -196,7 +196,7 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 			key: secretName,
 		}
 		smtc.secretOutput = keyvault.SecretBundle{
-			Value: pointer.String("foo"),
+			Value: pointer.To("foo"),
 		}
 		smtc.expectError = errNotManaged
 		smtc.deleteErr = autorest.DetailedError{StatusCode: 500, Method: "DELETE", Message: "Shouldnt happen"}
@@ -216,9 +216,9 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 		}
 		smtc.secretOutput = keyvault.SecretBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
-			Value: pointer.String("foo"),
+			Value: pointer.To("foo"),
 		}
 		smtc.expectError = errNoPermission
 		smtc.deleteErr = autorest.DetailedError{StatusCode: 403, Method: "DELETE", Message: errNoPermission}
@@ -238,7 +238,7 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 		smtc.deleteCertificateOutput = keyvault.DeletedCertificateBundle{}
@@ -274,7 +274,7 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 		smtc.expectError = "No certificate delete Permissions"
@@ -295,7 +295,7 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 		smtc.deleteKeyOutput = keyvault.DeletedKeyBundle{}
@@ -331,7 +331,7 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 		smtc.expectError = errNoPermission
@@ -369,7 +369,7 @@ func TestAzureKeyVaultDeleteSecret(t *testing.T) {
 	}
 
 	sm := Azure{
-		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.String(fakeURL)},
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.To(fakeURL)},
 	}
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
@@ -400,7 +400,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.secretOutput = keyvault.SecretBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 			Value: &goodSecret,
 		}
@@ -412,7 +412,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.secretOutput = keyvault.SecretBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 			Value: &goodSecret,
 		}
@@ -424,7 +424,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.secretOutput = keyvault.SecretBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("nope"),
+				"managed-by": pointer.To("nope"),
 			},
 			Value: &goodSecret,
 		}
@@ -480,7 +480,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String(managerLabel),
+				"managed-by": pointer.To(managerLabel),
 			},
 			Key: &keyvault.JSONWebKey{},
 		}
@@ -492,7 +492,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String(managerLabel),
+				"managed-by": pointer.To(managerLabel),
 			},
 			Key: &keyvault.JSONWebKey{},
 		}
@@ -504,7 +504,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String(managerLabel),
+				"managed-by": pointer.To(managerLabel),
 			},
 			Key: &keyvault.JSONWebKey{},
 		}
@@ -516,7 +516,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String(managerLabel),
+				"managed-by": pointer.To(managerLabel),
 			},
 			Key: &keyvault.JSONWebKey{},
 		}
@@ -528,7 +528,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String(managerLabel),
+				"managed-by": pointer.To(managerLabel),
 			},
 			Key: &keyvault.JSONWebKey{},
 		}
@@ -553,7 +553,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		}
 		smtc.keyOutput = keyvault.KeyBundle{
 			Tags: map[string]*string{
-				"managed-by": pointer.String("internal-secrets"),
+				"managed-by": pointer.To("internal-secrets"),
 			},
 			Key: &keyvault.JSONWebKey{},
 		}
@@ -590,9 +590,9 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 	}
@@ -603,9 +603,9 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 	}
@@ -617,9 +617,9 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 	}
@@ -631,9 +631,9 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 	}
@@ -645,9 +645,9 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 		smtc.expectError = "could not import certificate certname: error"
@@ -663,7 +663,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 		smtc.certOutput = keyvault.CertificateBundle{
 			Cer: &cert,
 			Tags: map[string]*string{
-				"managed-by": pointer.String("external-secrets"),
+				"managed-by": pointer.To("external-secrets"),
 			},
 		}
 	}
@@ -674,9 +674,9 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 			Tags: map[string]*string{
-				"managed-by": pointer.String("foobar"),
+				"managed-by": pointer.To("foobar"),
 			},
 		}
 		smtc.expectError = "certificate certname: not managed by external-secrets"
@@ -688,7 +688,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 		}
 		smtc.expectError = "certificate certname: not managed by external-secrets"
 	}
@@ -699,7 +699,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 		}
 		smtc.expectError = "value from secret is not a valid certificate: could not parse certificate value as PKCS#12, DER or PEM"
 	}
@@ -715,7 +715,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 			key: certName,
 		}
 		smtc.certOutput = keyvault.CertificateBundle{
-			X509Thumbprint: pointer.String("123"),
+			X509Thumbprint: pointer.To("123"),
 		}
 		smtc.expectError = errAPI
 	}
@@ -753,7 +753,7 @@ func TestAzureKeyVaultPushSecret(t *testing.T) {
 	}
 
 	sm := Azure{
-		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.String(fakeURL)},
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.To(fakeURL)},
 	}
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
@@ -1129,7 +1129,7 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 	}
 
 	sm := Azure{
-		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.String(fakeURL)},
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.To(fakeURL)},
 	}
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
@@ -1288,7 +1288,7 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 	}
 
 	sm := Azure{
-		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.String(fakeURL)},
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.To(fakeURL)},
 	}
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
@@ -1443,7 +1443,7 @@ func TestAzureKeyVaultSecretManagerGetAllSecrets(t *testing.T) {
 	}
 
 	sm := Azure{
-		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.String(fakeURL)},
+		provider: &esv1beta1.AzureKVProvider{VaultURL: pointer.To(fakeURL)},
 	}
 	for k, v := range successCases {
 		sm.baseClient = v.mockClient
@@ -1552,7 +1552,7 @@ func TestValidateStore(t *testing.T) {
 							AzureKV: &esv1beta1.AzureKVProvider{
 								AuthSecretRef: &esv1beta1.AzureKVAuth{
 									ClientID: &v1.SecretKeySelector{
-										Namespace: pointer.String("invalid"),
+										Namespace: pointer.To("invalid"),
 									},
 								},
 							},
@@ -1571,7 +1571,7 @@ func TestValidateStore(t *testing.T) {
 							AzureKV: &esv1beta1.AzureKVProvider{
 								AuthSecretRef: &esv1beta1.AzureKVAuth{
 									ClientSecret: &v1.SecretKeySelector{
-										Namespace: pointer.String("invalid"),
+										Namespace: pointer.To("invalid"),
 									},
 								},
 							},

+ 148 - 0
pkg/provider/delinea/client.go

@@ -0,0 +1,148 @@
+/*
+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 delinea
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+
+	"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
+	"github.com/tidwall/gjson"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+const (
+	errSecretKeyFmt  = "cannot find secret data for key: %q"
+	errUnexpectedKey = "unexpected key in data: %s"
+	errSecretFormat  = "secret data for property %s not in expected format: %s"
+)
+
+type client struct {
+	api secretAPI
+}
+
+var _ esv1beta1.SecretsClient = &client{}
+
+// GetSecret supports two types:
+//  1. get the full secret as json-encoded value
+//     by leaving the ref.Property empty.
+//  2. get a key from the secret.
+//     Nested values are supported by specifying a gjson expression
+func (c *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	secret, err := c.getSecret(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+	// Return nil if secret value is null
+	if secret.Data == nil {
+		return nil, nil
+	}
+	jsonStr, err := json.Marshal(secret.Data)
+	if err != nil {
+		return nil, err
+	}
+	// return raw json if no property is defined
+	if ref.Property == "" {
+		return jsonStr, nil
+	}
+	// extract key from secret using gjson
+	val := gjson.Get(string(jsonStr), ref.Property)
+	if !val.Exists() {
+		return nil, esv1beta1.NoSecretError{}
+	}
+	return []byte(val.String()), nil
+}
+
+func (c *client) PushSecret(_ context.Context, _ []byte, _ esv1beta1.PushRemoteRef) error {
+	return errors.New("pushing secrets is not supported by Delinea DevOps Secrets Vault")
+}
+
+func (c *client) DeleteSecret(_ context.Context, _ esv1beta1.PushRemoteRef) error {
+	return errors.New("deleting secrets is not supported by Delinea DevOps Secrets Vault")
+}
+
+func (c *client) Validate() (esv1beta1.ValidationResult, error) {
+	return esv1beta1.ValidationResultReady, nil
+}
+
+// GetSecret gets the full secret as json-encoded value.
+func (c *client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	secret, err := c.getSecret(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+	byteMap := make(map[string][]byte, len(secret.Data))
+	for k := range secret.Data {
+		byteMap[k], err = getTypedKey(secret.Data, k)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return byteMap, nil
+}
+
+// GetAllSecrets lists secrets matching the given criteria and return their latest versions.
+func (c *client) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	return nil, errors.New("getting all secrets is not supported by Delinea DevOps Secrets Vault")
+}
+
+func (c *client) Close(context.Context) error {
+	return nil
+}
+
+// getSecret retrieves the secret referenced by ref from the Vault API.
+func (c *client) getSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*vault.Secret, error) {
+	if ref.Version != "" {
+		return nil, errors.New("specifying a version is not yet supported")
+	}
+	return c.api.Secret(ref.Key)
+}
+
+// getTypedKey is copied from pkg/provider/vault/vault.go.
+func getTypedKey(data map[string]interface{}, key string) ([]byte, error) {
+	v, ok := data[key]
+	if !ok {
+		return nil, fmt.Errorf(errUnexpectedKey, key)
+	}
+	switch t := v.(type) {
+	case string:
+		return []byte(t), nil
+	case map[string]interface{}:
+		return json.Marshal(t)
+	case []string:
+		return []byte(strings.Join(t, "\n")), nil
+	case []byte:
+		return t, nil
+	// also covers int and float32 due to json.Marshal
+	case float64:
+		return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil
+	case json.Number:
+		return []byte(t.String()), nil
+	case []interface{}:
+		return json.Marshal(t)
+	case bool:
+		return []byte(strconv.FormatBool(t)), nil
+	case nil:
+		return []byte(nil), nil
+	default:
+		return nil, fmt.Errorf(errSecretFormat, key, reflect.TypeOf(t))
+	}
+}

+ 117 - 0
pkg/provider/delinea/client_test.go

@@ -0,0 +1,117 @@
+/*
+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 delinea
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
+	"github.com/stretchr/testify/assert"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+type fakeAPI struct {
+	secrets []*vault.Secret
+}
+
+// createVaultSecret assembles a vault.Secret.
+// vault.Secret has unexported nested types, and is therefore quite
+// tricky from outside the vault package. This function facilitates easy setup.
+func createVaultSecret(path string, data map[string]interface{}) *vault.Secret {
+	s := &vault.Secret{}
+	s.Path = path
+	s.Data = data
+	return s
+}
+
+// Secret returns secret matching path.
+func (f *fakeAPI) Secret(path string) (*vault.Secret, error) {
+	for _, s := range f.secrets {
+		if s.Path == path {
+			return s, nil
+		}
+	}
+	return nil, errors.New("not found")
+}
+
+func newTestClient() esv1beta1.SecretsClient {
+	return &client{
+		api: &fakeAPI{
+			secrets: []*vault.Secret{
+				createVaultSecret("a", map[string]interface{}{}),
+				createVaultSecret("b", map[string]interface{}{
+					"hello": "world",
+				}),
+				createVaultSecret("c", map[string]interface{}{
+					"foo": map[string]string{"bar": "baz"},
+				}),
+			},
+		},
+	}
+}
+
+func TestGetSecret(t *testing.T) {
+	ctx := context.Background()
+	c := newTestClient()
+
+	testCases := map[string]struct {
+		ref  esv1beta1.ExternalSecretDataRemoteRef
+		want []byte
+		err  error
+	}{
+		"querying for the key returns the map": {
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key: "b",
+			},
+			want: []byte(`{"hello":"world"}`),
+		},
+		"querying for the key and property returns a single value": {
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "b",
+				Property: "hello",
+			},
+			want: []byte(`world`),
+		},
+		"querying for the key and nested property returns a single value": {
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "c",
+				Property: "foo.bar",
+			},
+			want: []byte(`baz`),
+		},
+		"querying for existent key and non-existing propery": {
+			ref: esv1beta1.ExternalSecretDataRemoteRef{
+				Key:      "c",
+				Property: "foo.bar.x",
+			},
+			err: esv1beta1.NoSecretErr,
+		},
+	}
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
+			got, err := c.GetSecret(ctx, tc.ref)
+			if tc.err == nil {
+				assert.NoError(t, err)
+				assert.Equal(t, tc.want, got)
+			} else {
+				assert.Nil(t, got)
+				assert.ErrorIs(t, err, tc.err)
+				assert.Equal(t, tc.err, err)
+			}
+		})
+	}
+}

+ 207 - 0
pkg/provider/delinea/provider.go

@@ -0,0 +1,207 @@
+/*
+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 delinea
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
+	corev1 "k8s.io/api/core/v1"
+	kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+)
+
+var (
+	errEmptyTenant                   = errors.New("tenant must not be empty")
+	errEmptyClientID                 = errors.New("clientID must be set")
+	errEmptyClientSecret             = errors.New("clientSecret must be set")
+	errSecretRefAndValueConflict     = errors.New("cannot specify both secret reference and value")
+	errSecretRefAndValueMissing      = errors.New("must specify either secret reference or direct value")
+	errMissingStore                  = errors.New("missing store specification")
+	errInvalidSpec                   = errors.New("invalid specification for delinea provider")
+	errMissingSecretName             = errors.New("must specify a secret name")
+	errMissingSecretKey              = errors.New("must specify a secret key")
+	errClusterStoreRequiresNamespace = errors.New("when using a ClusterSecretStore, namespaces must be explicitly set")
+
+	errNoSuchKeyFmt = "no such key in secret: %q"
+)
+
+type Provider struct{}
+
+var _ esv1beta1.Provider = &Provider{}
+
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kubeClient.Client, namespace string) (esv1beta1.SecretsClient, error) {
+	cfg, err := getConfig(store)
+	if err != nil {
+		return nil, err
+	}
+
+	if store.GetKind() == esv1beta1.ClusterSecretStoreKind && doesConfigDependOnNamespace(cfg) {
+		// we are not attached to a specific namespace, but some config values are dependent on it
+		return nil, errClusterStoreRequiresNamespace
+	}
+
+	clientID, err := loadConfigSecret(ctx, cfg.ClientID, kube, namespace)
+	if err != nil {
+		return nil, err
+	}
+
+	clientSecret, err := loadConfigSecret(ctx, cfg.ClientSecret, kube, namespace)
+	if err != nil {
+		return nil, err
+	}
+
+	dsvClient, err := vault.New(vault.Configuration{
+		Credentials: vault.ClientCredential{
+			ClientID:     clientID,
+			ClientSecret: clientSecret,
+		},
+		Tenant:      cfg.Tenant,
+		TLD:         cfg.TLD,
+		URLTemplate: cfg.URLTemplate,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return &client{
+		api: dsvClient,
+	}, nil
+}
+
+func loadConfigSecret(ctx context.Context, ref *esv1beta1.DelineaProviderSecretRef, kube kubeClient.Client, defaultNamespace string) (string, error) {
+	if ref.SecretRef == nil {
+		return ref.Value, nil
+	}
+
+	if err := validateSecretRef(ref); err != nil {
+		return "", err
+	}
+
+	namespace := defaultNamespace
+	if ref.SecretRef.Namespace != nil {
+		namespace = *ref.SecretRef.Namespace
+	}
+
+	objKey := kubeClient.ObjectKey{Namespace: namespace, Name: ref.SecretRef.Name}
+	secret := corev1.Secret{}
+	err := kube.Get(ctx, objKey, &secret)
+	if err != nil {
+		return "", err
+	}
+
+	value, ok := secret.Data[ref.SecretRef.Key]
+	if !ok {
+		return "", fmt.Errorf(errNoSuchKeyFmt, ref.SecretRef.Key)
+	}
+
+	return string(value), nil
+}
+
+func validateStoreSecretRef(store esv1beta1.GenericStore, ref *esv1beta1.DelineaProviderSecretRef) error {
+	if ref.SecretRef != nil {
+		if err := utils.ValidateReferentSecretSelector(store, *ref.SecretRef); err != nil {
+			return err
+		}
+	}
+
+	return validateSecretRef(ref)
+}
+
+func validateSecretRef(ref *esv1beta1.DelineaProviderSecretRef) error {
+	if ref.SecretRef != nil {
+		if ref.Value != "" {
+			return errSecretRefAndValueConflict
+		}
+		if ref.SecretRef.Name == "" {
+			return errMissingSecretName
+		}
+		if ref.SecretRef.Key == "" {
+			return errMissingSecretKey
+		}
+	} else if ref.Value == "" {
+		return errSecretRefAndValueMissing
+	}
+
+	return nil
+}
+
+func doesConfigDependOnNamespace(cfg *esv1beta1.DelineaProvider) bool {
+	if cfg.ClientID.SecretRef != nil && cfg.ClientID.SecretRef.Namespace == nil {
+		return true
+	}
+
+	if cfg.ClientSecret.SecretRef != nil && cfg.ClientSecret.SecretRef.Namespace == nil {
+		return true
+	}
+
+	return false
+}
+
+func getConfig(store esv1beta1.GenericStore) (*esv1beta1.DelineaProvider, error) {
+	if store == nil {
+		return nil, errMissingStore
+	}
+	storeSpec := store.GetSpec()
+
+	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Delinea == nil {
+		return nil, errInvalidSpec
+	}
+	cfg := storeSpec.Provider.Delinea
+
+	if cfg.Tenant == "" {
+		return nil, errEmptyTenant
+	}
+
+	if cfg.ClientID == nil {
+		return nil, errEmptyClientID
+	}
+
+	if cfg.ClientSecret == nil {
+		return nil, errEmptyClientSecret
+	}
+
+	err := validateStoreSecretRef(store, cfg.ClientID)
+	if err != nil {
+		return nil, err
+	}
+
+	err = validateStoreSecretRef(store, cfg.ClientSecret)
+	if err != nil {
+		return nil, err
+	}
+
+	return cfg, nil
+}
+
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
+	_, err := getConfig(store)
+	return err
+}
+
+func init() {
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+		Delinea: &esv1beta1.DelineaProvider{},
+	})
+}

+ 369 - 0
pkg/provider/delinea/provider_test.go

@@ -0,0 +1,369 @@
+/*
+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 delinea
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	kubeErrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
+	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+)
+
+func TestDoesConfigDependOnNamespace(t *testing.T) {
+	tests := map[string]struct {
+		cfg  esv1beta1.DelineaProvider
+		want bool
+	}{
+		"true when client ID references a secret without explicit namespace": {
+			cfg: esv1beta1.DelineaProvider{
+				ClientID: &esv1beta1.DelineaProviderSecretRef{
+					SecretRef: &v1.SecretKeySelector{Name: "foo"},
+				},
+				ClientSecret: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
+			},
+			want: true,
+		},
+		"true when client secret references a secret without explicit namespace": {
+			cfg: esv1beta1.DelineaProvider{
+				ClientID: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
+				ClientSecret: &esv1beta1.DelineaProviderSecretRef{
+					SecretRef: &v1.SecretKeySelector{Name: "foo"},
+				},
+			},
+			want: true,
+		},
+		"false when neither client ID nor secret reference a secret": {
+			cfg: esv1beta1.DelineaProvider{
+				ClientID:     &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
+				ClientSecret: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
+			},
+			want: false,
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			got := doesConfigDependOnNamespace(&tc.cfg)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}
+
+func TestValidateStore(t *testing.T) {
+	validSecretRefUsingValue := makeSecretRefUsingValue("foo")
+	ambiguousSecretRef := &esv1beta1.DelineaProviderSecretRef{
+		SecretRef: &v1.SecretKeySelector{Name: "foo"}, Value: "foo",
+	}
+	tests := map[string]struct {
+		cfg  esv1beta1.DelineaProvider
+		want error
+	}{
+		"invalid without tenant": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant:       "",
+				ClientID:     validSecretRefUsingValue,
+				ClientSecret: validSecretRefUsingValue,
+			},
+			want: errEmptyTenant,
+		},
+		"invalid without clientID": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant: "foo",
+				// ClientID omitted
+				ClientSecret: validSecretRefUsingValue,
+			},
+			want: errEmptyClientID,
+		},
+		"invalid without clientSecret": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant:   "foo",
+				ClientID: validSecretRefUsingValue,
+				// ClientSecret omitted
+			},
+			want: errEmptyClientSecret,
+		},
+		"invalid with ambiguous clientID": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant:       "foo",
+				ClientID:     ambiguousSecretRef,
+				ClientSecret: validSecretRefUsingValue,
+			},
+			want: errSecretRefAndValueConflict,
+		},
+		"invalid with ambiguous clientSecret": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant:       "foo",
+				ClientID:     validSecretRefUsingValue,
+				ClientSecret: ambiguousSecretRef,
+			},
+			want: errSecretRefAndValueConflict,
+		},
+		"invalid with invalid clientID": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant:       "foo",
+				ClientID:     makeSecretRefUsingValue(""),
+				ClientSecret: validSecretRefUsingValue,
+			},
+			want: errSecretRefAndValueMissing,
+		},
+		"invalid with invalid clientSecret": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant:       "foo",
+				ClientID:     validSecretRefUsingValue,
+				ClientSecret: makeSecretRefUsingValue(""),
+			},
+			want: errSecretRefAndValueMissing,
+		},
+		"valid with tenant/clientID/clientSecret": {
+			cfg: esv1beta1.DelineaProvider{
+				Tenant:       "foo",
+				ClientID:     validSecretRefUsingValue,
+				ClientSecret: validSecretRefUsingValue,
+			},
+			want: nil,
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			s := esv1beta1.SecretStore{
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Delinea: &tc.cfg,
+					},
+				},
+			}
+			p := &Provider{}
+			got := p.ValidateStore(&s)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}
+
+func TestValidateStoreBailsOnUnexpectedStore(t *testing.T) {
+	tests := map[string]struct {
+		store esv1beta1.GenericStore
+		want  error
+	}{
+		"missing store": {nil, errMissingStore},
+		"missing spec":  {&esv1beta1.SecretStore{}, errInvalidSpec},
+		"missing provider": {&esv1beta1.SecretStore{
+			Spec: esv1beta1.SecretStoreSpec{Provider: nil},
+		}, errInvalidSpec},
+		"missing delinea": {&esv1beta1.SecretStore{
+			Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{
+				Delinea: nil,
+			}},
+		}, errInvalidSpec},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			p := &Provider{}
+			got := p.ValidateStore(tc.store)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}
+
+func TestNewClient(t *testing.T) {
+	tenant := "foo"
+	clientIDKey := "username"
+	clientIDValue := "client id"
+	clientSecretKey := "password"
+	clientSecretValue := "client secret"
+
+	clientSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
+		Data: map[string][]byte{
+			clientIDKey:     []byte(clientIDValue),
+			clientSecretKey: []byte(clientSecretValue),
+		},
+	}
+
+	validProvider := &esv1beta1.DelineaProvider{
+		Tenant:       tenant,
+		ClientID:     makeSecretRefUsingRef(clientSecret.Name, clientIDKey),
+		ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
+	}
+
+	tests := map[string]struct {
+		store    esv1beta1.GenericStore     // leave nil for namespaced store
+		provider *esv1beta1.DelineaProvider // discarded when store is set
+		kube     kubeClient.Client
+		errCheck func(t *testing.T, err error)
+	}{
+		"missing provider config": {
+			provider: nil,
+			errCheck: func(t *testing.T, err error) {
+				assert.ErrorIs(t, err, errInvalidSpec)
+			},
+		},
+		"namespace-dependent cluster secret store": {
+			store: &esv1beta1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{Kind: esv1beta1.ClusterSecretStoreKind},
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Delinea: validProvider,
+					},
+				},
+			},
+			errCheck: func(t *testing.T, err error) {
+				assert.ErrorIs(t, err, errClusterStoreRequiresNamespace)
+			},
+		},
+		"dangling client ID ref": {
+			provider: &esv1beta1.DelineaProvider{
+				Tenant:       tenant,
+				ClientID:     makeSecretRefUsingRef("typo", clientIDKey),
+				ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+			errCheck: func(t *testing.T, err error) {
+				assert.True(t, kubeErrors.IsNotFound(err))
+			},
+		},
+		"dangling client secret ref": {
+			provider: &esv1beta1.DelineaProvider{
+				Tenant:       tenant,
+				ClientID:     makeSecretRefUsingRef(clientSecret.Name, clientIDKey),
+				ClientSecret: makeSecretRefUsingRef("typo", clientSecretKey),
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+			errCheck: func(t *testing.T, err error) {
+				assert.True(t, kubeErrors.IsNotFound(err))
+			},
+		},
+		"secret ref without name": {
+			provider: &esv1beta1.DelineaProvider{
+				Tenant:       tenant,
+				ClientID:     makeSecretRefUsingRef("", clientIDKey),
+				ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+			errCheck: func(t *testing.T, err error) {
+				assert.ErrorIs(t, err, errMissingSecretName)
+			},
+		},
+		"secret ref without key": {
+			provider: &esv1beta1.DelineaProvider{
+				Tenant:       tenant,
+				ClientID:     makeSecretRefUsingRef(clientSecret.Name, ""),
+				ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+			errCheck: func(t *testing.T, err error) {
+				assert.ErrorIs(t, err, errMissingSecretKey)
+			},
+		},
+		"secret ref with non-existent keys": {
+			provider: &esv1beta1.DelineaProvider{
+				Tenant:       tenant,
+				ClientID:     makeSecretRefUsingRef(clientSecret.Name, "typo"),
+				ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+			errCheck: func(t *testing.T, err error) {
+				assert.EqualError(t, err, fmt.Sprintf(errNoSuchKeyFmt, "typo"))
+			},
+		},
+		"valid secret refs": {
+			provider: validProvider,
+			kube:     clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+		},
+		"secret values": {
+			provider: &esv1beta1.DelineaProvider{
+				Tenant:       tenant,
+				ClientID:     makeSecretRefUsingValue(clientIDValue),
+				ClientSecret: makeSecretRefUsingValue(clientSecretValue),
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+		},
+		"cluster secret store": {
+			store: &esv1beta1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{Kind: esv1beta1.ClusterSecretStoreKind},
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Delinea: &esv1beta1.DelineaProvider{
+							Tenant:       tenant,
+							ClientID:     makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, clientIDKey),
+							ClientSecret: makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, clientSecretKey),
+						},
+					},
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			p := &Provider{}
+			store := tc.store
+			if store == nil {
+				store = &esv1beta1.SecretStore{
+					TypeMeta: metav1.TypeMeta{Kind: esv1beta1.SecretStoreKind},
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Delinea: tc.provider,
+						},
+					},
+				}
+			}
+			sc, err := p.NewClient(context.Background(), store, tc.kube, clientSecret.Namespace)
+			if tc.errCheck == nil {
+				assert.NoError(t, err)
+				delineaClient, ok := sc.(*client)
+				assert.True(t, ok)
+				dsvClient, ok := delineaClient.api.(*vault.Vault)
+				assert.True(t, ok)
+				assert.Equal(t, vault.Configuration{
+					Credentials: vault.ClientCredential{
+						ClientID:     clientIDValue,
+						ClientSecret: clientSecretValue,
+					},
+					Tenant:      tenant,
+					TLD:         "com",                                     // Default from Delinea
+					URLTemplate: "https://%s.secretsvaultcloud.%s/v1/%s%s", // Default from Delinea
+				}, dsvClient.Configuration)
+			} else {
+				assert.Nil(t, sc)
+				tc.errCheck(t, err)
+			}
+		})
+	}
+}
+
+func makeSecretRefUsingRef(name, key string) *esv1beta1.DelineaProviderSecretRef {
+	return &esv1beta1.DelineaProviderSecretRef{
+		SecretRef: &v1.SecretKeySelector{Name: name, Key: key},
+	}
+}
+
+func makeSecretRefUsingNamespacedRef(namespace, name, key string) *esv1beta1.DelineaProviderSecretRef {
+	return &esv1beta1.DelineaProviderSecretRef{
+		SecretRef: &v1.SecretKeySelector{Namespace: utils.Ptr(namespace), Name: name, Key: key},
+	}
+}
+
+func makeSecretRefUsingValue(val string) *esv1beta1.DelineaProviderSecretRef {
+	return &esv1beta1.DelineaProviderSecretRef{Value: val}
+}

+ 25 - 0
pkg/provider/delinea/secret_api.go

@@ -0,0 +1,25 @@
+/*
+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 delinea
+
+import (
+	"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
+)
+
+// secretAPI represents the subset of the Delinea DevOps Secrets Vault API
+// which is supported by dsv-sdk-go/v2.
+// See https://dsv.secretsvaultcloud.com/api for full API documentation.
+type secretAPI interface {
+	Secret(path string) (*vault.Secret, error)
+}

+ 101 - 71
pkg/provider/gcp/secretmanager/client.go

@@ -27,8 +27,11 @@ import (
 	"github.com/googleapis/gax-go/v2"
 	"github.com/googleapis/gax-go/v2/apierror"
 	"github.com/tidwall/gjson"
+	"github.com/tidwall/sjson"
 	"google.golang.org/api/iterator"
+	"google.golang.org/genproto/protobuf/field_mask"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 	ctrl "sigs.k8s.io/controller-runtime"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -61,6 +64,9 @@ const (
 	errInvalidAuthSecretRef   = "invalid auth secret ref: %w"
 	errInvalidWISARef         = "invalid workload identity service account reference: %w"
 	errUnexpectedFindOperator = "unexpected find operator"
+
+	managedByKey   = "managed-by"
+	managedByValue = "external-secrets"
 )
 
 type Client struct {
@@ -82,32 +88,25 @@ type GoogleSecretManagerClient interface {
 	CreateSecret(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
 	Close() error
 	GetSecret(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
+	UpdateSecret(context.Context, *secretmanagerpb.UpdateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error)
 }
 
 var log = ctrl.Log.WithName("provider").WithName("gcp").WithName("secretsmanager")
 
 func (c *Client) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
-	var gcpSecret *secretmanagerpb.Secret
-	var err error
-
-	gcpSecret, err = c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
+	gcpSecret, err := c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
 		Name: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
 	})
 	metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMGetSecret, err)
-	var gErr *apierror.APIError
-
-	if errors.As(err, &gErr) {
-		if gErr.GRPCStatus().Code() == codes.NotFound {
+	if err != nil {
+		if status.Code(err) == codes.NotFound {
 			return nil
 		}
+
 		return err
 	}
-	if err != nil {
-		return err
-	}
-	manager, ok := gcpSecret.Labels["managed-by"]
 
-	if !ok || manager != "external-secrets" {
+	if manager, ok := gcpSecret.Labels[managedByKey]; !ok || manager != managedByValue {
 		return nil
 	}
 
@@ -129,47 +128,60 @@ func parseError(err error) error {
 
 // PushSecret pushes a kubernetes secret key into gcp provider Secret.
 func (c *Client) PushSecret(ctx context.Context, payload []byte, remoteRef esv1beta1.PushRemoteRef) error {
-	createSecretReq := &secretmanagerpb.CreateSecretRequest{
-		Parent:   fmt.Sprintf("projects/%s", c.store.ProjectID),
-		SecretId: remoteRef.GetRemoteKey(),
-		Secret: &secretmanagerpb.Secret{
-			Labels: map[string]string{
-				"managed-by": "external-secrets",
-			},
-			Replication: &secretmanagerpb.Replication{
-				Replication: &secretmanagerpb.Replication_Automatic_{
-					Automatic: &secretmanagerpb.Replication_Automatic{},
-				},
-			},
-		},
-	}
-
-	var gcpSecret *secretmanagerpb.Secret
-	var err error
-
-	gcpSecret, err = c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
+	gcpSecret, err := c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
 		Name: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
 	})
 	metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMGetSecret, err)
 
-	var gErr *apierror.APIError
+	if err != nil {
+		if status.Code(err) != codes.NotFound {
+			return err
+		}
 
-	if err != nil && errors.As(err, &gErr) {
-		if gErr.GRPCStatus().Code() == codes.NotFound {
-			gcpSecret, err = c.smClient.CreateSecret(ctx, createSecretReq)
-			metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMCreateSecret, err)
-			if err != nil {
-				return err
-			}
-		} else {
+		gcpSecret, err = c.smClient.CreateSecret(ctx, &secretmanagerpb.CreateSecretRequest{
+			Parent:   fmt.Sprintf("projects/%s", c.store.ProjectID),
+			SecretId: remoteRef.GetRemoteKey(),
+			Secret: &secretmanagerpb.Secret{
+				Labels: map[string]string{
+					managedByKey: managedByValue,
+				},
+				Replication: &secretmanagerpb.Replication{
+					Replication: &secretmanagerpb.Replication_Automatic_{
+						Automatic: &secretmanagerpb.Replication_Automatic{},
+					},
+				},
+			},
+		})
+		metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMCreateSecret, err)
+		if err != nil {
 			return err
 		}
 	}
 
-	manager, ok := gcpSecret.Labels["managed-by"]
+	manager, ok := gcpSecret.Labels[managedByKey]
+	if !ok || manager != managedByValue {
+		if remoteRef.GetProperty() == "" {
+			return fmt.Errorf("secret %v is not managed by external secrets", remoteRef.GetRemoteKey())
+		}
 
-	if !ok || manager != "external-secrets" {
-		return fmt.Errorf("secret %v is not managed by external secrets", remoteRef.GetRemoteKey())
+		labels := map[string]string{}
+		for k, v := range gcpSecret.Labels {
+			labels[k] = v
+		}
+		labels[managedByKey] = managedByValue
+		_, err = c.smClient.UpdateSecret(ctx, &secretmanagerpb.UpdateSecretRequest{
+			Secret: &secretmanagerpb.Secret{
+				Name:   gcpSecret.Name,
+				Labels: labels,
+			},
+			UpdateMask: &field_mask.FieldMask{
+				Paths: []string{"labels"},
+			},
+		})
+		metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMUpdateSecret, err)
+		if err != nil {
+			return err
+		}
 	}
 
 	gcpVersion, err := c.smClient.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
@@ -177,32 +189,46 @@ func (c *Client) PushSecret(ctx context.Context, payload []byte, remoteRef esv1b
 	})
 	metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMAccessSecretVersion, err)
 
-	if errors.As(err, &gErr) {
-		if err != nil && gErr.GRPCStatus().Code() != codes.NotFound {
-			return err
-		}
-	} else if err != nil {
+	if err != nil && status.Code(err) != codes.NotFound {
 		return err
 	}
 
-	if gcpVersion != nil && gcpVersion.Payload != nil && bytes.Equal(payload, gcpVersion.Payload.Data) {
-		return nil
+	if gcpVersion != nil && gcpVersion.Payload != nil {
+		if remoteRef.GetProperty() == "" && bytes.Equal(payload, gcpVersion.Payload.Data) {
+			return nil
+		}
+
+		if remoteRef.GetProperty() != "" {
+			val := getDataByProperty(gcpVersion, remoteRef.GetProperty())
+			if val.Exists() && val.String() == string(payload) {
+				return nil
+			}
+		}
+	}
+
+	data := payload
+	if remoteRef.GetProperty() != "" {
+		var base []byte
+		if gcpVersion != nil && gcpVersion.Payload != nil {
+			base = gcpVersion.Payload.Data
+		}
+
+		data, err = sjson.SetBytes(base, remoteRef.GetProperty(), payload)
+		if err != nil {
+			return err
+		}
 	}
 
 	addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
 		Parent: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
 		Payload: &secretmanagerpb.SecretPayload{
-			Data: payload,
+			Data: data,
 		},
 	}
 
 	_, err = c.smClient.AddSecretVersion(ctx, addSecretVersionReq)
 	metrics.ObserveAPICall(constants.ProviderGCPSM, constants.CallGCPSMAddSecretVersion, err)
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return err
 }
 
 // GetAllSecrets syncs multiple secrets from gcp provider into a single Kubernetes Secret.
@@ -363,20 +389,7 @@ func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
 		return nil, fmt.Errorf("invalid secret received. no secret string for key: %s", ref.Key)
 	}
 
-	var payload string
-	if result.Payload.Data != nil {
-		payload = string(result.Payload.Data)
-	}
-	idx := strings.Index(ref.Property, ".")
-	refProperty := ref.Property
-	if idx > 0 {
-		refProperty = strings.ReplaceAll(refProperty, ".", "\\.")
-		val := gjson.Get(payload, refProperty)
-		if val.Exists() {
-			return []byte(val.String()), nil
-		}
-	}
-	val := gjson.Get(payload, ref.Property)
+	val := getDataByProperty(result, ref.Property)
 	if !val.Exists() {
 		return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
 	}
@@ -509,3 +522,20 @@ func (c *Client) Validate() (esv1beta1.ValidationResult, error) {
 	}
 	return esv1beta1.ValidationResultReady, nil
 }
+
+func getDataByProperty(resp *secretmanagerpb.AccessSecretVersionResponse, property string) gjson.Result {
+	var payload string
+	if resp.Payload.Data != nil {
+		payload = string(resp.Payload.Data)
+	}
+	idx := strings.Index(property, ".")
+	refProperty := property
+	if idx > 0 {
+		refProperty = strings.ReplaceAll(refProperty, ".", "\\.")
+		val := gjson.Get(payload, refProperty)
+		if val.Exists() {
+			return val
+		}
+	}
+	return gjson.Get(payload, property)
+}

+ 230 - 32
pkg/provider/gcp/secretmanager/client_test.go

@@ -22,11 +22,13 @@ import (
 	"testing"
 
 	"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
+	"github.com/googleapis/gax-go/v2"
 	"github.com/googleapis/gax-go/v2/apierror"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
 	fakesm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager/fake"
@@ -197,7 +199,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 	tests := []struct {
 		name                string
 		ref                 esv1beta1.ExternalSecretDataRemoteRef
-		getSecretMockReturn fakesm.GetSecretMockReturn
+		getSecretMockReturn fakesm.SecretMockReturn
 		expectedSecret      string
 		expectedErr         string
 	}{
@@ -208,7 +210,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 				Property:       "annotations.managed-by",
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Annotations: map[string]string{
@@ -226,7 +228,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 				Property:       "labels.managed-by",
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Labels: map[string]string{
@@ -244,7 +246,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 				Property:       "annotations",
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Annotations: map[string]string{
@@ -267,7 +269,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 				Property:       "labels",
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Annotations: map[string]string{
@@ -289,7 +291,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				Key:            "bar",
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Labels: map[string]string{
@@ -310,7 +312,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 				Property:       "annotations.unknown",
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Annotations: map[string]string{
@@ -328,7 +330,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 				Property:       "labels.unknown",
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Labels: map[string]string{
@@ -346,7 +348,7 @@ func TestGetSecret_MetadataPolicyFetch(t *testing.T) {
 				MetadataPolicy: esv1beta1.ExternalSecretMetadataPolicyFetch,
 				Property:       "invalid.managed-by",
 			},
-			getSecretMockReturn: fakesm.GetSecretMockReturn{
+			getSecretMockReturn: fakesm.SecretMockReturn{
 				Secret: &secretmanagerpb.Secret{
 					Name: "projects/foo/secret/bar",
 					Labels: map[string]string{
@@ -414,7 +416,7 @@ func TestDeleteSecret(t *testing.T) {
 	fakeClient := fakesm.MockSMClient{}
 	type args struct {
 		client          fakesm.MockSMClient
-		getSecretOutput fakesm.GetSecretMockReturn
+		getSecretOutput fakesm.SecretMockReturn
 		deleteSecretErr error
 	}
 	type want struct {
@@ -429,7 +431,7 @@ func TestDeleteSecret(t *testing.T) {
 		"Deletes Successfully": {
 			args: args{
 				client: fakeClient,
-				getSecretOutput: fakesm.GetSecretMockReturn{
+				getSecretOutput: fakesm.SecretMockReturn{
 					Secret: &secretmanagerpb.Secret{
 
 						Name: "projects/foo/secret/bar",
@@ -444,7 +446,7 @@ func TestDeleteSecret(t *testing.T) {
 		"Not Managed by ESO": {
 			args: args{
 				client: fakeClient,
-				getSecretOutput: fakesm.GetSecretMockReturn{
+				getSecretOutput: fakesm.SecretMockReturn{
 					Secret: &secretmanagerpb.Secret{
 
 						Name:   "projects/foo/secret/bar",
@@ -457,7 +459,7 @@ func TestDeleteSecret(t *testing.T) {
 		"Secret Not Found": {
 			args: args{
 				client: fakeClient,
-				getSecretOutput: fakesm.GetSecretMockReturn{
+				getSecretOutput: fakesm.SecretMockReturn{
 					Secret: nil,
 					Err:    notFoundError,
 				},
@@ -466,7 +468,7 @@ func TestDeleteSecret(t *testing.T) {
 		"Random Error": {
 			args: args{
 				client: fakeClient,
-				getSecretOutput: fakesm.GetSecretMockReturn{
+				getSecretOutput: fakesm.SecretMockReturn{
 					Secret: nil,
 					Err:    errors.New("This errored out"),
 				},
@@ -478,7 +480,7 @@ func TestDeleteSecret(t *testing.T) {
 		"Random GError": {
 			args: args{
 				client: fakeClient,
-				getSecretOutput: fakesm.GetSecretMockReturn{
+				getSecretOutput: fakesm.SecretMockReturn{
 					Secret: nil,
 					Err:    permissionDeniedError,
 				},
@@ -515,7 +517,7 @@ func TestDeleteSecret(t *testing.T) {
 	}
 }
 
-func TestSetSecret(t *testing.T) {
+func TestPushSecret(t *testing.T) {
 	ref := fakeRef{key: "/baz"}
 
 	notFoundError := status.Error(codes.NotFound, "failed")
@@ -584,10 +586,10 @@ func TestSetSecret(t *testing.T) {
 
 	type args struct {
 		mock                          *fakesm.MockSMClient
-		GetSecretMockReturn           fakesm.GetSecretMockReturn
+		GetSecretMockReturn           fakesm.SecretMockReturn
 		AccessSecretVersionMockReturn fakesm.AccessSecretVersionMockReturn
 		AddSecretVersionMockReturn    fakesm.AddSecretVersionMockReturn
-		CreateSecretMockReturn        fakesm.CreateSecretMockReturn
+		CreateSecretMockReturn        fakesm.SecretMockReturn
 	}
 
 	type want struct {
@@ -602,7 +604,7 @@ func TestSetSecret(t *testing.T) {
 			reason: "SetSecret successfully pushes a secret",
 			args: args{
 				mock:                          smtc.mockClient,
-				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				GetSecretMockReturn:           fakesm.SecretMockReturn{Secret: &secret, Err: nil},
 				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res, Err: nil},
 				AddSecretVersionMockReturn:    fakesm.AddSecretVersionMockReturn{SecretVersion: &secretVersion, Err: nil}},
 			want: want{
@@ -613,7 +615,7 @@ func TestSetSecret(t *testing.T) {
 			reason: "secret not pushed if AddSecretVersion errors",
 			args: args{
 				mock:                          smtc.mockClient,
-				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				GetSecretMockReturn:           fakesm.SecretMockReturn{Secret: &secret, Err: nil},
 				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res, Err: nil},
 				AddSecretVersionMockReturn:    fakesm.AddSecretVersionMockReturn{SecretVersion: nil, Err: APIerror},
 			},
@@ -625,7 +627,7 @@ func TestSetSecret(t *testing.T) {
 			reason: "secret not pushed if AccessSecretVersion errors",
 			args: args{
 				mock:                          smtc.mockClient,
-				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				GetSecretMockReturn:           fakesm.SecretMockReturn{Secret: &secret, Err: nil},
 				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: APIerror},
 			},
 			want: want{
@@ -636,7 +638,7 @@ func TestSetSecret(t *testing.T) {
 			reason: "secret not pushed if not managed-by external-secrets",
 			args: args{
 				mock:                smtc.mockClient,
-				GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &wrongLabelSecret, Err: nil},
+				GetSecretMockReturn: fakesm.SecretMockReturn{Secret: &wrongLabelSecret, Err: nil},
 			},
 			want: want{
 				err: labelError,
@@ -647,7 +649,7 @@ func TestSetSecret(t *testing.T) {
 			args: args{
 				mock:                          smtc.mockClient,
 				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res2, Err: nil},
-				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				GetSecretMockReturn:           fakesm.SecretMockReturn{Secret: &secret, Err: nil},
 			},
 			want: want{
 				err: nil,
@@ -657,10 +659,10 @@ func TestSetSecret(t *testing.T) {
 			reason: "secret is created if one doesn't already exist",
 			args: args{
 				mock:                          smtc.mockClient,
-				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: nil, Err: notFoundError},
+				GetSecretMockReturn:           fakesm.SecretMockReturn{Secret: nil, Err: notFoundError},
 				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: notFoundError},
 				AddSecretVersionMockReturn:    fakesm.AddSecretVersionMockReturn{SecretVersion: &secretVersion, Err: nil},
-				CreateSecretMockReturn:        fakesm.CreateSecretMockReturn{Secret: &secret, Err: nil},
+				CreateSecretMockReturn:        fakesm.SecretMockReturn{Secret: &secret, Err: nil},
 			},
 			want: want{
 				err: nil,
@@ -670,8 +672,8 @@ func TestSetSecret(t *testing.T) {
 			reason: "secret not created if CreateSecret returns not found error",
 			args: args{
 				mock:                   smtc.mockClient,
-				GetSecretMockReturn:    fakesm.GetSecretMockReturn{Secret: nil, Err: notFoundError},
-				CreateSecretMockReturn: fakesm.CreateSecretMockReturn{Secret: &secret, Err: notFoundError},
+				GetSecretMockReturn:    fakesm.SecretMockReturn{Secret: nil, Err: notFoundError},
+				CreateSecretMockReturn: fakesm.SecretMockReturn{Secret: &secret, Err: notFoundError},
 			},
 			want: want{
 				err: notFoundError,
@@ -681,7 +683,7 @@ func TestSetSecret(t *testing.T) {
 			reason: "secret not created if CreateSecret returns error",
 			args: args{
 				mock:                smtc.mockClient,
-				GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: nil, Err: canceledError},
+				GetSecretMockReturn: fakesm.SecretMockReturn{Secret: nil, Err: canceledError},
 			},
 			want: want{
 				err: canceledError,
@@ -691,7 +693,7 @@ func TestSetSecret(t *testing.T) {
 			reason: "access secret version for an existing secret returns error",
 			args: args{
 				mock:                          smtc.mockClient,
-				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				GetSecretMockReturn:           fakesm.SecretMockReturn{Secret: &secret, Err: nil},
 				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: canceledError},
 			},
 			want: want{
@@ -728,6 +730,202 @@ func TestSetSecret(t *testing.T) {
 	}
 }
 
+func TestPushSecret_Property(t *testing.T) {
+	defaultAddSecretVersionMockReturn := func(gotPayload, expectedPayload string) (*secretmanagerpb.SecretVersion, error) {
+		if gotPayload != expectedPayload {
+			t.Fatalf("payload does not match: got %s, expected: %s", gotPayload, expectedPayload)
+		}
+
+		return nil, nil
+	}
+
+	tests := []struct {
+		desc                          string
+		payload                       string
+		ref                           esv1beta1.PushRemoteRef
+		getSecretMockReturn           fakesm.SecretMockReturn
+		createSecretMockReturn        fakesm.SecretMockReturn
+		updateSecretMockReturn        fakesm.SecretMockReturn
+		accessSecretVersionMockReturn fakesm.AccessSecretVersionMockReturn
+		addSecretVersionMockReturn    func(gotPayload, expectedPayload string) (*secretmanagerpb.SecretVersion, error)
+		expectedPayload               string
+		expectedErr                   string
+	}{
+		{
+			desc:    "Add new key value paris",
+			payload: "testValue2",
+			ref: esv1alpha1.PushSecretRemoteRef{
+				Property: "testKey2",
+			},
+			getSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{
+					Labels: map[string]string{
+						managedByKey: managedByValue,
+					},
+				},
+			},
+			accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
+				Res: &secretmanagerpb.AccessSecretVersionResponse{
+					Payload: &secretmanagerpb.SecretPayload{
+						Data: []byte(`{"testKey1":"testValue1"}`),
+					},
+				},
+			},
+			addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
+			expectedPayload:            `{"testKey1":"testValue1","testKey2":"testValue2"}`,
+		},
+		{
+			desc:    "Update existing value",
+			payload: "testValue2",
+			ref: esv1alpha1.PushSecretRemoteRef{
+				Property: "testKey1.testKey2",
+			},
+			getSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{
+					Labels: map[string]string{
+						managedByKey: managedByValue,
+					},
+				},
+			},
+			accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
+				Res: &secretmanagerpb.AccessSecretVersionResponse{
+					Payload: &secretmanagerpb.SecretPayload{
+						Data: []byte(`{"testKey1":{"testKey2":"testValue1"}}`),
+					},
+				},
+			},
+			addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
+			expectedPayload:            `{"testKey1":{"testKey2":"testValue2"}}`,
+		},
+		{
+			desc:    "Secret not found",
+			payload: "testValue2",
+			ref: esv1alpha1.PushSecretRemoteRef{
+				Property: "testKey1.testKey3",
+			},
+			getSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{},
+				Err:    status.Error(codes.NotFound, "failed to find a Secret"),
+			},
+			createSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{
+					Labels: map[string]string{managedByKey: managedByValue},
+				},
+			},
+			accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
+				Res: &secretmanagerpb.AccessSecretVersionResponse{
+					Payload: &secretmanagerpb.SecretPayload{
+						Data: []byte(`{"testKey1":{"testKey2":"testValue1"}}`),
+					},
+				},
+			},
+			addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
+			expectedPayload:            `{"testKey1":{"testKey2":"testValue1","testKey3":"testValue2"}}`,
+		},
+		{
+			desc:    "Secret version is not found",
+			payload: "testValue1",
+			ref: esv1alpha1.PushSecretRemoteRef{
+				Property: "testKey1",
+			},
+			getSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{
+					Labels: map[string]string{managedByKey: managedByValue},
+				},
+			},
+			accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
+				Err: status.Error(codes.NotFound, "failed to find a Secret Version"),
+			},
+			addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
+			expectedPayload:            `{"testKey1":"testValue1"}`,
+		},
+		{
+			desc:    "Secret is not managed by the controller",
+			payload: "testValue1",
+			ref: esv1alpha1.PushSecretRemoteRef{
+				Property: "testKey1.testKey2",
+			},
+			getSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{},
+			},
+			updateSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{
+					Labels: map[string]string{managedByKey: managedByValue},
+				},
+			},
+			accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
+				Res: &secretmanagerpb.AccessSecretVersionResponse{
+					Payload: &secretmanagerpb.SecretPayload{
+						Data: []byte(""),
+					},
+				},
+			},
+			addSecretVersionMockReturn: defaultAddSecretVersionMockReturn,
+			expectedPayload:            `{"testKey1":{"testKey2":"testValue1"}}`,
+		},
+		{
+			desc:    "Payload is the same with the existing one",
+			payload: "testValue1",
+			ref: esv1alpha1.PushSecretRemoteRef{
+				Property: "testKey1.testKey2",
+			},
+			getSecretMockReturn: fakesm.SecretMockReturn{
+				Secret: &secretmanagerpb.Secret{
+					Labels: map[string]string{
+						managedByKey: managedByValue,
+					},
+				},
+			},
+			accessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{
+				Res: &secretmanagerpb.AccessSecretVersionResponse{
+					Payload: &secretmanagerpb.SecretPayload{
+						Data: []byte(`{"testKey1":{"testKey2":"testValue1"}}`),
+					},
+				},
+			},
+			addSecretVersionMockReturn: func(gotPayload, expectedPayload string) (*secretmanagerpb.SecretVersion, error) {
+				return nil, errors.New("should not be called")
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.desc, func(t *testing.T) {
+			smClient := &fakesm.MockSMClient{
+				AddSecretFn: func(_ context.Context, req *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
+					return tc.addSecretVersionMockReturn(string(req.Payload.Data), tc.expectedPayload)
+				},
+			}
+			smClient.NewGetSecretFn(tc.getSecretMockReturn)
+			smClient.NewCreateSecretFn(tc.createSecretMockReturn)
+			smClient.NewUpdateSecretFn(tc.updateSecretMockReturn)
+			smClient.NewAccessSecretVersionFn(tc.accessSecretVersionMockReturn)
+
+			client := Client{
+				smClient: smClient,
+				store:    &esv1beta1.GCPSMProvider{},
+			}
+
+			err := client.PushSecret(context.Background(), []byte(tc.payload), tc.ref)
+			if err != nil {
+				if tc.expectedErr == "" {
+					t.Fatalf("PushSecret returns unexpected error: %v", err)
+				}
+
+				if !strings.Contains(err.Error(), tc.expectedErr) {
+					t.Fatalf("PushSecret returns unexpected error: %q is supposed to contain %q", err, tc.expectedErr)
+				}
+
+				return
+			}
+
+			if tc.expectedErr != "" {
+				t.Fatal("PushSecret is expected to return error but got nil")
+			}
+		})
+	}
+}
+
 func TestGetSecretMap(t *testing.T) {
 	// good case: default version & deserialization
 	setDeserialization := func(smtc *secretManagerTestCase) {
@@ -802,7 +1000,7 @@ func TestValidateStore(t *testing.T) {
 					SecretRef: &esv1beta1.GCPSMAuthSecretRef{
 						SecretAccessKey: v1.SecretKeySelector{
 							Name:      "foo",
-							Namespace: pointer.String("invalid"),
+							Namespace: pointer.To("invalid"),
 						},
 					},
 				},
@@ -816,7 +1014,7 @@ func TestValidateStore(t *testing.T) {
 					WorkloadIdentity: &esv1beta1.GCPWorkloadIdentity{
 						ServiceAccountRef: v1.ServiceAccountSelector{
 							Name:      "foo",
-							Namespace: pointer.String("invalid"),
+							Namespace: pointer.To("invalid"),
 						},
 					},
 				},

+ 18 - 12
pkg/provider/gcp/secretmanager/fake/fake.go

@@ -28,8 +28,9 @@ import (
 type MockSMClient struct {
 	accessSecretFn func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
 	ListSecretsFn  func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator
-	addSecretFn    func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
+	AddSecretFn    func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
 	createSecretFn func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
+	updateSecretFn func(ctx context.Context, req *secretmanagerpb.UpdateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
 	closeFn        func() error
 	GetSecretFn    func(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
 	DeleteSecretFn func(ctx context.Context, req *secretmanagerpb.DeleteSecretRequest, opts ...gax.CallOption) error
@@ -45,12 +46,7 @@ type AddSecretVersionMockReturn struct {
 	Err           error
 }
 
-type GetSecretMockReturn struct {
-	Secret *secretmanagerpb.Secret
-	Err    error
-}
-
-type CreateSecretMockReturn struct {
+type SecretMockReturn struct {
 	Secret *secretmanagerpb.Secret
 	Err    error
 }
@@ -67,7 +63,7 @@ func (mc *MockSMClient) GetSecret(ctx context.Context, req *secretmanagerpb.GetS
 	return mc.GetSecretFn(ctx, req)
 }
 
-func (mc *MockSMClient) NewGetSecretFn(mock GetSecretMockReturn) {
+func (mc *MockSMClient) NewGetSecretFn(mock SecretMockReturn) {
 	mc.GetSecretFn = func(_ context.Context, _ *secretmanagerpb.GetSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
 		return mock.Secret, mock.Err
 	}
@@ -91,11 +87,11 @@ func (mc *MockSMClient) Close() error {
 }
 
 func (mc *MockSMClient) AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
-	return mc.addSecretFn(ctx, req)
+	return mc.AddSecretFn(ctx, req)
 }
 
 func (mc *MockSMClient) NewAddSecretVersionFn(mock AddSecretVersionMockReturn) {
-	mc.addSecretFn = func(_ context.Context, _ *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
+	mc.AddSecretFn = func(_ context.Context, _ *secretmanagerpb.AddSecretVersionRequest, _ ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
 		return mock.SecretVersion, mock.Err
 	}
 }
@@ -104,7 +100,7 @@ func (mc *MockSMClient) CreateSecret(ctx context.Context, req *secretmanagerpb.C
 	return mc.createSecretFn(ctx, req)
 }
 
-func (mc *MockSMClient) NewCreateSecretFn(mock CreateSecretMockReturn) {
+func (mc *MockSMClient) NewCreateSecretFn(mock SecretMockReturn) {
 	mc.createSecretFn = func(_ context.Context, _ *secretmanagerpb.CreateSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
 		return mock.Secret, mock.Err
 	}
@@ -146,7 +142,7 @@ func (mc *MockSMClient) DefaultCreateSecret(wantedSecretID, wantedParent string)
 }
 
 func (mc *MockSMClient) DefaultAddSecretVersion(wantedData, wantedParent, versionName string) {
-	mc.addSecretFn = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
+	mc.AddSecretFn = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
 		if string(req.Payload.Data) != wantedData {
 			return nil, fmt.Errorf("add version req wrong data got: %v want %v ", req.Payload.Data, wantedData)
 		}
@@ -177,6 +173,16 @@ func (mc *MockSMClient) AccessSecretVersionWithError(err error) {
 	}
 }
 
+func (mc *MockSMClient) UpdateSecret(ctx context.Context, req *secretmanagerpb.UpdateSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+	return mc.updateSecretFn(ctx, req)
+}
+
+func (mc *MockSMClient) NewUpdateSecretFn(mock SecretMockReturn) {
+	mc.updateSecretFn = func(_ context.Context, _ *secretmanagerpb.UpdateSecretRequest, _ ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+		return mock.Secret, mock.Err
+	}
+}
+
 func (mc *MockSMClient) WithValue(_ context.Context, req *secretmanagerpb.AccessSecretVersionRequest, val *secretmanagerpb.AccessSecretVersionResponse, err error) {
 	if mc != nil {
 		mc.accessSecretFn = func(paramCtx context.Context, paramReq *secretmanagerpb.AccessSecretVersionRequest, paramOpts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {

+ 39 - 16
pkg/provider/ibm/provider.go

@@ -56,6 +56,7 @@ const (
 	errFetchSAKSecret                        = "could not fetch SecretAccessKey secret: %w"
 	errMissingSAK                            = "missing SecretAccessKey"
 	errJSONSecretUnmarshal                   = "unable to unmarshal secret: %w"
+	errJSONSecretMarshal                     = "unable to marshal secret: %w"
 	errExtractingSecret                      = "unable to extract the fetched secret %s of type %s while performing %s"
 
 	defaultCacheSize   = 100
@@ -187,7 +188,15 @@ func (ibm *providerIBM) GetSecret(_ context.Context, ref esv1beta1.ExternalSecre
 
 	case sm.Secret_SecretType_Kv:
 
-		return getKVSecret(ibm, &secretName, ref)
+		response, err := getSecretData(ibm, &secretName, sm.Secret_SecretType_Kv)
+		if err != nil {
+			return nil, err
+		}
+		secret, ok := response.(*sm.KVSecret)
+		if !ok {
+			return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_Kv, "GetSecret")
+		}
+		return getKVSecret(ref, secret)
 
 	default:
 		return nil, fmt.Errorf("unknown secret type %s", secretType)
@@ -301,15 +310,7 @@ func getUsernamePasswordSecret(ibm *providerIBM, secretName *string, ref esv1bet
 }
 
 // Returns a secret of type kv and supports json path.
-func getKVSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	response, err := getSecretData(ibm, secretName, sm.Secret_SecretType_Kv)
-	if err != nil {
-		return nil, err
-	}
-	secret, ok := response.(*sm.KVSecret)
-	if !ok {
-		return nil, fmt.Errorf(errExtractingSecret, *secretName, sm.Secret_SecretType_Kv, "getKVSecret")
-	}
+func getKVSecret(ref esv1beta1.ExternalSecretDataRemoteRef, secret *sm.KVSecret) ([]byte, error) {
 	payloadJSONByte, err := json.Marshal(secret.Data)
 	if err != nil {
 		return nil, fmt.Errorf("marshaling payload from secret failed. %w", err)
@@ -440,6 +441,11 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 	if err != nil {
 		return nil, err
 	}
+	if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+		if err := populateSecretMap(secretMap, response); err != nil {
+			return nil, err
+		}
+	}
 
 	switch secretType {
 	case sm.Secret_SecretType_Arbitrary:
@@ -502,7 +508,11 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 		return secretMap, nil
 
 	case sm.Secret_SecretType_Kv:
-		secret, err := getKVSecret(ibm, &secretName, ref)
+		secretData, ok := response.(*sm.KVSecret)
+		if !ok {
+			return nil, fmt.Errorf(errExtractingSecret, secretName, sm.Secret_SecretType_Kv, "GetSecretMap")
+		}
+		secret, err := getKVSecret(ref, secretData)
 		if err != nil {
 			return nil, err
 		}
@@ -511,9 +521,7 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 		if err != nil {
 			return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
 		}
-
-		secretMap := byteArrayMap(m)
-
+		secretMap = byteArrayMap(m, secretMap)
 		return secretMap, nil
 
 	default:
@@ -521,9 +529,8 @@ func (ibm *providerIBM) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSe
 	}
 }
 
-func byteArrayMap(secretData map[string]interface{}) map[string][]byte {
+func byteArrayMap(secretData map[string]interface{}, secretMap map[string][]byte) map[string][]byte {
 	var err error
-	secretMap := make(map[string][]byte)
 	for k, v := range secretData {
 		secretMap[k], err = getTypedKey(v)
 		if err != nil {
@@ -695,3 +702,19 @@ func init() {
 		IBM: &esv1beta1.IBMProvider{},
 	})
 }
+
+// populateSecretMap populates the secretMap with metadata information that is pulled from IBM provider.
+func populateSecretMap(secretMap map[string][]byte, secretData interface{}) error {
+	secretDataMap := make(map[string]interface{})
+	data, err := json.Marshal(secretData)
+	if err != nil {
+		return fmt.Errorf(errJSONSecretMarshal, err)
+	}
+	if err := json.Unmarshal(data, &secretDataMap); err != nil {
+		return fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+	for key, value := range secretDataMap {
+		secretMap[key] = []byte(fmt.Sprintf("%v", value))
+	}
+	return nil
+}

+ 411 - 121
pkg/provider/ibm/provider_test.go

@@ -18,15 +18,17 @@ import (
 	"encoding/json"
 	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
 
 	"github.com/IBM/go-sdk-core/v5/core"
 	sm "github.com/IBM/secrets-manager-go-sdk/v2/secretsmanagerv2"
+	"github.com/go-openapi/strfmt"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	utilpointer "k8s.io/utils/pointer"
+	utilpointer "k8s.io/utils/ptr"
 	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -35,9 +37,10 @@ import (
 )
 
 const (
-	errExpectedErr = "wanted error got nil"
-	secretKey      = "test-secret"
-	secretUUID     = "d5deb37a-7883-4fe2-a5e7-3c15420adc76"
+	errExpectedErr       = "wanted error got nil"
+	secretKey            = "test-secret"
+	secretUUID           = "d5deb37a-7883-4fe2-a5e7-3c15420adc76"
+	iamCredentialsSecret = "iam_credentials/"
 )
 
 type secretManagerTestCase struct {
@@ -93,15 +96,15 @@ func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 
 func makeValidAPIInput() *sm.GetSecretOptions {
 	return &sm.GetSecretOptions{
-		ID: utilpointer.String(secretUUID),
+		ID: utilpointer.To(secretUUID),
 	}
 }
 
 func makeValidAPIOutput() sm.SecretIntf {
 	secret := &sm.Secret{
-		SecretType: utilpointer.String(sm.Secret_SecretType_Arbitrary),
-		Name:       utilpointer.String("testyname"),
-		ID:         utilpointer.String(secretUUID),
+		SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
+		Name:       utilpointer.To("testyname"),
+		ID:         utilpointer.To(secretUUID),
 	}
 	var i sm.SecretIntf = secret
 	return i
@@ -208,28 +211,28 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	// key is passed in, output is sent back
 	setSecretString := func(smtc *secretManagerTestCase) {
 		secret := &sm.ArbitrarySecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Arbitrary),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Payload:    &secretString,
 		}
 		smtc.name = "good case: default version is set"
 		smtc.apiOutput = secret
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.expectedSecret = secretString
 	}
 
 	// good case: custom version set
 	setCustomKey := func(smtc *secretManagerTestCase) {
 		secret := &sm.ArbitrarySecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Arbitrary),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Payload:    &secretString,
 		}
 		smtc.name = "good case: custom version set"
 		smtc.ref.Key = "arbitrary/" + secretUUID
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.expectedSecret = secretString
 	}
@@ -238,14 +241,14 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	secretUserPass := "username_password/" + secretUUID
 	badSecretUserPass := func(smtc *secretManagerTestCase) {
 		secret := &sm.UsernamePasswordSecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_UsernamePassword),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_UsernamePassword),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Username:   &secretUsername,
 			Password:   &secretPassword,
 		}
 		smtc.name = "bad case: username_password type without property"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretUserPass
 		smtc.expectError = "remoteRef.property required for secret type username_password"
@@ -255,20 +258,20 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	funcSetUserPass := func(secretName, property, name string) func(smtc *secretManagerTestCase) {
 		return func(smtc *secretManagerTestCase) {
 			secret := &sm.UsernamePasswordSecret{
-				SecretType: utilpointer.String(sm.Secret_SecretType_UsernamePassword),
-				Name:       utilpointer.String("testyname"),
-				ID:         utilpointer.String(secretUUID),
+				SecretType: utilpointer.To(sm.Secret_SecretType_UsernamePassword),
+				Name:       utilpointer.To("testyname"),
+				ID:         utilpointer.To(secretUUID),
 				Username:   &secretUsername,
 				Password:   &secretPassword,
 			}
 			secretMetadata := &sm.UsernamePasswordSecretMetadata{
-				Name: utilpointer.String("testyname"),
-				ID:   utilpointer.String(secretUUID),
+				Name: utilpointer.To("testyname"),
+				ID:   utilpointer.To(secretUUID),
 			}
 			smtc.name = name
-			smtc.apiInput.ID = utilpointer.String(secretUUID)
+			smtc.apiInput.ID = utilpointer.To(secretUUID)
 			smtc.apiOutput = secret
-			smtc.listInput.Search = utilpointer.String("testyname")
+			smtc.listInput.Search = utilpointer.To("testyname")
 			smtc.listOutput.Secrets = make([]sm.SecretMetadataIntf, 1)
 			smtc.listOutput.Secrets[0] = secretMetadata
 			smtc.ref.Key = "username_password/" + secretName
@@ -288,22 +291,22 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	funcSetSecretIam := func(secretName, name string) func(*secretManagerTestCase) {
 		return func(smtc *secretManagerTestCase) {
 			secret := &sm.IAMCredentialsSecret{
-				SecretType: utilpointer.String(sm.Secret_SecretType_IamCredentials),
-				Name:       utilpointer.String("testyname"),
-				ID:         utilpointer.String(secretUUID),
-				ApiKey:     utilpointer.String(secretAPIKey),
+				SecretType: utilpointer.To(sm.Secret_SecretType_IamCredentials),
+				Name:       utilpointer.To("testyname"),
+				ID:         utilpointer.To(secretUUID),
+				ApiKey:     utilpointer.To(secretAPIKey),
 			}
 			secretMetadata := &sm.IAMCredentialsSecretMetadata{
-				Name: utilpointer.String("testyname"),
-				ID:   utilpointer.String(secretUUID),
+				Name: utilpointer.To("testyname"),
+				ID:   utilpointer.To(secretUUID),
 			}
-			smtc.apiInput.ID = utilpointer.String(secretUUID)
+			smtc.apiInput.ID = utilpointer.To(secretUUID)
 			smtc.name = name
 			smtc.apiOutput = secret
-			smtc.listInput.Search = utilpointer.String("testyname")
+			smtc.listInput.Search = utilpointer.To("testyname")
 			smtc.listOutput.Secrets = make([]sm.SecretMetadataIntf, 1)
 			smtc.listOutput.Secrets[0] = secretMetadata
-			smtc.ref.Key = "iam_credentials/" + secretName
+			smtc.ref.Key = iamCredentialsSecret + secretName
 			smtc.expectedSecret = secretAPIKey
 		}
 	}
@@ -314,7 +317,7 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	funcSetCertSecretTest := func(secret sm.SecretIntf, name, certType string, good bool) func(*secretManagerTestCase) {
 		return func(smtc *secretManagerTestCase) {
 			smtc.name = name
-			smtc.apiInput.ID = utilpointer.String(secretUUID)
+			smtc.apiInput.ID = utilpointer.To(secretUUID)
 			smtc.apiOutput = secret
 			smtc.ref.Key = certType + "/" + secretUUID
 			if good {
@@ -328,12 +331,12 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 
 	// good case: imported_cert type with property
 	importedCert := &sm.ImportedCertificate{
-		SecretType:   utilpointer.String(sm.Secret_SecretType_ImportedCert),
-		Name:         utilpointer.String("testyname"),
-		ID:           utilpointer.String(secretUUID),
-		Certificate:  utilpointer.String(secretCertificate),
-		Intermediate: utilpointer.String("intermediate"),
-		PrivateKey:   utilpointer.String("private_key"),
+		SecretType:   utilpointer.To(sm.Secret_SecretType_ImportedCert),
+		Name:         utilpointer.To("testyname"),
+		ID:           utilpointer.To(secretUUID),
+		Certificate:  utilpointer.To(secretCertificate),
+		Intermediate: utilpointer.To("intermediate"),
+		PrivateKey:   utilpointer.To("private_key"),
 	}
 	setSecretCert := funcSetCertSecretTest(importedCert, "good case: imported_cert type with property", sm.Secret_SecretType_ImportedCert, true)
 
@@ -342,12 +345,12 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 
 	// good case: public_cert type with property
 	publicCert := &sm.PublicCertificate{
-		SecretType:   utilpointer.String(sm.Secret_SecretType_PublicCert),
-		Name:         utilpointer.String("testyname"),
-		ID:           utilpointer.String(secretUUID),
-		Certificate:  utilpointer.String(secretCertificate),
-		Intermediate: utilpointer.String("intermediate"),
-		PrivateKey:   utilpointer.String("private_key"),
+		SecretType:   utilpointer.To(sm.Secret_SecretType_PublicCert),
+		Name:         utilpointer.To("testyname"),
+		ID:           utilpointer.To(secretUUID),
+		Certificate:  utilpointer.To(secretCertificate),
+		Intermediate: utilpointer.To("intermediate"),
+		PrivateKey:   utilpointer.To("private_key"),
 	}
 	setSecretPublicCert := funcSetCertSecretTest(publicCert, "good case: public_cert type with property", sm.Secret_SecretType_PublicCert, true)
 
@@ -356,11 +359,11 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 
 	// good case: private_cert type with property
 	privateCert := &sm.PrivateCertificate{
-		SecretType:  utilpointer.String(sm.Secret_SecretType_PublicCert),
-		Name:        utilpointer.String("testyname"),
-		ID:          utilpointer.String(secretUUID),
-		Certificate: utilpointer.String(secretCertificate),
-		PrivateKey:  utilpointer.String("private_key"),
+		SecretType:  utilpointer.To(sm.Secret_SecretType_PublicCert),
+		Name:        utilpointer.To("testyname"),
+		ID:          utilpointer.To(secretUUID),
+		Certificate: utilpointer.To(secretCertificate),
+		PrivateKey:  utilpointer.To("private_key"),
 	}
 	setSecretPrivateCert := funcSetCertSecretTest(privateCert, "good case: private_cert type with property", sm.Secret_SecretType_PrivateCert, true)
 
@@ -379,13 +382,13 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	// bad case: kv type with key which is not in payload
 	badSecretKV := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Data:       secretDataKV,
 		}
 		smtc.name = "bad case: kv type with key which is not in payload"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretKV
 		smtc.ref.Property = "other-key"
@@ -395,13 +398,13 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	// good case: kv type with property
 	setSecretKV := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Data:       secretDataKV,
 		}
 		smtc.name = "good case: kv type with property"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretKV
 		smtc.ref.Property = "key1"
@@ -411,13 +414,13 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	// good case: kv type with property, returns specific value
 	setSecretKVWithKey := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Data:       secretDataKVComplex,
 		}
 		smtc.name = "good case: kv type with property, returns specific value"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretKV
 		smtc.ref.Property = "key2"
@@ -427,13 +430,13 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	// good case: kv type with property and path, returns specific value
 	setSecretKVWithKeyPath := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Data:       secretDataKVComplex,
 		}
 		smtc.name = "good case: kv type with property and path, returns specific value"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretKV
 		smtc.ref.Property = "keyC.keyC2"
@@ -443,13 +446,13 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	// good case: kv type with property and dot, returns specific value
 	setSecretKVWithKeyDot := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Data:       secretDataKVComplex,
 		}
 		smtc.name = "good case: kv type with property and dot, returns specific value"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretKV
 		smtc.ref.Property = "special.log"
@@ -459,13 +462,13 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 	// good case: kv type without property, returns all
 	setSecretKVWithOutKey := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
 			Data:       secretDataKVComplex,
 		}
 		smtc.name = "good case: kv type without property, returns all"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretKV
 		smtc.expectedSecret = secretKVComplex
@@ -516,9 +519,11 @@ func TestGetSecretMap(t *testing.T) {
 	secretUsername := "user1"
 	secretPassword := "P@ssw0rd"
 	secretAPIKey := "01234567890"
+	nilValue := "<nil>"
 	secretCertificate := "certificate_value"
 	secretPrivateKey := "private_key_value"
 	secretIntermediate := "intermediate_value"
+	timeValue := "0001-01-01T00:00:00.000Z"
 
 	secretComplex := map[string]interface{}{
 		"key1": "val1",
@@ -535,13 +540,13 @@ func TestGetSecretMap(t *testing.T) {
 	setArbitrary := func(smtc *secretManagerTestCase) {
 		payload := `{"foo":"bar"}`
 		secret := &sm.ArbitrarySecret{
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
-			SecretType: utilpointer.String(sm.Secret_SecretType_Arbitrary),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Arbitrary),
 			Payload:    &payload,
 		}
 		smtc.name = "good case: arbitrary"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = secretUUID
 		smtc.expectedData["arbitrary"] = []byte(payload)
@@ -550,14 +555,14 @@ func TestGetSecretMap(t *testing.T) {
 	// good case: username_password
 	setSecretUserPass := func(smtc *secretManagerTestCase) {
 		secret := &sm.UsernamePasswordSecret{
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
-			SecretType: utilpointer.String(sm.Secret_SecretType_UsernamePassword),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_UsernamePassword),
 			Username:   &secretUsername,
 			Password:   &secretPassword,
 		}
 		smtc.name = "good case: username_password"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = "username_password/" + secretUUID
 		smtc.expectedData["username"] = []byte(secretUsername)
@@ -567,22 +572,22 @@ func TestGetSecretMap(t *testing.T) {
 	// good case: iam_credentials
 	setSecretIam := func(smtc *secretManagerTestCase) {
 		secret := &sm.IAMCredentialsSecret{
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
-			SecretType: utilpointer.String(sm.Secret_SecretType_IamCredentials),
-			ApiKey:     utilpointer.String(secretAPIKey),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_IamCredentials),
+			ApiKey:     utilpointer.To(secretAPIKey),
 		}
 		smtc.name = "good case: iam_credentials"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
-		smtc.ref.Key = "iam_credentials/" + secretUUID
+		smtc.ref.Key = iamCredentialsSecret + secretUUID
 		smtc.expectedData["apikey"] = []byte(secretAPIKey)
 	}
 
 	funcCertTest := func(secret sm.SecretIntf, name, certType string) func(*secretManagerTestCase) {
 		return func(smtc *secretManagerTestCase) {
 			smtc.name = name
-			smtc.apiInput.ID = utilpointer.String(secretUUID)
+			smtc.apiInput.ID = utilpointer.To(secretUUID)
 			smtc.apiOutput = secret
 			smtc.ref.Key = certType + "/" + secretUUID
 			smtc.expectedData["certificate"] = []byte(secretCertificate)
@@ -593,50 +598,327 @@ func TestGetSecretMap(t *testing.T) {
 
 	// good case: imported_cert
 	importedCert := &sm.ImportedCertificate{
-		SecretType:   utilpointer.String(sm.Secret_SecretType_ImportedCert),
-		Name:         utilpointer.String("testyname"),
-		ID:           utilpointer.String(secretUUID),
-		Certificate:  utilpointer.String(secretCertificate),
-		Intermediate: utilpointer.String(secretIntermediate),
-		PrivateKey:   utilpointer.String(secretPrivateKey),
+		SecretType:   utilpointer.To(sm.Secret_SecretType_ImportedCert),
+		Name:         utilpointer.To("testyname"),
+		ID:           utilpointer.To(secretUUID),
+		Certificate:  utilpointer.To(secretCertificate),
+		Intermediate: utilpointer.To(secretIntermediate),
+		PrivateKey:   utilpointer.To(secretPrivateKey),
 	}
 	setSecretCert := funcCertTest(importedCert, "good case: imported_cert", sm.Secret_SecretType_ImportedCert)
 
 	// good case: public_cert
 	publicCert := &sm.PublicCertificate{
-		SecretType:   utilpointer.String(sm.Secret_SecretType_PublicCert),
-		Name:         utilpointer.String("testyname"),
-		ID:           utilpointer.String(secretUUID),
-		Certificate:  utilpointer.String(secretCertificate),
-		Intermediate: utilpointer.String(secretIntermediate),
-		PrivateKey:   utilpointer.String(secretPrivateKey),
+		SecretType:   utilpointer.To(sm.Secret_SecretType_PublicCert),
+		Name:         utilpointer.To("testyname"),
+		ID:           utilpointer.To(secretUUID),
+		Certificate:  utilpointer.To(secretCertificate),
+		Intermediate: utilpointer.To(secretIntermediate),
+		PrivateKey:   utilpointer.To(secretPrivateKey),
 	}
 	setSecretPublicCert := funcCertTest(publicCert, "good case: public_cert", sm.Secret_SecretType_PublicCert)
 
 	// good case: private_cert
 	setSecretPrivateCert := func(smtc *secretManagerTestCase) {
 		secret := &sm.PrivateCertificate{
-			Name:        utilpointer.String("testyname"),
-			ID:          utilpointer.String(secretUUID),
-			SecretType:  utilpointer.String(sm.Secret_SecretType_PrivateCert),
+			Name:        utilpointer.To("testyname"),
+			ID:          utilpointer.To(secretUUID),
+			SecretType:  utilpointer.To(sm.Secret_SecretType_PrivateCert),
 			Certificate: &secretCertificate,
 			PrivateKey:  &secretPrivateKey,
 		}
 		smtc.name = "good case: private_cert"
-		smtc.apiInput.ID = utilpointer.String(secretUUID)
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
 		smtc.apiOutput = secret
 		smtc.ref.Key = "private_cert/" + secretUUID
 		smtc.expectedData["certificate"] = []byte(secretCertificate)
 		smtc.expectedData["private_key"] = []byte(secretPrivateKey)
 	}
 
+	// good case: arbitrary with metadata
+	setArbitraryWithMetadata := func(smtc *secretManagerTestCase) {
+		payload := `{"foo":"bar"}`
+		secret := &sm.ArbitrarySecret{
+			CreatedBy:  utilpointer.To("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.To(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.To(int64(20)),
+			Payload:    &payload,
+		}
+		smtc.name = "good case: arbitrary with metadata"
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{"arbitrary": []byte(payload),
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"payload":         []byte(payload),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: iam_credentials with metadata
+	setSecretIamWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.IAMCredentialsSecret{
+			CreatedBy:  utilpointer.To("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.To(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.To(int64(20)),
+			ApiKey:     utilpointer.To(secretAPIKey),
+		}
+		smtc.name = "good case: iam_credentials with metadata"
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = iamCredentialsSecret + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{"api_key": []byte(secretAPIKey),
+			"apikey":          []byte(secretAPIKey),
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"reuse_api_key":   []byte(nilValue),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"ttl":             []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// "good case: username_password with metadata
+	setSecretUserPassWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.UsernamePasswordSecret{
+			CreatedBy:  utilpointer.To("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.To(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.To(int64(20)),
+			Username:   &secretUsername,
+			Password:   &secretPassword,
+		}
+		smtc.name = "good case: username_password with metadata"
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "username_password/" + secretUUID
+		smtc.expectedData["username"] = []byte(secretUsername)
+		smtc.expectedData["password"] = []byte(secretPassword)
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"password":        []byte(secretPassword),
+			"rotation":        []byte(nilValue),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"username":        []byte(secretUsername),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: imported_cert with metadata
+	setimportedCertWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.ImportedCertificate{
+			CreatedBy:    utilpointer.To("testCreatedBy"),
+			CreatedAt:    &strfmt.DateTime{},
+			Downloaded:   utilpointer.To(false),
+			Labels:       []string{"abc", "def", "xyz"},
+			LocksTotal:   utilpointer.To(int64(20)),
+			Certificate:  utilpointer.To(secretCertificate),
+			Intermediate: utilpointer.To(secretIntermediate),
+			PrivateKey:   utilpointer.To(secretPrivateKey),
+		}
+		smtc.name = "good case: imported_cert with metadata"
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "imported_cert" + "/" + secretUUID
+
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"certificate":           []byte(secretCertificate),
+			"created_at":            []byte(timeValue),
+			"created_by":            []byte(*secret.CreatedBy),
+			"crn":                   []byte(nilValue),
+			"downloaded":            []byte(strconv.FormatBool(*secret.Downloaded)),
+			"expiration_date":       []byte(nilValue),
+			"id":                    []byte(nilValue),
+			"intermediate":          []byte(secretIntermediate),
+			"intermediate_included": []byte(nilValue),
+			"issuer":                []byte(nilValue),
+			"labels":                []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":           []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"private_key":           []byte(secretPrivateKey),
+			"private_key_included":  []byte(nilValue),
+			"secret_group_id":       []byte(nilValue),
+			"secret_type":           []byte(nilValue),
+			"serial_number":         []byte(nilValue),
+			"signing_algorithm":     []byte(nilValue),
+			"updated_at":            []byte(nilValue),
+			"validity":              []byte(nilValue),
+			"versions_total":        []byte(nilValue),
+		}
+	}
+
+	// good case: public_cert with metadata
+	setPublicCertWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.PublicCertificate{
+			CreatedBy:    utilpointer.To("testCreatedBy"),
+			CreatedAt:    &strfmt.DateTime{},
+			Downloaded:   utilpointer.To(false),
+			Labels:       []string{"abc", "def", "xyz"},
+			LocksTotal:   utilpointer.To(int64(20)),
+			Certificate:  utilpointer.To(secretCertificate),
+			Intermediate: utilpointer.To(secretIntermediate),
+			PrivateKey:   utilpointer.To(secretPrivateKey),
+		}
+		smtc.name = "good case: public_cert with metadata"
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "public_cert" + "/" + secretUUID
+
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"certificate":     []byte(secretCertificate),
+			"common_name":     []byte(nilValue),
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"intermediate":    []byte(secretIntermediate),
+			"key_algorithm":   []byte(nilValue),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"private_key":     []byte(secretPrivateKey),
+			"rotation":        []byte(nilValue),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: private_cert with metadata
+	setPrivateCertWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.PrivateCertificate{
+			CreatedBy:   utilpointer.To("testCreatedBy"),
+			CreatedAt:   &strfmt.DateTime{},
+			Downloaded:  utilpointer.To(false),
+			Labels:      []string{"abc", "def", "xyz"},
+			LocksTotal:  utilpointer.To(int64(20)),
+			Certificate: utilpointer.To(secretCertificate),
+			PrivateKey:  utilpointer.To(secretPrivateKey),
+		}
+		smtc.name = "good case: private_cert with metadata"
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "private_cert" + "/" + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"certificate":          []byte(secretCertificate),
+			"certificate_template": []byte(nilValue),
+			"common_name":          []byte(nilValue),
+			"created_at":           []byte(timeValue),
+			"created_by":           []byte(*secret.CreatedBy),
+			"crn":                  []byte(nilValue),
+			"downloaded":           []byte(strconv.FormatBool(*secret.Downloaded)),
+			"expiration_date":      []byte(nilValue),
+			"id":                   []byte(nilValue),
+			"issuer":               []byte(nilValue),
+			"labels":               []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":          []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"private_key":          []byte(secretPrivateKey),
+			"secret_group_id":      []byte(nilValue),
+			"secret_type":          []byte(nilValue),
+			"serial_number":        []byte(nilValue),
+			"signing_algorithm":    []byte(nilValue),
+			"updated_at":           []byte(nilValue),
+			"validity":             []byte(nilValue),
+			"versions_total":       []byte(nilValue),
+		}
+	}
+
+	// good case: kv with property and metadata
+	setSecretKVWithMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.KVSecret{
+			CreatedBy:  utilpointer.To("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.To(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.To(int64(20)),
+			Data:       secretComplex,
+		}
+		smtc.name = "good case: kv, with property and with metadata"
+		smtc.apiInput.ID = core.StringPtr(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = "kv/" + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedData = map[string][]byte{
+			"created_at":      []byte(timeValue),
+			"created_by":      []byte(*secret.CreatedBy),
+			"crn":             []byte(nilValue),
+			"data":            []byte("map[key1:val1 key2:val2 keyC:map[keyC1:map[keyA:valA keyB:valB]]]"),
+			"downloaded":      []byte(strconv.FormatBool(*secret.Downloaded)),
+			"id":              []byte(nilValue),
+			"key1":            []byte("val1"),
+			"key2":            []byte("val2"),
+			"keyC":            []byte(`{"keyC1":{"keyA":"valA","keyB":"valB"}}`),
+			"labels":          []byte("[" + strings.Join(secret.Labels, " ") + "]"),
+			"locks_total":     []byte(strconv.Itoa(int(*secret.LocksTotal))),
+			"secret_group_id": []byte(nilValue),
+			"secret_type":     []byte(nilValue),
+			"updated_at":      []byte(nilValue),
+			"versions_total":  []byte(nilValue),
+		}
+	}
+
+	// good case: iam_credentials without metadata
+	setSecretIamWithoutMetadata := func(smtc *secretManagerTestCase) {
+		secret := &sm.IAMCredentialsSecret{
+			CreatedBy:  utilpointer.To("testCreatedBy"),
+			CreatedAt:  &strfmt.DateTime{},
+			Downloaded: utilpointer.To(false),
+			Labels:     []string{"abc", "def", "xyz"},
+			LocksTotal: utilpointer.To(int64(20)),
+			ApiKey:     utilpointer.To(secretAPIKey),
+		}
+		smtc.name = "good case: iam_credentials without metadata"
+		smtc.apiInput.ID = utilpointer.To(secretUUID)
+		smtc.apiOutput = secret
+		smtc.ref.Key = iamCredentialsSecret + secretUUID
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyNone
+		smtc.expectedData = map[string][]byte{
+			"apikey": []byte(secretAPIKey),
+		}
+	}
+
 	secretKeyKV := "kv/" + secretUUID
 	// good case: kv, no property, return entire payload as key:value pairs
 	setSecretKV := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
 			Data:       secretComplex,
 		}
 		smtc.name = "good case: kv, no property, return entire payload as key:value pairs"
@@ -651,9 +933,9 @@ func TestGetSecretMap(t *testing.T) {
 	// good case: kv, with property
 	setSecretKVWithProperty := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			Name:       utilpointer.String("d5deb37a-7883-4fe2-a5e7-3c15420adc76"),
-			ID:         utilpointer.String(secretUUID),
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("d5deb37a-7883-4fe2-a5e7-3c15420adc76"),
+			ID:         utilpointer.To(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
 			Data:       secretComplex,
 		}
 		smtc.name = "good case: kv, with property"
@@ -667,9 +949,9 @@ func TestGetSecretMap(t *testing.T) {
 	// good case: kv, with property and path
 	setSecretKVWithPathAndProperty := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			Name:       utilpointer.String(secretUUID),
-			ID:         utilpointer.String(secretUUID),
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To(secretUUID),
+			ID:         utilpointer.To(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
 			Data:       secretComplex,
 		}
 		smtc.name = "good case: kv, with property and path"
@@ -684,9 +966,9 @@ func TestGetSecretMap(t *testing.T) {
 	// bad case: kv, with property and path
 	badSecretKVWithUnknownProperty := func(smtc *secretManagerTestCase) {
 		secret := &sm.KVSecret{
-			Name:       utilpointer.String("testyname"),
-			ID:         utilpointer.String(secretUUID),
-			SecretType: utilpointer.String(sm.Secret_SecretType_Kv),
+			Name:       utilpointer.To("testyname"),
+			ID:         utilpointer.To(secretUUID),
+			SecretType: utilpointer.To(sm.Secret_SecretType_Kv),
 			Data:       secretComplex,
 		}
 		smtc.name = "bad case: kv, with property and path"
@@ -710,6 +992,14 @@ func TestGetSecretMap(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(badSecretKVWithUnknownProperty),
 		makeValidSecretManagerTestCaseCustom(setSecretPublicCert),
 		makeValidSecretManagerTestCaseCustom(setSecretPrivateCert),
+		makeValidSecretManagerTestCaseCustom(setSecretIamWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setArbitraryWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setSecretUserPassWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setimportedCertWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setPublicCertWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setPrivateCertWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setSecretKVWithMetadata),
+		makeValidSecretManagerTestCaseCustom(setSecretIamWithoutMetadata),
 	}
 
 	sm := providerIBM{}

+ 3 - 3
pkg/provider/kubernetes/auth_test.go

@@ -21,7 +21,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	fclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
@@ -151,7 +151,7 @@ func TestSetAuth(t *testing.T) {
 						Token: &esv1beta1.TokenAuth{
 							BearerToken: v1.SecretKeySelector{
 								Name:      "foobar",
-								Namespace: pointer.String("shouldnotberelevant"),
+								Namespace: pointer.To("shouldnotberelevant"),
 								Key:       "token",
 							},
 						},
@@ -221,7 +221,7 @@ func TestSetAuth(t *testing.T) {
 					Auth: esv1beta1.KubernetesAuth{
 						ServiceAccount: &v1.ServiceAccountSelector{
 							Name:      "my-sa",
-							Namespace: pointer.String("shouldnotberelevant"),
+							Namespace: pointer.To("shouldnotberelevant"),
 						},
 					},
 				},

+ 3 - 3
pkg/provider/kubernetes/provider_test.go

@@ -22,7 +22,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	clientgofake "k8s.io/client-go/kubernetes/fake"
-	"k8s.io/utils/pointer"
+	pointer "k8s.io/utils/ptr"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 	fclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
@@ -138,7 +138,7 @@ func TestNewClient(t *testing.T) {
 									Token: &esv1beta1.TokenAuth{
 										BearerToken: v1.SecretKeySelector{
 											Name:      "foo",
-											Namespace: pointer.String("default"),
+											Namespace: pointer.To("default"),
 											Key:       "token",
 										},
 									},
@@ -172,7 +172,7 @@ func TestNewClient(t *testing.T) {
 									Token: &esv1beta1.TokenAuth{
 										BearerToken: v1.SecretKeySelector{
 											Name:      "foo",
-											Namespace: pointer.String("default"),
+											Namespace: pointer.To("default"),
 											Key:       "token",
 										},
 									},

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません