Преглед изворни кода

e2e: migrate provider suite to v2 kubernetes mode

Moritz Johner пре 3 месеци
родитељ
комит
8906a441c1

+ 332 - 0
E2E_V2_PLAN.md

@@ -0,0 +1,332 @@
+# E2E V2 Migration Plan
+
+## Goal
+
+Reuse the existing provider e2e suite for the new V2 architecture, starting with the Kubernetes provider, and delete `e2e/suites/v2` after its reusable logic has been migrated.
+
+The target outcome is:
+
+- `provider.test` remains the single suite binary for provider e2e tests.
+- legacy and V2 runs are selected by configuration, not by separate suites.
+- existing table-driven provider assertions are reused.
+- V2-only Kubernetes tests for push, capabilities, and metrics live under `e2e/suites/provider`.
+- only the Kubernetes V2 provider is enabled initially.
+
+## Agreed Decisions
+
+- Use `provider.test` for both legacy and V2 runs.
+- Replace the current `test.e2e.v2` flow so it runs `provider.test` in V2 mode instead of `v2.test`.
+- Reuse the existing table-driven tests in `e2e/suites/provider/cases/kubernetes`.
+- Also migrate the V2 push, capabilities, and metrics tests into `e2e/suites/provider`.
+- For referent-auth equivalence in V2, use `ClusterProvider` with `AuthenticationScopeManifestNamespace`.
+- In the first migration step, enable only the Kubernetes provider deployment in V2 mode.
+
+## Proposed Design
+
+### 1. Introduce a provider suite mode switch
+
+Add an environment-driven mode switch for the provider suite.
+
+Suggested env var:
+
+- `E2E_PROVIDER_MODE=legacy|v2`
+
+Behavior:
+
+- default: `legacy`
+- `v2`: install ESO with V2 enabled, create Provider/ClusterProvider CRDs, and enable the Kubernetes provider deployment
+
+This keeps the suite layout stable and avoids carrying `e2e/suites/v2` as a parallel test hierarchy.
+
+### 2. Move reusable V2 bootstrap into framework/addon
+
+The V2 install logic currently proven in `e2e/suites/v2/suite_test.go` should be promoted into reusable addon mutators instead of staying in suite code.
+
+Recommended shape:
+
+- add V2 mutators in `e2e/framework/addon`
+- use `addon.NewESO(...)` for both legacy and V2
+- do **not** rely on the bespoke installer in `e2e/framework/addon/eso_v2.go`; either remove it later or repurpose it so there is only one supported installation path
+
+Suggested mutators:
+
+- `addon.WithV2Mode()`
+- `addon.WithProviderNamespace("external-secrets-system")`
+- `addon.WithV2Providers(...providers)` or a narrower `addon.WithKubernetesV2Provider()`
+
+Minimum V2 Helm settings:
+
+- `v2.enabled=true`
+- `crds.createProvider=true`
+- `crds.createClusterProvider=true`
+- `providers.enabled=true`
+- one provider entry for Kubernetes
+- release namespace `external-secrets-system`
+- release name `external-secrets`
+
+### 3. Make framework defaults mode-aware
+
+The main framework coupling today is that the default testcase builder assumes a namespaced `SecretStore` reference and no ref kind.
+
+Add mode-aware defaults to `framework.Framework` so existing test tables can continue to build their manifests the same way.
+
+Suggested new fields on `framework.Framework`:
+
+- `DefaultSecretStoreRefKind string`
+- `DefaultPushSecretStoreRefKind string`
+- optionally `DefaultPushSecretStoreRefAPIVersion string`
+- optionally `ProviderMode string`
+
+Set them in `framework.New(...)` based on `E2E_PROVIDER_MODE`:
+
+- legacy:
+  - `DefaultSecretStoreRefKind = ""`
+  - `DefaultPushSecretStoreRefKind = ""`
+- v2:
+  - `DefaultSecretStoreRefKind = esv1.ProviderKindStr`
+  - `DefaultPushSecretStoreRefKind = esv1.ProviderKindStr`
+
+Update default testcase creation in `e2e/framework/testcase.go` so:
+
+- `makeDefaultExternalSecretTestCase()` sets `Spec.SecretStoreRef.Kind` from framework defaults
+- `makeDefaultPushSecretTestCase()` sets `Spec.SecretStoreRefs[0].Kind` from framework defaults
+
+This allows existing table-driven tests to reuse the same setup with no broad manifest rewrite.
+
+### 4. Move V2 resource helpers out of `e2e/suites/v2`
+
+Anything that is reusable test infrastructure should move out of the `v2` suite package before that package is deleted.
+
+Recommended destination:
+
+- new package under `e2e/framework/v2` or similar
+
+Helpers to move first:
+
+- cluster CA bundle lookup
+- Kubernetes provider CR creation
+- Provider creation
+- ClusterProvider creation
+- provider readiness waits
+- RBAC helper for Kubernetes provider access
+- metrics scraping helpers
+
+Likely source files:
+
+- `e2e/suites/v2/helpers.go`
+- `e2e/suites/v2/metrics_helpers.go`
+
+After migration, provider cases should import only framework packages, never `e2e/suites/v2`.
+
+## Kubernetes Provider Migration
+
+### 5. Add a V2 setup path to the Kubernetes provider case
+
+Extend `e2e/suites/provider/cases/kubernetes/provider.go` so the provider setup can operate in both modes.
+
+Recommended approach:
+
+- keep the current `Provider` as the abstraction used by the table tests
+- branch internally on provider mode
+- legacy mode keeps creating `SecretStore` and `ClusterSecretStore`
+- V2 mode creates:
+  - namespaced `Kind=Kubernetes`
+  - namespaced `Kind=Provider`
+  - `ClusterProvider` for the referent-auth equivalent path
+
+Recommended structure:
+
+- `BeforeEach()` decides `setupLegacy()` vs `setupV2()`
+- `CreateStore()` keeps legacy behavior
+- add `CreateStoreV2()`
+- `CreateReferentStore()` keeps legacy behavior
+- add `CreateReferentStoreV2()`
+
+### 6. Preserve existing testcase naming semantics
+
+To minimize rewiring, keep the resource names aligned with what the table tests already expect.
+
+For the default case in V2 mode:
+
+- `Provider.Name = f.Namespace.Name`
+- `Provider.Namespace = f.Namespace.Name`
+- `ExternalSecret.Spec.SecretStoreRef.Name` remains untouched by tests and still resolves correctly
+
+For referent-auth cases in V2 mode:
+
+- create a `ClusterProvider` using the existing referent naming pattern
+- wire it with `AuthenticationScopeManifestNamespace`
+
+### 7. Map referent-auth behavior explicitly
+
+Today the Kubernetes suite expresses referent auth by switching to `ClusterSecretStore`.
+
+In V2, the semantic equivalent should be:
+
+- `SecretStoreRef.Kind = ClusterProvider`
+- `SecretStoreRef.Name = <referent-name>`
+- `ClusterProvider.Spec.AuthenticationScope = ManifestNamespace`
+
+Update `withReferentStore(...)` so it branches by mode:
+
+- legacy: `ClusterSecretStore`
+- V2: `ClusterProvider`
+
+Use API constants where available:
+
+- `esv1.ProviderKindStr`
+- `esv1.ClusterProviderKindStr`
+- `esv1.AuthenticationScopeManifestNamespace`
+
+### 8. RBAC model for Kubernetes V2 tests
+
+The V2 Kubernetes provider needs explicit RBAC to read or write secrets in the target namespace.
+
+For namespaced provider tests:
+
+- create role + rolebinding in the remote namespace
+- bind to the manifest namespace service account used by the provider auth configuration
+
+For referent/cluster-provider cases:
+
+- ensure RBAC is granted in the namespace the provider should access
+- when `AuthenticationScopeManifestNamespace` is used, bind the service account identity from the ExternalSecret/PushSecret namespace
+
+The plan should prefer one RBAC helper with explicit parameters over many ad-hoc copies.
+
+## Test Migration Scope
+
+### 9. Reuse the existing table-driven Kubernetes tests
+
+Keep the existing table-driven assertions in:
+
+- `e2e/suites/provider/cases/kubernetes/kubernetes.go`
+
+These should run in both modes, but V2 execution should be label-gated so we can migrate incrementally.
+
+Recommended labeling:
+
+- retain current `kubernetes` label
+- add V2-specific coverage behind `v2`
+- optionally add `legacy` label to the old suite bootstrap only if needed later
+
+Recommended strategy:
+
+- do not duplicate the common table entries
+- instantiate the same tables with a V2-aware provider setup
+
+### 10. Migrate V2-only Kubernetes tests into provider suite
+
+Move the following test coverage into `e2e/suites/provider/cases/kubernetes` or a sibling under `e2e/suites/provider`:
+
+- capabilities tests
+- push tests
+- metrics tests
+- any cluster-provider tests that express Kubernetes V2 behavior not already covered by the legacy tables
+
+Suggested placement:
+
+- `e2e/suites/provider/cases/kubernetes/capabilities_v2_test.go`
+- `e2e/suites/provider/cases/kubernetes/push_v2_test.go`
+- `e2e/suites/provider/cases/kubernetes/metrics_v2_test.go`
+- `e2e/suites/provider/cases/kubernetes/cluster_provider_v2_test.go`
+
+Guideline:
+
+- keep table-driven sync assertions in the existing `kubernetes.go`
+- keep new V2-only behavior in separate files so the legacy flow stays readable
+
+### 11. Metrics migration approach
+
+The metrics coverage in `e2e/suites/v2/metrics_test.go` is useful and should remain, but it should be narrowed to Kubernetes-only assumptions for this first step.
+
+First migration scope:
+
+- provider readiness metrics for `Provider`
+- readiness metrics for `ClusterProvider`
+- gRPC client/server request metrics after an `ExternalSecret` sync
+- any Kubernetes-specific cache metrics that remain stable enough for CI
+
+Avoid in the first pass:
+
+- fake-provider metrics dependencies
+- broad provider-agnostic abstractions that are not needed yet
+
+## CI / Build Changes
+
+### 12. Switch `test.e2e.v2` to run the provider suite
+
+Update the E2E execution path so V2 uses the provider suite binary instead of `v2.test`.
+
+Planned changes:
+
+- `e2e/Makefile`
+  - keep loading the controller image
+  - load only `provider-kubernetes` for V2 initially
+  - run `TEST_SUITES="provider"`
+  - run with `E2E_PROVIDER_MODE=v2`
+  - keep `GINKGO_LABELS="v2"`
+- `e2e/Dockerfile`
+  - remove `ADD e2e/suites/v2/v2.test /v2.test` once the old suite is gone
+
+Target command shape:
+
+```bash
+GINKGO_LABELS="v2" E2E_PROVIDER_MODE="v2" TEST_SUITES="provider" ./run.sh
+```
+
+### 13. Delete `e2e/suites/v2` only after parity is reached
+
+Deletion should happen only after:
+
+- V2 bootstrap moved out of `e2e/suites/v2/suite_test.go`
+- helpers moved out of `e2e/suites/v2/helpers.go`
+- metrics helpers moved out of `e2e/suites/v2/metrics_helpers.go`
+- Kubernetes V2 tests live under `e2e/suites/provider`
+- `test.e2e.v2` no longer depends on `v2.test`
+
+At that point remove:
+
+- `e2e/suites/v2/`
+- `v2.test` build/copy path
+- any stale docs or make targets referencing the old suite layout
+
+## Suggested Implementation Sequence
+
+1. Add provider-mode env handling to the provider suite bootstrap.
+2. Extract V2 Helm mutators from `e2e/suites/v2/suite_test.go` into `e2e/framework/addon`.
+3. Make framework testcase defaults mode-aware for `Provider` refs.
+4. Move reusable V2 helpers into a framework package.
+5. Extend Kubernetes provider setup to support legacy and V2.
+6. Reuse the existing table-driven Kubernetes tests in V2 mode.
+7. Migrate V2 capabilities tests into the provider suite.
+8. Migrate V2 push tests into the provider suite.
+9. Migrate V2 metrics tests into the provider suite.
+10. Update `test.e2e.v2` to run `provider.test`.
+11. Delete `e2e/suites/v2` and remove `v2.test` packaging.
+
+## Acceptance Criteria
+
+The migration is complete when all of the following are true:
+
+- `make test.e2e` still runs the legacy provider suite unchanged.
+- `make test.e2e.v2` runs `provider.test` with `E2E_PROVIDER_MODE=v2`.
+- the Kubernetes table-driven tests pass in V2 mode using `Provider` / `ClusterProvider` resources.
+- V2 push, capabilities, and metrics tests pass from within `e2e/suites/provider`.
+- only the Kubernetes V2 provider deployment is enabled in the initial V2 run.
+- `e2e/suites/v2` is deleted.
+- the e2e image no longer bundles `v2.test`.
+
+## Risks / Watchouts
+
+- The existing `e2e/framework/addon/eso_v2.go` appears to describe a second installation approach; keeping both active will create drift. Consolidate on the Helm-mutation path.
+- Metrics tests may be brittle if they assert on counters that can vary with retries or background reconciliation. Prefer existence and lower-bound assertions over exact counts.
+- `ClusterProvider` resources are cluster-scoped and must be cleaned up carefully to avoid cross-test leakage.
+- The default testcase ref-kind switch must not affect non-V2 suites; it should be entirely gated by `E2E_PROVIDER_MODE`.
+- Referent-auth semantics should be validated carefully: for V2 this migration assumes `AuthenticationScopeManifestNamespace` is the intended equivalent.
+
+## Out of Scope for This First Step
+
+- Migrating all other providers to V2.
+- Enabling AWS, Fake, or other provider deployments in the V2 provider-suite bootstrap.
+- Building a generic V2 abstraction for every provider before the Kubernetes migration proves the pattern.

+ 0 - 1
e2e/Dockerfile

@@ -25,6 +25,5 @@ ADD e2e/suites/provider/provider.test /provider.test
 ADD e2e/suites/argocd/argocd.test /argocd.test
 ADD e2e/suites/flux/flux.test /flux.test
 ADD e2e/suites/generator/generator.test /generator.test
-ADD e2e/suites/v2/v2.test /v2.test
 
 CMD [ "/entrypoint.sh" ]

+ 3 - 2
e2e/Makefile

@@ -6,7 +6,7 @@ KIND_IMG       = "kindest/node:v1.33.4@sha256:25a6018e48dfcaee478f4a59af81157a43
 DOCKER_BUILD_ARGS     ?=
 
 export E2E_IMAGE_NAME ?= ghcr.io/external-secrets/external-secrets-e2e
-export GINKGO_LABELS ?= !managed
+export GINKGO_LABELS ?= !managed && !v2
 export TEST_SUITES ?= provider generator flux argocd
 
 export OCI_IMAGE_NAME = ghcr.io/external-secrets/external-secrets
@@ -52,7 +52,8 @@ test.v2: e2e-image ## Run v2 e2e tests against current kube context
 	kind load docker-image --name="external-secrets" $(IMAGE_NAME):$(VERSION)
 	kind load docker-image --name="external-secrets" $(OCI_IMAGE_NAME):$(VERSION)
 	kind load docker-image --name="external-secrets" $(E2E_IMAGE_NAME):$(VERSION)
-	GINKGO_LABELS="v2" TEST_SUITES="v2" ./run.sh
+	kind load docker-image --name="external-secrets" ghcr.io/external-secrets/provider-kubernetes:$(VERSION)
+	GINKGO_LABELS="v2" E2E_PROVIDER_MODE="v2" TEST_SUITES="provider" ./run.sh
 
 test.managed: e2e-image ## Run e2e tests against current kube context
 	$(MAKE) -C ../ docker.build \

+ 77 - 0
e2e/framework/addon/eso_v2_mutators.go

@@ -0,0 +1,77 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+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
+
+    https://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 "os"
+
+const (
+	v2HelmNamespace   = "external-secrets-system"
+	v2HelmReleaseName = "external-secrets"
+)
+
+func WithV2Namespace() MutationFunc {
+	return func(eso *ESO) {
+		eso.HelmChart.Namespace = v2HelmNamespace
+		eso.HelmChart.ReleaseName = v2HelmReleaseName
+		if !containsArg(eso.HelmChart.Args, "--create-namespace") {
+			eso.HelmChart.Args = append(eso.HelmChart.Args, "--create-namespace")
+		}
+	}
+}
+
+func WithV2KubernetesProvider() MutationFunc {
+	return func(eso *ESO) {
+		version := os.Getenv("VERSION")
+		vars := []StringTuple{
+			{Key: "replicaCount", Value: "1"},
+			{Key: "v2.enabled", Value: "true"},
+			{Key: "crds.createProvider", Value: "true"},
+			{Key: "crds.createClusterProvider", Value: "true"},
+			{Key: "providers.enabled", Value: "true"},
+			{Key: "providerDefaults.replicaCount", Value: "1"},
+			{Key: "providers.list[0].name", Value: "kubernetes"},
+			{Key: "providers.list[0].type", Value: "kubernetes"},
+			{Key: "providers.list[0].enabled", Value: "true"},
+			{Key: "providers.list[0].replicaCount", Value: "1"},
+			{Key: "providers.list[0].image.repository", Value: "ghcr.io/external-secrets/provider-kubernetes"},
+			{Key: "providers.list[0].image.tag", Value: version},
+			{Key: "providers.list[0].image.pullPolicy", Value: "IfNotPresent"},
+		}
+		for _, variable := range vars {
+			setOrAppendVar(eso.HelmChart, variable)
+		}
+	}
+}
+
+func setOrAppendVar(chart *HelmChart, variable StringTuple) {
+	for i := range chart.Vars {
+		if chart.Vars[i].Key == variable.Key {
+			chart.Vars[i].Value = variable.Value
+			return
+		}
+	}
+	chart.Vars = append(chart.Vars, variable)
+}
+
+func containsArg(args []string, target string) bool {
+	for _, arg := range args {
+		if arg == target {
+			return true
+		}
+	}
+	return false
+}

+ 9 - 2
e2e/framework/eso.go

@@ -75,9 +75,16 @@ func (f *Framework) printESDebugLogs(esName, esNamespace string) {
 	}
 
 	// print most recent logs of default eso installation
