Browse Source

Merge branch 'main' into beach-team

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
Co-authored-by: William Young <will.young@engineerbetter.com>
Co-authored-by: Dominic Meddick <dom.meddick@engineerbetter.com>
Gustavo Carvalho 4 years ago
parent
commit
ae05c51972
89 changed files with 2349 additions and 282 deletions
  1. 6 10
      .github/PAUL.yaml
  2. 2 2
      .github/workflows/e2e-managed.yml
  3. 3 1
      .gitignore
  4. 1 0
      ADOPTERS.md
  5. 5 5
      Makefile
  6. 14 2
      apis/externalsecrets/v1beta1/externalsecret_types.go
  7. 10 0
      config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml
  8. 10 0
      config/crds/bases/external-secrets.io_externalsecrets.yaml
  9. 10 0
      deploy/charts/external-secrets/README.md
  10. 1 1
      deploy/charts/external-secrets/templates/cert-controller-deployment.yaml
  11. 19 0
      deploy/charts/external-secrets/templates/cert-controller-poddisruptionbudget.yaml
  12. 19 0
      deploy/charts/external-secrets/templates/poddisruptionbudget.yaml
  13. 20 0
      deploy/charts/external-secrets/templates/webhook-poddisruptionbudget.yaml
  14. 18 0
      deploy/charts/external-secrets/values.yaml
  15. 12 0
      deploy/crds/bundle.yaml
  16. 8 1
      docs/contributing-process.md
  17. 82 0
      docs/faq.md
  18. BIN
      docs/pictures/diagrams-high-level-simple.png
  19. 5 2
      docs/provider-azure-key-vault.md
  20. 5 4
      docs/provider-ibm-secrets-manager.md
  21. 15 16
      docs/provider-kubernetes.md
  22. 31 0
      docs/roadmap.md
  23. 27 0
      docs/snippets/azkv-datafrom-external-secret.yaml
  24. 14 2
      docs/snippets/azkv-external-secret.yaml
  25. 9 5
      docs/snippets/ibm-es-types.yaml
  26. 7 6
      docs/stability-support.md
  27. 4 2
      e2e/Dockerfile
  28. 2 1
      e2e/Makefile
  29. 7 6
      e2e/entrypoint.sh
  30. 1 1
      e2e/framework/addon/addon.go
  31. 12 0
      e2e/framework/addon/chart.go
  32. 28 1
      e2e/framework/addon/eso.go
  33. 144 0
      e2e/framework/addon/eso_argocd_application.go
  34. 166 0
      e2e/framework/addon/eso_flux_helm.go
  35. 99 0
      e2e/framework/addon/helmserver.go
  36. 45 0
      e2e/framework/addon/uninstall_eso_crds.go
  37. 0 9
      e2e/framework/framework.go
  38. 2 1
      e2e/framework/testcase.go
  39. 18 0
      e2e/framework/util/util.go
  40. 2 2
      e2e/run.sh
  41. 37 0
      e2e/suites/argocd/argocd.go
  42. 81 0
      e2e/suites/argocd/install.go
  43. 53 0
      e2e/suites/argocd/suite_test.go
  44. 37 0
      e2e/suites/flux/flux.go
  45. 79 0
      e2e/suites/flux/install.go
  46. 53 0
      e2e/suites/flux/suite_test.go
  47. 1 1
      e2e/suites/provider/cases/akeyless/akeyless.go
  48. 0 0
      e2e/suites/provider/cases/akeyless/provider.go
  49. 1 1
      e2e/suites/provider/cases/alibaba/alibaba.go
  50. 0 0
      e2e/suites/provider/cases/alibaba/provider.go
  51. 0 0
      e2e/suites/provider/cases/aws/common.go
  52. 0 0
      e2e/suites/provider/cases/aws/parameterstore/find_by_name.go
  53. 0 0
      e2e/suites/provider/cases/aws/parameterstore/find_by_tags.go
  54. 1 1
      e2e/suites/provider/cases/aws/parameterstore/parameterstore.go
  55. 2 2
      e2e/suites/provider/cases/aws/parameterstore/parameterstore_managed.go
  56. 1 1
      e2e/suites/provider/cases/aws/parameterstore/provider.go
  57. 1 1
      e2e/suites/provider/cases/aws/secretsmanager/provider.go
  58. 1 1
      e2e/suites/provider/cases/aws/secretsmanager/secretsmanager.go
  59. 2 2
      e2e/suites/provider/cases/aws/secretsmanager/secretsmanager_managed.go
  60. 0 0
      e2e/suites/provider/cases/azure/azure_cert.go
  61. 0 0
      e2e/suites/provider/cases/azure/azure_key.go
  62. 1 1
      e2e/suites/provider/cases/azure/azure_secret.go
  63. 5 3
      e2e/suites/provider/cases/azure/provider.go
  64. 1 1
      e2e/suites/provider/cases/common/common.go
  65. 0 8
      e2e/suites/provider/cases/common/find_by_name.go
  66. 0 8
      e2e/suites/provider/cases/common/find_by_tags.go
  67. 106 0
      e2e/suites/provider/cases/fake/provider.go
  68. 1 1
      e2e/suites/provider/cases/gcp/gcp.go
  69. 1 1
      e2e/suites/provider/cases/gcp/gcp_managed.go
  70. 0 0
      e2e/suites/provider/cases/gcp/provider.go
  71. 1 1
      e2e/suites/provider/cases/gitlab/gitlab.go
  72. 0 0
      e2e/suites/provider/cases/gitlab/provider.go
  73. 6 6
      e2e/suites/provider/cases/import.go
  74. 1 1
      e2e/suites/provider/cases/oracle/oracle.go
  75. 0 0
      e2e/suites/provider/cases/oracle/provider.go
  76. 0 0
      e2e/suites/provider/cases/template/provider.go
  77. 0 0
      e2e/suites/provider/cases/template/template.go
  78. 3 2
      e2e/suites/provider/cases/vault/provider.go
  79. 1 1
      e2e/suites/provider/cases/vault/vault.go
  80. 8 4
      e2e/suites/provider/e2e_test.go
  81. 87 13
      go.mod
  82. 460 10
      go.sum
  83. 2 0
      hack/api-docs/mkdocs.yml
  84. 13 15
      pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller.go
  85. 42 1
      pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller_test.go
  86. 74 18
      pkg/provider/azure/keyvault/keyvault.go
  87. 197 0
      pkg/provider/azure/keyvault/keyvault_test.go
  88. 44 0
      pkg/provider/ibm/provider.go
  89. 62 97
      pkg/provider/ibm/provider_test.go

+ 6 - 10
.github/PAUL.yaml

@@ -1,19 +1,15 @@
 maintainers:
 - knelasevero
 - gusfcarvalho
+- moolen
 - sebagomez
-- serdarkalayci
+- rodrmartinez
+# Emeritus Approvers
+- Flydiverny
+- silasbw
+- mcavoyk
 - riccardomc
-- iamcaleberic
 - jonatasbaldin
-- mircea-cosbuc
-- mcavoyk
-- moolen
-- silasbw
-- Flydiverny
-- gabibeyer
-- ricardoptcosta
-- rodrmartinez
 # Allows for the /label and /remove-label commands
 # usage: /label enhancement
 # usage: /remove-label enhancement

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

@@ -191,8 +191,8 @@ jobs:
       run: |
         export PATH=$PATH:$(go env GOPATH)/bin
         PROVIDER=${{github.event.client_payload.slash_command.args.named.provider}}
-        go get github.com/onsi/ginkgo/v2/ginkgo
-        make test.e2e.managed GINKGO_LABELS="${PROVIDER}"
+        go install -mod=mod github.com/onsi/ginkgo/v2/ginkgo
+        make test.e2e.managed GINKGO_LABELS="${PROVIDER}" TEST_SUITES="provider"
 
     - name: Destroy TF
       if: always()

+ 3 - 1
.gitignore