-	podList, err := f.KubeClientSet.CoreV1().Pods("default").List(
+	esoNamespace := "default"
+	labelSelector := "app.kubernetes.io/instance=eso,app.kubernetes.io/name=external-secrets"
+	if IsV2ProviderMode() {
+		esoNamespace = "external-secrets-system"
+		labelSelector = "app.kubernetes.io/instance=external-secrets,app.kubernetes.io/name=external-secrets"
+	}
+
+	podList, err := f.KubeClientSet.CoreV1().Pods(esoNamespace).List(
 		GinkgoT().Context(),
-		metav1.ListOptions{LabelSelector: "app.kubernetes.io/instance=eso,app.kubernetes.io/name=external-secrets"})
+		metav1.ListOptions{LabelSelector: labelSelector})
 	Expect(err).ToNot(HaveOccurred())
 	numLines := int64(60)
 	for i := range podList.Items {

+ 14 - 2
e2e/framework/framework.go

@@ -34,6 +34,7 @@ import (
 	"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"
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
 )
 
 type Framework struct {
@@ -54,13 +55,24 @@ type Framework struct {
 	Addons []addon.Addon
 
 	MakeRemoteRefKey func(base string) string
+
+	ProviderMode                        string
+	DefaultSecretStoreRefKind           string
+	DefaultPushSecretStoreRefKind       string
+	DefaultPushSecretStoreRefAPIVersion string
 }
 
 // New returns a new framework instance with defaults.
 func New(baseName string) *Framework {
 	f := &Framework{
-		BaseName:         baseName,
-		MakeRemoteRefKey: func(base string) string { return base },
+		BaseName:                            baseName,
+		MakeRemoteRefKey:                    func(base string) string { return base },
+		ProviderMode:                        GetProviderMode(),
+		DefaultPushSecretStoreRefAPIVersion: esv1.SchemeGroupVersion.String(),
+	}
+	if f.ProviderMode == ProviderModeV2 {
+		f.DefaultSecretStoreRefKind = esv1.ProviderKindStr
+		f.DefaultPushSecretStoreRefKind = esv1.ProviderKindStr
 	}
 	f.KubeConfig, f.KubeClientSet, f.CRClient = util.NewConfig()
 

+ 39 - 0
e2e/framework/provider_mode.go

@@ -0,0 +1,39 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+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
+
+    https://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 framework
+
+import (
+	"os"
+	"strings"
+)
+
+const (
+	ProviderModeEnvVar = "E2E_PROVIDER_MODE"
+	ProviderModeLegacy = "legacy"
+	ProviderModeV2     = "v2"
+)
+
+func GetProviderMode() string {
+	if strings.EqualFold(os.Getenv(ProviderModeEnvVar), ProviderModeV2) {
+		return ProviderModeV2
+	}
+	return ProviderModeLegacy
+}
+
+func IsV2ProviderMode() bool {
+	return GetProviderMode() == ProviderModeV2
+}

+ 4 - 1
e2e/framework/testcase.go

@@ -174,6 +174,7 @@ func makeDefaultExternalSecretTestCase(f *Framework) *TestCase {
 				RefreshInterval: &metav1.Duration{Duration: time.Second * 5},
 				SecretStoreRef: esv1.SecretStoreRef{
 					Name: f.Namespace.Name,
+					Kind: f.DefaultSecretStoreRefKind,
 				},
 				Target: esv1.ExternalSecretTarget{
 					Name: TargetSecretName,
@@ -195,7 +196,9 @@ func makeDefaultPushSecretTestCase(f *Framework) *TestCase {
 				RefreshInterval: &metav1.Duration{Duration: time.Second * 5},
 				SecretStoreRefs: []esv1alpha1.PushSecretStoreRef{
 					{
-						Name: f.Namespace.Name,
+						Name:       f.Namespace.Name,
+						Kind:       f.DefaultPushSecretStoreRefKind,
+						APIVersion: f.DefaultPushSecretStoreRefAPIVersion,
 					},
 				},
 			},

+ 238 - 0
e2e/framework/v2/helpers.go

@@ -0,0 +1,238 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+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
+
+    https://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 v2
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	"github.com/external-secrets/external-secrets-e2e/framework/log"
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	k8sv2alpha1 "github.com/external-secrets/external-secrets/apis/provider/kubernetes/v2alpha1"
+)
+
+const (
+	ProviderNamespace = "external-secrets-system"
+	DefaultSAName     = "default"
+)
+
+func ProviderAddress(providerName string) string {
+	return fmt.Sprintf("provider-%s.%s.svc:8080", providerName, ProviderNamespace)
+}
+
+func GetClusterCABundle(f *framework.Framework, namespace string) []byte {
+	var caBundle []byte
+	krc := &corev1.ConfigMap{}
+	err := f.CRClient.Get(context.Background(),
+		types.NamespacedName{Name: "kube-root-ca.crt", Namespace: namespace},
+		krc)
+	if err == nil {
+		caBundle = []byte(krc.Data["ca.crt"])
+	}
+	return caBundle
+}
+
+func CreateKubernetesAccessRole(f *framework.Framework, name, serviceAccountName, serviceAccountNamespace, remoteNamespace string) {
+	role := &rbacv1.Role{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: remoteNamespace,
+		},
+		Rules: []rbacv1.PolicyRule{
+			{
+				APIGroups: []string{""},
+				Resources: []string{"secrets"},
+				Verbs:     []string{"get", "list", "watch", "create", "update", "patch", "delete"},
+			},
+			{
+				APIGroups: []string{"authorization.k8s.io"},
+				Resources: []string{"selfsubjectrulesreviews", "selfsubjectaccessreviews"},
+				Verbs:     []string{"create"},
+			},
+		},
+	}
+	Expect(createOrIgnoreAlreadyExists(f, role)).To(Succeed())
+
+	roleBinding := &rbacv1.RoleBinding{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: remoteNamespace,
+		},
+		Subjects: []rbacv1.Subject{
+			{
+				Kind:      "ServiceAccount",
+				Name:      serviceAccountName,
+				Namespace: serviceAccountNamespace,
+			},
+		},
+		RoleRef: rbacv1.RoleRef{
+			APIGroup: "rbac.authorization.k8s.io",
+			Kind:     "Role",
+			Name:     name,
+		},
+	}
+	Expect(createOrIgnoreAlreadyExists(f, roleBinding)).To(Succeed())
+}
+
+func CreateKubernetesProvider(f *framework.Framework, namespace, name, remoteNamespace, serviceAccountName string, serviceAccountNamespace *string, caBundle []byte) *k8sv2alpha1.Kubernetes {
+	k8ss := &k8sv2alpha1.Kubernetes{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Kubernetes",
+			APIVersion: "provider.external-secrets.io/v2alpha1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: esv1.KubernetesProvider{
+			Server: esv1.KubernetesServer{
+				URL:      "https://kubernetes.default.svc",
+				CABundle: caBundle,
+			},
+			RemoteNamespace: remoteNamespace,
+			Auth: &esv1.KubernetesAuth{
+				ServiceAccount: &esmeta.ServiceAccountSelector{
+					Name:      serviceAccountName,
+					Namespace: serviceAccountNamespace,
+				},
+			},
+		},
+	}
+	Expect(createOrIgnoreAlreadyExists(f, k8ss)).To(Succeed())
+	log.Logf("created Kubernetes provider: %s/%s", namespace, name)
+	return k8ss
+}
+
+func CreateProviderConnection(f *framework.Framework, namespace, name, address, providerAPIVersion, providerKind, providerName, providerNamespace string) *esv1.Provider {
+	providerConnection := &esv1.Provider{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: namespace,
+		},
+		Spec: esv1.ProviderSpec{
+			Config: esv1.ProviderConfig{
+				Address: address,
+				ProviderRef: esv1.ProviderReference{
+					APIVersion: providerAPIVersion,
+					Kind:       providerKind,
+					Name:       providerName,
+					Namespace:  providerNamespace,
+				},
+			},
+		},
+	}
+	Expect(createOrIgnoreAlreadyExists(f, providerConnection)).To(Succeed())
+	log.Logf("created Provider: %s/%s", namespace, name)
+	return providerConnection
+}
+
+func CreateClusterProviderConnection(f *framework.Framework, name, address, providerAPIVersion, providerKind, providerName, providerNamespace string, authScope esv1.AuthenticationScope, conditions []esv1.ClusterSecretStoreCondition) *esv1.ClusterProvider {
+	clusterProvider := &esv1.ClusterProvider{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: name,
+		},
+		Spec: esv1.ClusterProviderSpec{
+			Config: esv1.ProviderConfig{
+				Address: address,
+				ProviderRef: esv1.ProviderReference{
+					APIVersion: providerAPIVersion,
+					Kind:       providerKind,
+					Name:       providerName,
+					Namespace:  providerNamespace,
+				},
+			},
+			AuthenticationScope: authScope,
+			Conditions:          conditions,
+		},
+	}
+	Expect(createOrIgnoreAlreadyExists(f, clusterProvider)).To(Succeed())
+	log.Logf("created ClusterProvider: %s", name)
+	return clusterProvider
+}
+
+func WaitForProviderConnectionReady(f *framework.Framework, namespace, name string, timeout time.Duration) *esv1.Provider {
+	var providerConnection esv1.Provider
+	Eventually(func() bool {
+		err := f.CRClient.Get(context.Background(),
+			types.NamespacedName{Name: name, Namespace: namespace},
+			&providerConnection)
+		if err != nil {
+			log.Logf("failed to get Provider: %v", err)
+			return false
+		}
+
+		for _, condition := range providerConnection.Status.Conditions {
+			if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue {
+				return true
+			}
+		}
+		return false
+	}, timeout, time.Second).Should(BeTrue(), "Provider should become ready")
+
+	return &providerConnection
+}
+
+func WaitForClusterProviderReady(f *framework.Framework, name string, timeout time.Duration) *esv1.ClusterProvider {
+	var clusterProvider esv1.ClusterProvider
+	Eventually(func() bool {
+		err := f.CRClient.Get(context.Background(),
+			types.NamespacedName{Name: name},
+			&clusterProvider)
+		if err != nil {
+			log.Logf("failed to get ClusterProvider: %v", err)
+			return false
+		}
+
+		for _, condition := range clusterProvider.Status.Conditions {
+			if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue {
+				return true
+			}
+		}
+		return false
+	}, timeout, time.Second).Should(BeTrue(), "ClusterProvider should become ready")
+
+	return &clusterProvider
+}
+
+func VerifyProviderConnectionCapabilities(f *framework.Framework, namespace, name string, expected esv1.ProviderCapabilities) {
+	var provider esv1.Provider
+	Expect(f.CRClient.Get(context.Background(),
+		types.NamespacedName{Name: name, Namespace: namespace},
+		&provider)).To(Succeed())
+
+	Expect(provider.Status.Capabilities).NotTo(BeEmpty())
+	Expect(provider.Status.Capabilities).To(Equal(expected))
+}
+
+func createOrIgnoreAlreadyExists(f *framework.Framework, obj client.Object) error {
+	err := f.CRClient.Create(context.Background(), obj)
+	if err == nil || apierrors.IsAlreadyExists(err) {
+		return nil
+	}
+	return err
+}

+ 130 - 236
e2e/suites/v2/metrics_helpers.go → e2e/framework/v2/metrics.go

@@ -1,9 +1,11 @@
 /*
+Copyright © 2025 ESO Maintainer Team
+
 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
+    https://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,
@@ -25,50 +27,146 @@ import (
 	"strings"
 	"time"
 
-	v1 "k8s.io/api/core/v1"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/portforward"
 	"k8s.io/client-go/transport/spdy"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
 )
 
-// MetricSample represents a single Prometheus metric sample
 type MetricSample struct {
 	Name   string
 	Labels map[string]string
 	Value  float64
 }
 
-// MetricsMap is a map of metric names to their samples
 type MetricsMap map[string][]MetricSample
 
-// setupPortForward creates a port-forward to a pod and returns the local address and cleanup function
-func setupPortForward(ctx context.Context, config *rest.Config, clientset *kubernetes.Clientset, namespace, podName string, podPort int) (string, func(), error) {
-	// Find an available local port
-	localPort := 0 // Let the system choose
+func ScrapeControllerMetrics(ctx context.Context, config *rest.Config, clientset kubernetes.Interface, namespace string) (MetricsMap, error) {
+	podName, err := findPod(ctx, clientset, namespace, "app.kubernetes.io/name=external-secrets")
+	if err != nil {
+		return nil, err
+	}
+
+	return scrapePodMetrics(ctx, config, clientset, namespace, podName, 8080)
+}
+
+func ScrapeProviderMetrics(ctx context.Context, config *rest.Config, clientset kubernetes.Interface, namespace, providerName string) (MetricsMap, error) {
+	labelSelector := fmt.Sprintf("app.kubernetes.io/name=external-secrets-provider-%s", providerName)
+	podName, err := findPod(ctx, clientset, namespace, labelSelector)
+	if err != nil {
+		return nil, err
+	}
+
+	return scrapePodMetrics(ctx, config, clientset, namespace, podName, 8081)
+}
+
+func GetMetricValue(metrics MetricsMap, metricName string, matchLabels map[string]string) (float64, bool) {
+	samples, exists := metrics[metricName]
+	if !exists {
+		return 0, false
+	}
+
+	for _, sample := range samples {
+		if labelsMatch(sample.Labels, matchLabels) {
+			return sample.Value, true
+		}
+	}
 
-	// Get the pod
+	return 0, false
+}
+
+func ExpectMetricExists(metrics MetricsMap, metricName string) {
+	_, exists := metrics[metricName]
+	if !exists {
+		availableMetrics := []string{}
+		for name := range metrics {
+			availableMetrics = append(availableMetrics, name)
+		}
+		GinkgoWriter.Printf("Available metrics: %v\n", availableMetrics)
+	}
+	Expect(exists).To(BeTrue(), "metric %s should exist", metricName)
+}
+
+func ExpectMetricValue(metrics MetricsMap, metricName string, matchLabels map[string]string, expectedValue float64) {
+	value, found := GetMetricValue(metrics, metricName, matchLabels)
+	Expect(found).To(BeTrue(), "metric %s with labels %v should exist", metricName, matchLabels)
+	Expect(value).To(Equal(expectedValue), "metric %s value mismatch", metricName)
+}
+
+func ExpectMetricGreaterThan(metrics MetricsMap, metricName string, matchLabels map[string]string, threshold float64) {
+	value, found := GetMetricValue(metrics, metricName, matchLabels)
+	Expect(found).To(BeTrue(), "metric %s with labels %v should exist", metricName, matchLabels)
+	Expect(value).To(BeNumerically(">", threshold), "metric %s should be greater than %f", metricName, threshold)
+}
+
+func WaitForMetric(ctx context.Context, scraper func() (MetricsMap, error), metricName string, matchLabels map[string]string, minValue float64, timeout time.Duration) error {
+	deadline := time.Now().Add(timeout)
+	for time.Now().Before(deadline) {
+		metrics, err := scraper()
+		if err == nil {
+			value, found := GetMetricValue(metrics, metricName, matchLabels)
+			if found && value >= minValue {
+				return nil
+			}
+		}
+		time.Sleep(time.Second)
+	}
+
+	return fmt.Errorf("timeout waiting for metric %s with labels %v to reach %f", metricName, matchLabels, minValue)
+}
+
+func scrapePodMetrics(ctx context.Context, config *rest.Config, clientset kubernetes.Interface, namespace, podName string, podPort int) (MetricsMap, error) {
+	address, cleanup, err := setupPortForward(ctx, config, clientset, namespace, podName, podPort)
+	if err != nil {
+		return nil, fmt.Errorf("failed to setup port forward: %w", err)
+	}
+	defer cleanup()
+
+	time.Sleep(time.Second)
+
+	body, err := scrapeMetrics(ctx, address)
+	if err != nil {
+		return nil, err
+	}
+
+	return parsePrometheusMetrics(body), nil
+}
+
+func findPod(ctx context.Context, clientset kubernetes.Interface, namespace, labelSelector string) (string, error) {
+	pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
+		LabelSelector: labelSelector,
+	})
+	if err != nil {
+		return "", fmt.Errorf("failed to list pods: %w", err)
+	}
+
+	for _, pod := range pods.Items {
+		if pod.Status.Phase == corev1.PodRunning {
+			return pod.Name, nil
+		}
+	}
+
+	return "", fmt.Errorf("no running pod found for selector %s", labelSelector)
+}
+
+func setupPortForward(ctx context.Context, config *rest.Config, clientset kubernetes.Interface, namespace, podName string, podPort int) (string, func(), error) {
 	pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
 	if err != nil {
 		return "", nil, fmt.Errorf("failed to get pod: %w", err)
 	}
-
-	// Ensure pod is running
-	if pod.Status.Phase != v1.PodRunning {
+	if pod.Status.Phase != corev1.PodRunning {
 		return "", nil, fmt.Errorf("pod %s is not running: %s", podName, pod.Status.Phase)
 	}
 
-	// Create the port-forward request
 	transport, upgrader, err := spdy.RoundTripperFor(config)
 	if err != nil {
 		return "", nil, fmt.Errorf("failed to create round tripper: %w", err)
 	}
 
-	// Build the URL for port forwarding
 	url := clientset.CoreV1().RESTClient().Post().
 		Resource("pods").
 		Namespace(namespace).
@@ -80,49 +178,36 @@ func setupPortForward(ctx context.Context, config *rest.Config, clientset *kuber
 
 	stopChan := make(chan struct{}, 1)
 	readyChan := make(chan struct{}, 1)
+	ports := []string{fmt.Sprintf("0:%d", podPort)}
 
-	// Create port forwarder
-	ports := []string{fmt.Sprintf("%d:%d", localPort, podPort)}
-	
-	out := GinkgoWriter
-	errOut := GinkgoWriter
-
-	pf, err := portforward.New(dialer, ports, stopChan, readyChan, out, errOut)
+	pf, err := portforward.New(dialer, ports, stopChan, readyChan, GinkgoWriter, GinkgoWriter)
 	if err != nil {
 		return "", nil, fmt.Errorf("failed to create port forwarder: %w", err)
 	}
 
-	// Start port forwarding in background
 	errChan := make(chan error, 1)
 	go func() {
-		if err := pf.ForwardPorts(); err != nil {
-			errChan <- err
+		if forwardErr := pf.ForwardPorts(); forwardErr != nil {
+			errChan <- forwardErr
 		}
 	}()
 
-	// Wait for ready or error
 	select {
 	case <-readyChan:
-		// Port forward is ready
-		forwardedPorts, err := pf.GetPorts()
-		if err != nil {
+		forwardedPorts, portErr := pf.GetPorts()
+		if portErr != nil {
 			close(stopChan)
-			return "", nil, fmt.Errorf("failed to get forwarded ports: %w", err)
+			return "", nil, fmt.Errorf("failed to get forwarded ports: %w", portErr)
 		}
-		
 		if len(forwardedPorts) == 0 {
 			close(stopChan)
 			return "", nil, fmt.Errorf("no ports were forwarded")
 		}
-
-		localAddr := fmt.Sprintf("localhost:%d", forwardedPorts[0].Local)
-		
 		cleanup := func() {
 			close(stopChan)
 		}
-
-		return localAddr, cleanup, nil
-	case err := <-errChan:
+		return fmt.Sprintf("localhost:%d", forwardedPorts[0].Local), cleanup, nil
+	case err = <-errChan:
 		close(stopChan)
 		return "", nil, fmt.Errorf("port forward failed: %w", err)
 	case <-time.After(30 * time.Second):
@@ -131,20 +216,13 @@ func setupPortForward(ctx context.Context, config *rest.Config, clientset *kuber
 	}
 }
 
-// scrapeMetrics fetches metrics from an HTTP endpoint
 func scrapeMetrics(ctx context.Context, address string) (string, error) {
-	url := fmt.Sprintf("http://%s/metrics", address)
-	
-	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://%s/metrics", address), nil)
 	if err != nil {
 		return "", fmt.Errorf("failed to create request: %w", err)
 	}
 
-	client := &http.Client{
-		Timeout: 10 * time.Second,
-	}
-
-	resp, err := client.Do(req)
+	resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req)
 	if err != nil {
 		return "", fmt.Errorf("failed to scrape metrics: %w", err)
 	}
@@ -158,24 +236,16 @@ func scrapeMetrics(ctx context.Context, address string) (string, error) {
 	if err != nil {
 		return "", fmt.Errorf("failed to read response body: %w", err)
 	}
-
 	return string(body), nil
 }
 
-// parsePrometheusMetrics parses Prometheus text format into a structured map
 func parsePrometheusMetrics(body string) MetricsMap {
 	metrics := make(MetricsMap)
-	
-	// Regular expression to parse metric lines
-	// Format: metric_name{label1="value1",label2="value2"} value
-	// or: metric_name value
 	metricRegex := regexp.MustCompile(`^([a-zA-Z_:][a-zA-Z0-9_:]*?)(?:\{([^}]*)\})?\s+([^\s]+)`)
 
 	scanner := bufio.NewScanner(strings.NewReader(body))
 	for scanner.Scan() {
 		line := scanner.Text()
-		
-		// Skip comments and empty lines
 		if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" {
 			continue
 		}
@@ -185,67 +255,38 @@ func parsePrometheusMetrics(body string) MetricsMap {
 			continue
 		}
 
-		name := matches[1]
-		labelsStr := matches[2]
-		valueStr := matches[3]
-
-		value, err := strconv.ParseFloat(valueStr, 64)
+		value, err := strconv.ParseFloat(matches[3], 64)
 		if err != nil {
 			continue
 		}
 
-		labels := parseLabels(labelsStr)
-
 		sample := MetricSample{
-			Name:   name,
-			Labels: labels,
+			Name:   matches[1],
+			Labels: parseLabels(matches[2]),
 			Value:  value,
 		}
-
-		metrics[name] = append(metrics[name], sample)
+		metrics[sample.Name] = append(metrics[sample.Name], sample)
 	}
 
 	return metrics
 }
 
-// parseLabels parses label string into a map
 func parseLabels(labelsStr string) map[string]string {
 	labels := make(map[string]string)
-	
 	if labelsStr == "" {
 		return labels
 	}
 
-	// Split by comma, but respect quotes
 	labelRegex := regexp.MustCompile(`([a-zA-Z_][a-zA-Z0-9_]*)="([^"]*)"`)
 	matches := labelRegex.FindAllStringSubmatch(labelsStr, -1)
-	
 	for _, match := range matches {
 		if len(match) == 3 {
 			labels[match[1]] = match[2]
 		}
 	}
-
 	return labels
 }
 
-// getMetricValue finds a metric with specific labels and returns its value
-func getMetricValue(metrics MetricsMap, metricName string, matchLabels map[string]string) (float64, bool) {
-	samples, exists := metrics[metricName]
-	if !exists {
-		return 0, false
-	}
-
-	for _, sample := range samples {
-		if labelsMatch(sample.Labels, matchLabels) {
-			return sample.Value, true
-		}
-	}
-
-	return 0, false
-}
-
-// labelsMatch checks if sample labels match the required labels
 func labelsMatch(sampleLabels, matchLabels map[string]string) bool {
 	for key, value := range matchLabels {
 		if sampleLabels[key] != value {
@@ -254,150 +295,3 @@ func labelsMatch(sampleLabels, matchLabels map[string]string) bool {
 	}
 	return true
 }
-
-// findControllerPod finds the external-secrets controller pod
-func findControllerPod(ctx context.Context, clientset *kubernetes.Clientset, namespace string) (string, error) {
-	pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
-		LabelSelector: "app.kubernetes.io/name=external-secrets",
-	})
-	if err != nil {
-		return "", fmt.Errorf("failed to list controller pods: %w", err)
-	}
-
-	if len(pods.Items) == 0 {
-		return "", fmt.Errorf("no controller pods found")
-	}
-
-	// Return the first running pod
-	for _, pod := range pods.Items {
-		if pod.Status.Phase == v1.PodRunning {
-			return pod.Name, nil
-		}
-	}
-
-	return "", fmt.Errorf("no running controller pods found")
-}
-
-// findProviderPod finds a provider pod by label
-func findProviderPod(ctx context.Context, clientset *kubernetes.Clientset, namespace string, providerType string) (string, error) {
-	labelSelector := fmt.Sprintf("app.kubernetes.io/name=external-secrets-provider-%s", providerType)
-	
-	pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
-		LabelSelector: labelSelector,
-	})
-	if err != nil {
-		return "", fmt.Errorf("failed to list provider pods: %w", err)
-	}
-
-	if len(pods.Items) == 0 {
-		return "", fmt.Errorf("no %s provider pods found", providerType)
-	}
-
-	// Return the first running pod
-	for _, pod := range pods.Items {
-		if pod.Status.Phase == v1.PodRunning {
-			return pod.Name, nil
-		}
-	}
-
-	return "", fmt.Errorf("no running %s provider pods found", providerType)
-}
-
-// scrapeControllerMetrics scrapes metrics from the controller pod
-func scrapeControllerMetrics(ctx context.Context, config *rest.Config, clientset *kubernetes.Clientset, namespace string) (MetricsMap, error) {
-	podName, err := findControllerPod(ctx, clientset, namespace)
-	if err != nil {
-		return nil, err
-	}
-
-	address, cleanup, err := setupPortForward(ctx, config, clientset, namespace, podName, 8080)
-	if err != nil {
-		return nil, fmt.Errorf("failed to setup port forward: %w", err)
-	}
-	defer cleanup()
-
-	// Give port-forward a moment to stabilize
-	time.Sleep(1 * time.Second)
-
-	body, err := scrapeMetrics(ctx, address)
-	if err != nil {
-		return nil, err
-	}
-
-	return parsePrometheusMetrics(body), nil
-}
-
-// scrapeProviderMetrics scrapes metrics from a provider pod
-func scrapeProviderMetrics(ctx context.Context, config *rest.Config, clientset *kubernetes.Clientset, namespace string, providerType string) (MetricsMap, error) {
-	podName, err := findProviderPod(ctx, clientset, namespace, providerType)
-	if err != nil {
-		return nil, err
-	}
-
-	address, cleanup, err := setupPortForward(ctx, config, clientset, namespace, podName, 8081)
-	if err != nil {
-		return nil, fmt.Errorf("failed to setup port forward: %w", err)
-	}
-	defer cleanup()
-
-	// Give port-forward a moment to stabilize
-	time.Sleep(1 * time.Second)
-
-	body, err := scrapeMetrics(ctx, address)
-	if err != nil {
-		return nil, err
-	}
-
-	return parsePrometheusMetrics(body), nil
-}
-
-// waitForMetric polls until a metric reaches a minimum value or times out
-func waitForMetric(ctx context.Context, scraper func() (MetricsMap, error), metricName string, matchLabels map[string]string, minValue float64, timeout time.Duration) error {
-	deadline := time.Now().Add(timeout)
-	
-	for time.Now().Before(deadline) {
-		metrics, err := scraper()
-		if err != nil {
-			time.Sleep(1 * time.Second)
-			continue
-		}
-
-		value, found := getMetricValue(metrics, metricName, matchLabels)
-		if found && value >= minValue {
-			return nil
-		}
-
-		time.Sleep(1 * time.Second)
-	}
-
-	return fmt.Errorf("timeout waiting for metric %s with labels %v to reach %f", metricName, matchLabels, minValue)
-}
-
-// ExpectMetricExists asserts that a metric exists
-func ExpectMetricExists(metrics MetricsMap, metricName string) {
-	_, exists := metrics[metricName]
-	if !exists {
-		// Debug: print available metrics for troubleshooting
-		availableMetrics := []string{}
-		for name := range metrics {
-			availableMetrics = append(availableMetrics, name)
-		}
-		GinkgoWriter.Printf("Available metrics: %v\n", availableMetrics)
-	}
-	Expect(exists).To(BeTrue(), "metric %s should exist", metricName)
-}
-
-// ExpectMetricValue asserts that a metric has a specific value with given labels
-func ExpectMetricValue(metrics MetricsMap, metricName string, matchLabels map[string]string, expectedValue float64) {
-	value, found := getMetricValue(metrics, metricName, matchLabels)
-	Expect(found).To(BeTrue(), "metric %s with labels %v should exist", metricName, matchLabels)
-	Expect(value).To(Equal(expectedValue), "metric %s value mismatch", metricName)
-}
-
-// ExpectMetricGreaterThan asserts that a metric value is greater than a threshold
-func ExpectMetricGreaterThan(metrics MetricsMap, metricName string, matchLabels map[string]string, threshold float64) {
-	value, found := getMetricValue(metrics, metricName, matchLabels)
-	Expect(found).To(BeTrue(), "metric %s with labels %v should exist", metricName, matchLabels)
-	Expect(value).To(BeNumerically(">", threshold), "metric %s should be greater than %f", metricName, threshold)
-}
-

+ 1 - 0
e2e/run.sh

@@ -93,6 +93,7 @@ kubectl run --rm \
   --env="SECRETSERVER_URL=${SECRETSERVER_URL:-}" \
   --env="GRAFANA_URL=${GRAFANA_URL:-}" \
   --env="GRAFANA_TOKEN=${GRAFANA_TOKEN:-}" \
+  --env="E2E_PROVIDER_MODE=${E2E_PROVIDER_MODE:-}" \
   --env="VERSION=${VERSION}" \
   --env="TEST_SUITES=${TEST_SUITES}" \
   --overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \

+ 41 - 0
e2e/suites/provider/cases/kubernetes/capabilities_v2_test.go

@@ -0,0 +1,41 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+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
+
+    https://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 kubernetes
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	frameworkv2 "github.com/external-secrets/external-secrets-e2e/framework/v2"
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+var _ = Describe("[kubernetes] v2 capabilities", Label("kubernetes", "v2", "capabilities"), func() {
+	f := framework.New("eso-kubernetes-v2-capabilities")
+	NewProvider(f)
+
+	BeforeEach(func() {
+		if !framework.IsV2ProviderMode() {
+			Skip("v2 mode only")
+		}
+	})
+
+	It("reports READ_WRITE capabilities for the Kubernetes provider", func() {
+		frameworkv2.WaitForProviderConnectionReady(f, f.Namespace.Name, f.Namespace.Name, defaultV2WaitTimeout)
+		frameworkv2.VerifyProviderConnectionCapabilities(f, f.Namespace.Name, f.Namespace.Name, esv1.ProviderReadWrite)
+	})
+})

+ 14 - 2
e2e/suites/provider/cases/kubernetes/kubernetes.go

@@ -30,7 +30,15 @@ import (
 
 const referentAuth = "with referent auth"
 
-var _ = Describe("[kubernetes] ", Label("kubernetes"), func() {
+func describeLabels() []any {
+	labels := []any{Label("kubernetes")}
+	if framework.IsV2ProviderMode() {
+		labels = append(labels, Label("v2"))
+	}
+	return labels
+}
+
+var _ = Describe("[kubernetes] ", append(describeLabels(), func() {
 	f := framework.New("eso-kubernetes")
 	prov := NewProvider(f)
 
@@ -50,10 +58,14 @@ var _ = Describe("[kubernetes] ", Label("kubernetes"), func() {
 		framework.Compose(referentAuth, f, common.JSONDataWithProperty, withReferentStore),
 		framework.Compose(referentAuth, f, common.JSONDataWithoutTargetName, withReferentStore),
 	)
-})
+})...)
 
 func withReferentStore(tc *framework.TestCase) {
 	tc.ExternalSecret.Spec.SecretStoreRef.Name = referentStoreName(tc.Framework)
+	if framework.IsV2ProviderMode() {
+		tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterProviderKindStr
+		return
+	}
 	tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
 }
 

+ 200 - 0
e2e/suites/provider/cases/kubernetes/metrics_v2_test.go

@@ -0,0 +1,200 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+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
+
+    https://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 kubernetes
+
+import (
+	"context"
+	"fmt"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	frameworkv2 "github.com/external-secrets/external-secrets-e2e/framework/v2"
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+var _ = Describe("[kubernetes] v2 metrics", Label("kubernetes", "v2", "metrics"), func() {
+	f := framework.New("eso-kubernetes-v2-metrics")
+	NewProvider(f)
+
+	controllerClientMethods := []string{"GetSecret", "GetSecretMap"}
+	providerClientMethods := []string{
+		"/provider.v1.SecretStoreProvider/GetSecret",
+		"/provider.v1.SecretStoreProvider/GetSecretMap",
+	}
+
+	hasSuccessfulClientRequest := func(metrics frameworkv2.MetricsMap, methods []string) bool {
+		for _, method := range methods {
+			value, found := frameworkv2.GetMetricValue(metrics, "grpc_client_requests_total", map[string]string{
+				"method": method,
+				"status": "success",
+			})
+			if found && value >= 1.0 {
+				return true
+			}
+		}
+		return false
+	}
+
+	hasSuccessfulServerRequest := func(metrics frameworkv2.MetricsMap, methods []string) bool {
+		for _, method := range methods {
+			value, found := frameworkv2.GetMetricValue(metrics, "grpc_server_requests_total", map[string]string{
+				"method": method,
+				"status": "success",
+			})
+			if found && value >= 1.0 {
+				return true
+			}
+		}
+		return false
+	}
+
+	BeforeEach(func() {
+		if !framework.IsV2ProviderMode() {
+			Skip("v2 mode only")
+		}
+		frameworkv2.WaitForProviderConnectionReady(f, f.Namespace.Name, f.Namespace.Name, defaultV2WaitTimeout)
+		frameworkv2.WaitForClusterProviderReady(f, referentStoreName(f), defaultV2WaitTimeout)
+	})
+
+	It("exposes Provider and ClusterProvider controller metrics", func() {
+		metrics, err := frameworkv2.ScrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet, frameworkv2.ProviderNamespace)
+		Expect(err).ToNot(HaveOccurred())
+
+		frameworkv2.ExpectMetricExists(metrics, "provider_status_condition")
+		frameworkv2.ExpectMetricValue(metrics, "provider_status_condition", map[string]string{
+			"name":      f.Namespace.Name,
+			"namespace": f.Namespace.Name,
+			"condition": "Ready",
+			"status":    "True",
+		}, 1.0)
+		frameworkv2.ExpectMetricGreaterThan(metrics, "provider_reconcile_duration", map[string]string{
+			"name":      f.Namespace.Name,
+			"namespace": f.Namespace.Name,
+		}, 0.0)
+
+		frameworkv2.ExpectMetricExists(metrics, "clusterprovider_status_condition")
+		frameworkv2.ExpectMetricValue(metrics, "clusterprovider_status_condition", map[string]string{
+			"name":      referentStoreName(f),
+			"condition": "Ready",
+			"status":    "True",
+		}, 1.0)
+		frameworkv2.ExpectMetricGreaterThan(metrics, "clusterprovider_reconcile_duration", map[string]string{
+			"name": referentStoreName(f),
+		}, 0.0)
+	})
+
+	It("tracks client, server, and cache metrics during secret sync", func() {
+		externalSecretName := "test-es-metrics"
+		targetSecretName := "test-secret-metrics"
+		tcSecretOne := fmt.Sprintf("%s-one", f.Namespace.Name)
+		tcSecretTwo := fmt.Sprintf("%s-two", f.Namespace.Name)
+
+		secretOne := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      tcSecretOne,
+				Namespace: f.Namespace.Name,
+			},
+			Data: map[string][]byte{
+				"foo": []byte("bar"),
+			},
+		}
+		secretTwo := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      tcSecretTwo,
+				Namespace: f.Namespace.Name,
+			},
+			Data: map[string][]byte{
+				"baz": []byte("qux"),
+			},
+		}
+		Expect(f.CRClient.Create(context.Background(), secretOne)).To(Succeed())
+		Expect(f.CRClient.Create(context.Background(), secretTwo)).To(Succeed())
+
+		es := &esv1.ExternalSecret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      externalSecretName,
+				Namespace: f.Namespace.Name,
+			},
+			Spec: esv1.ExternalSecretSpec{
+				RefreshInterval: &metav1.Duration{Duration: defaultV2RefreshInterval},
+				SecretStoreRef: esv1.SecretStoreRef{
+					Name: f.Namespace.Name,
+					Kind: esv1.ProviderKindStr,
+				},
+				Target: esv1.ExternalSecretTarget{
+					Name: targetSecretName,
+				},
+				Data: []esv1.ExternalSecretData{
+					{
+						SecretKey: "one",
+						RemoteRef: esv1.ExternalSecretDataRemoteRef{
+							Key:      tcSecretOne,
+							Property: "foo",
+						},
+					},
+					{
+						SecretKey: "two",
+						RemoteRef: esv1.ExternalSecretDataRemoteRef{
+							Key:      tcSecretTwo,
+							Property: "baz",
+						},
+					},
+				},
+			},
+		}
+		Expect(f.CRClient.Create(context.Background(), es)).To(Succeed())
+
+		Eventually(func(g Gomega) {
+			var secret corev1.Secret
+			g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: targetSecretName, Namespace: f.Namespace.Name}, &secret)).To(Succeed())
+			g.Expect(secret.Data["one"]).To(Equal([]byte("bar")))
+			g.Expect(secret.Data["two"]).To(Equal([]byte("qux")))
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(Succeed())
+
+		Eventually(func() bool {
+			metrics, err := frameworkv2.ScrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet, frameworkv2.ProviderNamespace)
+			if err != nil {
+				return false
+			}
+			return hasSuccessfulClientRequest(metrics, controllerClientMethods)
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(BeTrue())
+
+		Eventually(func() bool {
+			metrics, err := frameworkv2.ScrapeProviderMetrics(context.Background(), f.KubeConfig, f.KubeClientSet, frameworkv2.ProviderNamespace, "kubernetes")
+			if err != nil {
+				return false
+			}
+			return hasSuccessfulServerRequest(metrics, providerClientMethods)
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(BeTrue())
+
+		Eventually(func() bool {
+			metrics, err := frameworkv2.ScrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet, frameworkv2.ProviderNamespace)
+			if err != nil {
+				return false
+			}
+			value, found := frameworkv2.GetMetricValue(metrics, "clientmanager_cache_hits_total", map[string]string{
+				"provider_type": "provider",
+			})
+			return found && value >= 1.0
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(BeTrue())
+	})
+})

+ 99 - 4
e2e/suites/provider/cases/kubernetes/provider.go

@@ -19,6 +19,7 @@ package kubernetes
 import (
 	"encoding/json"
 	"fmt"
+	"time"
 
 	// nolint
 	. "github.com/onsi/ginkgo/v2"
@@ -31,10 +32,13 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/external-secrets/external-secrets-e2e/framework"
+	frameworkv2 "github.com/external-secrets/external-secrets-e2e/framework/v2"
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 )
 
+const kubernetesProviderAPIVersion = "provider.external-secrets.io/v2alpha1"
+
 type Provider struct {
 	framework *framework.Framework
 }
@@ -68,6 +72,12 @@ func (s *Provider) CreateSecret(key string, val framework.SecretEntry) {
 }
 
 func (s *Provider) BeforeEach() {
+	if framework.IsV2ProviderMode() {
+		s.CreateStoreV2()
+		s.CreateReferentStoreV2()
+		return
+	}
+
 	s.CreateStore()
 	s.CreateReferentStore()
 }
@@ -153,12 +163,12 @@ func makeDefaultStore(suffix, namespace string) (*rbac.Role, *rbac.RoleBinding,
 }
 
 func (s *Provider) CreateStore() {
-	rb, role, store := makeDefaultStore("", s.framework.Namespace.Name)
+	role, roleBinding, store := makeDefaultStore("", s.framework.Namespace.Name)
 
 	err := s.framework.CRClient.Create(GinkgoT().Context(), role)
 	Expect(err).ToNot(HaveOccurred())
 
-	err = s.framework.CRClient.Create(GinkgoT().Context(), rb)
+	err = s.framework.CRClient.Create(GinkgoT().Context(), roleBinding)
 	Expect(err).ToNot(HaveOccurred())
 
 	err = s.framework.CRClient.Create(GinkgoT().Context(), store)
@@ -166,7 +176,7 @@ func (s *Provider) CreateStore() {
 }
 
 func (s *Provider) CreateReferentStore() {
-	rb, role, store := makeDefaultStore("referent", s.framework.Namespace.Name)
+	role, roleBinding, store := makeDefaultStore("referent", s.framework.Namespace.Name)
 	// ServiceAccount Namespace is not set, this will be inferred
 	// from the ExternalSecret
 	css := &esv1.ClusterSecretStore{
@@ -180,13 +190,98 @@ func (s *Provider) CreateReferentStore() {
 	err := s.framework.CRClient.Create(GinkgoT().Context(), role)
 	Expect(err).ToNot(HaveOccurred())
 
-	err = s.framework.CRClient.Create(GinkgoT().Context(), rb)
+	err = s.framework.CRClient.Create(GinkgoT().Context(), roleBinding)
 	Expect(err).ToNot(HaveOccurred())
 
 	err = s.framework.CRClient.Create(GinkgoT().Context(), css)
 	Expect(err).ToNot(HaveOccurred())
 }
 
+func (s *Provider) CreateStoreV2() {
+	namespace := s.framework.Namespace.Name
+
+	frameworkv2.CreateKubernetesAccessRole(
+		s.framework,
+		storeAccessName(namespace, ""),
+		frameworkv2.DefaultSAName,
+		namespace,
+		namespace,
+	)
+
+	serviceAccountNamespace := namespace
+	frameworkv2.CreateKubernetesProvider(
+		s.framework,
+		namespace,
+		providerConfigName(namespace, ""),
+		namespace,
+		frameworkv2.DefaultSAName,
+		&serviceAccountNamespace,
+		frameworkv2.GetClusterCABundle(s.framework, namespace),
+	)
+
+	frameworkv2.CreateProviderConnection(
+		s.framework,
+		namespace,
+		namespace,
+		frameworkv2.ProviderAddress("kubernetes"),
+		kubernetesProviderAPIVersion,
+		"Kubernetes",
+		providerConfigName(namespace, ""),
+		namespace,
+	)
+	frameworkv2.WaitForProviderConnectionReady(s.framework, namespace, namespace, 30*time.Second)
+}
+
+func (s *Provider) CreateReferentStoreV2() {
+	namespace := s.framework.Namespace.Name
+	referentName := referentStoreName(s.framework)
+
+	frameworkv2.CreateKubernetesAccessRole(
+		s.framework,
+		storeAccessName(namespace, "referent"),
+		frameworkv2.DefaultSAName,
+		namespace,
+		namespace,
+	)
+
+	frameworkv2.CreateKubernetesProvider(
+		s.framework,
+		namespace,
+		providerConfigName(namespace, "referent"),
+		namespace,
+		frameworkv2.DefaultSAName,
+		nil,
+		frameworkv2.GetClusterCABundle(s.framework, namespace),
+	)
+
+	frameworkv2.CreateClusterProviderConnection(
+		s.framework,
+		referentName,
+		frameworkv2.ProviderAddress("kubernetes"),
+		kubernetesProviderAPIVersion,
+		"Kubernetes",
+		providerConfigName(namespace, "referent"),
+		namespace,
+		esv1.AuthenticationScopeManifestNamespace,
+		nil,
+	)
+	frameworkv2.WaitForClusterProviderReady(s.framework, referentName, 30*time.Second)
+}
+
 func referentStoreName(f *framework.Framework) string {
 	return fmt.Sprintf("%s-referent", f.Namespace.Name)
 }
+
+func providerConfigName(namespace, suffix string) string {
+	if suffix == "" {
+		return fmt.Sprintf("%s-kubernetes", namespace)
+	}
+	return fmt.Sprintf("%s-kubernetes-%s", namespace, suffix)
+}
+
+func storeAccessName(namespace, suffix string) string {
+	if suffix == "" {
+		return fmt.Sprintf("%s-provider-access", namespace)
+	}
+	return fmt.Sprintf("%s-provider-access-%s", namespace, suffix)
+}

+ 183 - 0
e2e/suites/provider/cases/kubernetes/push_v2_test.go

@@ -0,0 +1,183 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+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
+
+    https://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 kubernetes
+
+import (
+	"context"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	"github.com/external-secrets/external-secrets-e2e/framework"
+	frameworkv2 "github.com/external-secrets/external-secrets-e2e/framework/v2"
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+)
+
+var _ = Describe("[kubernetes] v2 push secret", Label("kubernetes", "v2", "push-secret"), func() {
+	f := framework.New("eso-kubernetes-v2-push")
+	NewProvider(f)
+
+	BeforeEach(func() {
+		if !framework.IsV2ProviderMode() {
+			Skip("v2 mode only")
+		}
+		frameworkv2.WaitForProviderConnectionReady(f, f.Namespace.Name, f.Namespace.Name, defaultV2WaitTimeout)
+	})
+
+	It("pushes a secret to the Kubernetes provider", func() {
+		sourceSecret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "source-secret",
+				Namespace: f.Namespace.Name,
+			},
+			Data: map[string][]byte{
+				"username": []byte("admin"),
+				"password": []byte("secret123"),
+			},
+		}
+		Expect(f.CRClient.Create(context.Background(), sourceSecret)).To(Succeed())
+
+		pushSecret := &esv1alpha1.PushSecret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-pushsecret",
+				Namespace: f.Namespace.Name,
+			},
+			Spec: esv1alpha1.PushSecretSpec{
+				RefreshInterval: &metav1.Duration{Duration: defaultV2RefreshInterval},
+				SecretStoreRefs: []esv1alpha1.PushSecretStoreRef{
+					{
+						Name:       f.Namespace.Name,
+						Kind:       f.DefaultPushSecretStoreRefKind,
+						APIVersion: f.DefaultPushSecretStoreRefAPIVersion,
+					},
+				},
+				Selector: esv1alpha1.PushSecretSelector{
+					Secret: &esv1alpha1.PushSecretSecret{
+						Name: sourceSecret.Name,
+					},
+				},
+				Data: []esv1alpha1.PushSecretData{
+					{
+						Match: esv1alpha1.PushSecretMatch{
+							SecretKey: "username",
+							RemoteRef: esv1alpha1.PushSecretRemoteRef{
+								RemoteKey: "pushed-secret",
+								Property:  "username",
+							},
+						},
+					},
+					{
+						Match: esv1alpha1.PushSecretMatch{
+							SecretKey: "password",
+							RemoteRef: esv1alpha1.PushSecretRemoteRef{
+								RemoteKey: "pushed-secret",
+								Property:  "password",
+							},
+						},
+					},
+				},
+			},
+		}
+		Expect(f.CRClient.Create(context.Background(), pushSecret)).To(Succeed())
+
+		Eventually(func(g Gomega) {
+			var ps esv1alpha1.PushSecret
+			g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: pushSecret.Name, Namespace: f.Namespace.Name}, &ps)).To(Succeed())
+			g.Expect(ps.Status.Conditions).NotTo(BeEmpty())
+			ready := false
+			for _, condition := range ps.Status.Conditions {
+				if condition.Type == esv1alpha1.PushSecretReady && condition.Status == corev1.ConditionTrue {
+					ready = true
+				}
+			}
+			g.Expect(ready).To(BeTrue())
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(Succeed())
+
+		var pushedSecret corev1.Secret
+		Eventually(func(g Gomega) {
+			g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: "pushed-secret", Namespace: f.Namespace.Name}, &pushedSecret)).To(Succeed())
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(Succeed())
+
+		Expect(string(pushedSecret.Data["username"])).To(Equal("admin"))
+		Expect(string(pushedSecret.Data["password"])).To(Equal("secret123"))
+	})
+
+	It("deletes pushed secrets when DeletionPolicy=Delete", func() {
+		sourceSecret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "source-secret-delete",
+				Namespace: f.Namespace.Name,
+			},
+			Data: map[string][]byte{
+				"key1": []byte("value1"),
+			},
+		}
+		Expect(f.CRClient.Create(context.Background(), sourceSecret)).To(Succeed())
+
+		pushSecret := &esv1alpha1.PushSecret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-pushsecret-delete",
+				Namespace: f.Namespace.Name,
+			},
+			Spec: esv1alpha1.PushSecretSpec{
+				RefreshInterval: &metav1.Duration{Duration: defaultV2RefreshInterval},
+				DeletionPolicy:  esv1alpha1.PushSecretDeletionPolicyDelete,
+				SecretStoreRefs: []esv1alpha1.PushSecretStoreRef{
+					{
+						Name:       f.Namespace.Name,
+						Kind:       f.DefaultPushSecretStoreRefKind,
+						APIVersion: f.DefaultPushSecretStoreRefAPIVersion,
+					},
+				},
+				Selector: esv1alpha1.PushSecretSelector{
+					Secret: &esv1alpha1.PushSecretSecret{
+						Name: sourceSecret.Name,
+					},
+				},
+				Data: []esv1alpha1.PushSecretData{
+					{
+						Match: esv1alpha1.PushSecretMatch{
+							SecretKey: "key1",
+							RemoteRef: esv1alpha1.PushSecretRemoteRef{
+								RemoteKey: "pushed-secret-delete",
+								Property:  "key1",
+							},
+						},
+					},
+				},
+			},
+		}
+		Expect(f.CRClient.Create(context.Background(), pushSecret)).To(Succeed())
+
+		Eventually(func(g Gomega) {
+			var pushedSecret corev1.Secret
+			g.Expect(f.CRClient.Get(context.Background(), types.NamespacedName{Name: "pushed-secret-delete", Namespace: f.Namespace.Name}, &pushedSecret)).To(Succeed())
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(Succeed())
+
+		Expect(f.CRClient.Delete(context.Background(), pushSecret)).To(Succeed())
+
+		Eventually(func() bool {
+			var pushedSecret corev1.Secret
+			err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: "pushed-secret-delete", Namespace: f.Namespace.Name}, &pushedSecret)
+			return apierrors.IsNotFound(err)
+		}, defaultV2WaitTimeout, defaultV2PollInterval).Should(BeTrue())
+	})
+})

+ 9 - 11
e2e/suites/v2/suite_test.go → e2e/suites/provider/cases/kubernetes/v2_constants_test.go

@@ -1,9 +1,11 @@
 /*
+Copyright © 2025 ESO Maintainer Team
+
 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
+    https://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,
@@ -12,16 +14,12 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package v2
+package kubernetes
 
-import (
-	"testing"
+import "time"
 
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
+const (
+	defaultV2WaitTimeout     = 60 * time.Second
+	defaultV2PollInterval    = 2 * time.Second
+	defaultV2RefreshInterval = 10 * time.Second
 )
-
-func TestV2Suite(t *testing.T) {
-	RegisterFailHandler(Fail)
-	RunSpecs(t, "V2 E2E Suite")
-}

+ 11 - 0
e2e/suites/provider/suite_test.go

@@ -25,6 +25,7 @@ import (
 	// nolint
 	. "github.com/onsi/gomega"
 
+	"github.com/external-secrets/external-secrets-e2e/framework"
 	"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/suites/provider/cases"
@@ -33,6 +34,16 @@ import (
 )
 
 var _ = SynchronizedBeforeSuite(func() []byte {
+	if framework.IsV2ProviderMode() {
+		By("installing eso in provider v2 mode")
+		addon.InstallGlobalAddon(addon.NewESO(
+			addon.WithCRDs(),
+			addon.WithV2Namespace(),
+			addon.WithV2KubernetesProvider(),
+		))
+		return nil
+	}
+
 	By("installing eso")
 	addon.InstallGlobalAddon(addon.NewESO(addon.WithCRDs()))
 

+ 0 - 322
e2e/suites/v2/README.md

@@ -1,322 +0,0 @@
-# External Secrets Operator V2 E2E Test Suite
-
-This directory contains End-to-End tests for ESO V2.
-
-## Test Coverage
-
-### ✅ Implemented Tests
-
-| Test Case | Description | Status |
-|-----------|-------------|--------|
-| **Basic Secret Sync** | Cross-namespace secret synchronization | ✅ |
-| **Key Extraction** | Extract specific keys with `data[]` | ✅ |
-| **DataFrom** | Full secret extraction with `dataFrom[]` | ✅ |
-| **Secret Updates** | Automatic refresh when source changes | ✅ |
-| **Deletion Cleanup** | Owner policy cleanup on deletion | ✅ |
-| **Error Handling** | Error conditions for missing secrets | ✅ |
-
-### 🚧 Future Test Coverage
-
-| Test Case | Priority | Notes |
-|-----------|----------|-------|
-| ClusterSecretStore | P1 | Cross-namespace store |
-| Multiple providers | P1 | AWS, GCP, Azure providers |
-| Secret templates | P2 | Template transformations |
-| Generators | P2 | Generator integration |
-| PushSecret | P2 | Reverse sync |
-| Concurrency | P2 | Multiple ExternalSecrets |
-| TLS/mTLS | P3 | Provider authentication |
-| Metrics | P3 | Prometheus metrics validation |
-| Performance | P3 | Latency and throughput |
-
-## Running Tests
-
-### All V2 Tests
-
-```bash
-make test.e2e.v2
-```
-
-### Specific Tests
-
-```bash
-cd e2e
-
-# Run single test
-ginkgo -v --focus="should sync secrets across namespaces" ./suites/v2/
-
-# Run with labels
-ginkgo -v --label-filter="v2 && !slow" ./suites/v2/
-
-# Verbose output
-ginkgo -vv ./suites/v2/
-```
-
-## Test Architecture
-
-### Framework Components
-
-```
-┌─────────────────────────────────────┐
-│       Ginkgo Test Suite             │
-│   (suite_test.go, v2_test.go)       │
-└─────────────────┬───────────────────┘
-                  │
-                  ▼
-┌─────────────────────────────────────┐
-│      E2E Framework                  │
-│   (framework/framework.go)          │
-│   - Kubernetes client               │
-│   - Test utilities                  │
-└─────────────────┬───────────────────┘
-                  │
-                  ▼
-┌─────────────────────────────────────┐
-│      ESO V2 Addon                   │
-│   (framework/addon/eso_v2.go)       │
-│   - Controller installation         │
-│   - Provider installation           │
-│   - RBAC setup                      │
-└─────────────────────────────────────┘
-```
-
-### Test Flow
-
-```
-1. BeforeSuite
-   ├── Initialize framework
-   ├── Install ESO V2 controller
-   ├── Install Kubernetes provider
-   └── Wait for ready
-
-2. BeforeEach (per test)
-   ├── Create test namespace
-   └── Create source secret
-
-3. Test Execution
-   ├── Create SecretStore
-   ├── Create ExternalSecret
-   ├── Wait for sync (Eventually)
-   └── Assert results (Gomega)
-
-4. AfterEach (per test)
-   └── Delete test namespace
-
-5. AfterSuite
-   └── Uninstall ESO V2
-```
-
-## Test Patterns
-
-### Creating Resources
-
-```go
-secretStore := &ssv2alpha1.SecretStore{
-    ObjectMeta: metav1.ObjectMeta{
-        Name:      "test-store",
-        Namespace: namespace,
-    },
-    Spec: ssv2alpha1.SecretStoreSpec{
-        Provider: ssv2alpha1.ProviderConfig{
-            Address: "kubernetes-provider:5000",
-        },
-    },
-}
-Expect(f.CRClient.Create(ctx, secretStore)).To(Succeed())
-```
-
-### Waiting for Conditions
-
-```go
-Eventually(func() bool {
-    var ss ssv2alpha1.SecretStore
-    err := f.CRClient.Get(ctx, client.ObjectKeyFromObject(secretStore), &ss)
-    if err != nil {
-        return false
-    }
-    
-    for _, cond := range ss.Status.Conditions {
-        if cond.Type == "Ready" && cond.Status == metav1.ConditionTrue {
-            return true
-        }
-    }
-    return false
-}, 30*time.Second, 1*time.Second).Should(BeTrue())
-```
-
-### Validating Data
-
-```go
-var targetSecret corev1.Secret
-Expect(f.CRClient.Get(ctx, targetKey, &targetSecret)).To(Succeed())
-Expect(targetSecret.Data).To(HaveKeyWithValue("username", []byte("admin")))
-```
-
-## Debugging
-
-### Enable Verbose Logging
-
-```bash
-# Ginkgo verbose
-ginkgo -v ./suites/v2/
-
-# Very verbose (includes test internals)
-ginkgo -vv ./suites/v2/
-
-# Trace level (framework logs)
-ginkgo -v -trace ./suites/v2/
-```
-
-### View Controller Logs
-
-```bash
-kubectl logs -n external-secrets-system \
-  -l app.kubernetes.io/name=external-secrets-v2 \
-  --tail=100 -f
-```
-
-### View Provider Logs
-
-```bash
-kubectl logs -n external-secrets-system \
-  -l app.kubernetes.io/name=kubernetes-provider \
-  --tail=100 -f
-```
-
-### Inspect Resources
-
-```bash
-# List all test namespaces
-kubectl get ns | grep v2-test
-
-# Check SecretStores
-kubectl get secretstore --all-namespaces
-
-# Check ExternalSecrets
-kubectl get externalsecret --all-namespaces
-
-# Describe for details
-kubectl describe externalsecret <name> -n <namespace>
-```
-
-### Keep Environment After Failure
-
-```bash
-# Run without cleanup
-ginkgo -v ./suites/v2/ || true
-
-# Inspect manually
-kubectl get all --all-namespaces | grep v2-test
-
-# Manual cleanup when done
-kubectl delete ns external-secrets-system
-```
-
-## Adding New Tests
-
-### Test Template
-
-```go
-It("should <test description>", Label("v2", "feature-name"), func() {
-    By("step 1: setup")
-    // Create resources
-    
-    By("step 2: action")
-    // Trigger behavior
-    
-    By("step 3: verification")
-    Eventually(func() bool {
-        // Check condition
-        return true
-    }, timeout, interval).Should(BeTrue())
-    
-    By("step 4: assert")
-    // Final assertions
-    Expect(actual).To(Equal(expected))
-})
-```
-
-### Checklist
-
-- [ ] Descriptive test name
-- [ ] Appropriate labels
-- [ ] Clear `By()` steps
-- [ ] Use `Eventually()` for async
-- [ ] Proper cleanup in `AfterEach()`
-- [ ] Meaningful assertions
-- [ ] Error messages for failures
-
-## CI Integration
-
-Tests run automatically on:
-- Pull requests touching V2 code
-- Nightly builds
-- Release branches
-
-### GitHub Actions
-
-```yaml
-- name: Run V2 E2E
-  run: make test.e2e.v2
-```
-
-### Required Checks
-
-- All tests pass
-- No resource leaks
-- Controller logs clean
-- No memory/CPU spikes
-
-## Metrics
-
-### Current Stats
-
-- **Test Files**: 2
-- **Test Cases**: 6
-- **Coverage**: Core functionality
-- **Duration**: ~2-3 minutes
-
-### Performance Benchmarks
-
-| Operation | P50 | P95 | P99 |
-|-----------|-----|-----|-----|
-| SecretStore ready | 2s | 5s | 10s |
-| ExternalSecret sync | 3s | 8s | 15s |
-| Secret update | 5s | 12s | 20s |
-
-## Troubleshooting
-
-### Test Fails: "SecretStore not ready"
-
-**Cause**: Provider not reachable  
-**Fix**: Check provider pod status
-
-```bash
-kubectl get pods -n external-secrets-system
-kubectl logs -n external-secrets-system -l app.kubernetes.io/name=kubernetes-provider
-```
-
-### Test Fails: "Secret not synced"
-
-**Cause**: Source secret missing or permissions  
-**Fix**: Verify source secret exists
-
-```bash
-kubectl get secret -n <source-namespace>
-kubectl get sa -n external-secrets-system
-```
-
-### Test Timeout
-
-**Cause**: Slow cluster or image pull  
-**Fix**: Increase timeout or pre-pull images
-
-```go
-Eventually(..., 60*time.Second, ...).Should(...)
-```
-
-## Resources
-
-- [V2 E2E Testing Guide](../../../docs/contributing/v2/e2e-testing.md)
-- [V2 Design Doc](../../../design/014-secretstore-generator-v2.md)
-- [Ginkgo Documentation](https://onsi.github.io/ginkgo/)

+ 0 - 408
e2e/suites/v2/fake_cluster_provider_test.go

@@ -1,408 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-	v1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("[v2] Fake ClusterProvider", Label("v2", "fake", "cluster-provider"), func() {
-	f := framework.New("v2-fake-cluster-provider")
-
-	Context("GetSecret with ClusterProvider", func() {
-		var testNamespace *corev1.Namespace
-
-		BeforeEach(func() {
-			testNamespace = SetupTestNamespace(f, "v2-fake-cluster-")
-
-			// Create Fake provider with test data
-			CreateFakeProvider(f, testNamespace.Name, "fake-provider-cluster", []v1.FakeProviderData{
-				{Key: "cluster-username", Value: "cluster-user"},
-				{Key: "cluster-password", Value: "cluster-password"},
-				{Key: "cluster-token", Value: "cluster-token-12345"},
-			})
-		})
-
-		AfterEach(func() {
-			if testNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-			}
-		})
-
-		It("should sync secrets from ClusterProvider", func() {
-			By("creating a ClusterProvider pointing to Fake provider")
-			CreateClusterProvider(f, "cluster-fake-provider",
-				"provider-fake.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1",
-				"Fake",
-				"fake-provider-cluster",
-				testNamespace.Name,
-				esv1.AuthenticationScopeProviderNamespace,
-				nil)
-
-			By("waiting for ClusterProvider to be ready")
-			WaitForClusterProviderReady(f, "cluster-fake-provider", 30*time.Second)
-
-			By("creating an ExternalSecret")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-cluster",
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: "cluster-fake-provider",
-						Kind: "ClusterProvider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "synced-cluster-secret",
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "cluster-username",
-							},
-						},
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "cluster-password",
-							},
-						},
-						{
-							SecretKey: "token",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "cluster-token",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), es)).To(Succeed())
-
-			By("waiting for secret to be synced")
-			var syncedSecret corev1.Secret
-			Eventually(func() bool {
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "synced-cluster-secret", Namespace: testNamespace.Name},
-					&syncedSecret)
-				return err == nil
-			}, 30*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("verifying the synced secret data")
-			Expect(syncedSecret.Data["username"]).To(Equal([]byte("cluster-user")))
-			Expect(syncedSecret.Data["password"]).To(Equal([]byte("cluster-password")))
-			Expect(syncedSecret.Data["token"]).To(Equal([]byte("cluster-token-12345")))
-		})
-
-		It("should work from multiple namespaces", func() {
-			testNamespace2 := SetupTestNamespace(f, "v2-fake-cluster-2-")
-			defer func() {
-				Expect(f.CRClient.Delete(context.Background(), testNamespace2)).To(Succeed())
-			}()
-
-			By("creating a ClusterProvider")
-			CreateClusterProvider(f, "cluster-fake-multi-ns",
-				"provider-fake.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1",
-				"Fake",
-				"fake-provider-cluster",
-				testNamespace.Name,
-				esv1.AuthenticationScopeProviderNamespace,
-				nil)
-
-			WaitForClusterProviderReady(f, "cluster-fake-multi-ns", 30*time.Second)
-
-			By("creating ExternalSecrets in both namespaces")
-			for _, ns := range []string{testNamespace.Name, testNamespace2.Name} {
-				es := &esv1.ExternalSecret{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test-es-multi",
-						Namespace: ns,
-					},
-					Spec: esv1.ExternalSecretSpec{
-						SecretStoreRef: esv1.SecretStoreRef{
-							Name: "cluster-fake-multi-ns",
-							Kind: "ClusterProvider",
-						},
-						Target: esv1.ExternalSecretTarget{
-							Name: "multi-ns-secret",
-						},
-						Data: []esv1.ExternalSecretData{
-							{
-								SecretKey: "username",
-								RemoteRef: esv1.ExternalSecretDataRemoteRef{
-									Key: "cluster-username",
-								},
-							},
-						},
-					},
-				}
-				Expect(f.CRClient.Create(context.Background(), es)).To(Succeed())
-			}
-
-			By("verifying secrets are synced in both namespaces")
-			for _, ns := range []string{testNamespace.Name, testNamespace2.Name} {
-				var syncedSecret corev1.Secret
-				Eventually(func() bool {
-					err := f.CRClient.Get(context.Background(),
-						types.NamespacedName{Name: "multi-ns-secret", Namespace: ns},
-						&syncedSecret)
-					return err == nil
-				}, 30*time.Second, 1*time.Second).Should(BeTrue(), "Secret should be synced in namespace "+ns)
-
-				Expect(syncedSecret.Data["username"]).To(Equal([]byte("cluster-user")))
-			}
-		})
-	})
-
-	Context("Generator Support with ClusterProvider", func() {
-		var testNamespace *corev1.Namespace
-
-		BeforeEach(func() {
-			testNamespace = SetupTestNamespace(f, "v2-fake-cluster-gen-")
-			CreateFakeProvider(f, testNamespace.Name, "fake-provider-gen", []v1.FakeProviderData{})
-		})
-
-		AfterEach(func() {
-			if testNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-			}
-		})
-
-		It("should generate secrets from Fake generator with ClusterProvider", func() {
-			By("creating a Fake generator")
-			CreateFakeGenerator(f, testNamespace.Name, "test-cluster-generator", map[string]string{
-				"gen-username": "generated-cluster-user",
-				"gen-password": "generated-cluster-password",
-				"gen-api-key":  "generated-cluster-api-key",
-			})
-
-			By("creating a ClusterProvider for generator support")
-			CreateClusterProvider(f, "cluster-fake-generator",
-				"provider-fake.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1",
-				"Fake",
-				"fake-provider-gen",
-				testNamespace.Name,
-				esv1.AuthenticationScopeProviderNamespace,
-				nil)
-
-			WaitForClusterProviderReady(f, "cluster-fake-generator", 30*time.Second)
-
-			By("creating an ExternalSecret with dataFrom referencing the generator")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-cluster-generator",
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: "cluster-fake-generator",
-						Kind: "ClusterProvider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "cluster-generated-secret",
-					},
-					DataFrom: []esv1.ExternalSecretDataFromRemoteRef{
-						{
-							SourceRef: &esv1.StoreGeneratorSourceRef{
-								GeneratorRef: &esv1.GeneratorRef{
-									APIVersion: "generators.external-secrets.io/v1alpha1",
-									Kind:       "Fake",
-									Name:       "test-cluster-generator",
-								},
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), es)).To(Succeed())
-
-			By("waiting for secret to be synced")
-			var syncedSecret corev1.Secret
-			Eventually(func() bool {
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "cluster-generated-secret", Namespace: testNamespace.Name},
-					&syncedSecret)
-				return err == nil
-			}, 30*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("verifying the generated secret data")
-			Expect(syncedSecret.Data["gen-username"]).To(Equal([]byte("generated-cluster-user")))
-			Expect(syncedSecret.Data["gen-password"]).To(Equal([]byte("generated-cluster-password")))
-			Expect(syncedSecret.Data["gen-api-key"]).To(Equal([]byte("generated-cluster-api-key")))
-		})
-	})
-
-	Context("Namespace Conditions", func() {
-		var (
-			testNamespaceAllowed *corev1.Namespace
-			testNamespaceDenied  *corev1.Namespace
-		)
-
-		BeforeEach(func() {
-			testNamespaceAllowed = SetupTestNamespace(f, "v2-fake-cluster-allowed-")
-			testNamespaceDenied = SetupTestNamespace(f, "v2-fake-cluster-denied-")
-
-			// Label the allowed namespace
-			testNamespaceAllowed.Labels = map[string]string{"team": "platform"}
-			Expect(f.CRClient.Update(context.Background(), testNamespaceAllowed)).To(Succeed())
-
-			// Create Fake provider
-			CreateFakeProvider(f, testNamespaceAllowed.Name, "fake-provider-conditions", []v1.FakeProviderData{
-				{Key: "test-key", Value: "test-value"},
-			})
-		})
-
-		AfterEach(func() {
-			if testNamespaceAllowed != nil {
-				Expect(f.CRClient.Delete(context.Background(), testNamespaceAllowed)).To(Succeed())
-			}
-			if testNamespaceDenied != nil {
-				Expect(f.CRClient.Delete(context.Background(), testNamespaceDenied)).To(Succeed())
-			}
-		})
-
-		It("should enforce namespace label selectors", func() {
-			By("creating a ClusterProvider with namespace selector")
-			conditions := []esv1.ClusterSecretStoreCondition{
-				{
-					NamespaceSelector: &metav1.LabelSelector{
-						MatchLabels: map[string]string{"team": "platform"},
-					},
-				},
-			}
-			CreateClusterProvider(f, "cluster-fake-labeled",
-				"provider-fake.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1",
-				"Fake",
-				"fake-provider-conditions",
-				testNamespaceAllowed.Name,
-				esv1.AuthenticationScopeProviderNamespace,
-				conditions)
-
-			WaitForClusterProviderReady(f, "cluster-fake-labeled", 30*time.Second)
-
-			By("creating ExternalSecret in allowed namespace")
-			esAllowed := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-allowed",
-					Namespace: testNamespaceAllowed.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: "cluster-fake-labeled",
-						Kind: "ClusterProvider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "allowed-secret",
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "test-key",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "test-key",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), esAllowed)).To(Succeed())
-
-			By("verifying ExternalSecret in allowed namespace succeeds")
-			var allowedSecret corev1.Secret
-			Eventually(func() bool {
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "allowed-secret", Namespace: testNamespaceAllowed.Name},
-					&allowedSecret)
-				return err == nil
-			}, 30*time.Second, 1*time.Second).Should(BeTrue())
-
-			Expect(allowedSecret.Data["test-key"]).To(Equal([]byte("test-value")))
-
-			By("creating ExternalSecret in denied namespace")
-			esDenied := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-denied",
-					Namespace: testNamespaceDenied.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: "cluster-fake-labeled",
-						Kind: "ClusterProvider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "denied-secret",
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "test-key",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "test-key",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), esDenied)).To(Succeed())
-
-			By("verifying ExternalSecret in denied namespace fails")
-			// First wait for the ExternalSecret to be reconciled and have a condition
-			Eventually(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-es-denied", Namespace: testNamespaceDenied.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-				return len(es.Status.Conditions) > 0
-			}, 10*time.Second, 1*time.Second).Should(BeTrue(), "ExternalSecret should have conditions")
-
-			// Then verify it stays in error state
-			Consistently(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-es-denied", Namespace: testNamespaceDenied.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-				// Check for error condition
-				for _, condition := range es.Status.Conditions {
-					if condition.Type == "Ready" {
-						// Should be False (error state)
-						return condition.Status == corev1.ConditionFalse
-					}
-				}
-				return false
-			}, 5*time.Second, 1*time.Second).Should(BeTrue(), "ExternalSecret should have error condition")
-		})
-	})
-})
-

+ 0 - 274
e2e/suites/v2/fake_test.go

@@ -1,274 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-	v1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("[v2] Fake Provider", Label("v2", "fake"), func() {
-	f := framework.New("v2-fake-provider")
-
-	Context("GetSecret", func() {
-		var testNamespace *corev1.Namespace
-
-		BeforeEach(func() {
-			testNamespace = SetupTestNamespace(f, "v2-fake-")
-
-			// Create Fake provider with test data
-			CreateFakeProvider(f, testNamespace.Name, "fake-provider", []v1.FakeProviderData{
-				{Key: "username", Value: "test-user"},
-				{Key: "password", Value: "test-password"},
-			})
-
-			// Create ProviderConnection
-			CreateFakeProviderConnection(f, testNamespace.Name, "test-secretstore", "fake-provider", testNamespace.Name)
-		})
-
-		AfterEach(func() {
-			if testNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-			}
-		})
-
-		It("should sync secrets from Fake provider", func() {
-			By("waiting for ProviderConnection to be ready")
-			WaitForProviderConnectionReady(f, testNamespace.Name, "test-secretstore", 30*time.Second)
-
-			By("creating an ExternalSecret")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es",
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: "test-secretstore",
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "synced-secret",
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "username",
-							},
-						},
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "password",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), es)).To(Succeed())
-
-			By("waiting for secret to be synced")
-			var syncedSecret corev1.Secret
-			Eventually(func() bool {
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "synced-secret", Namespace: testNamespace.Name},
-					&syncedSecret)
-				return err == nil
-			}, 30*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("verifying the synced secret data")
-			Expect(syncedSecret.Data["username"]).To(Equal([]byte("test-user")))
-			Expect(syncedSecret.Data["password"]).To(Equal([]byte("test-password")))
-		})
-	})
-
-	Context("Capabilities", func() {
-		var testNamespace *corev1.Namespace
-
-		BeforeEach(func() {
-			testNamespace = SetupTestNamespace(f, "v2-fake-capabilities-")
-
-			CreateFakeProvider(f, testNamespace.Name, "fake-provider", []v1.FakeProviderData{
-				{Key: "test-key", Value: "test-value"},
-			})
-
-			CreateFakeProviderConnection(f, testNamespace.Name, "test-secretstore", "fake-provider", testNamespace.Name)
-		})
-
-		AfterEach(func() {
-			if testNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-			}
-		})
-
-		It("should report READ_WRITE capabilities", func() {
-			By("waiting for ProviderConnection to be ready")
-			WaitForProviderConnectionReady(f, testNamespace.Name, "test-secretstore", 30*time.Second)
-
-			By("verifying capabilities")
-			VerifyProviderConnectionCapabilities(f, testNamespace.Name, "test-secretstore", esv1.ProviderReadWrite)
-		})
-	})
-
-	Context("Generator Support", func() {
-		var testNamespace *corev1.Namespace
-
-		BeforeEach(func() {
-			testNamespace = SetupTestNamespace(f, "v2-fake-generator-")
-			CreateFakeProvider(f, testNamespace.Name, "fake-provider", []v1.FakeProviderData{})
-		})
-
-		AfterEach(func() {
-			if testNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-			}
-		})
-
-		It("should generate secrets from Fake generator", func() {
-			By("creating a Fake generator")
-			CreateFakeGenerator(f, testNamespace.Name, "test-generator", map[string]string{
-				"username": "generated-user",
-				"password": "generated-password",
-				"token":    "generated-token",
-			})
-
-			By("creating a ProviderConnection to the fake provider for generator support")
-			CreateFakeProviderConnection(f, testNamespace.Name, "fake-generator-connection", "fake-provider", testNamespace.Name)
-
-			By("waiting for ProviderConnection to be ready")
-			WaitForProviderConnectionReady(f, testNamespace.Name, "fake-generator-connection", 30*time.Second)
-
-			By("creating an ExternalSecret with dataFrom referencing the generator")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-generator",
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: "fake-generator-connection",
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "generated-secret",
-					},
-					DataFrom: []esv1.ExternalSecretDataFromRemoteRef{
-						{
-							SourceRef: &esv1.StoreGeneratorSourceRef{
-								GeneratorRef: &esv1.GeneratorRef{
-									APIVersion: "generators.external-secrets.io/v1alpha1",
-									Kind:       "Fake",
-									Name:       "test-generator",
-								},
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), es)).To(Succeed())
-
-			By("waiting for secret to be synced")
-			var syncedSecret corev1.Secret
-			Eventually(func() bool {
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "generated-secret", Namespace: testNamespace.Name},
-					&syncedSecret)
-				return err == nil
-			}, 30*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("verifying the generated secret data")
-			Expect(syncedSecret.Data["username"]).To(Equal([]byte("generated-user")))
-			Expect(syncedSecret.Data["password"]).To(Equal([]byte("generated-password")))
-			Expect(syncedSecret.Data["token"]).To(Equal([]byte("generated-token")))
-		})
-
-		It("should generate secrets with rewrite rules", func() {
-			By("creating a Fake generator")
-			CreateFakeGenerator(f, testNamespace.Name, "test-generator-rewrite", map[string]string{
-				"db-host": "localhost",
-				"db-port": "5432",
-				"db-name": "mydb",
-			})
-
-			By("creating a ProviderConnection to the fake provider for generator support")
-			CreateFakeProviderConnection(f, testNamespace.Name, "fake-generator-connection-rewrite", "fake-provider", testNamespace.Name)
-
-			By("waiting for ProviderConnection to be ready")
-			WaitForProviderConnectionReady(f, testNamespace.Name, "fake-generator-connection-rewrite", 30*time.Second)
-
-			By("creating an ExternalSecret with rewrite rules")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-generator-rewrite",
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: "fake-generator-connection-rewrite",
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "generated-secret-rewrite",
-					},
-					DataFrom: []esv1.ExternalSecretDataFromRemoteRef{
-						{
-							SourceRef: &esv1.StoreGeneratorSourceRef{
-								GeneratorRef: &esv1.GeneratorRef{
-									APIVersion: "generators.external-secrets.io/v1alpha1",
-									Kind:       "Fake",
-									Name:       "test-generator-rewrite",
-								},
-							},
-							Rewrite: []esv1.ExternalSecretRewrite{
-								{
-									Regexp: &esv1.ExternalSecretRewriteRegexp{
-										Source: "db-(.*)",
-										Target: "database_$1",
-									},
-								},
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), es)).To(Succeed())
-
-			By("waiting for secret to be synced")
-			var syncedSecret corev1.Secret
-			Eventually(func() bool {
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "generated-secret-rewrite", Namespace: testNamespace.Name},
-					&syncedSecret)
-				return err == nil
-			}, 30*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("verifying the rewritten secret data")
-			Expect(syncedSecret.Data["database_host"]).To(Equal([]byte("localhost")))
-			Expect(syncedSecret.Data["database_port"]).To(Equal([]byte("5432")))
-			Expect(syncedSecret.Data["database_name"]).To(Equal([]byte("mydb")))
-		})
-	})
-})

+ 0 - 335
e2e/suites/v2/helpers.go

@@ -1,335 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"strings"
-	"time"
-
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	rbacv1 "k8s.io/api/rbac/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	"github.com/external-secrets/external-secrets-e2e/framework/log"
-	v1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-	genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
-	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
-	fakev2alpha1 "github.com/external-secrets/external-secrets/apis/provider/fake/v2alpha1"
-	k8sv2alpha1 "github.com/external-secrets/external-secrets/apis/provider/kubernetes/v2alpha1"
-)
-
-// GetClusterCABundle retrieves the cluster CA certificate from the kube-root-ca.crt ConfigMap.
-// Returns empty []byte if not found (non-blocking).
-func GetClusterCABundle(f *framework.Framework) []byte {
-	var caBundle []byte
-	krc := &corev1.ConfigMap{}
-	err := f.CRClient.Get(context.Background(),
-		types.NamespacedName{Name: "kube-root-ca.crt", Namespace: "default"},
-		krc)
-	if err == nil {
-		caBundle = []byte(krc.Data["ca.crt"])
-	}
-	return caBundle
-}
-
-func CreateProviderSecretWriterRole(f *framework.Framework, namespace, remoteNamespace string) {
-	role := &rbacv1.Role{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "provider-secret-writer",
-			Namespace: remoteNamespace,
-		},
-		Rules: []rbacv1.PolicyRule{
-			{
-				APIGroups: []string{""},
-				Resources: []string{"secrets"},
-				Verbs:     []string{"get", "list", "watch", "create", "update", "patch", "delete"},
-			},
-			{
-				APIGroups: []string{"authorization.k8s.io"},
-				Resources: []string{"selfsubjectrulesreviews", "selfsubjectaccessreviews"},
-				Verbs:     []string{"create"},
-			},
-		},
-	}
-	// Try to create the role, ignore if it already exists
-	err := f.CRClient.Create(context.Background(), role)
-	if err != nil && !strings.Contains(err.Error(), "already exists") {
-		Expect(err).To(Succeed())
-	}
-
-	// Create a RoleBinding that grants the provider service account these permissions
-	roleBinding := &rbacv1.RoleBinding{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "provider-secret-writer-binding",
-			Namespace: remoteNamespace,
-		},
-		Subjects: []rbacv1.Subject{
-			{
-				Kind:      "ServiceAccount",
-				Name:      "default",
-				Namespace: namespace,
-			},
-		},
-		RoleRef: rbacv1.RoleRef{
-			APIGroup: "rbac.authorization.k8s.io",
-			Kind:     "Role",
-			Name:     "provider-secret-writer",
-		},
-	}
-	// Try to create the role binding, ignore if it already exists
-	err = f.CRClient.Create(context.Background(), roleBinding)
-	if err != nil && !strings.Contains(err.Error(), "already exists") {
-		Expect(err).To(Succeed())
-	}
-}
-
-// CreateKubernetes creates a Kubernetes provider CRD with standard configuration.
-// Uses default service account auth and returns the created provider object.
-func CreateKubernetes(f *framework.Framework, namespace, name, remoteNamespace string, caBundle []byte) *k8sv2alpha1.Kubernetes {
-	k8ss := &k8sv2alpha1.Kubernetes{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "Kubernetes",
-			APIVersion: "provider.external-secrets.io/v2alpha1",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-		Spec: v1.KubernetesProvider{
-			Server: v1.KubernetesServer{
-				URL:      "https://kubernetes.default.svc",
-				CABundle: caBundle,
-			},
-			RemoteNamespace: remoteNamespace,
-			Auth: &v1.KubernetesAuth{
-				ServiceAccount: &esmeta.ServiceAccountSelector{
-					Name:      "default",
-					Namespace: &namespace,
-				},
-			},
-		},
-	}
-	Expect(f.CRClient.Create(context.Background(), k8ss)).To(Succeed())
-	log.Logf("created Kubernetes provider: %s/%s", namespace, name)
-	return k8ss
-}
-
-// CreateProvider creates a ProviderConnection pointing to the specified provider.
-// Uses standard provider-kubernetes service address and returns the created ProviderConnection object.
-func CreateProvider(f *framework.Framework, namespace, name, providerName, providerNamespace string) *v1.Provider {
-	providerConnection := &v1.Provider{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-		Spec: v1.ProviderSpec{
-			Config: v1.ProviderConfig{
-				Address: "provider-kubernetes.external-secrets-system.svc:8080",
-				ProviderRef: v1.ProviderReference{
-					APIVersion: "provider.external-secrets.io/v2alpha1",
-					Kind:       "Kubernetes",
-					Name:       providerName,
-					Namespace:  providerNamespace,
-				},
-			},
-		},
-	}
-	Expect(f.CRClient.Create(context.Background(), providerConnection)).To(Succeed())
-	log.Logf("created ProviderConnection: %s/%s", namespace, name)
-	return providerConnection
-}
-
-// WaitForProviderConnectionReady polls until the ProviderConnection has Ready=True condition.
-// Returns the ready ProviderConnection object.
-func WaitForProviderConnectionReady(f *framework.Framework, namespace, name string, timeout time.Duration) *v1.Provider {
-	var providerConnection v1.Provider
-	Eventually(func() bool {
-		err := f.CRClient.Get(context.Background(),
-			types.NamespacedName{Name: name, Namespace: namespace},
-			&providerConnection)
-		if err != nil {
-			log.Logf("failed to get ProviderConnection: %v", err)
-			return false
-		}
-
-		for _, condition := range providerConnection.Status.Conditions {
-			if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue {
-				return true
-			}
-		}
-		return false
-	}, timeout, 1*time.Second).Should(BeTrue(), "ProviderConnection should become ready")
-
-	log.Logf("ProviderConnection %s/%s is ready", namespace, name)
-	return &providerConnection
-}
-
-// VerifyProviderConnectionCapabilities gets the ProviderConnection and checks its capabilities field.
-// Asserts capabilities match the expected value and logs the result.
-func VerifyProviderConnectionCapabilities(f *framework.Framework, namespace, name string, expected v1.ProviderCapabilities) {
-	var pc v1.Provider
-	Expect(f.CRClient.Get(context.Background(),
-		types.NamespacedName{Name: name, Namespace: namespace},
-		&pc)).To(Succeed())
-
-	Expect(pc.Status.Capabilities).NotTo(BeEmpty(), "Capabilities should be set")
-	Expect(string(pc.Status.Capabilities)).To(Equal(string(expected)), "Capabilities should match expected value")
-	log.Logf("successfully verified capabilities: %s", pc.Status.Capabilities)
-}
-
-// SetupTestNamespace creates a namespace with the given generateName prefix.
-// Logs creation and returns the namespace object.
-func SetupTestNamespace(f *framework.Framework, generateName string) *corev1.Namespace {
-	testNamespace := &corev1.Namespace{
-		ObjectMeta: metav1.ObjectMeta{
-			GenerateName: generateName,
-		},
-	}
-	Expect(f.CRClient.Create(context.Background(), testNamespace)).To(Succeed())
-	log.Logf("created test namespace: %s", testNamespace.Name)
-	return testNamespace
-}
-
-// CreateFakeProvider creates a Fake provider CRD with specified data.
-// Returns the created provider object.
-func CreateFakeProvider(f *framework.Framework, namespace, name string, data []v1.FakeProviderData) *fakev2alpha1.Fake {
-	fakeProvider := &fakev2alpha1.Fake{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "Fake",
-			APIVersion: "provider.external-secrets.io/v2alpha1",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-		Spec: v1.FakeProvider{
-			Data: data,
-		},
-	}
-	Expect(f.CRClient.Create(context.Background(), fakeProvider)).To(Succeed())
-	log.Logf("created Fake provider: %s/%s", namespace, name)
-	return fakeProvider
-}
-
-// CreateFakeProviderConnection creates a ProviderConnection pointing to the Fake provider.
-// Uses standard provider-fake service address and returns the created ProviderConnection object.
-func CreateFakeProviderConnection(f *framework.Framework, namespace, name, providerName, providerNamespace string) *v1.Provider {
-	providerConnection := &v1.Provider{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-		Spec: v1.ProviderSpec{
-			Config: v1.ProviderConfig{
-				Address: "provider-fake.external-secrets-system.svc:8080",
-				ProviderRef: v1.ProviderReference{
-					APIVersion: "provider.external-secrets.io/v2alpha1",
-					Kind:       "Fake",
-					Name:       providerName,
-					Namespace:  providerNamespace,
-				},
-			},
-		},
-	}
-	Expect(f.CRClient.Create(context.Background(), providerConnection)).To(Succeed())
-	log.Logf("created Fake ProviderConnection: %s/%s", namespace, name)
-	return providerConnection
-}
-
-// CreateFakeGenerator creates a Fake generator CR with specified data.
-// Returns the created generator object.
-func CreateFakeGenerator(f *framework.Framework, namespace, name string, data map[string]string) *genv1alpha1.Fake {
-	fakeGenerator := &genv1alpha1.Fake{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       string(genv1alpha1.GeneratorKindFake),
-			APIVersion: genv1alpha1.SchemeGroupVersion.String(),
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-		Spec: genv1alpha1.FakeSpec{
-			Data: data,
-		},
-	}
-	Expect(f.CRClient.Create(context.Background(), fakeGenerator)).To(Succeed())
-	log.Logf("created Fake generator: %s/%s", namespace, name)
-	return fakeGenerator
-}
-
-// CreateClusterProvider creates a ClusterProvider pointing to the specified provider.
-// Returns the created ClusterProvider object.
-func CreateClusterProvider(f *framework.Framework, name, address, providerAPIVersion, providerKind, providerName, providerNamespace string, authScope v1.AuthenticationScope, conditions []v1.ClusterSecretStoreCondition) *v1.ClusterProvider {
-	existing := &v1.ClusterProvider{}
-	err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: name}, existing)
-	if err == nil {
-		Expect(f.CRClient.Delete(context.Background(), existing)).To(Succeed())
-		Eventually(func() bool {
-			lookupErr := f.CRClient.Get(context.Background(), types.NamespacedName{Name: name}, &v1.ClusterProvider{})
-			return lookupErr != nil && strings.Contains(lookupErr.Error(), "not found")
-		}, 15*time.Second, 500*time.Millisecond).Should(BeTrue(), "existing ClusterProvider should be deleted before recreation")
-	}
-
-	clusterProvider := &v1.ClusterProvider{
-		ObjectMeta: metav1.ObjectMeta{
-			Name: name,
-		},
-		Spec: v1.ClusterProviderSpec{
-			Config: v1.ProviderConfig{
-				Address: address,
-				ProviderRef: v1.ProviderReference{
-					APIVersion: providerAPIVersion,
-					Kind:       providerKind,
-					Name:       providerName,
-					Namespace:  providerNamespace,
-				},
-			},
-			AuthenticationScope: authScope,
-			Conditions:          conditions,
-		},
-	}
-	Expect(f.CRClient.Create(context.Background(), clusterProvider)).To(Succeed())
-	log.Logf("created ClusterProvider: %s", name)
-	return clusterProvider
-}
-
-// WaitForClusterProviderReady polls until the ClusterProvider has Ready=True condition.
-// Returns the ready ClusterProvider object.
-func WaitForClusterProviderReady(f *framework.Framework, name string, timeout time.Duration) *v1.ClusterProvider {
-	var clusterProvider v1.ClusterProvider
-	Eventually(func() bool {
-		err := f.CRClient.Get(context.Background(),
-			types.NamespacedName{Name: name},
-			&clusterProvider)
-		if err != nil {
-			log.Logf("failed to get ClusterProvider: %v", err)
-			return false
-		}
-
-		for _, condition := range clusterProvider.Status.Conditions {
-			if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue {
-				return true
-			}
-		}
-		return false
-	}, timeout, 1*time.Second).Should(BeTrue(), "ClusterProvider should become ready")
-
-	log.Logf("ClusterProvider %s is ready", name)
-	return &clusterProvider
-}

+ 0 - 55
e2e/suites/v2/kubernetes_capabilities_test.go

@@ -1,55 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("[v2] Capabilities", Label("v2", "capabilities"), func() {
-	f := framework.New("v2-capabilities")
-
-	var (
-		testNamespace *corev1.Namespace
-	)
-
-	BeforeEach(func() {
-		testNamespace = SetupTestNamespace(f, "v2-capabilities-")
-		CreateProviderSecretWriterRole(f, testNamespace.Name, testNamespace.Name)
-	})
-
-	AfterEach(func() {
-		// Cleanup namespace
-		if testNamespace != nil {
-			Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-		}
-	})
-
-	It("should report READ_WRITE capabilities for Kubernetes provider", func() {
-		caBundle := GetClusterCABundle(f)
-		CreateKubernetes(f, testNamespace.Name, "k8s-provider", testNamespace.Name, caBundle)
-		CreateProvider(f, testNamespace.Name, "test-secretstore", "k8s-provider", testNamespace.Name)
-		WaitForProviderConnectionReady(f, testNamespace.Name, "test-secretstore", 5*time.Second)
-		VerifyProviderConnectionCapabilities(f, testNamespace.Name, "test-secretstore", esv1.ProviderReadWrite)
-	})
-})

+ 0 - 390
e2e/suites/v2/kubernetes_cluster_provider_test.go

@@ -1,390 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("V2 ClusterProvider Tests", Label("v2", "cluster-provider", "e2e"), func() {
-	f := framework.New("v2-cluster-provider")
-
-	Describe("Kubernetes ClusterProvider", func() {
-		const (
-			sourceSecretName = "source-secret-cluster"
-			targetSecretName = "target-secret-cluster"
-		)
-
-		var (
-			sourceNamespace *corev1.Namespace
-			targetNamespaceA *corev1.Namespace
-			targetNamespaceB *corev1.Namespace
-		)
-
-		BeforeEach(func() {
-			sourceNamespace = SetupTestNamespace(f, "v2-cluster-source-")
-			targetNamespaceA = SetupTestNamespace(f, "v2-cluster-target-a-")
-			targetNamespaceB = SetupTestNamespace(f, "v2-cluster-target-b-")
-
-			// Create RBAC roles for provider access
-			// For ClusterProvider with ProviderNamespace scope, the service account
-			// in sourceNamespace needs to access secrets in sourceNamespace
-			CreateProviderSecretWriterRole(f, sourceNamespace.Name, sourceNamespace.Name)
-
-			// Create source secret
-			sourceSecret := &corev1.Secret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      sourceSecretName,
-					Namespace: sourceNamespace.Name,
-				},
-				Type: corev1.SecretTypeOpaque,
-				Data: map[string][]byte{
-					"username": []byte("cluster-admin"),
-					"password": []byte("cluster-secret-password"),
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), sourceSecret)).To(Succeed())
-		})
-
-		AfterEach(func() {
-			if sourceNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), sourceNamespace)).To(Succeed())
-			}
-			if targetNamespaceA != nil {
-				Expect(f.CRClient.Delete(context.Background(), targetNamespaceA)).To(Succeed())
-			}
-			if targetNamespaceB != nil {
-				Expect(f.CRClient.Delete(context.Background(), targetNamespaceB)).To(Succeed())
-			}
-		})
-
-		It("should sync secrets with ProviderNamespace authentication scope", func() {
-			caBundle := GetClusterCABundle(f)
-			k8sStore := CreateKubernetes(f, sourceNamespace.Name, "k8s-store-cluster", sourceNamespace.Name, caBundle)
-
-			By("creating a ClusterProvider with ProviderNamespace authentication scope")
-			CreateClusterProvider(f, "cluster-k8s-provider",
-				"provider-kubernetes.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1",
-				"Kubernetes",
-				k8sStore.Name,
-				sourceNamespace.Name,
-				esv1.AuthenticationScopeProviderNamespace,
-				nil)
-
-			WaitForClusterProviderReady(f, "cluster-k8s-provider", 10*time.Second)
-
-			By("creating an ExternalSecret in target namespace A")
-			externalSecretA := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-cluster-a",
-					Namespace: targetNamespaceA.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Kind: "ClusterProvider",
-						Name: "cluster-k8s-provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name:           targetSecretName,
-						CreationPolicy: esv1.CreatePolicyOwner,
-					},
-					RefreshInterval: &metav1.Duration{Duration: 1 * time.Hour},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key:      sourceSecretName,
-								Property: "username",
-							},
-						},
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key:      sourceSecretName,
-								Property: "password",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), externalSecretA)).To(Succeed())
-
-			By("waiting for ExternalSecret A to sync")
-			Eventually(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-es-cluster-a", Namespace: targetNamespaceA.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-
-				for _, condition := range es.Status.Conditions {
-					if condition.Type == "Ready" && condition.Status == corev1.ConditionTrue {
-						return true
-					}
-				}
-				return false
-			}, 15*time.Second, 2*time.Second).Should(BeTrue(), "ExternalSecret should become ready")
-
-			By("verifying the synced secret in namespace A")
-			var targetSecret corev1.Secret
-			Expect(f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: targetSecretName, Namespace: targetNamespaceA.Name},
-				&targetSecret)).To(Succeed())
-
-			Expect(targetSecret.Data).To(HaveKeyWithValue("username", []byte("cluster-admin")))
-			Expect(targetSecret.Data).To(HaveKeyWithValue("password", []byte("cluster-secret-password")))
-		})
-
-		It("should sync secrets with ManifestNamespace authentication scope", func() {
-			caBundle := GetClusterCABundle(f)
-
-			// For ManifestNamespace scope, each namespace authenticates as itself
-			// Create RBAC for target namespace B
-			CreateProviderSecretWriterRole(f, targetNamespaceB.Name, targetNamespaceB.Name)
-
-			// Create a Kubernetes provider in any namespace (we'll use target A) with remoteNamespace set to B
-			CreateKubernetes(f, targetNamespaceA.Name, "k8s-store", targetNamespaceB.Name, caBundle)
-
-			// Create secret in namespace B (where we'll read from)
-			secretB := &corev1.Secret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "secret-b",
-					Namespace: targetNamespaceB.Name,
-				},
-				Type: corev1.SecretTypeOpaque,
-				Data: map[string][]byte{
-					"data": []byte("from-namespace-b"),
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), secretB)).To(Succeed())
-
-			By("creating a ClusterProvider with ManifestNamespace authentication scope")
-			// Point to Kubernetes provider in namespace A, but use ManifestNamespace auth
-			// This means auth will use namespace B's service account, which has RBAC in namespace B
-			CreateClusterProvider(f, "cluster-k8s-manifest-scope",
-				"provider-kubernetes.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1",
-				"Kubernetes",
-				"k8s-store",
-				targetNamespaceA.Name,
-				esv1.AuthenticationScopeManifestNamespace,
-				nil)
-
-			WaitForClusterProviderReady(f, "cluster-k8s-manifest-scope", 10*time.Second)
-
-			By("creating ExternalSecret in namespace B")
-			// Should authenticate using namespace B's credentials and access secrets in namespace B
-			externalSecretB := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-manifest-scope",
-					Namespace: targetNamespaceB.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Kind: "ClusterProvider",
-						Name: "cluster-k8s-manifest-scope",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name:           "synced-secret-b",
-						CreationPolicy: esv1.CreatePolicyOwner,
-					},
-					RefreshInterval: &metav1.Duration{Duration: 1 * time.Hour},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "data",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key:      "secret-b",
-								Property: "data",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), externalSecretB)).To(Succeed())
-
-			By("waiting for ExternalSecret B to sync")
-			Eventually(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-es-manifest-scope", Namespace: targetNamespaceB.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-
-				for _, condition := range es.Status.Conditions {
-					if condition.Type == "Ready" && condition.Status == corev1.ConditionTrue {
-						return true
-					}
-				}
-				return false
-			}, 15*time.Second, 2*time.Second).Should(BeTrue(), "ExternalSecret should become ready")
-
-			By("verifying the synced secret has data from namespace B")
-			var syncedSecret corev1.Secret
-			Expect(f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "synced-secret-b", Namespace: targetNamespaceB.Name},
-				&syncedSecret)).To(Succeed())
-
-			Expect(syncedSecret.Data).To(HaveKeyWithValue("data", []byte("from-namespace-b")))
-		})
-
-		It("should enforce namespace conditions", func() {
-			caBundle := GetClusterCABundle(f)
-			k8sStore := CreateKubernetes(f, sourceNamespace.Name, "k8s-store-conditions", sourceNamespace.Name, caBundle)
-
-			// Add label to namespace A
-			targetNamespaceA.Labels = map[string]string{"env": "prod"}
-			Expect(f.CRClient.Update(context.Background(), targetNamespaceA)).To(Succeed())
-
-			By("creating a ClusterProvider with namespace selector")
-			conditions := []esv1.ClusterSecretStoreCondition{
-				{
-					NamespaceSelector: &metav1.LabelSelector{
-						MatchLabels: map[string]string{"env": "prod"},
-					},
-				},
-			}
-			CreateClusterProvider(f, "cluster-k8s-conditions",
-				"provider-kubernetes.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1",
-				"Kubernetes",
-				k8sStore.Name,
-				sourceNamespace.Name,
-				esv1.AuthenticationScopeProviderNamespace,
-				conditions)
-
-			WaitForClusterProviderReady(f, "cluster-k8s-conditions", 10*time.Second)
-
-			By("creating ExternalSecret in matching namespace A")
-			esA := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-allowed",
-					Namespace: targetNamespaceA.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Kind: "ClusterProvider",
-						Name: "cluster-k8s-conditions",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name:           "allowed-secret",
-						CreationPolicy: esv1.CreatePolicyOwner,
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key:      sourceSecretName,
-								Property: "username",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), esA)).To(Succeed())
-
-			By("verifying ExternalSecret in namespace A succeeds")
-			Eventually(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-es-allowed", Namespace: targetNamespaceA.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-				for _, condition := range es.Status.Conditions {
-					if condition.Type == "Ready" && condition.Status == corev1.ConditionTrue {
-						return true
-					}
-				}
-				return false
-			}, 15*time.Second, 2*time.Second).Should(BeTrue())
-
-			By("creating ExternalSecret in non-matching namespace B")
-			esB := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-es-denied",
-					Namespace: targetNamespaceB.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Kind: "ClusterProvider",
-						Name: "cluster-k8s-conditions",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: "denied-secret",
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key:      sourceSecretName,
-								Property: "username",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), esB)).To(Succeed())
-
-			By("verifying ExternalSecret in namespace B fails with condition error")
-			// First wait for the ExternalSecret to be reconciled and have a condition
-			Eventually(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-es-denied", Namespace: targetNamespaceB.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-				return len(es.Status.Conditions) > 0
-			}, 10*time.Second, 1*time.Second).Should(BeTrue(), "ExternalSecret should have conditions")
-
-			// Then verify it stays in error state
-			Consistently(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-es-denied", Namespace: targetNamespaceB.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-				// Check if it has an error condition
-				for _, condition := range es.Status.Conditions {
-					if condition.Type == "Ready" {
-						// Should be False (error state)
-						return condition.Status == corev1.ConditionFalse
-					}
-				}
-				return false
-			}, 5*time.Second, 1*time.Second).Should(BeTrue(), "ExternalSecret should have error condition")
-		})
-	})
-})
-

+ 0 - 70
e2e/suites/v2/kubernetes_delete_test.go

@@ -1,70 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("[v2] DeleteSecret", Label("v2", "kubernetes", "delete-secret"), func() {
-	f := framework.New("eso-v2-delete-secret")
-
-	var (
-		testNamespace *corev1.Namespace
-	)
-
-	BeforeEach(func() {
-		testNamespace = SetupTestNamespace(f, "v2-delete-secret-")
-		CreateProviderSecretWriterRole(f, testNamespace.Name, testNamespace.Name)
-	})
-
-	AfterEach(func() {
-		// Cleanup namespace
-		if testNamespace != nil {
-			Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-		}
-	})
-
-	It("should delete secret from Kubernetes provider", func() {
-		caBundle := GetClusterCABundle(f)
-		CreateKubernetes(f, testNamespace.Name, "k8s-provider", testNamespace.Name, caBundle)
-		CreateProvider(f, testNamespace.Name, "test-secretstore", "k8s-provider", testNamespace.Name)
-		WaitForProviderConnectionReady(f, testNamespace.Name, "test-secretstore", 5*time.Second)
-
-		By("creating test secret")
-		testSecret := &corev1.Secret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "test-secret",
-				Namespace: testNamespace.Name,
-			},
-			Data: map[string][]byte{
-				"key1": []byte("value1"),
-				"key2": []byte("value2"),
-			},
-		}
-		Expect(f.CRClient.Create(context.Background(), testSecret)).To(Succeed())
-
-		VerifyProviderConnectionCapabilities(f, testNamespace.Name, "test-secretstore", esv1.ProviderReadWrite)
-	})
-})

+ 0 - 240
e2e/suites/v2/kubernetes_find_test.go

@@ -1,240 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("[v2] GetAllSecrets", Label("v2", "get-all-secrets"), func() {
-	f := framework.New("v2-get-all-secrets")
-
-	var (
-		sourceNamespace *corev1.Namespace
-		targetNamespace *corev1.Namespace
-	)
-
-	BeforeEach(func() {
-		sourceNamespace = SetupTestNamespace(f, "v2-get-all-source-")
-		targetNamespace = SetupTestNamespace(f, "v2-get-all-target-")
-		CreateProviderSecretWriterRole(f, targetNamespace.Name, sourceNamespace.Name)
-
-		// Create test secrets with different labels
-		secrets := []struct {
-			name   string
-			labels map[string]string
-			data   map[string][]byte
-		}{
-			{
-				name:   "app-secret-1",
-				labels: map[string]string{"app": "myapp", "env": "prod"},
-				data:   map[string][]byte{"key1": []byte("value1")},
-			},
-			{
-				name:   "app-secret-2",
-				labels: map[string]string{"app": "myapp", "env": "dev"},
-				data:   map[string][]byte{"key2": []byte("value2")},
-			},
-			{
-				name:   "db-secret-1",
-				labels: map[string]string{"app": "database", "env": "prod"},
-				data:   map[string][]byte{"password": []byte("dbpass")},
-			},
-			{
-				name:   "other-secret",
-				labels: map[string]string{"type": "config"},
-				data:   map[string][]byte{"config": []byte("data")},
-			},
-		}
-
-		for _, s := range secrets {
-			secret := &corev1.Secret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      s.name,
-					Namespace: sourceNamespace.Name,
-					Labels:    s.labels,
-				},
-				Type: corev1.SecretTypeOpaque,
-				Data: s.data,
-			}
-			Expect(f.CRClient.Create(context.Background(), secret)).To(Succeed())
-		}
-
-	})
-
-	AfterEach(func() {
-		// Cleanup namespaces
-		if sourceNamespace != nil {
-			Expect(f.CRClient.Delete(context.Background(), sourceNamespace)).To(Succeed())
-		}
-		if targetNamespace != nil {
-			Expect(f.CRClient.Delete(context.Background(), targetNamespace)).To(Succeed())
-		}
-	})
-
-	It("should find secrets by tags (labels)", func() {
-		caBundle := GetClusterCABundle(f)
-		CreateKubernetes(f, targetNamespace.Name, "k8s-provider", sourceNamespace.Name, caBundle)
-		CreateProvider(f, targetNamespace.Name, "test-secretstore", "k8s-provider", targetNamespace.Name)
-		WaitForProviderConnectionReady(f, targetNamespace.Name, "test-secretstore", 5*time.Second)
-
-		By("creating an ExternalSecret with dataFrom using tags")
-		externalSecret := &esv1.ExternalSecret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "test-external-secret-tags",
-				Namespace: targetNamespace.Name,
-			},
-			Spec: esv1.ExternalSecretSpec{
-				SecretStoreRef: esv1.SecretStoreRef{
-					Kind: "Provider",
-					Name: "test-secretstore",
-				},
-				Target: esv1.ExternalSecretTarget{
-					Name:           "synced-secret-tags",
-					CreationPolicy: esv1.CreatePolicyOwner,
-				},
-				RefreshInterval: &metav1.Duration{Duration: 1 * time.Hour},
-				DataFrom: []esv1.ExternalSecretDataFromRemoteRef{
-					{
-						Find: &esv1.ExternalSecretFind{
-							Tags: map[string]string{
-								"app": "myapp",
-							},
-						},
-					},
-				},
-			},
-		}
-		Expect(f.CRClient.Create(context.Background(), externalSecret)).To(Succeed())
-
-		By("waiting for ExternalSecret to sync")
-		Eventually(func() bool {
-			var es esv1.ExternalSecret
-			err := f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "test-external-secret-tags", Namespace: targetNamespace.Name},
-				&es)
-			if err != nil {
-				return false
-			}
-
-			for _, condition := range es.Status.Conditions {
-				if condition.Type == "Ready" && condition.Status == corev1.ConditionTrue {
-					return true
-				}
-			}
-			return false
-		}, 10*time.Second, 2*time.Second).Should(BeTrue(), "ExternalSecret should become ready")
-
-		By("verifying the synced secret contains data from secrets with matching tags")
-		var syncedSecret corev1.Secret
-		Expect(f.CRClient.Get(context.Background(),
-			types.NamespacedName{Name: "synced-secret-tags", Namespace: targetNamespace.Name},
-			&syncedSecret)).To(Succeed())
-
-		// GetAllSecrets returns secret name -> JSON data
-		// Should contain keys for app-secret-1 and app-secret-2 (both have app=myapp)
-		Expect(syncedSecret.Data).To(HaveKey("app-secret-1"))
-		Expect(syncedSecret.Data).To(HaveKey("app-secret-2"))
-		// Should NOT contain data from db-secret-1 or other-secret
-		Expect(syncedSecret.Data).NotTo(HaveKey("db-secret-1"))
-		Expect(syncedSecret.Data).NotTo(HaveKey("other-secret"))
-
-		// Verify the values are JSON-encoded secret data
-		Expect(string(syncedSecret.Data["app-secret-1"])).To(ContainSubstring("key1"))
-		Expect(string(syncedSecret.Data["app-secret-2"])).To(ContainSubstring("key2"))
-	})
-
-	It("should find secrets by name regexp", func() {
-		caBundle := GetClusterCABundle(f)
-		CreateKubernetes(f, targetNamespace.Name, "k8s-provider-regex", sourceNamespace.Name, caBundle)
-		CreateProvider(f, targetNamespace.Name, "test-secretstore-regex", "k8s-provider-regex", targetNamespace.Name)
-		WaitForProviderConnectionReady(f, targetNamespace.Name, "test-secretstore-regex", 5*time.Second)
-
-		By("creating an ExternalSecret with dataFrom using name regexp")
-		externalSecret := &esv1.ExternalSecret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "test-external-secret-regex",
-				Namespace: targetNamespace.Name,
-			},
-			Spec: esv1.ExternalSecretSpec{
-				SecretStoreRef: esv1.SecretStoreRef{
-					Kind: "Provider",
-					Name: "test-secretstore-regex",
-				},
-				Target: esv1.ExternalSecretTarget{
-					Name:           "synced-secret-regex",
-					CreationPolicy: esv1.CreatePolicyOwner,
-				},
-				RefreshInterval: &metav1.Duration{Duration: 1 * time.Hour},
-				DataFrom: []esv1.ExternalSecretDataFromRemoteRef{
-					{
-						Find: &esv1.ExternalSecretFind{
-							Name: &esv1.FindName{
-								RegExp: "^app-secret-.*",
-							},
-						},
-					},
-				},
-			},
-		}
-		Expect(f.CRClient.Create(context.Background(), externalSecret)).To(Succeed())
-
-		By("waiting for ExternalSecret to sync")
-		Eventually(func() bool {
-			var es esv1.ExternalSecret
-			err := f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "test-external-secret-regex", Namespace: targetNamespace.Name},
-				&es)
-			if err != nil {
-				return false
-			}
-
-			for _, condition := range es.Status.Conditions {
-				if condition.Type == "Ready" && condition.Status == corev1.ConditionTrue {
-					return true
-				}
-			}
-			return false
-		}, 10*time.Second, 2*time.Second).Should(BeTrue(), "ExternalSecret should become ready")
-
-		By("verifying the synced secret contains data from secrets matching the regexp")
-		var syncedSecret corev1.Secret
-		Expect(f.CRClient.Get(context.Background(),
-			types.NamespacedName{Name: "synced-secret-regex", Namespace: targetNamespace.Name},
-			&syncedSecret)).To(Succeed())
-
-		// GetAllSecrets returns secret name -> JSON data
-		// Should contain keys for app-secret-1 and app-secret-2 (match ^app-secret-.*)
-		Expect(syncedSecret.Data).To(HaveKey("app-secret-1"))
-		Expect(syncedSecret.Data).To(HaveKey("app-secret-2"))
-		// Should NOT contain data from db-secret-1 or other-secret
-		Expect(syncedSecret.Data).NotTo(HaveKey("db-secret-1"))
-		Expect(syncedSecret.Data).NotTo(HaveKey("other-secret"))
-
-		// Verify the values are JSON-encoded secret data
-		Expect(string(syncedSecret.Data["app-secret-1"])).To(ContainSubstring("key1"))
-		Expect(string(syncedSecret.Data["app-secret-2"])).To(ContainSubstring("key2"))
-	})
-})

+ 0 - 158
e2e/suites/v2/kubernetes_get_test.go

@@ -1,158 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("V2 End-to-End Tests", Label("v2", "e2e"), func() {
-	f := framework.New("v2-e2e")
-
-	Describe("Kubernetes Provider", func() {
-		const (
-			sourceSecretName = "source-secret"
-			targetSecretName = "target-secret"
-			secretStoreName  = "kubernetes-secretstore"
-		)
-
-		var (
-			sourceNamespace *corev1.Namespace
-			targetNamespace *corev1.Namespace
-		)
-
-		BeforeEach(func() {
-			sourceNamespace = SetupTestNamespace(f, "v2-source-")
-			targetNamespace = SetupTestNamespace(f, "v2-target-")
-			CreateProviderSecretWriterRole(f, targetNamespace.Name, sourceNamespace.Name)
-
-			// Create source secret
-			sourceSecret := &corev1.Secret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      sourceSecretName,
-					Namespace: sourceNamespace.Name,
-				},
-				Type: corev1.SecretTypeOpaque,
-				Data: map[string][]byte{
-					"username": []byte("admin"),
-					"password": []byte("super-secret-password"),
-					"api-key":  []byte("abc123xyz789"),
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), sourceSecret)).To(Succeed())
-		})
-
-		AfterEach(func() {
-			// Cleanup namespaces
-			if sourceNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), sourceNamespace)).To(Succeed())
-			}
-			if targetNamespace != nil {
-				Expect(f.CRClient.Delete(context.Background(), targetNamespace)).To(Succeed())
-			}
-		})
-
-		It("should sync secrets across namespaces", func() {
-			caBundle := GetClusterCABundle(f)
-			CreateKubernetes(f, targetNamespace.Name, "k8s-store", sourceNamespace.Name, caBundle)
-			CreateProvider(f, targetNamespace.Name, secretStoreName, "k8s-store", targetNamespace.Name)
-			WaitForProviderConnectionReady(f, targetNamespace.Name, secretStoreName, 5*time.Second)
-
-			By("creating an ExternalSecret")
-			externalSecret := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "test-external-secret",
-					Namespace: targetNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					SecretStoreRef: esv1.SecretStoreRef{
-						Kind: "Provider",
-						Name: secretStoreName,
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name:           targetSecretName,
-						CreationPolicy: esv1.CreatePolicyOwner,
-					},
-					RefreshInterval: &metav1.Duration{Duration: 1 * time.Hour},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key:      sourceSecretName,
-								Property: "username",
-							},
-						},
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key:      sourceSecretName,
-								Property: "password",
-							},
-						},
-					},
-				},
-			}
-			Expect(f.CRClient.Create(context.Background(), externalSecret)).To(Succeed())
-
-			By("waiting for ExternalSecret to sync")
-			Eventually(func() bool {
-				var es esv1.ExternalSecret
-				err := f.CRClient.Get(context.Background(),
-					types.NamespacedName{Name: "test-external-secret", Namespace: targetNamespace.Name},
-					&es)
-				if err != nil {
-					return false
-				}
-
-				for _, condition := range es.Status.Conditions {
-					if condition.Type == "Ready" && condition.Status == corev1.ConditionTrue {
-						return true
-					}
-				}
-				return false
-			}, 10*time.Second, 2*time.Second).Should(BeTrue(), "ExternalSecret should become ready")
-
-			By("verifying the synced secret")
-			var targetSecret corev1.Secret
-			Expect(f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: targetSecretName, Namespace: targetNamespace.Name},
-				&targetSecret)).To(Succeed())
-
-			Expect(targetSecret.Data).To(HaveKeyWithValue("username", []byte("admin")))
-			Expect(targetSecret.Data).To(HaveKeyWithValue("password", []byte("super-secret-password")))
-
-			By("verifying ExternalSecret status")
-			var es esv1.ExternalSecret
-			Expect(f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "test-external-secret", Namespace: targetNamespace.Name},
-				&es)).To(Succeed())
-
-			Expect(es.Status.SyncedResourceVersion).NotTo(BeEmpty())
-			Expect(es.Status.RefreshTime).NotTo(BeNil())
-			Expect(es.Status.Conditions).NotTo(BeEmpty())
-		})
-
-	})
-})

+ 0 - 253
e2e/suites/v2/kubernetes_push_test.go

@@ -1,253 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	"github.com/external-secrets/external-secrets-e2e/framework/log"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
-)
-
-var _ = Describe("[v2] PushSecret", Label("v2", "kubernetes", "push-secret"), func() {
-	f := framework.New("eso-v2-push-secret")
-
-	var (
-		testNamespace *corev1.Namespace
-	)
-
-	BeforeEach(func() {
-		testNamespace = SetupTestNamespace(f, "v2-push-secret-")
-		CreateProviderSecretWriterRole(f, testNamespace.Name, testNamespace.Name)
-	})
-
-	AfterEach(func() {
-		// Cleanup namespace
-		if testNamespace != nil {
-			Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-		}
-	})
-
-	It("should push secret to Kubernetes provider", func() {
-		caBundle := GetClusterCABundle(f)
-		CreateKubernetes(f, testNamespace.Name, "k8s-provider", testNamespace.Name, caBundle)
-		CreateProvider(f, testNamespace.Name, "test-secretstore", "k8s-provider", testNamespace.Name)
-		WaitForProviderConnectionReady(f, testNamespace.Name, "test-secretstore", 5*time.Second)
-
-		By("creating source secret")
-		sourceSecret := &corev1.Secret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "source-secret",
-				Namespace: testNamespace.Name,
-			},
-			Data: map[string][]byte{
-				"username": []byte("admin"),
-				"password": []byte("secret123"),
-			},
-		}
-		Expect(f.CRClient.Create(context.Background(), sourceSecret)).To(Succeed())
-		log.Logf("created source secret: %s/%s", testNamespace.Name, "source-secret")
-
-		VerifyProviderConnectionCapabilities(f, testNamespace.Name, "test-secretstore", esv1.ProviderReadWrite)
-
-		By("creating PushSecret")
-		pushSecret := &esv1alpha1.PushSecret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "test-pushsecret",
-				Namespace: testNamespace.Name,
-			},
-			Spec: esv1alpha1.PushSecretSpec{
-				RefreshInterval: &metav1.Duration{Duration: 10 * time.Second},
-				SecretStoreRefs: []esv1alpha1.PushSecretStoreRef{
-					{
-						Name:       "test-secretstore",
-						Kind:       "Provider",
-						APIVersion: "external-secrets.io/v1",
-					},
-				},
-				Selector: esv1alpha1.PushSecretSelector{
-					Secret: &esv1alpha1.PushSecretSecret{
-						Name: "source-secret",
-					},
-				},
-				Data: []esv1alpha1.PushSecretData{
-					{
-						Match: esv1alpha1.PushSecretMatch{
-							SecretKey: "username",
-							RemoteRef: esv1alpha1.PushSecretRemoteRef{
-								RemoteKey: "pushed-secret",
-								Property:  "username",
-							},
-						},
-					},
-					{
-						Match: esv1alpha1.PushSecretMatch{
-							SecretKey: "password",
-							RemoteRef: esv1alpha1.PushSecretRemoteRef{
-								RemoteKey: "pushed-secret",
-								Property:  "password",
-							},
-						},
-					},
-				},
-			},
-		}
-		Expect(f.CRClient.Create(context.Background(), pushSecret)).To(Succeed())
-		log.Logf("created PushSecret: %s/%s", testNamespace.Name, "test-pushsecret")
-
-		By("verifying PushSecret is synced")
-		Eventually(func() bool {
-			var ps esv1alpha1.PushSecret
-			err := f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "test-pushsecret", Namespace: testNamespace.Name},
-				&ps)
-			if err != nil {
-				log.Logf("failed to get PushSecret: %v", err)
-				return false
-			}
-
-			for _, condition := range ps.Status.Conditions {
-				if condition.Type == esv1alpha1.PushSecretReady && condition.Status == corev1.ConditionTrue {
-					log.Logf("PushSecret is ready with status: %s", condition.Reason)
-					return true
-				}
-			}
-			log.Logf("PushSecret not ready yet, conditions: %+v", ps.Status.Conditions)
-			return false
-		}, 10*time.Second, 2*time.Second).Should(BeTrue(), "PushSecret should become ready")
-
-		By("verifying pushed secret exists in target namespace")
-		var pushedSecret corev1.Secret
-		Eventually(func() bool {
-			err := f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "pushed-secret", Namespace: testNamespace.Name},
-				&pushedSecret)
-			if err != nil {
-				log.Logf("pushed secret not found yet: %v", err)
-				return false
-			}
-			return true
-		}, 10*time.Second, 2*time.Second).Should(BeTrue(), "pushed secret should exist")
-
-		By("verifying pushed secret has correct data")
-		Expect(pushedSecret.Data).To(HaveKey("username"))
-		Expect(pushedSecret.Data).To(HaveKey("password"))
-		Expect(string(pushedSecret.Data["username"])).To(Equal("admin"))
-		Expect(string(pushedSecret.Data["password"])).To(Equal("secret123"))
-		log.Logf("successfully verified pushed secret data")
-	})
-
-	It("should delete secrets when DeletionPolicy=Delete", func() {
-		caBundle := GetClusterCABundle(f)
-		CreateKubernetes(f, testNamespace.Name, "k8s-provider", testNamespace.Name, caBundle)
-		CreateProvider(f, testNamespace.Name, "test-secretstore", "k8s-provider", testNamespace.Name)
-		WaitForProviderConnectionReady(f, testNamespace.Name, "test-secretstore", 5*time.Second)
-
-		By("creating source secret")
-		sourceSecret := &corev1.Secret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "source-secret-delete",
-				Namespace: testNamespace.Name,
-			},
-			Data: map[string][]byte{
-				"key1": []byte("value1"),
-			},
-		}
-		Expect(f.CRClient.Create(context.Background(), sourceSecret)).To(Succeed())
-
-		By("creating PushSecret with DeletionPolicy=Delete")
-		pushSecret := &esv1alpha1.PushSecret{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "test-pushsecret-delete",
-				Namespace: testNamespace.Name,
-			},
-			Spec: esv1alpha1.PushSecretSpec{
-				RefreshInterval: &metav1.Duration{Duration: 10 * time.Second},
-				DeletionPolicy:  esv1alpha1.PushSecretDeletionPolicyDelete,
-				SecretStoreRefs: []esv1alpha1.PushSecretStoreRef{
-					{
-						Name:       "test-secretstore",
-						Kind:       "Provider",
-						APIVersion: "external-secrets.io/v1",
-					},
-				},
-				Selector: esv1alpha1.PushSecretSelector{
-					Secret: &esv1alpha1.PushSecretSecret{
-						Name: "source-secret-delete",
-					},
-				},
-				Data: []esv1alpha1.PushSecretData{
-					{
-						Match: esv1alpha1.PushSecretMatch{
-							SecretKey: "key1",
-							RemoteRef: esv1alpha1.PushSecretRemoteRef{
-								RemoteKey: "pushed-secret-delete",
-								Property:  "key1",
-							},
-						},
-					},
-				},
-			},
-		}
-		Expect(f.CRClient.Create(context.Background(), pushSecret)).To(Succeed())
-		log.Logf("created PushSecret with Delete policy")
-
-		By("waiting for PushSecret to sync")
-		Eventually(func() bool {
-			var ps esv1alpha1.PushSecret
-			err := f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "test-pushsecret-delete", Namespace: testNamespace.Name},
-				&ps)
-			if err != nil {
-				return false
-			}
-			for _, condition := range ps.Status.Conditions {
-				if condition.Type == esv1alpha1.PushSecretReady && condition.Status == corev1.ConditionTrue {
-					return true
-				}
-			}
-			return false
-		}, 30*time.Second, 2*time.Second).Should(BeTrue())
-
-		By("verifying pushed secret was created")
-		var pushedSecret corev1.Secret
-		Expect(f.CRClient.Get(context.Background(),
-			types.NamespacedName{Name: "pushed-secret-delete", Namespace: testNamespace.Name},
-			&pushedSecret)).To(Succeed())
-
-		By("deleting PushSecret")
-		Expect(f.CRClient.Delete(context.Background(), pushSecret)).To(Succeed())
-
-		By("verifying pushed secret is deleted due to DeletionPolicy=Delete")
-		Eventually(func() bool {
-			err := f.CRClient.Get(context.Background(),
-				types.NamespacedName{Name: "pushed-secret-delete", Namespace: testNamespace.Name},
-				&pushedSecret)
-			return apierrors.IsNotFound(err)
-		}, 30*time.Second, 2*time.Second).Should(BeTrue(), "pushed secret should be deleted when PushSecret is deleted")
-
-		log.Logf("successfully verified Delete policy removes secrets")
-	})
-})

+ 0 - 500
e2e/suites/v2/metrics_test.go

@@ -1,500 +0,0 @@
-/*
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package v2
-
-import (
-	"context"
-	"time"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/kubernetes"
-
-	"github.com/external-secrets/external-secrets-e2e/framework"
-	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
-)
-
-var _ = Describe("V2 Provider Metrics", Label("v2", "metrics"), func() {
-	f := framework.New("v2-metrics")
-
-	var (
-		testNamespace  *corev1.Namespace
-		providerCRName string
-		providerName   string
-		secretName     string
-		externalSecret string
-		fakeData       []esv1.FakeProviderData
-	)
-
-	BeforeEach(func() {
-		testNamespace = SetupTestNamespace(f, "v2-metrics-")
-		providerCRName = "fake-provider-cr"
-		providerName = "fake-provider-conn"
-		secretName = "test-secret-metrics"
-		externalSecret = "test-es-metrics"
-
-		// Create fake provider configuration
-		fakeData = []esv1.FakeProviderData{
-			{
-				Key:   "password",
-				Value: "supersecret123",
-			},
-			{
-				Key:   "username",
-				Value: "admin",
-			},
-		}
-	})
-
-	AfterEach(func() {
-		if testNamespace != nil {
-			Expect(f.CRClient.Delete(context.Background(), testNamespace)).To(Succeed())
-		}
-	})
-
-	Describe("Controller Metrics", func() {
-		It("should expose Provider controller metrics", func() {
-			By("Creating a Fake provider CRD")
-			CreateFakeProvider(f, testNamespace.Name, providerCRName, fakeData)
-
-			By("Creating a Provider connection")
-			CreateFakeProviderConnection(f, testNamespace.Name, providerName, providerCRName, testNamespace.Name)
-
-			By("Waiting for Provider to be ready")
-			WaitForProviderConnectionReady(f, testNamespace.Name, providerName, 60*time.Second)
-
-			By("Scraping controller metrics")
-			metrics, err := scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Verifying provider_status_condition metric exists")
-			ExpectMetricExists(metrics, "provider_status_condition")
-
-			By("Verifying provider_status_condition shows Ready=True")
-			ExpectMetricValue(metrics, "provider_status_condition", map[string]string{
-				"name":      providerName,
-				"namespace": testNamespace.Name,
-				"condition": "Ready",
-				"status":    "True",
-			}, 1.0)
-
-			By("Verifying provider_reconcile_duration exists and is > 0")
-			ExpectMetricGreaterThan(metrics, "provider_reconcile_duration", map[string]string{
-				"name":      providerName,
-				"namespace": testNamespace.Name,
-			}, 0.0)
-		})
-
-		It("should expose ClusterProvider controller metrics", func() {
-			clusterProviderName := "fake-cluster-provider-metrics"
-
-			By("Creating a Fake provider CRD")
-			CreateFakeProvider(f, testNamespace.Name, providerCRName, fakeData)
-
-			By("Creating a ClusterProvider resource")
-			CreateClusterProvider(f, clusterProviderName, "provider-fake.external-secrets-system.svc:8080",
-				"provider.external-secrets.io/v2alpha1", "Fake", providerCRName, testNamespace.Name,
-				esv1.AuthenticationScopeProviderNamespace, nil)
-
-			By("Waiting for ClusterProvider to be ready")
-			WaitForClusterProviderReady(f, clusterProviderName, 60*time.Second)
-
-			By("Scraping controller metrics")
-			metrics, err := scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Verifying clusterprovider_status_condition metric exists")
-			ExpectMetricExists(metrics, "clusterprovider_status_condition")
-
-			By("Verifying clusterprovider_status_condition shows Ready=True")
-			ExpectMetricValue(metrics, "clusterprovider_status_condition", map[string]string{
-				"name":      clusterProviderName,
-				"condition": "Ready",
-				"status":    "True",
-			}, 1.0)
-
-			By("Verifying clusterprovider_reconcile_duration exists and is > 0")
-			ExpectMetricGreaterThan(metrics, "clusterprovider_reconcile_duration", map[string]string{
-				"name": clusterProviderName,
-			}, 0.0)
-		})
-
-		It("should track clientmanager cache hits and misses", func() {
-			By("Creating a Fake provider CRD")
-			CreateFakeProvider(f, testNamespace.Name, providerCRName, fakeData)
-
-			By("Creating a Provider connection")
-			CreateFakeProviderConnection(f, testNamespace.Name, providerName, providerCRName, testNamespace.Name)
-			WaitForProviderConnectionReady(f, testNamespace.Name, providerName, 60*time.Second)
-
-			By("Creating an ExternalSecret with multiple data entries to trigger cache hits")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      externalSecret,
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					RefreshInterval: &metav1.Duration{Duration: 60 * time.Second},
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: providerName,
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: secretName,
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "password",
-							},
-						},
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "username",
-							},
-						},
-					},
-				},
-			}
-			err := f.CRClient.Create(context.Background(), es)
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Waiting for secret to be created")
-			Eventually(func() bool {
-				secret := &corev1.Secret{}
-				err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: testNamespace.Name}, secret)
-				return err == nil && len(secret.Data) >= 2
-			}, 60*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("Waiting a moment for metrics to be recorded")
-			time.Sleep(2 * time.Second)
-
-			By("Scraping controller metrics")
-			metrics, err := scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Verifying clientmanager_cache_hits_total metric exists")
-			ExpectMetricExists(metrics, "clientmanager_cache_hits_total")
-
-			By("Verifying cache hits occurred within the reconcile")
-			// With 2 data entries, the second Get() call should hit the cache
-			value, found := getMetricValue(metrics, "clientmanager_cache_hits_total", map[string]string{
-				"provider_type": "provider",
-			})
-			Expect(found).To(BeTrue())
-			Expect(value).To(BeNumerically(">=", 1.0), "should have at least one cache hit from multiple data entries")
-		})
-	})
-
-	Describe("Provider Pod Metrics", func() {
-		BeforeEach(func() {
-			By("Creating a Fake provider CRD")
-			CreateFakeProvider(f, testNamespace.Name, providerCRName, fakeData)
-
-			By("Creating a Provider connection")
-			CreateFakeProviderConnection(f, testNamespace.Name, providerName, providerCRName, testNamespace.Name)
-			WaitForProviderConnectionReady(f, testNamespace.Name, providerName, 60*time.Second)
-		})
-
-		It("should expose connection pool metrics", func() {
-			By("Creating an ExternalSecret")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      externalSecret,
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					RefreshInterval: &metav1.Duration{Duration: 10 * time.Second},
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: providerName,
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: secretName,
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "password",
-							},
-						},
-					},
-				},
-			}
-			err := f.CRClient.Create(context.Background(), es)
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Waiting for secret to be created")
-			Eventually(func() bool {
-				secret := &corev1.Secret{}
-				err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: testNamespace.Name}, secret)
-				return err == nil && len(secret.Data) > 0
-			}, 60*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("Scraping controller metrics for pool stats")
-			metrics, err := scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Verifying grpc_pool_misses_total exists (first connection)")
-			ExpectMetricExists(metrics, "grpc_pool_misses_total")
-
-			By("Verifying at least one cache miss occurred")
-			ExpectMetricGreaterThan(metrics, "grpc_pool_misses_total", map[string]string{}, 0.0)
-
-			By("Verifying grpc_pool_connections_total exists")
-			ExpectMetricExists(metrics, "grpc_pool_connections_total")
-
-			By("Triggering another reconcile to test cache hit")
-			// Update the ExternalSecret to trigger reconciliation
-			err = f.CRClient.Get(context.Background(), types.NamespacedName{Name: externalSecret, Namespace: testNamespace.Name}, es)
-			Expect(err).ToNot(HaveOccurred())
-			es.Annotations = map[string]string{"test": "trigger-reconcile"}
-			err = f.CRClient.Update(context.Background(), es)
-			Expect(err).ToNot(HaveOccurred())
-
-			time.Sleep(5 * time.Second)
-
-			By("Scraping controller metrics again for pool hits")
-			metrics, err = scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Verifying grpc_pool_hits_total incremented")
-			ExpectMetricGreaterThan(metrics, "grpc_pool_hits_total", map[string]string{}, 0.0)
-		})
-
-		It("should expose gRPC client metrics", func() {
-			By("Creating an ExternalSecret")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      externalSecret,
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					RefreshInterval: &metav1.Duration{Duration: 60 * time.Second},
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: providerName,
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: secretName,
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "password",
-							},
-						},
-					},
-				},
-			}
-			err := f.CRClient.Create(context.Background(), es)
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Waiting for secret to be created")
-			Eventually(func() bool {
-				secret := &corev1.Secret{}
-				err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: testNamespace.Name}, secret)
-				return err == nil && len(secret.Data) > 0
-			}, 60*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("Scraping controller metrics for client stats")
-			metrics, err := scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Verifying grpc_client_requests_total exists")
-			ExpectMetricExists(metrics, "grpc_client_requests_total")
-
-			By("Verifying GetSecret requests were made successfully")
-			value, found := getMetricValue(metrics, "grpc_client_requests_total", map[string]string{
-				"method": "GetSecret",
-				"status": "success",
-			})
-			Expect(found).To(BeTrue())
-			Expect(value).To(BeNumerically(">=", 1.0), "should have at least one successful GetSecret call")
-
-			By("Verifying grpc_client_request_duration_seconds exists")
-			ExpectMetricExists(metrics, "grpc_client_request_duration_seconds_count")
-
-			By("Verifying request duration was recorded")
-			value, found = getMetricValue(metrics, "grpc_client_request_duration_seconds_count", map[string]string{
-				"method": "GetSecret",
-				"status": "success",
-			})
-			Expect(found).To(BeTrue())
-			Expect(value).To(BeNumerically(">=", 1.0))
-		})
-
-		It("should expose gRPC server metrics", func() {
-			By("Creating an ExternalSecret")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      externalSecret,
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					RefreshInterval: &metav1.Duration{Duration: 60 * time.Second},
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: providerName,
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: secretName,
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "password",
-							},
-						},
-					},
-				},
-			}
-			err := f.CRClient.Create(context.Background(), es)
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Waiting for secret to be created")
-			Eventually(func() bool {
-				secret := &corev1.Secret{}
-				err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: testNamespace.Name}, secret)
-				return err == nil && len(secret.Data) > 0
-			}, 60*time.Second, 1*time.Second).Should(BeTrue())
-
-			By("Scraping provider pod metrics")
-			metrics, err := scrapeProviderMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system", "fake")
-			Expect(err).ToNot(HaveOccurred())
-
-			By("Verifying grpc_server_requests_total exists")
-			ExpectMetricExists(metrics, "grpc_server_requests_total")
-
-			By("Verifying server handled GetSecret requests")
-			value, found := getMetricValue(metrics, "grpc_server_requests_total", map[string]string{
-				"method": "/provider.v1.SecretStoreProvider/GetSecret",
-				"status": "success",
-			})
-			Expect(found).To(BeTrue())
-			Expect(value).To(BeNumerically(">=", 1.0))
-
-			By("Verifying grpc_server_request_duration_seconds exists")
-			ExpectMetricExists(metrics, "grpc_server_request_duration_seconds_count")
-
-			By("Verifying server request duration was recorded")
-			value, found = getMetricValue(metrics, "grpc_server_request_duration_seconds_count", map[string]string{
-				"method": "/provider.v1.SecretStoreProvider/GetSecret",
-			})
-			Expect(found).To(BeTrue())
-			Expect(value).To(BeNumerically(">=", 1.0))
-		})
-	})
-
-	Describe("End-to-End Metrics Workflow", func() {
-		It("should track metrics through full Provider lifecycle", func() {
-			By("1. Creating Provider and verifying controller metrics")
-			CreateFakeProvider(f, testNamespace.Name, providerCRName, fakeData)
-			CreateFakeProviderConnection(f, testNamespace.Name, providerName, providerCRName, testNamespace.Name)
-			WaitForProviderConnectionReady(f, testNamespace.Name, providerName, 60*time.Second)
-
-			controllerMetrics, err := scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-			ExpectMetricValue(controllerMetrics, "provider_status_condition", map[string]string{
-				"name":      providerName,
-				"namespace": testNamespace.Name,
-				"condition": "Ready",
-				"status":    "True",
-			}, 1.0)
-
-			By("2. Creating ExternalSecret with multiple data entries and verifying metrics")
-			es := &esv1.ExternalSecret{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      externalSecret,
-					Namespace: testNamespace.Name,
-				},
-				Spec: esv1.ExternalSecretSpec{
-					RefreshInterval: &metav1.Duration{Duration: 10 * time.Second},
-					SecretStoreRef: esv1.SecretStoreRef{
-						Name: providerName,
-						Kind: "Provider",
-					},
-					Target: esv1.ExternalSecretTarget{
-						Name: secretName,
-					},
-					Data: []esv1.ExternalSecretData{
-						{
-							SecretKey: "password",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "password",
-							},
-						},
-						{
-							SecretKey: "username",
-							RemoteRef: esv1.ExternalSecretDataRemoteRef{
-								Key: "username",
-							},
-						},
-					},
-				},
-			}
-			err = f.CRClient.Create(context.Background(), es)
-			Expect(err).ToNot(HaveOccurred())
-
-			Eventually(func() bool {
-				secret := &corev1.Secret{}
-				err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: testNamespace.Name}, secret)
-				return err == nil && len(secret.Data) >= 2
-			}, 60*time.Second, 1*time.Second).Should(BeTrue())
-
-			// Pool and client metrics are on controller, server metrics on provider pod
-			controllerMetrics, err = scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-			ExpectMetricGreaterThan(controllerMetrics, "grpc_pool_misses_total", map[string]string{}, 0.0)
-			ExpectMetricGreaterThan(controllerMetrics, "grpc_client_requests_total", map[string]string{
-				"method": "GetSecret",
-				"status": "success",
-			}, 0.0)
-			
-			providerMetrics, err := scrapeProviderMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system", "fake")
-			Expect(err).ToNot(HaveOccurred())
-			ExpectMetricGreaterThan(providerMetrics, "grpc_server_requests_total", map[string]string{
-				"method": "/provider.v1.SecretStoreProvider/GetSecret",
-				"status": "success",
-			}, 0.0)
-
-			By("3. Waiting for refresh and verifying pool hits")
-			time.Sleep(15 * time.Second)
-
-			controllerMetrics, err = scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-			ExpectMetricGreaterThan(controllerMetrics, "grpc_pool_hits_total", map[string]string{}, 0.0)
-
-			// Clientmanager cache hits occur within a single reconcile when multiple data entries exist
-			controllerMetrics, err = scrapeControllerMetrics(context.Background(), f.KubeConfig, f.KubeClientSet.(*kubernetes.Clientset), "external-secrets-system")
-			Expect(err).ToNot(HaveOccurred())
-			ExpectMetricGreaterThan(controllerMetrics, "clientmanager_cache_hits_total", map[string]string{
-				"provider_type": "provider",
-			}, 0.0)
-
-			By("4. Workflow completed successfully with all metrics tracked")
-		})
-	})
-})
-