@@ -20,7 +20,9 @@ deploy/charts/external-secrets/templates/crds/*.yaml
 
 site/
 e2e/k8s/deploy
-e2e/e2e.test
+e2e/suites/argocd/argocd.test
+e2e/suites/flux/flux.test
+e2e/suites/provider/provider.test
 
 # tf ignores
 # Local .terraform directories

+ 1 - 0
ADOPTERS.md

@@ -10,6 +10,7 @@
 - [Form3](https://www.form3.tech/)
 - [Pier Insurance](https://www.pier.digital/)
 - [Heureka Group](https://heureka.group)
+- [OpenClassrooms](https://openclassrooms.com)
 
 
 Countless others that can't disclose that information! :)

+ 5 - 5
Makefile

@@ -82,7 +82,7 @@ license.check: golicenses.check
 	 grep -v -E '${LICENSES}' | \
 	 tr "," " " | awk '{print "Invalid License " $$3 " for dependency " $$1 }'|| ok=1; \
 	 if [[ $$ok -eq 1 ]]; then $(OK) dependencies are compliant; else $(FAIL); fi
-	 
+
 check-diff: reviewable ## Ensure branch is clean.
 	@$(INFO) checking that branch is clean
 	@test -z "$$(git status --porcelain)" || (echo "$$(git status --porcelain)" && $(FAIL))
@@ -207,12 +207,12 @@ helm.generate:
 		echo "{{- end }}" >> "$$i" && \
 		rm "$$i.bkp" && \
 		if [[ "$$OSTYPE" == "darwin"* ]]; then \
-		  export SED_I="gsed -i"; \
+		  SEDPRG="gsed"; \
 		else \
-		  export SED_I="sed -i"; \
+		  SEDPRG="sed"; \
 		fi; \
-		$$SED_I 's/name: kubernetes/name: {{ include "external-secrets.fullname" . }}-webhook/g' "$$i" && \
-		$$SED_I 's/namespace: default/namespace: {{ .Release.Namespace | quote }}/g' "$$i" && \
+		$$SEDPRG -i 's/name: kubernetes/name: {{ include "external-secrets.fullname" . }}-webhook/g' "$$i" && \
+		$$SEDPRG -i 's/namespace: default/namespace: {{ .Release.Namespace | quote }}/g' "$$i" && \
 		mv "$$i" "$${i%.yml}.yaml"; \
 	done
 	@$(OK) Finished generating helm chart files

+ 14 - 2
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -166,20 +166,31 @@ type ExternalSecretDataRemoteRef struct {
 	// Key is the key used in the Provider, mandatory
 	Key string `json:"key"`
 
-	// Used to select a specific version of the Provider value, if supported
 	// +optional
-	Version string `json:"version,omitempty"`
+	// Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+	MetadataPolicy ExternalSecretMetadataPolicy `json:"metadataPolicy,omitempty"`
 
 	// +optional
 	// Used to select a specific property of the Provider value (if a map), if supported
 	Property string `json:"property,omitempty"`
 
+	// +optional
+	// Used to select a specific version of the Provider value, if supported
+	Version string `json:"version,omitempty"`
+
 	// +optional
 	// Used to define a conversion Strategy
 	// +kubebuilder:default="Default"
 	ConversionStrategy ExternalSecretConversionStrategy `json:"conversionStrategy,omitempty"`
 }
 
+type ExternalSecretMetadataPolicy string
+
+const (
+	ExternalSecretMetadataPolicyNone  ExternalSecretMetadataPolicy = "None"
+	ExternalSecretMetadataPolicyFetch ExternalSecretMetadataPolicy = "Fetch"
+)
+
 type ExternalSecretConversionStrategy string
 
 const (
@@ -209,6 +220,7 @@ type ExternalSecretFind struct {
 	// Find secrets based on tags.
 	// +optional
 	Tags map[string]string `json:"tags,omitempty"`
+
 	// +optional
 	// Used to define a conversion Strategy
 	// +kubebuilder:default="Default"

+ 10 - 0
config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml

@@ -65,6 +65,11 @@ spec:
                             key:
                               description: Key is the key used in the Provider, mandatory
                               type: string
+                            metadataPolicy:
+                              description: Policy for fetching tags/labels from provider
+                                secrets, possible options are Fetch, None. Defaults
+                                to None
+                              type: string
                             property:
                               description: Used to select a specific property of the
                                 Provider value (if a map), if supported
@@ -102,6 +107,11 @@ spec:
                             key:
                               description: Key is the key used in the Provider, mandatory
                               type: string
+                            metadataPolicy:
+                              description: Policy for fetching tags/labels from provider
+                                secrets, possible options are Fetch, None. Defaults
+                                to None
+                              type: string
                             property:
                               description: Used to select a specific property of the
                                 Provider value (if a map), if supported

+ 10 - 0
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -310,6 +310,11 @@ spec:
                         key:
                           description: Key is the key used in the Provider, mandatory
                           type: string
+                        metadataPolicy:
+                          description: Policy for fetching tags/labels from provider
+                            secrets, possible options are Fetch, None. Defaults to
+                            None
+                          type: string
                         property:
                           description: Used to select a specific property of the Provider
                             value (if a map), if supported
@@ -347,6 +352,11 @@ spec:
                         key:
                           description: Key is the key used in the Provider, mandatory
                           type: string
+                        metadataPolicy:
+                          description: Policy for fetching tags/labels from provider
+                            secrets, possible options are Fetch, None. Defaults to
+                            None
+                          type: string
                         property:
                           description: Used to select a specific property of the Provider
                             value (if a map), if supported

+ 10 - 0
deploy/charts/external-secrets/README.md

@@ -37,6 +37,7 @@ The command removes all the Kubernetes components associated with the chart and
 | affinity | object | `{}` |  |
 | certController.affinity | object | `{}` |  |
 | certController.create | bool | `true` | Specifies whether a certificate controller deployment be created. |
+| certController.replicaCount | int | `1` |  |
 | certController.deploymentAnnotations | object | `{}` | Annotations to add to Deployment |
 | certController.extraArgs | object | `{}` |  |
 | certController.extraEnv | list | `[]` |  |
@@ -51,6 +52,9 @@ The command removes all the Kubernetes components associated with the chart and
 | certController.podLabels | object | `{}` |  |
 | certController.podSecurityContext | object | `{}` |  |
 | certController.priorityClassName | string | `""` | Pod priority class name. |
+| certController.podDisruptionBudget.enabled | bool | `false` | Enable Pod disruption budget. |
+| certController.podDisruptionBudget.minAvailable | int | `1` | Specifies min available pods. |
+| certController.podDisruptionBudget.maxUnavailable | int | `0` | Specifies max unavailable pods. |
 | 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. |
@@ -86,6 +90,9 @@ The command removes all the Kubernetes components associated with the chart and
 | podLabels | object | `{}` |  |
 | podSecurityContext | object | `{}` |  |
 | priorityClassName | string | `""` | Pod priority class name. |
+| podDisruptionBudget.enabled | bool | `false` | Enable Pod disruption budget. |
+| podDisruptionBudget.minAvailable | int | `1` | Specifies min available pods. |
+| podDisruptionBudget.maxUnavailable | int | `0` | Specifies max unavailable pods. |
 | 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. |
 | prometheus.enabled | bool | `false` | deprecated. will be removed with 0.7.0, use serviceMonitor instead. |
@@ -125,6 +132,9 @@ The command removes all the Kubernetes components associated with the chart and
 | webhook.podSecurityContext | object | `{}` |  |
 | webhook.port | int | `10250` | The port the webhook will listen to |
 | webhook.priorityClassName | string | `""` | Pod priority class name. |
+| webhook.podDisruptionBudget.enabled | bool | `false` | Enable Pod disruption budget. |
+| webhook.podDisruptionBudget.minAvailable | int | `1` | Specifies min available pods. |
+| webhook.podDisruptionBudget.maxUnavailable | int | `0` | Specifies max unavailable pods. |
 | webhook.prometheus.enabled | bool | `false` | deprecated. will be removed with 0.7.0, use serviceMonitor instead |
 | webhook.prometheus.service.port | int | `8080` | deprecated. will be removed with 0.7.0, use serviceMonitor instead |
 | webhook.rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. |

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

@@ -11,7 +11,7 @@ metadata:
     {{- toYaml . | nindent 4 }}
   {{- end }}
 spec:
-  replicas: 1
+  replicas: {{ .Values.certController.replicaCount }}
   selector:
     matchLabels:
       {{- include "external-secrets-cert-controller.selectorLabels" . | nindent 6 }}

+ 19 - 0
deploy/charts/external-secrets/templates/cert-controller-poddisruptionbudget.yaml

@@ -0,0 +1,19 @@
+{{- if and .Values.certController.create .Values.certController.podDisruptionBudget.enabled }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: {{ include "external-secrets.fullname" . }}-cert-controller-pdb
+  namespace: {{ .Release.Namespace | quote }}
+  labels:
+    {{- include "external-secrets-cert-controller.labels" . | nindent 4 }}
+spec:
+  {{- if .Values.certController.podDisruptionBudget.minAvailable }}
+  minAvailable: {{ .Values.certController.podDisruptionBudget.minAvailable }}
+  {{- end }}
+  {{- if .Values.certController.podDisruptionBudget.maxUnavailable }}
+  maxUnavailable: {{ .Values.certController.podDisruptionBudget.maxUnavailable }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "external-secrets-cert-controller.selectorLabels" . | nindent 6 }}
+{{- end }}

+ 19 - 0
deploy/charts/external-secrets/templates/poddisruptionbudget.yaml

@@ -0,0 +1,19 @@
+{{- if .Values.podDisruptionBudget.enabled }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: {{ include "external-secrets.fullname" . }}-pdb
+  namespace: {{ .Release.Namespace | quote }}
+  labels:
+    {{- include "external-secrets.labels" . | nindent 4 }}
+spec:
+  {{- if .Values.podDisruptionBudget.minAvailable }}
+  minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
+  {{- end }}
+  {{- if .Values.podDisruptionBudget.maxUnavailable }}
+  maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "external-secrets.selectorLabels" . | nindent 6 }}
+{{- end }}

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

@@ -0,0 +1,20 @@
+{{- if and .Values.webhook.create .Values.webhook.podDisruptionBudget.enabled }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: {{ include "external-secrets.fullname" . }}-webhook-pdb
+  namespace: {{ .Release.Namespace | quote }}
+  labels:
+    {{- include "external-secrets-webhook.labels" . | nindent 4 }}
+    external-secrets.io/component : webhook
+spec:
+  {{- if .Values.webhook.podDisruptionBudget.minAvailable }}
+  minAvailable: {{ .Values.webhook.podDisruptionBudget.minAvailable }}
+  {{- end }}
+  {{- if .Values.webhook.podDisruptionBudget.maxUnavailable }}
+  maxUnavailable: {{ .Values.webhook.podDisruptionBudget.maxUnavailable }}
+  {{- end }}
+  selector:
+    matchLabels:
+      {{- include "external-secrets-webhook.selectorLabels" . | nindent 6 }}
+{{- end }}

+ 18 - 0
deploy/charts/external-secrets/values.yaml

@@ -120,6 +120,12 @@ affinity: {}
 # -- Pod priority class name.
 priorityClassName: ""
 
+# -- Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/
+podDisruptionBudget:
+  enabled: false
+  minAvailable: 1
+  # maxUnavailable: 1
+
 webhook:
   # -- Specifies whether a webhook deployment be created.
   create: true
@@ -160,6 +166,11 @@ webhook:
     # -- Pod priority class name.
   priorityClassName: ""
 
+  # -- Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/
+  podDisruptionBudget:
+    enabled: false
+    minAvailable: 1
+    # maxUnavailable: 1
   prometheus:
     # -- deprecated. will be removed with 0.7.0, use serviceMonitor instead
     enabled: false
@@ -217,6 +228,7 @@ certController:
   # -- Specifies whether a certificate controller deployment be created.
   create: true
   requeueInterval: "5m"
+  replicaCount: 1
   image:
     repository: ghcr.io/external-secrets/external-secrets
     pullPolicy: IfNotPresent
@@ -244,6 +256,12 @@ certController:
     # -- Pod priority class name.
   priorityClassName: ""
 
+  # -- Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/
+  podDisruptionBudget:
+    enabled: false
+    minAvailable: 1
+    # maxUnavailable: 1
+
   prometheus:
     # -- deprecated. will be removed with 0.7.0, use serviceMonitor instead
     enabled: false

+ 12 - 0
deploy/crds/bundle.yaml

@@ -55,6 +55,9 @@ spec:
                               key:
                                 description: Key is the key used in the Provider, mandatory
                                 type: string
+                              metadataPolicy:
+                                description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                                type: string
                               property:
                                 description: Used to select a specific property of the Provider value (if a map), if supported
                                 type: string
@@ -87,6 +90,9 @@ spec:
                               key:
                                 description: Key is the key used in the Provider, mandatory
                                 type: string
+                              metadataPolicy:
+                                description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                                type: string
                               property:
                                 description: Used to select a specific property of the Provider value (if a map), if supported
                                 type: string
@@ -2784,6 +2790,9 @@ spec:
                           key:
                             description: Key is the key used in the Provider, mandatory
                             type: string
+                          metadataPolicy:
+                            description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                            type: string
                           property:
                             description: Used to select a specific property of the Provider value (if a map), if supported
                             type: string
@@ -2816,6 +2825,9 @@ spec:
                           key:
                             description: Key is the key used in the Provider, mandatory
                             type: string
+                          metadataPolicy:
+                            description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                            type: string
                           property:
                             description: Used to select a specific property of the Provider value (if a map), if supported
                             type: string

+ 8 - 1
docs/contributing-process.md

@@ -2,6 +2,7 @@
 The Code, our TODOs and Documentation is maintained on
 [GitHub](https://github.com/external-secrets/external-secrets). All Issues
 should be opened in that repository.
+We have a [Roadmap](roadmap.md) to track progress for our road towards GA.
 
 ## Issues
 
@@ -111,6 +112,7 @@ make test.e2e.managed GINKGO_LABELS='gcp'
 Before we introduce significant changes to the project we want to gather feedback
 from the community to ensure that we progress in the right direction before we
 develop and release big changes. Significant changes include for example:
+
 * creating new custom resources
 * proposing breaking changes
 * changing the behavior of the controller significantly
@@ -122,7 +124,11 @@ and fill in your proposal. Open a pull request in draft mode and request feedbac
 
 We have a [GitHub Project Board](https://github.com/orgs/external-secrets/projects/2/views/1) where we organize issues on a high level. We group issues by milestone. Once all issues of a given milestone are closed we should prepare a new feature release. Issues of the next milestone have priority over other issues - but that does not mean that no one is allowed to start working on them.
 
-Issues must be _manually_ added to that board (at least for now, see [GH Roadmap](https://github.com/github/roadmap/issues/286)). Milestones must be assigned manually as well. If no milestone is assigned it is basically a backlog item. It is the responsibility of the maintainers to (1) assign new issues to the GH Project, (2) add a milestone if needed and (3) add appropriate labels.
+Issues must be _manually_ added to that board (at least for now, see [GH Roadmap](https://github.com/github/roadmap/issues/286)). Milestones must be assigned manually as well. If no milestone is assigned it is basically a backlog item. It is the responsibility of the maintainers to:
+
+1. assign new issues to the GH Project
+2. add a milestone if needed
+3. add appropriate labels
 
 If you would like to raise the priority of an issue for whatever reason feel free to comment on the issue or ping a maintainer.
 
@@ -136,6 +142,7 @@ We have three different channels through which support questions arise:
 3. GitHub Issues
 
 We use labels to identify GitHub Issues. Specifically for managing support cases we use the following labels to identify the state a support case is in:
+
 * `triage/needs-information`: Indicates an issue needs more information in order to work on it.
 * `triage/not-reproducible`: Indicates an issue can not be reproduced as described.
 * `triage/support`: Indicates an issue that is a support question.

+ 82 - 0
docs/faq.md

@@ -0,0 +1,82 @@
+## Can i manually trigger a secret refresh?
+
+You can trigger a secret refresh by using kubectl or any other kubernetes api client.
+You just need to change an annotation, label or the spec of the resource:
+
+```
+kubectl annotate es my-es force-sync=$(date +%s) --overwrite
+```
+
+## How do i know when my secret was last synced?
+
+Every ExternalSecret resource contains a status condition that indicates the time when the secret was last synced:
+
+```
+kubectl get es my-external-secret -o yaml | grep condition -A 5
+  conditions:
+  - lastTransitionTime: "2022-05-21T21:02:47Z"
+    message: Secret was synced
+    reason: SecretSynced
+    status: "True"
+    type: Ready
+```
+
+## Differences to csi-secret-store
+Please take a look at this [issue comment here](https://github.com/external-secrets/external-secrets/issues/478#issuecomment-964413129).
+
+## How do i debug an external-secret that doesn't sync?
+
+First, check the status of the ExternalSecret resource using `kubectl describe`. That displays the status conditions as well as recent events.
+You should expect a status condition with `Type=Ready`, `Status=True`. Further you shouldn't see any events with `Type=Warning`. Read carefully if they exist.
+
+```
+kubectl describe es my-external-secret
+[...]
+Status:
+  Conditions:
+    Last Transition Time:   2022-05-21T21:02:47Z
+    Message:                Secret was synced
+    Reason:                 SecretSynced
+    Status:                 True
+    Type:                   Ready
+  Refresh Time:             2022-05-21T21:06:47Z
+  Synced Resource Version:  1-5c833527afd7ba3f426cb0082ee7e083
+Events:
+  Type     Reason        Age                  From              Message
+  ----     ------        ----                 ----              -------
+  Warning  UpdateFailed  4m12s                external-secrets  secrets "yyyyyyy" already exists
+  Normal   Updated       12s (x4 over 3m12s)  external-secrets  Updated Secret
+```
+
+If everything looks good you should check the corresponding secret store resource that is referenced from an ExternalSecret. Again, use `kubectl describe` to show status conditions and events and look for warning signs as described above.
+
+In an ideally, the store should be validated and Ready.
+
+```
+kubectl describe css kubernetes
+[...]
+Status:
+  Conditions:
+    Last Transition Time:  2022-05-21T21:02:47Z
+    Message:               store validated
+    Reason:                Valid
+    Status:                True
+    Type:                  Ready
+Events:
+  Type    Reason  Age                From                  Message
+  ----    ------  ----               ----                  -------
+  Normal  Valid   52s (x4 over 10m)  cluster-secret-store  store validated
+  Normal  Valid   52s (x4 over 10m)  cluster-secret-store  store validated
+```
+
+If everything looks normal so far, please go ahead and ensure that the created secret has the expected value. Also, take a look at the logs of the controller.
+
+## Upgrading from KES to ESO
+
+Migrating from KES to ESO is quite tricky! There is a tool we built to help users out available [here](https://github.com/external-secrets/kes-to-eso), and there is a small migration procedure.
+
+There are some incompatibilities between KES to ESO, and while the tool tries to cover most of them, some of them will require manual intervention. We recommend to first convert the manifest files, and actually see if the tool provides a warning about any file needed to be changed.
+Beware that the tool points the SecretStores to use KES Service Account, so you'll also need to tweak that if you plan to uninstall KES after the upgrade.
+
+
+

BIN
docs/pictures/diagrams-high-level-simple.png


+ 5 - 2
docs/provider-azure-key-vault.md

@@ -96,13 +96,16 @@ To create a kubernetes secret from the Azure Key vault secret a `Kind=ExternalSe
 
 You can manage keys/secrets/certificates saved inside the keyvault , by setting a "/" prefixed type in the secret name , the default type is a `secret`. other supported values are `cert` and `key`
 
-to select all secrets inside the key vault , you can use the `dataFrom` directive
+to select all secrets inside the key vault or all tags inside a secret, you can use the `dataFrom` directive
 
 ```yaml
 {% include 'azkv-external-secret.yaml' %}
 ```
+```yaml
+{% include 'azkv-datafrom-external-secret.yaml' %}
+```
 
 The operator will fetch the Azure Key vault secret and inject it as a `Kind=Secret`
 ```
 kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.dev-secret-test}' | base64 -d
-```
+```

+ 5 - 4
docs/provider-ibm-secrets-manager.md

@@ -51,12 +51,13 @@ See here for a list of [publicly available endpoints](https://cloud.ibm.com/apid
 ### Secret Types
 We support the following secret types of [IBM Secrets Manager](https://cloud.ibm.com/apidocs/secrets-manager):
 
-* `arbitrary` 
+* `arbitrary`
 * `username_password`
 * `iam_credentials`
 * `imported_cert`
 * `public_cert`
-* `kv` 
+* `private_cert`
+* `kv`
 
 To define the type of secret you would like to sync you need to prefix the secret id with the desired type. If the secret type is not specified it is defaulted to `arbitrary`:
 
@@ -80,7 +81,7 @@ The behavior for the different secret types is as following:
 * `remoteRef` retrieves an apikey from secrets manager and sets it for specified `secretKey`
 * `dataFrom` retrieves an apikey from secrets manager and sets it for the `apikey` Kubernetes secret key
 
-#### imported_cert and public_cert
+#### imported_cert, public_cert and private_cert
 * `remoteRef` requires a `property` to be set for either `certificate`, `private_key` or `intermediate` to retrieve respective fields from the secrets manager secret and set in specified `secretKey`
 * `dataFrom` retrieves all `certificate`, `private_key` and `intermediate` fields from the secrets manager secret and sets appropriate key:value pairs in the resulting Kubernetes secret
 
@@ -127,7 +128,7 @@ data:
   key3_keyB: ... #valB
   special_key: ... #special-content
   key_all: ... #{"key1":"val1","key2":"val2", ..."special.key":"special-content"}
-  
+
   # secrets from dataFrom
   keyA: ... #valA
   keyB: ... #valB

+ 15 - 16
docs/provider-kubernetes.md

@@ -12,7 +12,7 @@ It's possible to authenticate against the Kubernetes API using client certificat
 
 1. Create a K8s Secret with a client token for the default service account
 
-```
+```yaml
 apiVersion: v1
 kind: Secret
 metadata:
@@ -27,7 +27,7 @@ The Servers `url` won't be present as it will default to `kubernetes.default`, a
 
 The `auth` section indicates that the type `token` will be used for authentication, it includes the path to fetch the token. Set `remoteNamespace` to the name of the namespace where your target secrets reside.
 
-```
+```yaml
 apiVersion: external-secrets.io/v1beta1
 kind: SecretStore
 metadata:
@@ -47,26 +47,25 @@ spec:
             key: token
       remoteNamespace: default
 ```
-3. Create the local secret that will be synced 
-              
-```
----
+3. Create the local secret that will be synced
+
+```yaml
 apiVersion: v1
 kind: Secret
 metadata:
   name: secret-example
 data:
   extra: YmFyCg==
-```     
+```
 4. Finally create the ExternalSecret resource
 
-```
+```yaml
 apiVersion: external-secrets.io/v1beta1
 kind: ExternalSecret
 metadata:
   name: example
 spec:
-  refreshInterval: 1h           
+  refreshInterval: 1h
   secretStoreRef:
     kind: SecretStore
     name: example               # name of the SecretStore (or kind specified)
@@ -83,8 +82,8 @@ spec:
 ### Remote Secret using a Token
 
 1. Create a K8s Secret with the encoded base64 ca and client token.
-   
-```
+
+```yaml
 apiVersion: v1
 kind: Secret
 metadata:
@@ -98,11 +97,11 @@ stringData:
 ```
 2. Create a SecretStore
 
-The Server section specifies the `url` of the remote Kubernetes API. In this example the Certificate Authority is fetch using the encoded base64 `caBundle`. 
+The Server section specifies the `url` of the remote Kubernetes API. In this example the Certificate Authority is fetch using the encoded base64 `caBundle`.
 
 The `auth` section indicates that the  `token` type will be used for authentication, it includes the path to fetch the token.
 
-```
+```yaml
 apiVersion: external-secrets.io/v1beta1
 kind: SecretStore
 metadata:
@@ -122,16 +121,16 @@ spec:
           bearerToken:
             name: cluster-secrets
             key: bearerToken
-```     
+```
 4. Finally create the ExternalSecret resource
 
-```
+```yaml
 apiVersion: external-secrets.io/v1beta1
 kind: ExternalSecret
 metadata:
   name: example
 spec:
-  refreshInterval: 1h           
+  refreshInterval: 1h
   secretStoreRef:
     kind: SecretStore
     name: example               # name of the SecretStore (or kind specified)

+ 31 - 0
docs/roadmap.md

@@ -0,0 +1,31 @@
+# The road to external-secrets GA
+
+The following external-secret custom resource APIs are considered stable:
+
+* `ExternalSecret`
+* `ClusterExternalSecret`
+* `SecretStore`
+* `ClusterSecretStore`
+
+These CRDs are currently at `v1beta1` and are considered production ready. Going forward, breaking changes to these APIs will be accompanied by a conversion mechanism.
+
+We have identified the following areas of work. This is subject to change while we gather feedback. We have a [GitHub Project Board](https://github.com/orgs/external-secrets/projects/2/views/1) where we organize issues and milestones on a high level.
+
+* Conformance testing
+    * end to end testing with ArgoCD and Flux
+    * end to end testing for all project maintained providers
+* API enhancements
+    * consolidate provider fields
+    * dataFrom key rewrites
+    * provider versioning strategy
+    * pushing secrets to a provider
+* Documentation Improvements
+    * Troubleshooting Guides
+    * FAQ
+    * review multi tenancy docs
+    * provide security model for infosec teams
+    * provider specific guides
+* Observability
+    * Provide Grafana Dashboard and Prometheus alerts
+    * add provider-level metrics
+* Pentest & SBOM

+ 27 - 0
docs/snippets/azkv-datafrom-external-secret.yaml

@@ -0,0 +1,27 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: example
+spec:
+  refreshInterval: 1h           # rate SecretManager pulls Azure Key Vault
+  secretStoreRef:
+    kind: SecretStore
+    name: example               # name of the SecretStore (or kind specified)
+  target:
+    name: secret-to-be-created  # name of the k8s Secret to be created
+    creationPolicy: Owner
+  dataFrom:
+  - find:
+      name:
+        regexp: "^example"
+  - find:
+      tags:
+        author: seb
+        environment: dev
+  # secret value is in JSON format and we unmarshall it into multiple key/values in k8s secret
+  - extract: 
+      key: test
+  # get all tags and the tags in JSON format will be unmarshall 
+  - extract: 
+      key: test
+      metadataPolicy: Fetch

+ 14 - 2
docs/snippets/azkv-external-secret.yaml

@@ -23,6 +23,19 @@ spec:
     remoteRef:
       key: secret/dev-secret-test
 
+  # metadataPolicy to fetch all the tags in JSON format
+  - secretKey: dev-secret-test
+    remoteRef:
+      key: dev-secret-test
+      metadataPolicy: Fetch
+
+  # metadataPolicy to fetch a specific tag which name must be in property
+  - secretKey: dev-secret-test
+    remoteRef:
+      key: dev-secret-test
+      metadataPolicy: Fetch
+      property: tagname
+
   # type/name of certificate in the Azure KV
   # raw value will be returned, use templating features for data processing
   - secretKey: dev-cert-test
@@ -33,5 +46,4 @@ spec:
   # the key is returned PEM encoded
   - secretKey: dev-key-test
     remoteRef:
-      key: key/dev-key-test
-
+      key: key/dev-key-test

+ 9 - 5
docs/snippets/ibm-es-types.yaml

@@ -9,21 +9,25 @@ spec:
     remoteRef:
       # defaults to type=arbitrary
       key: xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
-  - secretKey: foo
+  - secretKey: usr_pass
     remoteRef:
       key: username_password/yyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
       property: username
-  - secretKey: bar
+  - secretKey: iam_cred
     remoteRef:
       key: iam_credentials/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
-  - secretKey: baz
+  - secretKey: imp_cert
     remoteRef:
       key: imported_cert/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
       property: certificate
-  - secretKey: bap
+  - secretKey: pub_cert
     remoteRef:
       key: public_cert/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
       property: certificate
+  - secretKey: prvt_cert
+      remoteRef:
+        key: private_cert/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
+        property: certificate
   - secretKey: kv_without_key
     remoteRef:
       key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
@@ -36,4 +40,4 @@ spec:
       key: kv/zzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
       property: 'key.path'
   dataFrom:
-  
+

+ 7 - 6
docs/stability-support.md

@@ -6,9 +6,9 @@ We are currently in beta and support **only the latest release** for the time be
 
 | ESO Version | Kubernetes Version |
 | ----------- | ------------------ |
-| 0.5.x       | 1.19 → 1.23        |
-| 0.4.x       | 1.16 → 1.23        |
-| 0.3.x       | 1.16 → 1.23        |
+| 0.5.x       | 1.19 → 1.24        |
+| 0.4.x       | 1.16 → 1.24        |
+| 0.3.x       | 1.16 → 1.24        |
 
 ## Provider Stability and Support Level
 
@@ -20,7 +20,8 @@ The following table describes the stability level of each provider and who's res
 | [AWS Parameter Store](https://external-secrets.io/latest/provider-aws-parameter-store/)                    |  stable   |                                                                                                                   [external-secrets](https://github.com/external-secrets) |
 | [Hashicorp Vault](https://external-secrets.io/latest/provider-hashicorp-vault/)                            |  stable   |                                                                                                                   [external-secrets](https://github.com/external-secrets) |
 | [GCP Secret Manager](https://external-secrets.io/latest/provider-google-secrets-manager/)                  |  stable   |                                                                                                                   [external-secrets](https://github.com/external-secrets) |
-| [Azure Keyvault](https://external-secrets.io/latest/provider-azure-key-vault/)                             |   beta    | [@ahmedmus-1A](https://github.com/ahmedmus-1A) [@asnowfix](https://github.com/asnowfix) [@ncourbet-1A](https://github.com/ncourbet-1A) [@1A-mj](https://github.com/1A-mj) |
+| [Azure Keyvault](https://external-secrets.io/latest/provider-azure-key-vault/)                             |   stable    | [external-secrets](https://github.com/external-secrets) |
+| [Kubernetes](https://external-secrets.io/latest/provider-kubernetes) |   alpha   |                                                                                                                                      [external-secrets](https://github.com/external-secrets) |
 | [IBM Secrets Manager](https://external-secrets.io/latest/provider-ibm-secrets-manager/)                    |   alpha   |                            [@knelasevero](https://github.com/knelasevero) [@sebagomez](https://github.com/sebagomez) [@ricardoptcosta](https://github.com/ricardoptcosta) |
 | [Yandex Lockbox](https://external-secrets.io/latest/provider-yandex-lockbox/)                              |   alpha   |                                                                       [@AndreyZamyslov](https://github.com/AndreyZamyslov) [@knelasevero](https://github.com/knelasevero) |
 | [Gitlab Project Variables](https://external-secrets.io/latest/provider-gitlab-project-variables/)          |   alpha   |                                                                                                                                    [@Jabray5](https://github.com/Jabray5) |
@@ -30,14 +31,14 @@ The following table describes the stability level of each provider and who's res
 | [1Password](https://external-secrets.io/latest/provider-1password-automation)                              |   alpha   |                                                                         [@SimSpaceCorp](https://github.com/Simspace) [@snarlysodboxer](https://github.com/snarlysodboxer) |
 | [Generic Webhook](https://external-secrets.io/latest/provider-webhook)                                     |   alpha   |                                                                                                                                    [@willemm](https://github.com/willemm) |
 | [senhasegura DevOps Secrets Management (DSM)](https://external-secrets.io/latest/provider-senhasegura-dsm) |   alpha   |                                                                                                                                      [@lfraga](https://github.com/lfraga) |
-| [Kubernetes](https://external-secrets.io/latest/provider-kubernetes) |   alpha   |                                                                                                                                      [@rodrmartinez](https://github.com/rodrmartinez) |
 
 ## Support Policy
 
-We provide technical support and provide security & bug fixes for the above listed versions.
+We provide technical support and security / bug fixes for the above listed versions.
 
 ### Technical support
 We provide assistance for deploying/upgrading etc. on a best-effort basis. You can request support through the following channels:
+
 * [Kubernetes Slack
   #external-secrets](https://kubernetes.slack.com/messages/external-secrets)
 * GitHub [Issues](https://github.com/external-secrets/external-secrets/issues)

+ 4 - 2
e2e/Dockerfile

@@ -1,7 +1,7 @@
 ARG GO_VERSION=1.17
 FROM golang:$GO_VERSION-buster as builder
 
-ENV KUBECTL_VERSION="v1.21.2"
+ENV KUBECTL_VERSION="v1.23.6"
 ENV HELM_VERSION="v3.7.1"
 
 RUN go get -u github.com/onsi/ginkgo/v2/ginkgo
@@ -24,7 +24,9 @@ COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/
 COPY --from=builder /usr/local/bin/helm /usr/local/bin/
 
 COPY entrypoint.sh                  /entrypoint.sh
-COPY e2e.test                       /e2e.test
+COPY suites/provider/provider.test  /provider.test
+COPY suites/argocd/argocd.test      /argocd.test
+COPY suites/flux/flux.test          /flux.test
 COPY k8s                            /k8s
 
 CMD [ "/entrypoint.sh" ]

+ 2 - 1
e2e/Makefile

@@ -7,6 +7,7 @@ BUILD_ARGS     ?=
 
 export E2E_IMAGE_REGISTRY ?= ghcr.io/external-secrets/external-secrets-e2e
 export GINKGO_LABELS ?= !managed
+export TEST_SUITES ?= provider flux argocd
 
 start-kind: ## Start kind cluster
 	kind create cluster \
@@ -38,7 +39,7 @@ test.managed: e2e-image ## Run e2e tests against current kube context
 	./run.sh
 
 e2e-bin:
-	CGO_ENABLED=0 go run github.com/onsi/ginkgo/v2/ginkgo build .
+	CGO_ENABLED=0 go run github.com/onsi/ginkgo/v2/ginkgo build ./suites/...
 
 e2e-image: e2e-bin
 	-rm -rf ./k8s/deploy

+ 7 - 6
e2e/entrypoint.sh

@@ -41,10 +41,11 @@ ginkgo_args=(
   "-timeout=45m"
 )
 
-kubectl apply -f /k8s/deploy/crds
+for SUITE in ${TEST_SUITES}; do
+  echo -e "${BGREEN}Running suite ${SUITE} (LABELS=${GINKGO_LABELS})...${NC}"
+  ACK_GINKGO_RC=true ginkgo "${ginkgo_args[@]}" \
+    -label-filter="${GINKGO_LABELS}"            \
+    -nodes="${E2E_NODES}"                       \
+    /${SUITE}.test
+done
 
-echo -e "${BGREEN}Running e2e test suite (LABELS=${GINKGO_LABELS})...${NC}"
-ACK_GINKGO_RC=true ginkgo "${ginkgo_args[@]}" \
-  -label-filter="${GINKGO_LABELS}"            \
-  -nodes="${E2E_NODES}"                       \
-  /e2e.test

+ 1 - 1
e2e/framework/addon/addon.go

@@ -50,7 +50,7 @@ type Addon interface {
 func InstallGlobalAddon(addon Addon, cfg *Config) {
 	globalAddons = append(globalAddons, addon)
 
-	ginkgo.By("installing addon")
+	ginkgo.By("installing global addon")
 	err := addon.Setup(cfg)
 	gomega.Expect(err).NotTo(gomega.HaveOccurred())
 

+ 12 - 0
e2e/framework/addon/chart.go

@@ -33,6 +33,7 @@ type HelmChart struct {
 	Repo         ChartRepo
 	Vars         []StringTuple
 	Values       []string
+	Args         []string
 
 	config *Config
 }
@@ -81,6 +82,8 @@ func (c *HelmChart) Install() error {
 		args = append(args, "--set", fmt.Sprintf("%s=%s", s.Key, s.Value))
 	}
 
+	args = append(args, c.Args...)
+
 	var sout, serr bytes.Buffer
 	log.Logf("installing chart with args: %+q", args)
 	cmd := exec.Command("helm", args...)
@@ -174,3 +177,12 @@ func (c *HelmChart) Logs() error {
 	}
 	return nil
 }
+
+func (c *HelmChart) HasVar(key, value string) bool {
+	for _, v := range c.Vars {
+		if v.Key == key && v.Value == value {
+			return true
+		}
+	}
+	return false
+}

+ 28 - 1
e2e/framework/addon/eso.go

@@ -24,6 +24,8 @@ type ESO struct {
 	*HelmChart
 }
 
+const installCRDsVar = "installCRDs"
+
 func NewESO(mutators ...MutationFunc) *ESO {
 	eso := &ESO{
 		&HelmChart{
@@ -60,7 +62,7 @@ func NewESO(mutators ...MutationFunc) *ESO {
 					Value: os.Getenv("VERSION"),
 				},
 				{
-					Key:   "installCRDs",
+					Key:   installCRDsVar,
 					Value: "false",
 				},
 			},
@@ -139,6 +141,19 @@ func WithControllerClass(class string) MutationFunc {
 	}
 }
 
+// By default ESO is installed without CRDs
+// when using WithCRDs() the CRDs will be installed before
+// and uninstalled after use.
+func WithCRDs() MutationFunc {
+	return func(eso *ESO) {
+		for i, v := range eso.HelmChart.Vars {
+			if v.Key == installCRDsVar {
+				eso.HelmChart.Vars[i].Value = "true"
+			}
+		}
+	}
+}
+
 func (l *ESO) Install() error {
 	By("Installing eso\n")
 	err := l.HelmChart.Install()
@@ -148,3 +163,15 @@ func (l *ESO) Install() error {
 
 	return nil
 }
+
+func (l *ESO) Uninstall() error {
+	By("Uninstalling eso")
+	err := l.HelmChart.Uninstall()
+	if err != nil {
+		return err
+	}
+	if l.HelmChart.HasVar(installCRDsVar, "true") {
+		return uninstallCRDs(l.config)
+	}
+	return nil
+}

+ 144 - 0
e2e/framework/addon/eso_argocd_application.go

@@ -0,0 +1,144 @@
+/*
+Copyright 2020 The cert-manager Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package addon
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"net/http"
+	"time"
+
+	argoapp "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	"github.com/onsi/ginkgo/v2"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
+)
+
+// HelmChart installs the specified Chart into the cluster.
+type ArgoCDApplication struct {
+	Name                 string
+	Namespace            string
+	DestinationNamespace string
+	HelmChart            string
+	HelmRepo             string
+	HelmRevision         string
+	HelmValues           string
+
+	config *Config
+}
+
+// Setup stores the config in an internal field
+// to get access to the k8s api in orderto fetch logs.
+func (c *ArgoCDApplication) Setup(cfg *Config) error {
+	c.config = cfg
+	return nil
+}
+
+// Install adds the chart repo and installs the helm chart.
+func (c *ArgoCDApplication) Install() error {
+	app := &argoapp.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      c.Name,
+			Namespace: c.Namespace,
+			Annotations: map[string]string{
+				"argocd.argoproj.io/refresh": "hard",
+			},
+		},
+		Spec: argoapp.ApplicationSpec{
+			Project: "default",
+			SyncPolicy: &argoapp.SyncPolicy{
+				Automated: &argoapp.SyncPolicyAutomated{
+					Prune:    true,
+					SelfHeal: true,
+				},
+				SyncOptions: argoapp.SyncOptions{
+					"CreateNamespace=true",
+				},
+			},
+			Source: argoapp.ApplicationSource{
+				Chart:          c.HelmChart,
+				RepoURL:        c.HelmRepo,
+				TargetRevision: c.HelmRevision,
+				Helm: &argoapp.ApplicationSourceHelm{
+					ReleaseName: c.Name,
+					Values:      c.HelmValues,
+				},
+			},
+			Destination: argoapp.ApplicationDestination{
+				Server:    "https://kubernetes.default.svc",
+				Namespace: c.DestinationNamespace,
+			},
+		},
+	}
+	err := c.config.CRClient.Create(context.Background(), app)
+	if err != nil {
+		return err
+	}
+
+	// wait for app to become ready
+	err = wait.PollImmediate(time.Second*5, time.Minute*10, func() (bool, error) {
+		var app argoapp.Application
+		err := c.config.CRClient.Get(context.Background(), types.NamespacedName{
+			Name:      c.Name,
+			Namespace: c.Namespace,
+		}, &app)
+		if err != nil {
+			return false, nil
+		}
+		return app.Status.Sync.Status == argoapp.SyncStatusCodeSynced, nil
+	})
+	if err != nil {
+		return err
+	}
+
+	// we have to wait for the webhook to become ready
+	tr := &http.Transport{
+		// nolint:gosec
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	}
+	client := &http.Client{Transport: tr}
+	return wait.PollImmediate(time.Second, time.Minute*5, func() (bool, error) {
+		const payload = `{"apiVersion": "apiextensions.k8s.io/v1","kind": "ConversionReview","request": {}}`
+		res, err := client.Post("https://external-secrets-webhook.external-secrets.svc.cluster.local/convert", "application/json", bytes.NewBufferString(payload))
+		if err != nil {
+			return false, nil
+		}
+		defer res.Body.Close()
+		ginkgo.GinkgoWriter.Printf("conversion res: %d", res.StatusCode)
+		return res.StatusCode == http.StatusOK, nil
+	})
+}
+
+// Uninstall removes the chart aswell as the repo.
+func (c *ArgoCDApplication) Uninstall() error {
+	err := c.config.CRClient.Delete(context.Background(), &argoapp.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      c.Name,
+			Namespace: c.Namespace,
+		},
+	})
+	if err != nil {
+		return err
+	}
+	err = uninstallCRDs(c.config)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (c *ArgoCDApplication) Logs() error {
+	return nil
+}

+ 166 - 0
e2e/framework/addon/eso_flux_helm.go

@@ -0,0 +1,166 @@
+/*
+Copyright 2020 The cert-manager Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package addon
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"net/http"
+	"time"
+
+	fluxhelm "github.com/fluxcd/helm-controller/api/v2beta1"
+	"github.com/fluxcd/pkg/apis/meta"
+	fluxsrc "github.com/fluxcd/source-controller/api/v1beta2"
+	"github.com/onsi/ginkgo/v2"
+	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
+)
+
+const fluxNamespace = "flux-system"
+
+// HelmChart installs the specified Chart into the cluster.
+type FluxHelmRelease struct {
+	Name            string
+	Namespace       string
+	TargetNamespace string
+	HelmChart       string
+	HelmRepo        string
+	HelmRevision    string
+	HelmValues      string
+
+	config *Config
+}
+
+// Setup stores the config in an internal field
+// to get access to the k8s api in orderto fetch logs.
+func (c *FluxHelmRelease) Setup(cfg *Config) error {
+	c.config = cfg
+	return nil
+}
+
+// Install adds the chart repo and installs the helm chart.
+func (c *FluxHelmRelease) Install() error {
+	app := &fluxsrc.HelmRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      c.Name,
+			Namespace: fluxNamespace,
+		},
+		Spec: fluxsrc.HelmRepositorySpec{
+			URL: c.HelmRepo,
+		},
+	}
+	err := c.config.CRClient.Create(context.Background(), app)
+	if err != nil {
+		return err
+	}
+
+	hr := &fluxhelm.HelmRelease{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      c.Name,
+			Namespace: c.Namespace,
+		},
+		Spec: fluxhelm.HelmReleaseSpec{
+			ReleaseName:     c.Name,
+			TargetNamespace: c.TargetNamespace,
+			Values: &v1.JSON{
+				Raw: []byte(c.HelmValues),
+			},
+			Install: &fluxhelm.Install{
+				CreateNamespace: true,
+				Remediation: &fluxhelm.InstallRemediation{
+					Retries: -1,
+				},
+			},
+			Chart: fluxhelm.HelmChartTemplate{
+				Spec: fluxhelm.HelmChartTemplateSpec{
+					Version: c.HelmRevision,
+					Chart:   c.HelmChart,
+					SourceRef: fluxhelm.CrossNamespaceObjectReference{
+						Kind:      "HelmRepository",
+						Name:      c.Name,
+						Namespace: fluxNamespace,
+					},
+				},
+			},
+		},
+	}
+	err = c.config.CRClient.Create(context.Background(), hr)
+	if err != nil {
+		return err
+	}
+
+	// wait for app to become ready
+	err = wait.PollImmediate(time.Second*5, time.Minute*3, func() (bool, error) {
+		var hr fluxhelm.HelmRelease
+		err := c.config.CRClient.Get(context.Background(), types.NamespacedName{
+			Name:      c.Name,
+			Namespace: c.Namespace,
+		}, &hr)
+		if err != nil {
+			return false, nil
+		}
+		for _, cond := range hr.GetConditions() {
+			ginkgo.GinkgoWriter.Printf("check condition: %s=%s: %s\n", cond.Type, cond.Status, cond.Message)
+			if cond.Type == meta.ReadyCondition && cond.Status == metav1.ConditionTrue {
+				return true, nil
+			}
+		}
+		return false, nil
+	})
+	if err != nil {
+		return err
+	}
+
+	// we have to wait for the webhook to become ready
+	tr := &http.Transport{
+		// nolint:gosec
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	}
+	client := &http.Client{Transport: tr}
+	return wait.PollImmediate(time.Second, time.Minute*5, func() (bool, error) {
+		const payload = `{"apiVersion": "apiextensions.k8s.io/v1","kind": "ConversionReview","request": {}}`
+		res, err := client.Post("https://external-secrets-webhook.external-secrets.svc.cluster.local/convert", "application/json", bytes.NewBufferString(payload))
+		if err != nil {
+			return false, nil
+		}
+		defer res.Body.Close()
+		ginkgo.GinkgoWriter.Printf("conversion res: %d", res.StatusCode)
+		return res.StatusCode == http.StatusOK, nil
+	})
+}
+
+// Uninstall removes the chart aswell as the repo.
+func (c *FluxHelmRelease) Uninstall() error {
+	err := c.config.CRClient.Delete(context.Background(), &fluxhelm.HelmRelease{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      c.Name,
+			Namespace: c.Namespace,
+		},
+	})
+	if err != nil {
+		return err
+	}
+	return c.config.CRClient.Delete(context.Background(), &fluxsrc.HelmRepository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      c.Name,
+			Namespace: fluxNamespace,
+		},
+	})
+}
+
+func (c *FluxHelmRelease) Logs() error {
+	return nil
+}

+ 99 - 0
e2e/framework/addon/helmserver.go

@@ -0,0 +1,99 @@
+/*
+Copyright 2020 The cert-manager Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package addon
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"os"
+	"os/exec"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+type HelmServer struct {
+	ChartDir      string
+	ChartRevision string
+
+	config   *Config
+	srv      *http.Server
+	serveDir string
+}
+
+func (s *HelmServer) Setup(config *Config) error {
+	s.config = config
+	var err error
+	s.serveDir, err = os.MkdirTemp("", "e2e-helm")
+	if err != nil {
+		return err
+	}
+
+	// nolint:gosec
+	cmd := exec.Command("helm", "package", s.ChartDir, "--version", s.ChartRevision)
+	cmd.Dir = s.serveDir
+	out, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("unable to package helm chart: %w %s", err, string(out))
+	}
+
+	cmd = exec.Command("helm", "repo", "index", ".")
+	cmd.Dir = s.serveDir
+	out, err = cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("unable to create helm index: %w %s", err, string(out))
+	}
+
+	_, err = s.config.KubeClientSet.CoreV1().Services("default").Create(context.Background(), &v1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "e2e-helmserver",
+		},
+		Spec: v1.ServiceSpec{
+			Selector: map[string]string{
+				// set via e2e/run.sh
+				"app": "eso-e2e",
+			},
+			Ports: []v1.ServicePort{
+				{
+					Name:       "http",
+					Port:       80,
+					TargetPort: intstr.FromInt(3000),
+				},
+			}},
+	}, metav1.CreateOptions{})
+	return err
+}
+
+func (s *HelmServer) Install() error {
+	fs := http.FileServer(http.Dir(s.serveDir))
+	http.Handle("/", fs)
+	s.srv = &http.Server{Addr: ":3000"}
+	go func() {
+		_ = s.srv.ListenAndServe()
+	}()
+	return nil
+}
+
+func (s *HelmServer) Logs() error {
+	return nil
+}
+
+func (s *HelmServer) Uninstall() error {
+	err := s.config.KubeClientSet.CoreV1().Services("default").Delete(context.Background(), "e2e-helmserver", metav1.DeleteOptions{})
+	if err != nil {
+		return err
+	}
+	return s.srv.Close()
+}

+ 45 - 0
e2e/framework/addon/uninstall_eso_crds.go

@@ -0,0 +1,45 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package addon
+
+import (
+	"context"
+
+	"github.com/onsi/ginkgo/v2"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func uninstallCRDs(cfg *Config) error {
+	ginkgo.By("Uninstalling eso CRDs")
+	for _, crdName := range []string{
+		"clusterexternalsecrets.external-secrets.io",
+		"clustersecretstores.external-secrets.io",
+		"externalsecrets.external-secrets.io",
+		"secretstores.external-secrets.io",
+	} {
+		crd := &apiextensionsv1.CustomResourceDefinition{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: crdName,
+			},
+		}
+		err := cfg.CRClient.Delete(context.Background(), crd, &client.DeleteOptions{})
+		if err != nil && !apierrors.IsNotFound(err) {
+			return err
+		}
+	}
+	return nil
+}

+ 0 - 9
e2e/framework/framework.go

@@ -22,23 +22,14 @@ import (
 	. "github.com/onsi/gomega"
 	api "k8s.io/api/core/v1"
 	"k8s.io/client-go/kubernetes"
-	kscheme "k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
 	crclient "sigs.k8s.io/controller-runtime/pkg/client"
 
-	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
-	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/e2e/framework/addon"
 	"github.com/external-secrets/external-secrets/e2e/framework/log"
 	"github.com/external-secrets/external-secrets/e2e/framework/util"
 )
 
-func init() {
-	_ = kscheme.AddToScheme(util.Scheme)
-	_ = esv1beta1.AddToScheme(util.Scheme)
-	_ = esv1alpha1.AddToScheme(util.Scheme)
-}
-
 type Framework struct {
 	BaseName string
 

+ 2 - 1
e2e/framework/testcase.go

@@ -15,6 +15,7 @@ package framework
 
 import (
 	"context"
+	"time"
 
 	//nolint
 	. "github.com/onsi/gomega"
@@ -93,7 +94,6 @@ func TableFunc(f *Framework, prov SecretStoreProvider) func(...func(*TestCase))
 		}
 
 		Expect(err).ToNot(HaveOccurred())
-
 		tc.AfterSync(prov, secret)
 	}
 }
@@ -108,6 +108,7 @@ func makeDefaultTestCase(f *Framework) *TestCase {
 				Namespace: f.Namespace.Name,
 			},
 			Spec: esv1beta1.ExternalSecretSpec{
+				RefreshInterval: &metav1.Duration{Duration: time.Second * 5},
 				SecretStoreRef: esv1beta1.SecretStoreRef{
 					Name: f.Namespace.Name,
 				},

+ 18 - 0
e2e/framework/util/util.go

@@ -21,9 +21,14 @@ import (
 	"os"
 	"time"
 
+	argoapp "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	fluxhelm "github.com/fluxcd/helm-controller/api/v2beta1"
+	fluxsrc "github.com/fluxcd/source-controller/api/v1beta2"
+
 	// nolint
 	. "github.com/onsi/ginkgo/v2"
 	v1 "k8s.io/api/core/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -34,10 +39,23 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/remotecommand"
 	crclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 )
 
 var Scheme = runtime.NewScheme()
 
+func init() {
+	_ = scheme.AddToScheme(Scheme)
+	_ = esv1beta1.AddToScheme(Scheme)
+	_ = esv1alpha1.AddToScheme(Scheme)
+	_ = argoapp.AddToScheme(Scheme)
+	_ = fluxhelm.AddToScheme(Scheme)
+	_ = fluxsrc.AddToScheme(Scheme)
+	_ = apiextensionsv1.AddToScheme(Scheme)
+}
+
 const (
 	// How often to poll for conditions.
 	Poll = 2 * time.Second

+ 2 - 2
e2e/run.sh

@@ -45,14 +45,13 @@ until kubectl get secret | grep -q -e ^external-secrets-e2e-token; do \
   sleep 3; \
 done
 
-kubectl apply -f ${DIR}/k8s/deploy/crds
-
 echo -e "Starting the e2e test pod ${E2E_IMAGE_REGISTRY}:${VERSION}"
 
 kubectl run --rm \
   --attach \
   --restart=Never \
   --pod-running-timeout=5m \
+  --labels="app=eso-e2e" \
   --env="GINKGO_LABELS=${GINKGO_LABELS:-.*}" \
   --env="GCP_SM_SA_JSON=${GCP_SM_SA_JSON:-}" \
   --env="GCP_PROJECT_ID=${GCP_PROJECT_ID:-}" \
@@ -80,5 +79,6 @@ kubectl run --rm \
   --env="ORACLE_KEY=${ORACLE_KEY:-}" \
   --env="IMAGE_REGISTRY=${IMAGE_REGISTRY}" \
   --env="VERSION=${VERSION}" \
+  --env="TEST_SUITES=${TEST_SUITES}" \
   --overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \
   e2e --image=${E2E_IMAGE_REGISTRY}:${VERSION}

+ 37 - 0
e2e/suites/argocd/argocd.go

@@ -0,0 +1,37 @@
+/*
+Copyright 2020 The cert-manager Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package argocd
+
+import (
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/fake"
+)
+
+var _ = Describe("argocd", Label("argocd"), func() {
+	f := framework.New("argocd")
+	prov := fake.NewProvider(f)
+
+	DescribeTable("sync secrets", framework.TableFunc(f, prov),
+		Entry(common.SimpleDataSync(f)),
+		Entry(common.JSONDataFromSync(f)),
+		Entry(common.SSHKeySync(f)),
+		Entry(common.SyncWithoutTargetName(f)),
+		Entry(common.SyncV1Alpha1(f)),
+		Entry(common.DeletionPolicyDelete(f)),
+	)
+})

+ 81 - 0
e2e/suites/argocd/install.go

@@ -0,0 +1,81 @@
+/*
+Copyright 2020 The cert-manager Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package argocd
+
+import (
+	"fmt"
+	"os"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	"github.com/external-secrets/external-secrets/e2e/framework/addon"
+)
+
+const (
+	helmChartRevision = "0.0.0-e2e"
+)
+
+func installArgo(cfg *addon.Config) {
+	By("installing argocd")
+	addon.InstallGlobalAddon(&addon.HelmChart{
+		Namespace:    "argocd",
+		ReleaseName:  "argocd",
+		Chart:        "argo-cd/argo-cd",
+		ChartVersion: "3.35.4",
+		Repo: addon.ChartRepo{
+			Name: "argo-cd",
+			URL:  "https://argoproj.github.io/argo-helm",
+		},
+		Vars: []addon.StringTuple{
+			{
+				Key:   "controller.args.appResyncPeriod",
+				Value: "15",
+			},
+		},
+		Args: []string{"--create-namespace"},
+	}, cfg)
+}
+
+func installESO(cfg *addon.Config) {
+	By("installing helm http server")
+	repo := os.Getenv("IMAGE_REGISTRY")
+	tag := os.Getenv("VERSION")
+	addon.InstallGlobalAddon(&addon.HelmServer{
+		ChartDir:      "/k8s/deploy/charts/external-secrets",
+		ChartRevision: helmChartRevision,
+	}, cfg)
+
+	By("installing eso through argo app")
+	addon.InstallGlobalAddon(&addon.ArgoCDApplication{
+		Name:                 "external-secrets",
+		Namespace:            "argocd",
+		DestinationNamespace: "external-secrets",
+		HelmChart:            "external-secrets",
+		HelmRepo:             "http://e2e-helmserver.default.svc.cluster.local",
+		HelmRevision:         helmChartRevision,
+		HelmValues: fmt.Sprintf(`
+installCRDs: true
+image:
+  repository: %s
+  tag: %s
+webhook:
+  image:
+    repository: %s
+    tag: %s
+certController:
+  image:
+    repository: %s
+    tag: %s`, repo, tag, repo, tag, repo, tag),
+	}, cfg)
+}

+ 53 - 0
e2e/suites/argocd/suite_test.go

@@ -0,0 +1,53 @@
+/*
+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 argocd
+
+import (
+	"testing"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	// nolint
+	. "github.com/onsi/gomega"
+
+	"github.com/external-secrets/external-secrets/e2e/framework/addon"
+	"github.com/external-secrets/external-secrets/e2e/framework/util"
+)
+
+var _ = SynchronizedBeforeSuite(func() []byte {
+	cfg := &addon.Config{}
+	cfg.KubeConfig, cfg.KubeClientSet, cfg.CRClient = util.NewConfig()
+	installArgo(cfg)
+	installESO(cfg)
+	return nil
+}, func([]byte) {
+	// noop
+})
+
+var _ = SynchronizedAfterSuite(func() {
+	// noop
+}, func() {
+	By("Cleaning up global addons")
+	addon.UninstallGlobalAddons()
+	if CurrentSpecReport().Failed() {
+		addon.PrintLogs()
+	}
+})
+
+func TestE2E(t *testing.T) {
+	NewWithT(t)
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "external-secrets argocd e2e suite", Label("argocd"))
+}

+ 37 - 0
e2e/suites/flux/flux.go

@@ -0,0 +1,37 @@
+/*
+Copyright 2020 The cert-manager Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package flux
+
+import (
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	"github.com/external-secrets/external-secrets/e2e/framework"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/fake"
+)
+
+var _ = Describe("flux", Label("flux"), func() {
+	f := framework.New("flux")
+	prov := fake.NewProvider(f)
+
+	DescribeTable("sync secrets", framework.TableFunc(f, prov),
+		Entry(common.SimpleDataSync(f)),
+		Entry(common.JSONDataFromSync(f)),
+		Entry(common.SSHKeySync(f)),
+		Entry(common.SyncWithoutTargetName(f)),
+		Entry(common.SyncV1Alpha1(f)),
+		Entry(common.DeletionPolicyDelete(f)),
+	)
+})

+ 79 - 0
e2e/suites/flux/install.go

@@ -0,0 +1,79 @@
+/*
+Copyright 2020 The cert-manager Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package flux
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	// nolint
+	. "github.com/onsi/gomega"
+
+	"github.com/external-secrets/external-secrets/e2e/framework/addon"
+)
+
+const (
+	helmChartRevision = "0.0.0-e2e"
+)
+
+func installFlux() {
+	By("installing flux")
+	fluxVersion := "v0.29.3"
+	url := fmt.Sprintf("https://github.com/fluxcd/flux2/releases/download/%s/install.yaml", fluxVersion)
+	cmd := exec.Command("kubectl", "apply", "-f", url)
+	out, err := cmd.CombinedOutput()
+	Expect(err).ToNot(HaveOccurred(), string(out))
+}
+
+func installESO(cfg *addon.Config) {
+	By("installing helm http server")
+	addon.InstallGlobalAddon(&addon.HelmServer{
+		ChartDir:      "/k8s/deploy/charts/external-secrets",
+		ChartRevision: helmChartRevision,
+	}, cfg)
+
+	By("installing eso through flux helmrelease app")
+	repo := os.Getenv("IMAGE_REGISTRY")
+	tag := os.Getenv("VERSION")
+	addon.InstallGlobalAddon(&addon.FluxHelmRelease{
+		Name:            "external-secrets",
+		Namespace:       "flux-system",
+		TargetNamespace: "external-secrets",
+		HelmChart:       "external-secrets",
+		HelmRepo:        "http://e2e-helmserver.default.svc.cluster.local",
+		HelmRevision:    helmChartRevision,
+		HelmValues: fmt.Sprintf(`{
+			"installCRDs": true,
+			"image": {
+			  "repository": "%s",
+			  "tag": "%s"
+			},
+			"webhook": {
+			  "image": {
+				"repository": "%s",
+				"tag": "%s"
+			  }
+			},
+			"certController": {
+			  "image": {
+				"repository": "%s",
+				"tag": "%s"
+			  }
+			}
+		  }`, repo, tag, repo, tag, repo, tag),
+	}, cfg)
+}

+ 53 - 0
e2e/suites/flux/suite_test.go

@@ -0,0 +1,53 @@
+/*
+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 flux
+
+import (
+	"testing"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	// nolint
+	. "github.com/onsi/gomega"
+
+	"github.com/external-secrets/external-secrets/e2e/framework/addon"
+	"github.com/external-secrets/external-secrets/e2e/framework/util"
+)
+
+var _ = SynchronizedBeforeSuite(func() []byte {
+	cfg := &addon.Config{}
+	cfg.KubeConfig, cfg.KubeClientSet, cfg.CRClient = util.NewConfig()
+	installFlux()
+	installESO(cfg)
+	return nil
+}, func([]byte) {
+	// noop
+})
+
+var _ = SynchronizedAfterSuite(func() {
+	// noop
+}, func() {
+	By("Cleaning up global addons")
+	addon.UninstallGlobalAddons()
+	if CurrentSpecReport().Failed() {
+		addon.PrintLogs()
+	}
+})
+
+func TestE2E(t *testing.T) {
+	NewWithT(t)
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "external-secrets flux e2e suite", Label("flux"))
+}

+ 1 - 1
e2e/suite/akeyless/akeyless.go → e2e/suites/provider/cases/akeyless/akeyless.go

@@ -20,7 +20,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 var _ = Describe("[akeyless]", Label("akeyless"), func() {

+ 0 - 0
e2e/suite/akeyless/provider.go → e2e/suites/provider/cases/akeyless/provider.go


+ 1 - 1
e2e/suite/alibaba/alibaba.go → e2e/suites/provider/cases/alibaba/alibaba.go

@@ -20,7 +20,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 var _ = Describe("[alibaba]", Label("alibaba"), func() {

+ 0 - 0
e2e/suite/alibaba/provider.go → e2e/suites/provider/cases/alibaba/provider.go


+ 0 - 0
e2e/suite/aws/common.go → e2e/suites/provider/cases/aws/common.go


+ 0 - 0
e2e/suite/aws/parameterstore/find_by_name.go → e2e/suites/provider/cases/aws/parameterstore/find_by_name.go


+ 0 - 0
e2e/suite/aws/parameterstore/find_by_tags.go → e2e/suites/provider/cases/aws/parameterstore/find_by_tags.go


+ 1 - 1
e2e/suite/aws/parameterstore/parameterstore.go → e2e/suites/provider/cases/aws/parameterstore/parameterstore.go

@@ -20,7 +20,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 var _ = Describe("[aws] ", Label("aws", "parameterstore"), func() {

+ 2 - 2
e2e/suite/aws/parameterstore/parameterstore_managed.go → e2e/suites/provider/cases/aws/parameterstore/parameterstore_managed.go

@@ -21,8 +21,8 @@ import (
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
 	"github.com/external-secrets/external-secrets/e2e/framework/addon"
-	awscommon "github.com/external-secrets/external-secrets/e2e/suite/aws"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	awscommon "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/aws"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 // here we use the global eso instance

+ 1 - 1
e2e/suite/aws/parameterstore/provider.go → e2e/suites/provider/cases/aws/parameterstore/provider.go

@@ -36,7 +36,7 @@ import (
 	esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
 	"github.com/external-secrets/external-secrets/e2e/framework/log"
-	common "github.com/external-secrets/external-secrets/e2e/suite/aws"
+	common "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/aws"
 )
 
 type Provider struct {

+ 1 - 1
e2e/suite/aws/secretsmanager/provider.go → e2e/suites/provider/cases/aws/secretsmanager/provider.go

@@ -37,7 +37,7 @@ import (
 	esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
 	"github.com/external-secrets/external-secrets/e2e/framework/log"
-	common "github.com/external-secrets/external-secrets/e2e/suite/aws"
+	common "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/aws"
 )
 
 type Provider struct {

+ 1 - 1
e2e/suite/aws/secretsmanager/secretsmanager.go → e2e/suites/provider/cases/aws/secretsmanager/secretsmanager.go

@@ -20,7 +20,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 var _ = Describe("[aws] ", Label("aws", "secretsmanager"), func() {

+ 2 - 2
e2e/suite/aws/secretsmanager/secretsmanager_managed.go → e2e/suites/provider/cases/aws/secretsmanager/secretsmanager_managed.go

@@ -21,8 +21,8 @@ import (
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
 	"github.com/external-secrets/external-secrets/e2e/framework/addon"
-	awscommon "github.com/external-secrets/external-secrets/e2e/suite/aws"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	awscommon "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/aws"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 // here we use the global eso instance

+ 0 - 0
e2e/suite/azure/azure_cert.go → e2e/suites/provider/cases/azure/azure_cert.go


+ 0 - 0
e2e/suite/azure/azure_key.go → e2e/suites/provider/cases/azure/azure_key.go


+ 1 - 1
e2e/suite/azure/azure_secret.go → e2e/suites/provider/cases/azure/azure_secret.go

@@ -18,7 +18,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 // keyvault type=secret should behave just like any other secret store.

+ 5 - 3
e2e/suite/azure/provider.go → e2e/suites/provider/cases/azure/provider.go

@@ -33,6 +33,8 @@ import (
 	"github.com/external-secrets/external-secrets/e2e/framework"
 )
 
+const providerSecretName = "provider-secret"
+
 type azureProvider struct {
 	clientID     string
 	clientSecret string
@@ -179,7 +181,7 @@ func (s *azureProvider) DeleteCertificate(key string) {
 func (s *azureProvider) CreateSecretStore() {
 	azureCreds := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      "provider-secret",
+			Name:      providerSecretName,
 			Namespace: s.framework.Namespace.Name,
 		},
 		StringData: map[string]string{
@@ -202,11 +204,11 @@ func (s *azureProvider) CreateSecretStore() {
 					VaultURL: &s.vaultURL,
 					AuthSecretRef: &esv1beta1.AzureKVAuth{
 						ClientID: &esmeta.SecretKeySelector{
-							Name: "provider-secret",
+							Name: providerSecretName,
 							Key:  "client-id",
 						},
 						ClientSecret: &esmeta.SecretKeySelector{
-							Name: "provider-secret",
+							Name: providerSecretName,
 							Key:  "client-secret",
 						},
 					},

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

@@ -63,6 +63,7 @@ func SyncV1Alpha1(f *framework.Framework) (string, func(*framework.TestCase)) {
 				Namespace: f.Namespace.Name,
 			},
 			Spec: esv1alpha1.ExternalSecretSpec{
+				RefreshInterval: &metav1.Duration{Duration: time.Second * 5},
 				SecretStoreRef: esv1alpha1.SecretStoreRef{
 					Name: f.Namespace.Name,
 				},
@@ -578,7 +579,6 @@ func DeletionPolicyDelete(f *framework.Framework) (string, func(*framework.TestC
 			},
 		}
 
-		tc.ExternalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second * 5}
 		tc.ExternalSecret.Spec.Target.DeletionPolicy = esv1beta1.DeletionPolicyDelete
 		tc.ExternalSecret.Spec.Data = []esv1beta1.ExternalSecretData{
 			{

+ 0 - 8
e2e/suite/common/find_by_name.go → e2e/suites/provider/cases/common/find_by_name.go

@@ -14,10 +14,8 @@ package common
 
 import (
 	"fmt"
-	"time"
 
 	v1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
@@ -44,9 +42,6 @@ func FindByName(f *framework.Framework) (string, func(*framework.TestCase)) {
 				secretKeyThree: []byte(secretValue),
 			},
 		}
-		// AWS Secrets Manager is eventually consistent
-		// specifying a low refresh interval avoids flaky tests
-		tc.ExternalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second * 5}
 		tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
 			{
 				Find: &esapi.ExternalSecretFind{
@@ -77,9 +72,6 @@ func FindByNameWithPath(f *framework.Framework) (string, func(*framework.TestCas
 				fmt.Sprintf("%s-three", f.Namespace.Name): []byte(secretValue),
 			},
 		}
-		// AWS Secrets Manager is eventually consistent
-		// specifying a low refresh interval avoids flaky tests
-		tc.ExternalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second * 5}
 		tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
 			{
 				Find: &esapi.ExternalSecretFind{

+ 0 - 8
e2e/suite/common/find_by_tags.go → e2e/suites/provider/cases/common/find_by_tags.go

@@ -14,10 +14,8 @@ package common
 
 import (
 	"fmt"
-	"time"
 
 	v1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
@@ -55,9 +53,6 @@ func FindByTag(f *framework.Framework) (string, func(*framework.TestCase)) {
 				fmt.Sprintf("e2e-find-name-%s-three", f.Namespace.Name): []byte(secretValue1),
 			},
 		}
-		// AWS Secrets Manager is eventually consistent
-		// specifying a low refresh interval avoids flaky tests
-		tc.ExternalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second * 5}
 		tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
 			{
 				Find: &esapi.ExternalSecretFind{
@@ -103,9 +98,6 @@ func FindByTagWithPath(f *framework.Framework) (string, func(*framework.TestCase
 				fmt.Sprintf("foobar-%s-three", f.Namespace.Name): []byte(secretValue),
 			},
 		}
-		// AWS Secrets Manager is eventually consistent
-		// specifying a low refresh interval avoids flaky tests
-		tc.ExternalSecret.Spec.RefreshInterval = &metav1.Duration{Duration: time.Second * 5}
 		tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataFromRemoteRef{
 			{
 				Find: &esapi.ExternalSecretFind{

+ 106 - 0
e2e/suites/provider/cases/fake/provider.go

@@ -0,0 +1,106 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package fake
+
+import (
+	"context"
+	"encoding/json"
+
+	// nolint
+	. "github.com/onsi/ginkgo/v2"
+
+	// nolint
+	. "github.com/onsi/gomega"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/e2e/framework"
+)
+
+type Provider struct {
+	framework *framework.Framework
+}
+
+func NewProvider(f *framework.Framework) *Provider {
+	prov := &Provider{
+		framework: f,
+	}
+	BeforeEach(prov.BeforeEach)
+	return prov
+}
+
+func (s *Provider) CreateSecret(key string, val framework.SecretEntry) {
+	var store esv1beta1.SecretStore
+	err := s.framework.CRClient.Get(context.Background(), types.NamespacedName{
+		Namespace: s.framework.Namespace.Name,
+		Name:      s.framework.Namespace.Name,
+	}, &store)
+	Expect(err).ToNot(HaveOccurred())
+	base := store.DeepCopy()
+
+	mapData := make(map[string]string)
+	_ = json.Unmarshal([]byte(val.Value), &mapData)
+	store.Spec.Provider.Fake.Data = append(store.Spec.Provider.Fake.Data, esv1beta1.FakeProviderData{
+		Key:      key,
+		Value:    val.Value,
+		ValueMap: mapData,
+	})
+	err = s.framework.CRClient.Patch(context.Background(), &store, client.MergeFrom(base))
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *Provider) BeforeEach() {
+	s.CreateStore()
+}
+
+func (s *Provider) DeleteSecret(key string) {
+	var store esv1beta1.SecretStore
+	err := s.framework.CRClient.Get(context.Background(), types.NamespacedName{
+		Namespace: s.framework.Namespace.Name,
+		Name:      s.framework.Namespace.Name,
+	}, &store)
+	Expect(err).ToNot(HaveOccurred())
+	base := store.DeepCopy()
+	data := make([]esv1beta1.FakeProviderData, 0)
+	for _, v := range store.Spec.Provider.Fake.Data {
+		if v.Key != key {
+			data = append(data, v)
+		}
+	}
+	store.Spec.Provider.Fake.Data = data
+	err = s.framework.CRClient.Patch(context.Background(), &store, client.MergeFrom(base))
+	Expect(err).ToNot(HaveOccurred())
+}
+
+func (s *Provider) CreateStore() {
+	// Create a secret store - change these values to match YAML
+	By("creating a secret store for credentials")
+	fakeStore := &esv1beta1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      s.framework.Namespace.Name,
+			Namespace: s.framework.Namespace.Name,
+		},
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				Fake: &esv1beta1.FakeProvider{
+					Data: []esv1beta1.FakeProviderData{},
+				},
+			},
+		},
+	}
+	err := s.framework.CRClient.Create(context.Background(), fakeStore)
+	Expect(err).ToNot(HaveOccurred())
+}

+ 1 - 1
e2e/suite/gcp/gcp.go → e2e/suites/provider/cases/gcp/gcp.go

@@ -26,7 +26,7 @@ import (
 	// nolint
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 // This test uses the global ESO.

+ 1 - 1
e2e/suite/gcp/gcp_managed.go → e2e/suites/provider/cases/gcp/gcp_managed.go

@@ -22,7 +22,7 @@ import (
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
 	"github.com/external-secrets/external-secrets/e2e/framework/addon"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 const (

+ 0 - 0
e2e/suite/gcp/provider.go → e2e/suites/provider/cases/gcp/provider.go


+ 1 - 1
e2e/suite/gitlab/gitlab.go → e2e/suites/provider/cases/gitlab/gitlab.go

@@ -23,7 +23,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 var _ = Describe("[gitlab]", Label("gitlab"), func() {

+ 0 - 0
e2e/suite/gitlab/provider.go → e2e/suites/provider/cases/gitlab/provider.go


+ 6 - 6
e2e/suite/import.go → e2e/suites/provider/cases/import.go

@@ -16,10 +16,10 @@ package suite
 import (
 
 	// import different e2e test suites.
-	_ "github.com/external-secrets/external-secrets/e2e/suite/aws/parameterstore"
-	_ "github.com/external-secrets/external-secrets/e2e/suite/aws/secretsmanager"
-	_ "github.com/external-secrets/external-secrets/e2e/suite/azure"
-	_ "github.com/external-secrets/external-secrets/e2e/suite/gcp"
-	_ "github.com/external-secrets/external-secrets/e2e/suite/template"
-	_ "github.com/external-secrets/external-secrets/e2e/suite/vault"
+	_ "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/gcp"
+	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/template"
+	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases/vault"
 )

+ 1 - 1
e2e/suite/oracle/oracle.go → e2e/suites/provider/cases/oracle/oracle.go

@@ -18,7 +18,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 var _ = Describe("[oracle]", Label("oracle"), func() {

+ 0 - 0
e2e/suite/oracle/provider.go → e2e/suites/provider/cases/oracle/provider.go


+ 0 - 0
e2e/suite/template/provider.go → e2e/suites/provider/cases/template/provider.go


+ 0 - 0
e2e/suite/template/template.go → e2e/suites/provider/cases/template/template.go


+ 3 - 2
e2e/suite/vault/provider.go → e2e/suites/provider/cases/vault/provider.go

@@ -45,6 +45,7 @@ const (
 	appRoleAuthProviderName = "app-role-provider"
 	kvv1ProviderName        = "kv-v1-provider"
 	jwtProviderName         = "jwt-provider"
+	jwtProviderSecretName   = "jwt-provider-credentials"
 	jwtK8sProviderName      = "jwt-k8s-provider"
 	kubernetesProviderName  = "kubernetes-provider"
 )
@@ -237,7 +238,7 @@ func (s vaultProvider) CreateV1Store(v *addon.Vault, ns string) {
 func (s vaultProvider) CreateJWTStore(v *addon.Vault, ns string) {
 	vaultCreds := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      "jwt-provider",
+			Name:      jwtProviderSecretName,
 			Namespace: ns,
 		},
 		Data: map[string][]byte{
@@ -252,7 +253,7 @@ func (s vaultProvider) CreateJWTStore(v *addon.Vault, ns string) {
 			Path: v.JWTPath,
 			Role: v.JWTRole,
 			SecretRef: &esmeta.SecretKeySelector{
-				Name: "jwt-provider",
+				Name: jwtProviderSecretName,
 				Key:  "jwt",
 			},
 		},

+ 1 - 1
e2e/suite/vault/vault.go → e2e/suites/provider/cases/vault/vault.go

@@ -21,7 +21,7 @@ import (
 
 	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/e2e/framework"
-	"github.com/external-secrets/external-secrets/e2e/suite/common"
+	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
 const (

+ 8 - 4
e2e/e2e_test.go → e2e/suites/provider/e2e_test.go

@@ -23,7 +23,7 @@ import (
 
 	"github.com/external-secrets/external-secrets/e2e/framework/addon"
 	"github.com/external-secrets/external-secrets/e2e/framework/util"
-	_ "github.com/external-secrets/external-secrets/e2e/suite"
+	_ "github.com/external-secrets/external-secrets/e2e/suites/provider/cases"
 )
 
 var _ = SynchronizedBeforeSuite(func() []byte {
@@ -31,12 +31,16 @@ var _ = SynchronizedBeforeSuite(func() []byte {
 	cfg.KubeConfig, cfg.KubeClientSet, cfg.CRClient = util.NewConfig()
 
 	By("installing eso")
-	addon.InstallGlobalAddon(addon.NewESO(), cfg)
+	addon.InstallGlobalAddon(addon.NewESO(addon.WithCRDs()), cfg)
 
 	return nil
-}, func([]byte) {})
+}, func([]byte) {
+	// noop
+})
 
-var _ = SynchronizedAfterSuite(func() {}, func() {
+var _ = SynchronizedAfterSuite(func() {
+	// noop
+}, func() {
 	By("Cleaning up global addons")
 	addon.UninstallGlobalAddons()
 	if CurrentSpecReport().Failed() {

+ 87 - 13
go.mod

@@ -3,9 +3,8 @@ module github.com/external-secrets/external-secrets
 go 1.17
 
 replace (
-	github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1 => ./apis/externalsecrets/v1alpha1
-	github.com/external-secrets/external-secrets/e2e/framework/log => ./e2e/framework/log
-	github.com/external-secrets/external-secrets/pkg/provider/gitlab => ./pkg/provider/gitlab
+	github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127
+	github.com/go-test/deep => github.com/go-test/deep v1.0.4
 	k8s.io/api => k8s.io/api v0.23.0
 	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.0
 	k8s.io/apimachinery => k8s.io/apimachinery v0.23.0
@@ -29,6 +28,7 @@ replace (
 	k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.0
 	k8s.io/metrics => k8s.io/metrics v0.23.0
 	k8s.io/mount-utils => k8s.io/mount-utils v0.23.0
+	k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.0
 	k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.0
 )
 
@@ -36,7 +36,7 @@ require (
 	cloud.google.com/go v0.100.2 // indirect
 	cloud.google.com/go/iam v0.3.0
 	cloud.google.com/go/secretmanager v1.4.0
-	github.com/Azure/azure-sdk-for-go v63.4.0+incompatible
+	github.com/Azure/azure-sdk-for-go v64.1.0+incompatible
 	github.com/Azure/go-autorest/autorest v0.11.27
 	github.com/Azure/go-autorest/autorest/adal v0.9.19
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
@@ -48,11 +48,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/akeylesslabs/akeyless-go/v2 v2.16.4
+	github.com/akeylesslabs/akeyless-go/v2 v2.16.6
 	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1562
-	github.com/aws/aws-sdk-go v1.44.10
+	github.com/aws/aws-sdk-go v1.44.19
 	github.com/crossplane/crossplane-runtime v0.15.1
 	github.com/go-logr/logr v1.2.3
+	github.com/go-test/deep v1.0.4 // indirect
 	github.com/golang-jwt/jwt/v4 v4.4.1
 	github.com/google/go-cmp v0.5.8
 	github.com/google/uuid v1.3.0
@@ -63,7 +64,7 @@ require (
 	github.com/hashicorp/vault/api/auth/ldap v0.1.0
 	github.com/huandu/xstrings v1.3.2 // indirect
 	github.com/lestrrat-go/jwx v1.2.23
-	github.com/onsi/ginkgo/v2 v2.1.3
+	github.com/onsi/ginkgo/v2 v2.1.4
 	github.com/onsi/gomega v1.19.0
 	github.com/oracle/oci-go-sdk/v56 v56.1.0
 	github.com/prometheus/client_golang v1.12.1
@@ -87,7 +88,7 @@ require (
 	k8s.io/apiextensions-apiserver v0.23.5
 	k8s.io/apimachinery v0.23.5
 	k8s.io/client-go v0.23.5
-	k8s.io/utils v0.0.0-20211116205334-6203023598ed
+	k8s.io/utils v0.0.0-20211208161948-7d6a63dca704
 	sigs.k8s.io/controller-runtime v0.11.2
 	sigs.k8s.io/controller-tools v0.8.0
 	software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
@@ -95,8 +96,16 @@ require (
 
 require github.com/1Password/connect-sdk-go v1.2.0
 
+require (
+	github.com/argoproj/argo-cd/v2 v2.3.3
+	github.com/fluxcd/helm-controller/api v0.20.1
+	github.com/fluxcd/pkg/apis/meta v0.12.2
+	github.com/fluxcd/source-controller/api v0.24.1
+)
+
 require (
 	cloud.google.com/go/compute v1.6.0 // indirect
+	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
 	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
@@ -104,39 +113,70 @@ require (
 	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
 	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
+	github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
 	github.com/Masterminds/semver/v3 v3.1.1 // indirect
 	github.com/PaesslerAG/gval v1.0.0 // indirect
+	github.com/PuerkitoBio/purell v1.1.1 // indirect
+	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
+	github.com/argoproj/gitops-engine v0.6.2 // indirect
+	github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 // indirect
 	github.com/armon/go-metrics v0.3.10 // indirect
 	github.com/armon/go-radix v1.0.0 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/bombsimon/logrusr/v2 v2.0.1 // indirect
+	github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 // indirect
 	github.com/cenkalti/backoff/v3 v3.2.2 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
+	github.com/docker/distribution v2.7.1+incompatible // indirect
+	github.com/emicklei/go-restful v2.9.5+incompatible // indirect
+	github.com/emirpasic/gods v1.12.0 // indirect
 	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
+	github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
+	github.com/fatih/camelcase v1.0.0 // indirect
 	github.com/fatih/color v1.13.0 // indirect
+	github.com/fluxcd/pkg/apis/acl v0.0.3 // indirect
+	github.com/fluxcd/pkg/apis/kustomize v0.3.3 // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
+	github.com/fvbommel/sortorder v1.0.1 // indirect
 	github.com/ghodss/yaml v1.0.0 // indirect
+	github.com/go-errors/errors v1.0.1 // indirect
+	github.com/go-git/gcfg v1.5.0 // indirect
+	github.com/go-git/go-billy/v5 v5.0.0 // indirect
+	github.com/go-git/go-git/v5 v5.2.0 // indirect
 	github.com/go-logr/zapr v1.2.0 // indirect
 	github.com/go-openapi/errors v0.19.8 // indirect
+	github.com/go-openapi/jsonpointer v0.19.5 // indirect
+	github.com/go-openapi/jsonreference v0.19.5 // indirect
 	github.com/go-openapi/strfmt v0.21.2 // indirect
+	github.com/go-openapi/swag v0.19.14 // indirect
 	github.com/go-playground/locales v0.14.0 // indirect
 	github.com/go-playground/universal-translator v0.18.0 // indirect
+	github.com/go-redis/cache/v8 v8.4.2 // indirect
+	github.com/go-redis/redis/v8 v8.11.3 // indirect
 	github.com/go-stack/stack v1.8.0 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/gobuffalo/flect v0.2.3 // indirect
+	github.com/gobwas/glob v0.2.3 // indirect
 	github.com/goccy/go-json v0.9.6 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
+	github.com/google/btree v1.0.1 // indirect
+	github.com/google/go-github/v41 v41.0.0 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
+	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/googleapis/gnostic v0.5.5 // indirect
+	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/hashicorp/go-hclog v1.1.0 // indirect
@@ -157,8 +197,14 @@ require (
 	github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
 	github.com/imdario/mergo v0.3.12 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
+	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/jonboulle/clockwork v0.2.2 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+	github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
+	github.com/klauspost/compress v1.13.6 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
@@ -166,47 +212,66 @@ require (
 	github.com/lestrrat-go/httpcc v1.0.1 // indirect
 	github.com/lestrrat-go/iter v1.0.1 // indirect
 	github.com/lestrrat-go/option v1.0.0 // indirect
+	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
+	github.com/mailru/easyjson v0.7.6 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
+	github.com/mitchellh/go-wordwrap v1.0.0 // indirect
 	github.com/mitchellh/mapstructure v1.4.3 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/moby/spdystream v0.2.0 // indirect
+	github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
 	github.com/oklog/run v1.1.0 // indirect
 	github.com/oklog/ulid v1.3.1 // indirect
+	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opentracing/opentracing-go v1.2.0 // indirect
+	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
 	github.com/pierrec/lz4 v2.6.1+incompatible // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/common v0.32.1 // indirect
 	github.com/prometheus/procfs v0.7.3 // indirect
+	github.com/robfig/cron v1.2.0 // indirect
+	github.com/russross/blackfriday v1.5.2 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/ryanuber/go-glob v1.0.0 // indirect
+	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/shopspring/decimal v1.2.0 // indirect
+	github.com/sirupsen/logrus v1.8.1 // indirect
 	github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b // indirect
 	github.com/spf13/cast v1.4.1 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/stretchr/objx v0.2.0 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
 	github.com/uber/jaeger-client-go v2.25.0+incompatible // indirect
 	github.com/uber/jaeger-lib v2.4.0+incompatible // indirect
+	github.com/vmihailenco/go-tinylfu v0.2.1 // indirect
+	github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
+	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
+	github.com/xanzy/ssh-agent v0.2.1 // indirect
+	github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
 	go.mongodb.org/mongo-driver v1.7.5 // indirect
 	go.opencensus.io v0.23.0 // indirect
+	go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
-	golang.org/x/mod v0.5.0 // indirect
+	golang.org/x/exp v0.0.0-20210901193431-a062eea981d2 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect
+	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
-	golang.org/x/tools v0.1.7 // indirect
+	golang.org/x/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
 	gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
@@ -215,13 +280,22 @@ require (
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.66.2 // indirect
 	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
+	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
+	k8s.io/apiserver v0.23.1 // indirect
+	k8s.io/cli-runtime v0.23.1 // indirect
 	k8s.io/component-base v0.23.5 // indirect
-	k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect
+	k8s.io/component-helpers v0.23.1 // indirect
+	k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect
 	k8s.io/klog v0.3.0 // indirect
 	k8s.io/klog/v2 v2.30.0 // indirect
+	k8s.io/kube-aggregator v0.23.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
-	sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
+	k8s.io/kubectl v0.23.1 // indirect
+	k8s.io/kubernetes v1.23.1 // indirect
+	sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
+	sigs.k8s.io/kustomize/api v0.10.1 // indirect
+	sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
 	sigs.k8s.io/yaml v1.3.0 // indirect
 )

File diff suppressed because it is too large
+ 460 - 10
go.sum


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

@@ -88,5 +88,7 @@ nav:
     - Contributing Process: contributing-process.md
     - Release Process: contributing-release.md
     - Code of Conduct: contributing-coc.md
+  - Roadmap: roadmap.md
+  - FAQ: faq.md
   - Stability and Support: stability-support.md
   - Deprecation Policy: deprecation-policy.md

+ 13 - 15
pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller.go

@@ -22,7 +22,6 @@ 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/types"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -43,16 +42,16 @@ type Reconciler struct {
 }
 
 const (
-	errGetCES              = "could not get ClusterExternalSecret"
-	errPatchStatus         = "unable to patch status"
-	errLabelMap            = "unable to get map from labels"
-	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"
+	errGetCES               = "could not get ClusterExternalSecret"
+	errPatchStatus          = "unable to patch status"
+	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) {
@@ -76,15 +75,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		refreshInt = clusterExternalSecret.Spec.RefreshInterval.Duration
 	}
 
-	labelMap, err := metav1.LabelSelectorAsMap(&clusterExternalSecret.Spec.NamespaceSelector)
+	labelSelector, err := metav1.LabelSelectorAsSelector(&clusterExternalSecret.Spec.NamespaceSelector)
 	if err != nil {
-		log.Error(err, errLabelMap)
+		log.Error(err, errConvertLabelSelector)
 		return ctrl.Result{RequeueAfter: refreshInt}, err
 	}
 
 	namespaceList := v1.NamespaceList{}
-
-	err = r.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap)})
+	err = r.List(ctx, &namespaceList, &client.ListOptions{LabelSelector: labelSelector})
 	if err != nil {
 		log.Error(err, errNamespaces)
 		return ctrl.Result{RequeueAfter: refreshInt}, err

+ 42 - 1
pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller_test.go

@@ -253,6 +253,46 @@ var _ = Describe("ClusterExternalSecret controller", func() {
 		}
 	}
 
+	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,
+				}
+				ns, err := ctest.CreateNamespaceWithLabels(prefix, k8sClient, labels)
+				Expect(err).ToNot(HaveOccurred())
+				tc.externalSecretNamespaces = append(tc.externalSecretNamespaces, testNamespace{
+					namespace: v1.Namespace{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: ns,
+						},
+					},
+					containsES: true,
+				})
+			}
+			tc.clusterExternalSecret.Spec.NamespaceSelector.MatchExpressions = []metav1.LabelSelectorRequirement{
+				{
+					Key:      "prefix",
+					Operator: metav1.LabelSelectorOpIn,
+					Values:   prefixes,
+				},
+			}
+		}
+		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()
@@ -326,7 +366,8 @@ var _ = Describe("ClusterExternalSecret controller", func() {
 		Entry("Should use cluster external secret name if external secret name isn't defined", syncWithoutESName),
 		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 delete external secrets when namespaces no longer match", deleteESInNonMatchingNS),
+		Entry("Should sync with label selector", syncWithMatchExpressions))
 })
 
 func sliceContainsString(toFind string, collection []string) bool {

+ 74 - 18
pkg/provider/azure/keyvault/keyvault.go

@@ -56,6 +56,7 @@ const (
 	errUnexpectedStoreSpec   = "unexpected store spec"
 	errMissingAuthType       = "cannot initialize Azure Client: no valid authType was specified"
 	errPropNotExist          = "property %s does not exist in key %s"
+	errTagNotExist           = "tag %s does not exist"
 	errUnknownObjectType     = "unknown Azure Keyvault object Type for %s"
 	errUnmarshalJSONData     = "error unmarshalling json data: %w"
 	errDataFromCert          = "cannot get use dataFrom to get certificate secret"
@@ -243,55 +244,107 @@ func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretF
 	return secretsMap, nil
 }
 
+// Retrieves a tag value if specified and all tags in JSON format if not.
+func getSecretTag(tags map[string]*string, property string) ([]byte, error) {
+	if property == "" {
+		secretTagsData := make(map[string]string)
+		for k, v := range tags {
+			secretTagsData[k] = *v
+		}
+		return json.Marshal(secretTagsData)
+	}
+	if val, exist := tags[property]; exist {
+		return []byte(*val), nil
+	}
+	return nil, fmt.Errorf(errTagNotExist, property)
+}
+
+// Retrieves a property value if specified and the secret value if not.
+func getProperty(secret, property, key string) ([]byte, error) {
+	if property == "" {
+		return []byte(secret), nil
+	}
+	res := gjson.Get(secret, property)
+	if !res.Exists() {
+		return nil, fmt.Errorf(errPropNotExist, property, key)
+	}
+	return []byte(res.String()), nil
+}
+
 // Implements store.Client.GetSecret Interface.
-// Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
+// Retrieves a secret/Key/Certificate/Tag with the secret name defined in ref.Name
 // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
 func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	version := ""
 	objectType, secretName := getObjType(ref)
 
-	if ref.Version != "" {
-		version = ref.Version
-	}
-
 	switch objectType {
 	case defaultObjType:
 		// returns a SecretBundle with the secret value
 		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
-		secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, version)
+		secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		if err != nil {
 			return nil, err
 		}
-		if ref.Property == "" {
-			return []byte(*secretResp.Value), nil
-		}
-		res := gjson.Get(*secretResp.Value, ref.Property)
-		if !res.Exists() {
-			return nil, fmt.Errorf(errPropNotExist, ref.Property, ref.Key)
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(secretResp.Tags, ref.Property)
 		}
-		return []byte(res.String()), err
+		return getProperty(*secretResp.Value, ref.Property, ref.Key)
 	case objectTypeCert:
 		// returns a CertBundle. We return CER contents of x509 certificate
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
-		secretResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, version)
+		certResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		if err != nil {
 			return nil, err
 		}
-		return *secretResp.Cer, nil
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(certResp.Tags, ref.Property)
+		}
+		return *certResp.Cer, nil
 	case objectTypeKey:
 		// returns a KeyBundle that contains a jwk
 		// azure kv returns only public keys
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
-		keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, version)
+		keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		if err != nil {
 			return nil, err
 		}
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(keyResp.Tags, ref.Property)
+		}
 		return json.Marshal(keyResp.Key)
 	}
 
 	return nil, fmt.Errorf(errUnknownObjectType, secretName)
 }
 
+// returns a SecretBundle with the tags values.
+func (a *Azure) getSecretTags(ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	_, secretName := getObjType(ref)
+	secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
+
+	if err != nil {
+		return nil, err
+	}
+
+	secretTagsData := make(map[string][]byte)
+
+	for tagname, tagval := range secretResp.Tags {
+		name := secretName + "_" + tagname
+		kv := make(map[string]string)
+		err = json.Unmarshal([]byte(*tagval), &kv)
+		// if the tagvalue is not in JSON format then we added to secretTagsData we added as it is
+		if err != nil {
+			secretTagsData[name] = []byte(*tagval)
+		} else {
+			for k, v := range kv {
+				keyName := name + "_" + k
+				secretTagsData[keyName] = []byte(v)
+			}
+		}
+	}
+	return secretTagsData, nil
+}
+
 // Implements store.Client.GetSecretMap Interface.
 // New version of GetSecretMap.
 func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
@@ -304,6 +357,10 @@ func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDa
 			return nil, err
 		}
 
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return a.getSecretTags(ref)
+		}
+
 		kv := make(map[string]string)
 		err = json.Unmarshal(data, &kv)
 		if err != nil {
@@ -321,7 +378,6 @@ func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDa
 	case objectTypeKey:
 		return nil, fmt.Errorf(errDataFromKey)
 	}
-
 	return nil, fmt.Errorf(errUnknownObjectType, secretName)
 }
 

+ 197 - 0
pkg/provider/azure/keyvault/keyvault_test.go

@@ -89,15 +89,34 @@ const (
 	jwkPubEC             = `{"kid":"https://example.vault.azure.net/keys/ec-p-521/e3d0e9c179b54988860c69c6ae172c65","kty":"EC","key_ops":["sign","verify"],"crv":"P-521","x":"AedOAtb7H7Oz1C_cPKI_R4CN_eai5nteY6KFW07FOoaqgQfVCSkQDK22fCOiMT_28c8LZYJRsiIFz_IIbQUW7bXj","y":"AOnchHnmBphIWXvanmMAmcCDkaED6ycW8GsAl9fQ43BMVZTqcTkJYn6vGnhn7MObizmkNSmgZYTwG-vZkIg03HHs"}`
 	jsonTestString       = `{"Name": "External", "LastName": "Secret", "Address": { "Street": "Myroad st.", "CP": "J4K4T4" } }`
 	jsonSingleTestString = `{"Name": "External", "LastName": "Secret" }`
+	jsonTagTestString    = `{"tagname":"tagvalue","tagname2":"tagvalue2"}`
 	keyName              = "key/keyname"
 	certName             = "cert/certname"
 	secretString         = "changedvalue"
 	unexpectedError      = "[%d] unexpected error: %s, expected: '%s'"
 	unexpectedSecretData = "[%d] unexpected secret data: expected %#v, got %#v"
+	errorNoTag           = "tag something does not exist"
+	something            = "something"
+	tagname              = "tagname"
+	tagname2             = "tagname2"
+	tagvalue             = "tagvalue"
+	tagvalue2            = "tagvalue2"
 	secretName           = "example-1"
+	testsecret           = "test-secret"
 	fakeURL              = "noop"
 )
 
+func getTagMap() map[string]*string {
+	tag1 := "tagname"
+	tag2 := "tagname2"
+	value1 := "tagvalue"
+	value2 := "tagvalue2"
+	tagMap := make(map[string]*string)
+	tagMap[tag1] = &value1
+	tagMap[tag2] = &value2
+	return tagMap
+}
+
 func newKVJWK(b []byte) *keyvault.JSONWebKey {
 	var key keyvault.JSONWebKey
 	err := json.Unmarshal(b, &key)
@@ -112,6 +131,7 @@ func newKVJWK(b []byte) *keyvault.JSONWebKey {
 func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 	secretString := "changedvalue"
 	secretCertificate := "certificate_value"
+	tagMap := getTagMap()
 
 	// good case
 	setSecretString := func(smtc *secretManagerTestCase) {
@@ -188,6 +208,136 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 		smtc.ref.Key = fmt.Sprintf("dummy/%s", smtc.secretName)
 	}
 
+	setSecretWithTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = tagname
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString, Tags: tagMap,
+		}
+		smtc.expectedSecret = tagvalue
+	}
+
+	badSecretWithTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = something
+		smtc.expectedSecret = ""
+		smtc.expectError = errorNoTag
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
+	setSecretWithNoSpecificTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString, Tags: tagMap,
+		}
+		smtc.expectedSecret = jsonTagTestString
+	}
+
+	setSecretWithNoTags := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{}
+		smtc.expectedSecret = "{}"
+	}
+
+	setCertWithTag := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString, Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = tagname
+		smtc.expectedSecret = tagvalue
+		smtc.ref.Key = smtc.secretName
+	}
+
+	badCertWithTag := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.ref.Key = smtc.secretName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = something
+		smtc.expectedSecret = ""
+		smtc.expectError = errorNoTag
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
+	setCertWithNoSpecificTag := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.ref.Key = smtc.secretName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString, Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = jsonTagTestString
+	}
+
+	setCertWithNoTags := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.ref.Key = smtc.secretName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = "{}"
+	}
+
+	setKeyWithTag := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)), Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = tagname
+		smtc.expectedSecret = tagvalue
+		smtc.ref.Key = smtc.secretName
+	}
+
+	badKeyWithTag := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.ref.Key = smtc.secretName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)), Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = something
+		smtc.expectedSecret = ""
+		smtc.expectError = errorNoTag
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
+	setKeyWithNoSpecificTag := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.ref.Key = smtc.secretName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)), Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = jsonTagTestString
+	}
+
+	setKeyWithNoTags := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.ref.Key = smtc.secretName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)),
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = "{}"
+	}
+
+	badPropertyTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.Property = tagname
+		smtc.expectedSecret = ""
+		smtc.expectError = "property tagname does not exist in key test-secret"
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
 	successCases := []*secretManagerTestCase{
 		makeValidSecretManagerTestCase(),
 		makeValidSecretManagerTestCaseCustom(setSecretString),
@@ -198,6 +348,19 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setPubECKey),
 		makeValidSecretManagerTestCaseCustom(setCertificate),
 		makeValidSecretManagerTestCaseCustom(badSecretType),
+		makeValidSecretManagerTestCaseCustom(setSecretWithTag),
+		makeValidSecretManagerTestCaseCustom(badSecretWithTag),
+		makeValidSecretManagerTestCaseCustom(setSecretWithNoSpecificTag),
+		makeValidSecretManagerTestCaseCustom(setSecretWithNoTags),
+		makeValidSecretManagerTestCaseCustom(setCertWithTag),
+		makeValidSecretManagerTestCaseCustom(badCertWithTag),
+		makeValidSecretManagerTestCaseCustom(setCertWithNoSpecificTag),
+		makeValidSecretManagerTestCaseCustom(setCertWithNoTags),
+		makeValidSecretManagerTestCaseCustom(setKeyWithTag),
+		makeValidSecretManagerTestCaseCustom(badKeyWithTag),
+		makeValidSecretManagerTestCaseCustom(setKeyWithNoSpecificTag),
+		makeValidSecretManagerTestCaseCustom(setKeyWithNoTags),
+		makeValidSecretManagerTestCaseCustom(badPropertyTag),
 	}
 
 	sm := Azure{
@@ -218,6 +381,7 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 	secretString := "changedvalue"
 	secretCertificate := "certificate_value"
+	tagMap := getTagMap()
 
 	badSecretString := func(smtc *secretManagerTestCase) {
 		smtc.expectedSecret = secretString
@@ -286,6 +450,36 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 		smtc.ref.Key = fmt.Sprintf("dummy/%s", smtc.secretName)
 	}
 
+	setSecretTags := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{
+			Tags: tagMap,
+		}
+		smtc.expectedData[testsecret+"_"+tagname] = []byte(tagvalue)
+		smtc.expectedData[testsecret+"_"+tagname2] = []byte(tagvalue2)
+	}
+
+	setSecretWithJSONTag := func(smtc *secretManagerTestCase) {
+		tagJSONMap := make(map[string]*string)
+		tagJSONData := `{"keyname":"keyvalue","x":"y"}`
+		tagJSONMap["json"] = &tagJSONData
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString, Tags: tagJSONMap,
+		}
+		smtc.expectedData[testsecret+"_json_keyname"] = []byte("keyvalue")
+		smtc.expectedData[testsecret+"_json_x"] = []byte("y")
+	}
+
+	setSecretWithNoTags := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		tagMapTestEmpty := make(map[string]*string)
+		smtc.secretOutput = keyvault.SecretBundle{
+			Tags: tagMapTestEmpty,
+		}
+		smtc.expectedSecret = ""
+	}
+
 	successCases := []*secretManagerTestCase{
 		makeValidSecretManagerTestCaseCustom(badSecretString),
 		makeValidSecretManagerTestCaseCustom(setSecretJSON),
@@ -294,6 +488,9 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(badPubRSAKey),
 		makeValidSecretManagerTestCaseCustom(badCertificate),
 		makeValidSecretManagerTestCaseCustom(badSecretType),
+		makeValidSecretManagerTestCaseCustom(setSecretTags),
+		makeValidSecretManagerTestCaseCustom(setSecretWithJSONTag),
+		makeValidSecretManagerTestCaseCustom(setSecretWithNoTags),
 	}
 
 	sm := Azure{

+ 44 - 0
pkg/provider/ibm/provider.go

@@ -157,6 +157,14 @@ func (ibm *providerIBM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSec
 
 		return getPublicCertSecret(ibm, &secretName, ref)
 
+	case sm.CreateSecretOptionsSecretTypePrivateCertConst:
+
+		if ref.Property == "" {
+			return nil, fmt.Errorf("remoteRef.property required for secret type private_cert")
+		}
+
+		return getPrivateCertSecret(ibm, &secretName, ref)
+
 	case sm.CreateSecretOptionsSecretTypeKvConst:
 
 		return getKVSecret(ibm, &secretName, ref)
@@ -220,6 +228,25 @@ func getPublicCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.Ext
 	return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
 }
 
+func getPrivateCertSecret(ibm *providerIBM, secretName *string, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	response, _, err := ibm.IBMClient.GetSecret(
+		&sm.GetSecretOptions{
+			SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePrivateCertConst),
+			ID:         secretName,
+		})
+	if err != nil {
+		return nil, err
+	}
+
+	secret := response.Resources[0].(*sm.SecretResource)
+	secretData := secret.SecretData.(map[string]interface{})
+
+	if val, ok := secretData[ref.Property]; ok {
+		return []byte(val.(string)), nil
+	}
+	return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
+}
+
 func getIamCredentialsSecret(ibm *providerIBM, secretName *string) ([]byte, error) {
 	response, _, err := ibm.IBMClient.GetSecret(
 		&sm.GetSecretOptions{
@@ -442,6 +469,23 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1beta1.External
 
 		return secretMap, nil
 
+	case sm.CreateSecretOptionsSecretTypePrivateCertConst:
+		response, _, err := ibm.IBMClient.GetSecret(
+			&sm.GetSecretOptions{
+				SecretType: core.StringPtr(sm.CreateSecretOptionsSecretTypePrivateCertConst),
+				ID:         &secretName,
+			})
+		if err != nil {
+			return nil, err
+		}
+
+		secret := response.Resources[0].(*sm.SecretResource)
+		secretData := secret.SecretData.(map[string]interface{})
+
+		secretMap := byteArrayMap(secretData)
+
+		return secretMap, nil
+
 	case sm.CreateSecretOptionsSecretTypeKvConst:
 		secret, err := getKVSecret(ibm, &secretName, ref)
 		if err != nil {

+ 62 - 97
pkg/provider/ibm/provider_test.go

@@ -34,6 +34,7 @@ import (
 
 const (
 	errExpectedErr = "wanted error got nil"
+	secretKey      = "test-secret"
 )
 
 type secretManagerTestCase struct {
@@ -67,7 +68,7 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
 
 func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 	return &esv1beta1.ExternalSecretDataRemoteRef{
-		Key:     "test-secret",
+		Key:     secretKey,
 		Version: "default",
 	}
 }
@@ -75,7 +76,7 @@ func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
 func makeValidAPIInput() *sm.GetSecretOptions {
 	return &sm.GetSecretOptions{
 		SecretType: core.StringPtr(sm.GetSecretOptionsSecretTypeArbitraryConst),
-		ID:         utilpointer.StringPtr("test-secret"),
+		ID:         utilpointer.StringPtr(secretKey),
 	}
 }
 
@@ -239,69 +240,44 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 		smtc.expectedSecret = secretAPIKey
 	}
 
-	// good case: imported_cert type with property
-	secretCert := "imported_cert/test-secret"
-	setSecretCert := func(smtc *secretManagerTestCase) {
-		resources := []sm.SecretResourceIntf{
-			&sm.SecretResource{
-				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
-				Name:       utilpointer.StringPtr("testyname"),
-				SecretData: secretData,
-			}}
-
-		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
-		smtc.apiOutput.Resources = resources
-		smtc.ref.Key = secretCert
-		smtc.ref.Property = "certificate"
-		smtc.expectedSecret = secretCertificate
+	funcSetCertSecretTest := func(certType string, good bool) func(*secretManagerTestCase) {
+		return func(smtc *secretManagerTestCase) {
+			resources := []sm.SecretResourceIntf{
+				&sm.SecretResource{
+					SecretType: utilpointer.StringPtr(certType),
+					Name:       utilpointer.StringPtr("testyname"),
+					SecretData: secretData,
+				}}
+
+			smtc.apiInput.SecretType = core.StringPtr(certType)
+			smtc.apiOutput.Resources = resources
+			smtc.ref.Key = certType + "/" + secretKey
+			if good {
+				smtc.ref.Property = "certificate"
+				smtc.expectedSecret = secretCertificate
+			} else {
+				smtc.expectError = "remoteRef.property required for secret type " + certType
+			}
+		}
 	}
 
-	// bad case: imported_cert type without property
-	badSecretCert := func(smtc *secretManagerTestCase) {
-		resources := []sm.SecretResourceIntf{
-			&sm.SecretResource{
-				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
-				Name:       utilpointer.StringPtr("testyname"),
-				SecretData: secretData,
-			}}
+	// good case: imported_cert type with property
+	setSecretCert := funcSetCertSecretTest(sm.CreateSecretOptionsSecretTypeImportedCertConst, true)
 
-		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
-		smtc.apiOutput.Resources = resources
-		smtc.ref.Key = secretCert
-		smtc.expectError = "remoteRef.property required for secret type imported_cert"
-	}
+	// bad case: imported_cert type without property
+	badSecretCert := funcSetCertSecretTest(sm.CreateSecretOptionsSecretTypeImportedCertConst, false)
 
 	// good case: public_cert type with property
-	secretPublicCert := "public_cert/test-secret"
-	setSecretPublicCert := func(smtc *secretManagerTestCase) {
-		resources := []sm.SecretResourceIntf{
-			&sm.SecretResource{
-				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst),
-				Name:       utilpointer.StringPtr("testyname"),
-				SecretData: secretData,
-			}}
-
-		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst)
-		smtc.apiOutput.Resources = resources
-		smtc.ref.Key = secretPublicCert
-		smtc.ref.Property = "certificate"
-		smtc.expectedSecret = secretCertificate
-	}
+	setSecretPublicCert := funcSetCertSecretTest(sm.CreateSecretOptionsSecretTypePublicCertConst, true)
 
 	// bad case: public_cert type without property
-	badSecretPublicCert := func(smtc *secretManagerTestCase) {
-		resources := []sm.SecretResourceIntf{
-			&sm.SecretResource{
-				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst),
-				Name:       utilpointer.StringPtr("testyname"),
-				SecretData: secretData,
-			}}
+	badSecretPublicCert := funcSetCertSecretTest(sm.CreateSecretOptionsSecretTypePublicCertConst, false)
 
-		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst)
-		smtc.apiOutput.Resources = resources
-		smtc.ref.Key = secretPublicCert
-		smtc.expectError = "remoteRef.property required for secret type public_cert"
-	}
+	// good case: private_cert type with property
+	setSecretPrivateCert := funcSetCertSecretTest(sm.CreateSecretOptionsSecretTypePrivateCertConst, true)
+
+	// bad case: private_cert type without property
+	badSecretPrivateCert := funcSetCertSecretTest(sm.CreateSecretOptionsSecretTypePrivateCertConst, false)
 
 	secretDataKV := make(map[string]interface{})
 	secretKVPayload := make(map[string]interface{})
@@ -428,6 +404,8 @@ func TestIBMSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(badSecretKV),
 		makeValidSecretManagerTestCaseCustom(setSecretPublicCert),
 		makeValidSecretManagerTestCaseCustom(badSecretPublicCert),
+		makeValidSecretManagerTestCaseCustom(setSecretPrivateCert),
+		makeValidSecretManagerTestCaseCustom(badSecretPrivateCert),
 	}
 
 	sm := providerIBM{}
@@ -529,49 +507,35 @@ func TestGetSecretMap(t *testing.T) {
 		smtc.expectedData["apikey"] = []byte(secretAPIKey)
 	}
 
-	// good case: imported_cert
-	setSecretCert := func(smtc *secretManagerTestCase) {
-		secretData := make(map[string]interface{})
-		secretData["certificate"] = secretCertificate
-		secretData["private_key"] = secretPrivateKey
-		secretData["intermediate"] = secretIntermediate
-
-		resources := []sm.SecretResourceIntf{
-			&sm.SecretResource{
-				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst),
-				Name:       utilpointer.StringPtr("testyname"),
-				SecretData: secretData,
-			}}
-
-		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypeImportedCertConst)
-		smtc.apiOutput.Resources = resources
-		smtc.ref.Key = "imported_cert/test-secret"
-		smtc.expectedData["certificate"] = []byte(secretCertificate)
-		smtc.expectedData["private_key"] = []byte(secretPrivateKey)
-		smtc.expectedData["intermediate"] = []byte(secretIntermediate)
+	funcCertTest := func(certType string) func(*secretManagerTestCase) {
+		return func(smtc *secretManagerTestCase) {
+			secretData := make(map[string]interface{})
+			secretData["certificate"] = secretCertificate
+			secretData["private_key"] = secretPrivateKey
+			secretData["intermediate"] = secretIntermediate
+
+			resources := []sm.SecretResourceIntf{
+				&sm.SecretResource{
+					SecretType: utilpointer.StringPtr(certType),
+					Name:       utilpointer.StringPtr("testyname"),
+					SecretData: secretData,
+				}}
+
+			smtc.apiInput.SecretType = core.StringPtr(certType)
+			smtc.apiOutput.Resources = resources
+			smtc.ref.Key = certType + "/test-secret"
+			smtc.expectedData["certificate"] = []byte(secretCertificate)
+			smtc.expectedData["private_key"] = []byte(secretPrivateKey)
+			smtc.expectedData["intermediate"] = []byte(secretIntermediate)
+		}
 	}
 
+	// good case: imported_cert
+	setSecretCert := funcCertTest(sm.CreateSecretOptionsSecretTypeImportedCertConst)
 	// good case: public_cert
-	setSecretPublicCert := func(smtc *secretManagerTestCase) {
-		secretData := make(map[string]interface{})
-		secretData["certificate"] = secretCertificate
-		secretData["private_key"] = secretPrivateKey
-		secretData["intermediate"] = secretIntermediate
-
-		resources := []sm.SecretResourceIntf{
-			&sm.SecretResource{
-				SecretType: utilpointer.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst),
-				Name:       utilpointer.StringPtr("testyname"),
-				SecretData: secretData,
-			}}
-
-		smtc.apiInput.SecretType = core.StringPtr(sm.CreateSecretOptionsSecretTypePublicCertConst)
-		smtc.apiOutput.Resources = resources
-		smtc.ref.Key = "public_cert/test-secret"
-		smtc.expectedData["certificate"] = []byte(secretCertificate)
-		smtc.expectedData["private_key"] = []byte(secretPrivateKey)
-		smtc.expectedData["intermediate"] = []byte(secretIntermediate)
-	}
+	setSecretPublicCert := funcCertTest(sm.CreateSecretOptionsSecretTypePublicCertConst)
+	// good case: public_cert
+	setSecretPrivateCert := funcCertTest(sm.CreateSecretOptionsSecretTypePrivateCertConst)
 
 	// good case: kv, no property, return entire payload as key:value pairs
 	setSecretKV := func(smtc *secretManagerTestCase) {
@@ -664,6 +628,7 @@ func TestGetSecretMap(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setSecretKVWithPathAndProperty),
 		makeValidSecretManagerTestCaseCustom(badSecretKVWithUnknownProperty),
 		makeValidSecretManagerTestCaseCustom(setSecretPublicCert),
+		makeValidSecretManagerTestCaseCustom(setSecretPrivateCert),
 	}
 
 	sm := providerIBM{}

Some files were not shown because too many files changed in this diff