Procházet zdrojové kódy

Merge branch 'beach-team' of https://github.com/external-secrets/external-secrets into HEAD

Conch Horse (EngineerBetterCI) před 3 roky
rodič
revize
19c3f2e2d6
66 změnil soubory, kde provedl 6786 přidání a 93 odebrání
  1. 130 0
      apis/externalsecrets/v1alpha1/pushsecret_types.go
  2. 8 0
      apis/externalsecrets/v1alpha1/register.go
  3. 228 0
      apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go
  4. 102 0
      apis/externalsecrets/v1beta1/fakes/pushremoteref.go
  5. 5 0
      apis/externalsecrets/v1beta1/provider.go
  6. 9 0
      apis/externalsecrets/v1beta1/provider_schema_test.go
  7. 24 0
      apis/externalsecrets/v1beta1/pushsecret_interfaces.go
  8. 13 0
      apis/externalsecrets/v1beta1/secretstore_types.go
  9. 15 0
      cmd/root.go
  10. 7 0
      config/crds/bases/external-secrets.io_clustersecretstores.yaml
  11. 142 0
      config/crds/bases/external-secrets.io_pushsecrets.yaml
  12. 7 0
      config/crds/bases/external-secrets.io_secretstores.yaml
  13. 6 0
      deploy/charts/external-secrets/templates/rbac.yaml
  14. 2 0
      deploy/charts/external-secrets/values.yaml
  15. 156 0
      deploy/crds/bundle.yaml
  16. 16 16
      design/002-pushsecret.md
  17. 8 8
      e2e/framework/addon/vault.go
  18. 4 1
      go.mod
  19. 3 0
      go.sum
  20. 2 0
      hack/helm.generate.sh
  21. 784 0
      pkg/controllers/pushsecret/internal/fakes/client.go
  22. 1288 0
      pkg/controllers/pushsecret/internal/fakes/manager.go
  23. 179 0
      pkg/controllers/pushsecret/internal/fakes/recorder.go
  24. 196 0
      pkg/controllers/pushsecret/internal/fakes/statuswriter.go
  25. 247 0
      pkg/controllers/pushsecret/pushsecret_controller.go
  26. 440 0
      pkg/controllers/pushsecret/pushsecret_controller_test.go
  27. 44 0
      pkg/controllers/pushsecret/suite_test.go
  28. 9 0
      pkg/controllers/secretstore/common.go
  29. 33 0
      pkg/controllers/secretstore/common_test.go
  30. 9 0
      pkg/provider/akeyless/akeyless.go
  31. 9 0
      pkg/provider/alibaba/kms.go
  32. 49 6
      pkg/provider/aws/parameterstore/fake/fake.go
  33. 138 22
      pkg/provider/aws/parameterstore/parameterstore.go
  34. 220 3
      pkg/provider/aws/parameterstore/parameterstore_test.go
  35. 5 0
      pkg/provider/aws/provider.go
  36. 53 2
      pkg/provider/aws/secretsmanager/fake/fake.go
  37. 83 1
      pkg/provider/aws/secretsmanager/secretsmanager.go
  38. 230 0
      pkg/provider/aws/secretsmanager/secretsmanager_test.go
  39. 10 0
      pkg/provider/azure/keyvault/keyvault.go
  40. 75 13
      pkg/provider/fake/fake.go
  41. 74 2
      pkg/provider/fake/fake_test.go
  42. 85 2
      pkg/provider/gcp/secretmanager/client.go
  43. 219 1
      pkg/provider/gcp/secretmanager/client_test.go
  44. 123 0
      pkg/provider/gcp/secretmanager/fake/fake.go
  45. 516 0
      pkg/provider/gcp/secretmanager/internal/fakes/client.go
  46. 4 0
      pkg/provider/gcp/secretmanager/provider.go
  47. 10 0
      pkg/provider/gitlab/gitlab.go
  48. 0 1
      pkg/provider/gitlab/gitlab_test.go
  49. 10 0
      pkg/provider/ibm/provider.go
  50. 5 0
      pkg/provider/kubernetes/client.go
  51. 4 0
      pkg/provider/kubernetes/provider.go
  52. 10 0
      pkg/provider/onepassword/onepassword.go
  53. 10 0
      pkg/provider/oracle/oracle.go
  54. 5 0
      pkg/provider/senhasegura/dsm/dsm.go
  55. 5 0
      pkg/provider/senhasegura/provider.go
  56. 22 0
      pkg/provider/testing/fake/fake.go
  57. 21 0
      pkg/provider/vault/fake/vault.go
  58. 446 0
      pkg/provider/vault/internal/fakes/client.go
  59. 60 4
      pkg/provider/vault/vault.go
  60. 120 0
      pkg/provider/vault/vault_test.go
  61. 10 0
      pkg/provider/webhook/webhook.go
  62. 6 1
      pkg/provider/yandex/common/provider.go
  63. 14 9
      pkg/provider/yandex/common/secretsclient.go
  64. 18 0
      pkg/provider/yandex/common/secretsetter.go
  65. 0 1
      pkg/utils/utils.go
  66. 1 0
      tools.go

+ 130 - 0
apis/externalsecrets/v1alpha1/pushsecret_types.go

@@ -0,0 +1,130 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const (
+	ReasonSynced  = "Synced"
+	ReasonErrored = "Errored"
+)
+
+type PushSecretStoreRef struct {
+	// Name of the SecretStore resource
+	Name string `json:"name"`
+
+	// Kind of the SecretStore resource (SecretStore or ClusterSecretStore)
+	// Defaults to `SecretStore`
+	// +optional
+	Kind string `json:"kind,omitempty"`
+}
+
+// PushSecretSpec configures the behavior of the PushSecret.
+type PushSecretSpec struct {
+	RefreshInterval *metav1.Duration     `json:"refreshInterval,omitempty"`
+	SecretStoreRefs []PushSecretStoreRef `json:"secretStoreRefs"`
+	Selector        PushSecretSelector   `json:"selector"`
+	Data            []PushSecretData     `json:"data,omitempty"`
+}
+
+type PushSecretSecret struct {
+	Name string `json:"name"`
+}
+
+type PushSecretSelector struct {
+	Secret PushSecretSecret `json:"secret"`
+}
+
+type PushSecretRemoteRefs struct {
+	RemoteKey string `json:"remoteKey"`
+}
+
+func (r PushSecretRemoteRefs) GetRemoteKey() string {
+	return r.RemoteKey
+}
+
+type PushSecretMatch struct {
+	SecretKey  string                 `json:"secretKey"`
+	RemoteRefs []PushSecretRemoteRefs `json:"remoteRefs"`
+}
+
+type PushSecretData struct {
+	Match PushSecretMatch `json:"match"`
+}
+
+// PushSecretConditionType indicates the condition of the PushSecret.
+type PushSecretConditionType string
+
+const (
+	PushSecretReady PushSecretConditionType = "Ready"
+)
+
+// PushSecretStatusCondition indicates the status of the PushSecret.
+type PushSecretStatusCondition struct {
+	Type   PushSecretConditionType `json:"type"`
+	Status corev1.ConditionStatus  `json:"status"`
+
+	// +optional
+	Reason string `json:"reason,omitempty"`
+
+	// +optional
+	Message string `json:"message,omitempty"`
+
+	// +optional
+	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
+}
+
+// PushSecretStatus indicates the history of the status of PushSecret.
+type PushSecretStatus struct {
+	// +nullable
+	// refreshTime is the time and date the external secret was fetched and
+	// the target secret updated
+	RefreshTime metav1.Time `json:"refreshTime,omitempty"`
+
+	// SyncedResourceVersion keeps track of the last synced version.
+	SyncedResourceVersion string `json:"syncedResourceVersion,omitempty"`
+
+	// +optional
+	Conditions []PushSecretStatusCondition `json:"conditions,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:storageversion
+// PushSecrets is the Schema for the PushSecrets API.
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
+// +kubebuilder:subresource:status
+// +kubebuilder:resource:scope=Namespaced,categories={pushsecrets}
+
+type PushSecret struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   PushSecretSpec   `json:"spec,omitempty"`
+	Status PushSecretStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
+// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
+// PushSecretList contains a list of PushSecret resources.
+type PushSecretList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []PushSecret `json:"items"`
+}

+ 8 - 0
apis/externalsecrets/v1alpha1/register.go

@@ -60,8 +60,16 @@ var (
 	ClusterSecretStoreGroupVersionKind = SchemeGroupVersion.WithKind(ClusterSecretStoreKind)
 )
 
+var (
+	PushSecretKind             = reflect.TypeOf(PushSecret{}).Name()
+	PushSecretGroupKind        = schema.GroupKind{Group: Group, Kind: PushSecretKind}.String()
+	PushSecretKindAPIVersion   = PushSecretKind + "." + SchemeGroupVersion.String()
+	PushSecretGroupVersionKind = SchemeGroupVersion.WithKind(PushSecretKind)
+)
+
 func init() {
 	SchemeBuilder.Register(&ExternalSecret{}, &ExternalSecretList{})
 	SchemeBuilder.Register(&SecretStore{}, &SecretStoreList{})
 	SchemeBuilder.Register(&ClusterSecretStore{}, &ClusterSecretStoreList{})
+	SchemeBuilder.Register(&PushSecret{}, &PushSecretList{})
 }

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

@@ -994,6 +994,234 @@ func (in *OracleSecretRef) DeepCopy() *OracleSecretRef {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecret) DeepCopyInto(out *PushSecret) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecret.
+func (in *PushSecret) DeepCopy() *PushSecret {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecret)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PushSecret) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretData) DeepCopyInto(out *PushSecretData) {
+	*out = *in
+	in.Match.DeepCopyInto(&out.Match)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretData.
+func (in *PushSecretData) DeepCopy() *PushSecretData {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretData)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretList) DeepCopyInto(out *PushSecretList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]PushSecret, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretList.
+func (in *PushSecretList) DeepCopy() *PushSecretList {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *PushSecretList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretMatch) DeepCopyInto(out *PushSecretMatch) {
+	*out = *in
+	if in.RemoteRefs != nil {
+		in, out := &in.RemoteRefs, &out.RemoteRefs
+		*out = make([]PushSecretRemoteRefs, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretMatch.
+func (in *PushSecretMatch) DeepCopy() *PushSecretMatch {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretMatch)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretRemoteRefs) DeepCopyInto(out *PushSecretRemoteRefs) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretRemoteRefs.
+func (in *PushSecretRemoteRefs) DeepCopy() *PushSecretRemoteRefs {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretRemoteRefs)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretSecret) DeepCopyInto(out *PushSecretSecret) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretSecret.
+func (in *PushSecretSecret) DeepCopy() *PushSecretSecret {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretSecret)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretSelector) DeepCopyInto(out *PushSecretSelector) {
+	*out = *in
+	out.Secret = in.Secret
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretSelector.
+func (in *PushSecretSelector) DeepCopy() *PushSecretSelector {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretSelector)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretSpec) DeepCopyInto(out *PushSecretSpec) {
+	*out = *in
+	if in.RefreshInterval != nil {
+		in, out := &in.RefreshInterval, &out.RefreshInterval
+		*out = new(v1.Duration)
+		**out = **in
+	}
+	if in.SecretStoreRefs != nil {
+		in, out := &in.SecretStoreRefs, &out.SecretStoreRefs
+		*out = make([]PushSecretStoreRef, len(*in))
+		copy(*out, *in)
+	}
+	out.Selector = in.Selector
+	if in.Data != nil {
+		in, out := &in.Data, &out.Data
+		*out = make([]PushSecretData, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretSpec.
+func (in *PushSecretSpec) DeepCopy() *PushSecretSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretStatus) DeepCopyInto(out *PushSecretStatus) {
+	*out = *in
+	in.RefreshTime.DeepCopyInto(&out.RefreshTime)
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]PushSecretStatusCondition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretStatus.
+func (in *PushSecretStatus) DeepCopy() *PushSecretStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretStatusCondition) DeepCopyInto(out *PushSecretStatusCondition) {
+	*out = *in
+	in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretStatusCondition.
+func (in *PushSecretStatusCondition) DeepCopy() *PushSecretStatusCondition {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretStatusCondition)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PushSecretStoreRef) DeepCopyInto(out *PushSecretStoreRef) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretStoreRef.
+func (in *PushSecretStoreRef) DeepCopy() *PushSecretStoreRef {
+	if in == nil {
+		return nil
+	}
+	out := new(PushSecretStoreRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SecretStore) DeepCopyInto(out *SecretStore) {
 	*out = *in

+ 102 - 0
apis/externalsecrets/v1beta1/fakes/pushremoteref.go

@@ -0,0 +1,102 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package fakes
+
+import (
+	"sync"
+
+	"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+type PushRemoteRef struct {
+	GetRemoteKeyStub        func() string
+	getRemoteKeyMutex       sync.RWMutex
+	getRemoteKeyArgsForCall []struct {
+	}
+	getRemoteKeyReturns struct {
+		result1 string
+	}
+	getRemoteKeyReturnsOnCall map[int]struct {
+		result1 string
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *PushRemoteRef) GetRemoteKey() string {
+	fake.getRemoteKeyMutex.Lock()
+	ret, specificReturn := fake.getRemoteKeyReturnsOnCall[len(fake.getRemoteKeyArgsForCall)]
+	fake.getRemoteKeyArgsForCall = append(fake.getRemoteKeyArgsForCall, struct {
+	}{})
+	stub := fake.GetRemoteKeyStub
+	fakeReturns := fake.getRemoteKeyReturns
+	fake.recordInvocation("GetRemoteKey", []interface{}{})
+	fake.getRemoteKeyMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *PushRemoteRef) GetRemoteKeyCallCount() int {
+	fake.getRemoteKeyMutex.RLock()
+	defer fake.getRemoteKeyMutex.RUnlock()
+	return len(fake.getRemoteKeyArgsForCall)
+}
+
+func (fake *PushRemoteRef) GetRemoteKeyCalls(stub func() string) {
+	fake.getRemoteKeyMutex.Lock()
+	defer fake.getRemoteKeyMutex.Unlock()
+	fake.GetRemoteKeyStub = stub
+}
+
+func (fake *PushRemoteRef) GetRemoteKeyReturns(result1 string) {
+	fake.getRemoteKeyMutex.Lock()
+	defer fake.getRemoteKeyMutex.Unlock()
+	fake.GetRemoteKeyStub = nil
+	fake.getRemoteKeyReturns = struct {
+		result1 string
+	}{result1}
+}
+
+func (fake *PushRemoteRef) GetRemoteKeyReturnsOnCall(i int, result1 string) {
+	fake.getRemoteKeyMutex.Lock()
+	defer fake.getRemoteKeyMutex.Unlock()
+	fake.GetRemoteKeyStub = nil
+	if fake.getRemoteKeyReturnsOnCall == nil {
+		fake.getRemoteKeyReturnsOnCall = make(map[int]struct {
+			result1 string
+		})
+	}
+	fake.getRemoteKeyReturnsOnCall[i] = struct {
+		result1 string
+	}{result1}
+}
+
+func (fake *PushRemoteRef) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.getRemoteKeyMutex.RLock()
+	defer fake.getRemoteKeyMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *PushRemoteRef) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ v1beta1.PushRemoteRef = new(PushRemoteRef)

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

@@ -51,6 +51,8 @@ type Provider interface {
 
 	// ValidateStore checks if the provided store is valid
 	ValidateStore(store GenericStore) error
+	// Capabilities returns the provider Capabilities (Read, Write, ReadWrite)
+	Capabilities() SecretStoreCapabilities
 }
 
 // +kubebuilder:object:root=false
@@ -65,6 +67,9 @@ type SecretsClient interface {
 	// then the secret entry will be deleted depending on the deletionPolicy.
 	GetSecret(ctx context.Context, ref ExternalSecretDataRemoteRef) ([]byte, error)
 
+	// SetSecret will write a single secret into the provider
+	SetSecret(ctx context.Context, value []byte, remoteRef PushRemoteRef) error
+
 	// Validate checks if the client is configured correctly
 	// and is able to retrieve secrets from the provider.
 	// If the validation result is unknown it will be ignored.

+ 9 - 0
apis/externalsecrets/v1beta1/provider_schema_test.go

@@ -25,11 +25,20 @@ type PP struct{}
 
 const shouldBeRegistered = "provider should be registered"
 
+func (p *PP) Capabilities() SecretStoreCapabilities {
+	return SecretStoreReadOnly
+}
+
 // New constructs a SecretsManager Provider.
 func (p *PP) NewClient(ctx context.Context, store GenericStore, kube client.Client, namespace string) (SecretsClient, error) {
 	return p, nil
 }
 
+// SetSecret writes a single secret into a provider.
+func (p *PP) SetSecret(ctx context.Context, value []byte, remoteRef PushRemoteRef) error {
+	return nil
+}
+
 // GetSecret returns a single secret from the provider.
 func (p *PP) GetSecret(ctx context.Context, ref ExternalSecretDataRemoteRef) ([]byte, error) {
 	return []byte("NOOP"), nil

+ 24 - 0
apis/externalsecrets/v1beta1/pushsecret_interfaces.go

@@ -0,0 +1,24 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package v1beta1
+
+// +kubebuilder:object:root=false
+// +kubebuilder:object:generate:false
+// +k8s:deepcopy-gen:interfaces=nil
+// +k8s:deepcopy-gen=nil
+
+// This interface is to allow using v1alpha1 content in Provider registered in v1beta1.
+type PushRemoteRef interface {
+	GetRemoteKey() string
+}

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

@@ -165,10 +165,21 @@ type SecretStoreStatusCondition struct {
 	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
 }
 
+// SecretStoreCapabilities defines the possible operations a SecretStore can do.
+type SecretStoreCapabilities string
+
+const (
+	SecretStoreReadOnly  SecretStoreCapabilities = "ReadOnly"
+	SecretStoreWriteOnly SecretStoreCapabilities = "WriteOnly"
+	SecretStoreReadWrite SecretStoreCapabilities = "ReadWrite"
+)
+
 // SecretStoreStatus defines the observed state of the SecretStore.
 type SecretStoreStatus struct {
 	// +optional
 	Conditions []SecretStoreStatusCondition `json:"conditions"`
+	// +optional
+	Capabilities SecretStoreCapabilities `json:"capabilities"`
 }
 
 // +kubebuilder:object:root=true
@@ -177,6 +188,7 @@ type SecretStoreStatus struct {
 // SecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields.
 // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
 // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
+// +kubebuilder:printcolumn:name="Capabilities",type=string,JSONPath=`.status.capabilities`
 // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
 // +kubebuilder:subresource:status
 // +kubebuilder:resource:scope=Namespaced,categories={externalsecrets},shortName=ss
@@ -203,6 +215,7 @@ type SecretStoreList struct {
 // ClusterSecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields.
 // +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
 // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
+// +kubebuilder:printcolumn:name="Capabilities",type=string,JSONPath=`.status.capabilities`
 // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
 // +kubebuilder:subresource:status
 // +kubebuilder:resource:scope=Cluster,categories={externalsecrets},shortName=css

+ 15 - 0
cmd/root.go

@@ -36,6 +36,7 @@ import (
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret"
+	"github.com/external-secrets/external-secrets/pkg/controllers/pushsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
 	awsauth "github.com/external-secrets/external-secrets/pkg/provider/aws/auth"
 )
@@ -59,6 +60,7 @@ var (
 	namespace                             string
 	enableClusterStoreReconciler          bool
 	enableClusterExternalSecretReconciler bool
+	enablePushSecretReconciler            bool
 	enableFloodGate                       bool
 	storeRequeueInterval                  time.Duration
 	serviceName, serviceNamespace         string
@@ -160,6 +162,18 @@ var rootCmd = &cobra.Command{
 			setupLog.Error(err, errCreateController, "controller", "ExternalSecret")
 			os.Exit(1)
 		}
+		if enablePushSecretReconciler {
+			if err = (&pushsecret.Reconciler{
+				Client:          mgr.GetClient(),
+				Log:             ctrl.Log.WithName("controllers").WithName("PushSecret"),
+				Scheme:          mgr.GetScheme(),
+				ControllerClass: controllerClass,
+				RequeueInterval: time.Hour,
+			}).SetupWithManager(mgr); err != nil {
+				setupLog.Error(err, errCreateController, "controller", "PushSecret")
+				os.Exit(1)
+			}
+		}
 		if enableClusterExternalSecretReconciler {
 			if err = (&clusterexternalsecret.Reconciler{
 				Client:          mgr.GetClient(),
@@ -202,6 +216,7 @@ func init() {
 	rootCmd.Flags().StringVar(&namespace, "namespace", "", "watch external secrets scoped in the provided namespace only. ClusterSecretStore can be used but only work if it doesn't reference resources from other namespaces")
 	rootCmd.Flags().BoolVar(&enableClusterStoreReconciler, "enable-cluster-store-reconciler", true, "Enable cluster store reconciler.")
 	rootCmd.Flags().BoolVar(&enableClusterExternalSecretReconciler, "enable-cluster-external-secret-reconciler", true, "Enable cluster external secret reconciler.")
+	rootCmd.Flags().BoolVar(&enablePushSecretReconciler, "experimental-enable-push-secret-reconciler", false, "Enable push secret reconciler.")
 	rootCmd.Flags().BoolVar(&enableSecretsCache, "enable-secrets-caching", false, "Enable secrets caching for external-secrets pod.")
 	rootCmd.Flags().BoolVar(&enableConfigMapsCache, "enable-configmaps-caching", false, "Enable secrets caching for external-secrets pod.")
 	rootCmd.Flags().DurationVar(&storeRequeueInterval, "store-requeue-interval", time.Minute*5, "Default Time duration between reconciling (Cluster)SecretStores")

+ 7 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -1512,6 +1512,9 @@ spec:
     - jsonPath: .status.conditions[?(@.type=="Ready")].reason
       name: Status
       type: string
+    - jsonPath: .status.capabilities
+      name: Capabilities
+      type: string
     - jsonPath: .status.conditions[?(@.type=="Ready")].status
       name: Ready
       type: string
@@ -3163,6 +3166,10 @@ spec:
           status:
             description: SecretStoreStatus defines the observed state of the SecretStore.
             properties:
+              capabilities:
+                description: SecretStoreCapabilities defines the possible operations
+                  a SecretStore can do.
+                type: string
               conditions:
                 items:
                   properties:

+ 142 - 0
config/crds/bases/external-secrets.io_pushsecrets.yaml

@@ -0,0 +1,142 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: pushsecrets.external-secrets.io
+spec:
+  group: external-secrets.io
+  names:
+    categories:
+    - pushsecrets
+    kind: PushSecret
+    listKind: PushSecretList
+    plural: pushsecrets
+    singular: pushsecret
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .metadata.creationTimestamp
+      name: AGE
+      type: date
+    - jsonPath: .status.conditions[?(@.type=="Ready")].reason
+      name: Status
+      type: string
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: PushSecretSpec configures the behavior of the PushSecret.
+            properties:
+              data:
+                items:
+                  properties:
+                    match:
+                      properties:
+                        remoteRefs:
+                          items:
+                            properties:
+                              remoteKey:
+                                type: string
+                            required:
+                            - remoteKey
+                            type: object
+                          type: array
+                        secretKey:
+                          type: string
+                      required:
+                      - remoteRefs
+                      - secretKey
+                      type: object
+                  required:
+                  - match
+                  type: object
+                type: array
+              refreshInterval:
+                type: string
+              secretStoreRefs:
+                items:
+                  properties:
+                    kind:
+                      description: Kind of the SecretStore resource (SecretStore or
+                        ClusterSecretStore) Defaults to `SecretStore`
+                      type: string
+                    name:
+                      description: Name of the SecretStore resource
+                      type: string
+                  required:
+                  - name
+                  type: object
+                type: array
+              selector:
+                properties:
+                  secret:
+                    properties:
+                      name:
+                        type: string
+                    required:
+                    - name
+                    type: object
+                required:
+                - secret
+                type: object
+            required:
+            - secretStoreRefs
+            - selector
+            type: object
+          status:
+            description: PushSecretStatus indicates the history of the status of PushSecret.
+            properties:
+              conditions:
+                items:
+                  description: PushSecretStatusCondition indicates the status of the
+                    PushSecret.
+                  properties:
+                    lastTransitionTime:
+                      format: date-time
+                      type: string
+                    message:
+                      type: string
+                    reason:
+                      type: string
+                    status:
+                      type: string
+                    type:
+                      description: PushSecretConditionType indicates the condition
+                        of the PushSecret.
+                      type: string
+                  required:
+                  - status
+                  - type
+                  type: object
+                type: array
+              refreshTime:
+                description: refreshTime is the time and date the external secret
+                  was fetched and the target secret updated
+                format: date-time
+                nullable: true
+                type: string
+              syncedResourceVersion:
+                description: SyncedResourceVersion keeps track of the last synced
+                  version.
+                type: string
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}

+ 7 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -1512,6 +1512,9 @@ spec:
     - jsonPath: .status.conditions[?(@.type=="Ready")].reason
       name: Status
       type: string
+    - jsonPath: .status.capabilities
+      name: Capabilities
+      type: string
     - jsonPath: .status.conditions[?(@.type=="Ready")].status
       name: Ready
       type: string
@@ -3163,6 +3166,10 @@ spec:
           status:
             description: SecretStoreStatus defines the observed state of the SecretStore.
             properties:
+              capabilities:
+                description: SecretStoreCapabilities defines the possible operations
+                  a SecretStore can do.
+                type: string
               conditions:
                 items:
                   properties:

+ 6 - 0
deploy/charts/external-secrets/templates/rbac.yaml

@@ -20,6 +20,7 @@ rules:
     - "clustersecretstores"
     - "externalsecrets"
     - "clusterexternalsecrets"
+    - "pushsecrets"
     verbs:
     - "get"
     - "list"
@@ -39,6 +40,9 @@ rules:
     - "clusterexternalsecrets"
     - "clusterexternalsecrets/status"
     - "clusterexternalsecrets/finalizers"
+    - "pushsecrets"
+    - "pushsecrets/status"
+    - "pushsecrets/finalizers"
     verbs:
     - "update"
     - "patch"
@@ -115,6 +119,7 @@ rules:
       - "externalsecrets"
       - "secretstores"
       - "clustersecretstores"
+      - "pushsecrets"
     verbs:
       - "get"
       - "watch"
@@ -142,6 +147,7 @@ rules:
       - "externalsecrets"
       - "secretstores"
       - "clustersecretstores"
+      - "pushsecrets"
     verbs:
       - "create"
       - "delete"

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

@@ -14,6 +14,8 @@ crds:
   createClusterExternalSecret: true
   # -- If true, create CRDs for Cluster Secret Store.
   createClusterSecretStore: true
+  # -- If true, create CRDs for Push Secret.
+  createPushSecret: false
 
 imagePullSecrets: []
 nameOverride: ""

+ 156 - 0
deploy/crds/bundle.yaml

@@ -1478,6 +1478,9 @@ spec:
         - jsonPath: .status.conditions[?(@.type=="Ready")].reason
           name: Status
           type: string
+        - jsonPath: .status.capabilities
+          name: Capabilities
+          type: string
         - jsonPath: .status.conditions[?(@.type=="Ready")].status
           name: Ready
           type: string
@@ -2669,6 +2672,9 @@ spec:
             status:
               description: SecretStoreStatus defines the observed state of the SecretStore.
               properties:
+                capabilities:
+                  description: SecretStoreCapabilities defines the possible operations a SecretStore can do.
+                  type: string
                 conditions:
                   items:
                     properties:
@@ -3249,6 +3255,150 @@ spec:
 ---
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.9.2
+  creationTimestamp: null
+  name: pushsecrets.external-secrets.io
+spec:
+  group: external-secrets.io
+  names:
+    categories:
+      - pushsecrets
+    kind: PushSecret
+    listKind: PushSecretList
+    plural: pushsecrets
+    singular: pushsecret
+  scope: Namespaced
+  versions:
+    - additionalPrinterColumns:
+        - jsonPath: .metadata.creationTimestamp
+          name: AGE
+          type: date
+        - jsonPath: .status.conditions[?(@.type=="Ready")].reason
+          name: Status
+          type: string
+      name: v1alpha1
+      schema:
+        openAPIV3Schema:
+          properties:
+            apiVersion:
+              description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+              type: string
+            kind:
+              description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: PushSecretSpec configures the behavior of the PushSecret.
+              properties:
+                data:
+                  items:
+                    properties:
+                      match:
+                        properties:
+                          remoteRefs:
+                            items:
+                              properties:
+                                remoteKey:
+                                  type: string
+                              required:
+                                - remoteKey
+                              type: object
+                            type: array
+                          secretKey:
+                            type: string
+                        required:
+                          - remoteRefs
+                          - secretKey
+                        type: object
+                    required:
+                      - match
+                    type: object
+                  type: array
+                refreshInterval:
+                  type: string
+                secretStoreRefs:
+                  items:
+                    properties:
+                      kind:
+                        description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) Defaults to `SecretStore`
+                        type: string
+                      name:
+                        description: Name of the SecretStore resource
+                        type: string
+                    required:
+                      - name
+                    type: object
+                  type: array
+                selector:
+                  properties:
+                    secret:
+                      properties:
+                        name:
+                          type: string
+                      required:
+                        - name
+                      type: object
+                  required:
+                    - secret
+                  type: object
+              required:
+                - secretStoreRefs
+                - selector
+              type: object
+            status:
+              description: PushSecretStatus indicates the history of the status of PushSecret.
+              properties:
+                conditions:
+                  items:
+                    description: PushSecretStatusCondition indicates the status of the PushSecret.
+                    properties:
+                      lastTransitionTime:
+                        format: date-time
+                        type: string
+                      message:
+                        type: string
+                      reason:
+                        type: string
+                      status:
+                        type: string
+                      type:
+                        description: PushSecretConditionType indicates the condition of the PushSecret.
+                        type: string
+                    required:
+                      - status
+                      - type
+                    type: object
+                  type: array
+                refreshTime:
+                  description: refreshTime is the time and date the external secret was fetched and the target secret updated
+                  format: date-time
+                  nullable: true
+                  type: string
+                syncedResourceVersion:
+                  description: SyncedResourceVersion keeps track of the last synced version.
+                  type: string
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
+  conversion:
+    strategy: Webhook
+    webhook:
+      conversionReviewVersions:
+        - v1
+      clientConfig:
+        service:
+          name: kubernetes
+          namespace: default
+          path: /convert
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
 metadata:
   annotations:
     controller-gen.kubebuilder.io/version: v0.9.2
@@ -4349,6 +4499,9 @@ spec:
         - jsonPath: .status.conditions[?(@.type=="Ready")].reason
           name: Status
           type: string
+        - jsonPath: .status.capabilities
+          name: Capabilities
+          type: string
         - jsonPath: .status.conditions[?(@.type=="Ready")].status
           name: Ready
           type: string
@@ -5540,6 +5693,9 @@ spec:
             status:
               description: SecretStoreStatus defines the observed state of the SecretStore.
               properties:
+                capabilities:
+                  description: SecretStoreCapabilities defines the possible operations a SecretStore can do.
+                  type: string
                 conditions:
                   items:
                     properties:

+ 16 - 16
design/002-secretsink.md → design/002-pushsecret.md

@@ -1,8 +1,8 @@
 ```yaml
 ---
-title: SecretSink
+title: PushSecret
 version: v1alpha1
-authors: 
+authors:
 creation-date: 2022-01-25
 status: draft
 ---
@@ -18,15 +18,15 @@ status: draft
 
 
 ## Summary
-The Secret Sink is a feature to allow Secrets from Kubernetes to be saved back into some providers. Where ExternalSecret is responsible to download a Secret from a Provider into Kubernetes (as a K8s Secret), SecretSink will upload a Kubernetes Secret to a Provider.
+The Secret Sink is a feature to allow Secrets from Kubernetes to be saved back into some providers. Where ExternalSecret is responsible to download a Secret from a Provider into Kubernetes (as a K8s Secret), PushSecret will upload a Kubernetes Secret to a Provider.
 
 ## Motivation
 Secret Sink allows some inCluster generated secrets to also be available on a given secret provider. It also allows multiple Providers having the same secret (which means a way to perform failover in case a given secret provider is on downtime or compromised for whatever the reason).
 
 ### Goals
-- CRD Design for the SecretSink
+- CRD Design for the PushSecret
 - Define the need for a SinkStore
-- 
+-
 ### Non-Goals
 Do not implement full compatibility mechanisms with each provider (we are not Terraform neither Crossplane)
 
@@ -113,7 +113,7 @@ spec:
 
 ```yaml
 apiVersion: external-secrets.io/v1alpha1
-kind: SecretSink
+kind: PushSecret
 metadata:
   name: "hello-world"
   namespace: my-ns # Same of the SecretStores
@@ -130,17 +130,17 @@ spec:
     secret:
       name: foobar
   data:
-    match:
-    - secretKey: foobar
+  - match:
+      secretKey: foobar
       remoteRefs:
-      - remoteKey: my/path/foobar 
+      - remoteKey: my/path/foobar
         property: my-property #optional. To allow coming back from a 'dataFrom'
       - remoteKey: secret/my-path-foobar
         property: another-property
     rewrite:
-    - secretKey: game-(.+).(.+)
+      secretKey: game-(.+).(.+)
       remoteRefs:
-      - remoteKey: my/path/($1) 
+      - remoteKey: my/path/($1)
         property: prop-($2)
       - remoteKey: my-path-($1)-($2) #Applies this way to all other secretStores
 
@@ -148,7 +148,7 @@ status:
   refreshTime: "2019-08-12T12:33:02Z"
   conditions:
   - type: Ready
-    status: "True" 
+    status: "True"
     reason: "SecretSynced"
     message: "Secret was synced" #Fully synced
     lastTransitionTime: "2019-08-12T12:33:02Z"
@@ -165,7 +165,7 @@ status:
 ```
 
 ### Behavior
-When checking SecretSink for the Source Secret, check existing labels for SecretStore reference of that particular Secret. If this SecretStore reference is an object in SecretSink SecretStore lists, a SecretSyncError should be emited as we cannot sync the secret to the same SecretStore.
+When checking PushSecret for the Source Secret, check existing labels for SecretStore reference of that particular Secret. If this SecretStore reference is an object in PushSecret SecretStore lists, a SecretSyncError should be emited as we cannot sync the secret to the same SecretStore.
 
 If the SecretStores are all fine or if the Secret has no labels (secret created by user / another tool), for Each SecretStore, get the SyncState of this store (New, SecretSynced, SecretSyncedErr).
 
@@ -177,9 +177,9 @@ We had several discussions on how to implement this feature, and it turns out ju
 
 ### Acceptance Criteria
 + ExternalSecrets create appropriate labels on generated Secrets
-+ SecretSinks can read labels on source Secrets
-+ SecretSinks cannot have same references to SecretStores
-+ SecretSinks respect refreshInterval
++ PushSecrets can read labels on source Secrets
++ PushSecrets cannot have same references to SecretStores
++ PushSecrets respect refreshInterval
 ## Alternatives
 Using some integration with Crossplane can allow to sync the secrets. Cons is this must be either manual or through some integration that would be an independent project on its own.
 

+ 8 - 8
e2e/framework/addon/vault.go

@@ -32,7 +32,7 @@ import (
 	vault "github.com/hashicorp/vault/api"
 
 	// nolint
-	. "github.com/onsi/ginkgo/v2"
+	ginkgo "github.com/onsi/ginkgo/v2"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
@@ -93,7 +93,7 @@ type OperatorInitResponse struct {
 }
 
 func (l *Vault) Install() error {
-	By("Installing vault in " + l.Namespace)
+	ginkgo.By("Installing vault in " + l.Namespace)
 	err := l.chart.Install()
 	if err != nil {
 		return err
@@ -168,13 +168,13 @@ func (l *Vault) initVault() error {
 	l.KubernetesAuthPath = "mykubernetes"              // see configure-vault.sh
 	l.KubernetesAuthRole = "external-secrets-operator" // see configure-vault.sh
 
-	By("Creating vault TLS secret")
+	ginkgo.By("Creating vault TLS secret")
 	err = l.chart.config.CRClient.Create(context.Background(), sec)
 	if err != nil {
 		return err
 	}
 
-	By("Waiting for vault pods to be running")
+	ginkgo.By("Waiting for vault pods to be running")
 	pl, err := util.WaitForPodsRunning(l.chart.config.KubeClientSet, 1, l.Namespace, metav1.ListOptions{
 		LabelSelector: "app.kubernetes.io/name=vault",
 	})
@@ -183,7 +183,7 @@ func (l *Vault) initVault() error {
 	}
 	l.PodName = pl.Items[0].Name
 
-	By("Initializing vault")
+	ginkgo.By("Initializing vault")
 	out, err := util.ExecCmd(
 		l.chart.config.KubeClientSet,
 		l.chart.config.KubeConfig,
@@ -192,7 +192,7 @@ func (l *Vault) initVault() error {
 		return fmt.Errorf("error initializing vault: %w", err)
 	}
 
-	By("Parsing init response")
+	ginkgo.By("Parsing init response")
 	var res OperatorInitResponse
 	err = json.Unmarshal([]byte(out), &res)
 	if err != nil {
@@ -200,7 +200,7 @@ func (l *Vault) initVault() error {
 	}
 	l.RootToken = res.RootToken
 
-	By("Unsealing vault")
+	ginkgo.By("Unsealing vault")
 	for _, k := range res.UnsealKeysB64 {
 		_, err = util.ExecCmd(
 			l.chart.config.KubeClientSet,
@@ -238,7 +238,7 @@ func (l *Vault) initVault() error {
 }
 
 func (l *Vault) configureVault() error {
-	By("configuring vault")
+	ginkgo.By("configuring vault")
 	cmd := `sh /etc/vault-config/configure-vault.sh %s`
 	_, err := util.ExecCmd(
 		l.chart.config.KubeClientSet,

+ 4 - 1
go.mod

@@ -95,7 +95,10 @@ require (
 
 require github.com/1Password/connect-sdk-go v1.5.0
 
-require sigs.k8s.io/yaml v1.3.0
+require (
+	github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0
+	sigs.k8s.io/yaml v1.3.0
+)
 
 require (
 	cloud.google.com/go/compute v1.9.0 // indirect

+ 3 - 0
go.sum

@@ -625,6 +625,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0 h1:rBhB9Rls+yb8kA4x5a/cWxOufWfXt24E+kq4YlbGj3g=
+github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0/go.mod h1:fJ0UAZc1fx3xZhU4eSHQDJ1ApFmTVhp5VTpV9tm2ogg=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
@@ -757,6 +759,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
+github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=

+ 2 - 0
hack/helm.generate.sh

@@ -21,6 +21,8 @@ for i in "${HELM_DIR}"/templates/crds/*.yml; do
   cp "$i" "$i.bkp"
   if [[ "$CRDS_FLAG_NAME" == *"Cluster"* ]]; then
     echo "{{- if and (.Values.installCRDs) (.Values.crds.$CRDS_FLAG_NAME) }}" > "$i"
+  elif [[ "$$CRDS_FLAG_NAME" == *"PushSecret"* ]]; then 
+			echo "{{- if and (.Values.installCRDs) (.Values.crds.$$CRDS_FLAG_NAME) }}" > "$$i"
   else
     echo "{{- if .Values.installCRDs }}" > "$i"
   fi

+ 784 - 0
pkg/controllers/pushsecret/internal/fakes/client.go

@@ -0,0 +1,784 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package fakes
+
+import (
+	"context"
+	"sync"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type Client struct {
+	CreateStub        func(context.Context, client.Object, ...client.CreateOption) error
+	createMutex       sync.RWMutex
+	createArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.CreateOption
+	}
+	createReturns struct {
+		result1 error
+	}
+	createReturnsOnCall map[int]struct {
+		result1 error
+	}
+	DeleteStub        func(context.Context, client.Object, ...client.DeleteOption) error
+	deleteMutex       sync.RWMutex
+	deleteArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.DeleteOption
+	}
+	deleteReturns struct {
+		result1 error
+	}
+	deleteReturnsOnCall map[int]struct {
+		result1 error
+	}
+	DeleteAllOfStub        func(context.Context, client.Object, ...client.DeleteAllOfOption) error
+	deleteAllOfMutex       sync.RWMutex
+	deleteAllOfArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.DeleteAllOfOption
+	}
+	deleteAllOfReturns struct {
+		result1 error
+	}
+	deleteAllOfReturnsOnCall map[int]struct {
+		result1 error
+	}
+	GetStub        func(context.Context, types.NamespacedName, client.Object) error
+	getMutex       sync.RWMutex
+	getArgsForCall []struct {
+		arg1 context.Context
+		arg2 types.NamespacedName
+		arg3 client.Object
+	}
+	getReturns struct {
+		result1 error
+	}
+	getReturnsOnCall map[int]struct {
+		result1 error
+	}
+	ListStub        func(context.Context, client.ObjectList, ...client.ListOption) error
+	listMutex       sync.RWMutex
+	listArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.ObjectList
+		arg3 []client.ListOption
+	}
+	listReturns struct {
+		result1 error
+	}
+	listReturnsOnCall map[int]struct {
+		result1 error
+	}
+	PatchStub        func(context.Context, client.Object, client.Patch, ...client.PatchOption) error
+	patchMutex       sync.RWMutex
+	patchArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 client.Patch
+		arg4 []client.PatchOption
+	}
+	patchReturns struct {
+		result1 error
+	}
+	patchReturnsOnCall map[int]struct {
+		result1 error
+	}
+	RESTMapperStub        func() meta.RESTMapper
+	rESTMapperMutex       sync.RWMutex
+	rESTMapperArgsForCall []struct {
+	}
+	rESTMapperReturns struct {
+		result1 meta.RESTMapper
+	}
+	rESTMapperReturnsOnCall map[int]struct {
+		result1 meta.RESTMapper
+	}
+	SchemeStub        func() *runtime.Scheme
+	schemeMutex       sync.RWMutex
+	schemeArgsForCall []struct {
+	}
+	schemeReturns struct {
+		result1 *runtime.Scheme
+	}
+	schemeReturnsOnCall map[int]struct {
+		result1 *runtime.Scheme
+	}
+	StatusStub        func() client.StatusWriter
+	statusMutex       sync.RWMutex
+	statusArgsForCall []struct {
+	}
+	statusReturns struct {
+		result1 client.StatusWriter
+	}
+	statusReturnsOnCall map[int]struct {
+		result1 client.StatusWriter
+	}
+	UpdateStub        func(context.Context, client.Object, ...client.UpdateOption) error
+	updateMutex       sync.RWMutex
+	updateArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.UpdateOption
+	}
+	updateReturns struct {
+		result1 error
+	}
+	updateReturnsOnCall map[int]struct {
+		result1 error
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *Client) Create(arg1 context.Context, arg2 client.Object, arg3 ...client.CreateOption) error {
+	fake.createMutex.Lock()
+	ret, specificReturn := fake.createReturnsOnCall[len(fake.createArgsForCall)]
+	fake.createArgsForCall = append(fake.createArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.CreateOption
+	}{arg1, arg2, arg3})
+	stub := fake.CreateStub
+	fakeReturns := fake.createReturns
+	fake.recordInvocation("Create", []interface{}{arg1, arg2, arg3})
+	fake.createMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) CreateCallCount() int {
+	fake.createMutex.RLock()
+	defer fake.createMutex.RUnlock()
+	return len(fake.createArgsForCall)
+}
+
+func (fake *Client) CreateCalls(stub func(context.Context, client.Object, ...client.CreateOption) error) {
+	fake.createMutex.Lock()
+	defer fake.createMutex.Unlock()
+	fake.CreateStub = stub
+}
+
+func (fake *Client) CreateArgsForCall(i int) (context.Context, client.Object, []client.CreateOption) {
+	fake.createMutex.RLock()
+	defer fake.createMutex.RUnlock()
+	argsForCall := fake.createArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *Client) CreateReturns(result1 error) {
+	fake.createMutex.Lock()
+	defer fake.createMutex.Unlock()
+	fake.CreateStub = nil
+	fake.createReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) CreateReturnsOnCall(i int, result1 error) {
+	fake.createMutex.Lock()
+	defer fake.createMutex.Unlock()
+	fake.CreateStub = nil
+	if fake.createReturnsOnCall == nil {
+		fake.createReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.createReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) Delete(arg1 context.Context, arg2 client.Object, arg3 ...client.DeleteOption) error {
+	fake.deleteMutex.Lock()
+	ret, specificReturn := fake.deleteReturnsOnCall[len(fake.deleteArgsForCall)]
+	fake.deleteArgsForCall = append(fake.deleteArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.DeleteOption
+	}{arg1, arg2, arg3})
+	stub := fake.DeleteStub
+	fakeReturns := fake.deleteReturns
+	fake.recordInvocation("Delete", []interface{}{arg1, arg2, arg3})
+	fake.deleteMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) DeleteCallCount() int {
+	fake.deleteMutex.RLock()
+	defer fake.deleteMutex.RUnlock()
+	return len(fake.deleteArgsForCall)
+}
+
+func (fake *Client) DeleteCalls(stub func(context.Context, client.Object, ...client.DeleteOption) error) {
+	fake.deleteMutex.Lock()
+	defer fake.deleteMutex.Unlock()
+	fake.DeleteStub = stub
+}
+
+func (fake *Client) DeleteArgsForCall(i int) (context.Context, client.Object, []client.DeleteOption) {
+	fake.deleteMutex.RLock()
+	defer fake.deleteMutex.RUnlock()
+	argsForCall := fake.deleteArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *Client) DeleteReturns(result1 error) {
+	fake.deleteMutex.Lock()
+	defer fake.deleteMutex.Unlock()
+	fake.DeleteStub = nil
+	fake.deleteReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) DeleteReturnsOnCall(i int, result1 error) {
+	fake.deleteMutex.Lock()
+	defer fake.deleteMutex.Unlock()
+	fake.DeleteStub = nil
+	if fake.deleteReturnsOnCall == nil {
+		fake.deleteReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.deleteReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) DeleteAllOf(arg1 context.Context, arg2 client.Object, arg3 ...client.DeleteAllOfOption) error {
+	fake.deleteAllOfMutex.Lock()
+	ret, specificReturn := fake.deleteAllOfReturnsOnCall[len(fake.deleteAllOfArgsForCall)]
+	fake.deleteAllOfArgsForCall = append(fake.deleteAllOfArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.DeleteAllOfOption
+	}{arg1, arg2, arg3})
+	stub := fake.DeleteAllOfStub
+	fakeReturns := fake.deleteAllOfReturns
+	fake.recordInvocation("DeleteAllOf", []interface{}{arg1, arg2, arg3})
+	fake.deleteAllOfMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) DeleteAllOfCallCount() int {
+	fake.deleteAllOfMutex.RLock()
+	defer fake.deleteAllOfMutex.RUnlock()
+	return len(fake.deleteAllOfArgsForCall)
+}
+
+func (fake *Client) DeleteAllOfCalls(stub func(context.Context, client.Object, ...client.DeleteAllOfOption) error) {
+	fake.deleteAllOfMutex.Lock()
+	defer fake.deleteAllOfMutex.Unlock()
+	fake.DeleteAllOfStub = stub
+}
+
+func (fake *Client) DeleteAllOfArgsForCall(i int) (context.Context, client.Object, []client.DeleteAllOfOption) {
+	fake.deleteAllOfMutex.RLock()
+	defer fake.deleteAllOfMutex.RUnlock()
+	argsForCall := fake.deleteAllOfArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *Client) DeleteAllOfReturns(result1 error) {
+	fake.deleteAllOfMutex.Lock()
+	defer fake.deleteAllOfMutex.Unlock()
+	fake.DeleteAllOfStub = nil
+	fake.deleteAllOfReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) DeleteAllOfReturnsOnCall(i int, result1 error) {
+	fake.deleteAllOfMutex.Lock()
+	defer fake.deleteAllOfMutex.Unlock()
+	fake.DeleteAllOfStub = nil
+	if fake.deleteAllOfReturnsOnCall == nil {
+		fake.deleteAllOfReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.deleteAllOfReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) Get(arg1 context.Context, arg2 types.NamespacedName, arg3 client.Object) error {
+	fake.getMutex.Lock()
+	ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)]
+	fake.getArgsForCall = append(fake.getArgsForCall, struct {
+		arg1 context.Context
+		arg2 types.NamespacedName
+		arg3 client.Object
+	}{arg1, arg2, arg3})
+	stub := fake.GetStub
+	fakeReturns := fake.getReturns
+	fake.recordInvocation("Get", []interface{}{arg1, arg2, arg3})
+	fake.getMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) GetCallCount() int {
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	return len(fake.getArgsForCall)
+}
+
+func (fake *Client) GetCalls(stub func(context.Context, types.NamespacedName, client.Object) error) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = stub
+}
+
+func (fake *Client) GetArgsForCall(i int) (context.Context, types.NamespacedName, client.Object) {
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	argsForCall := fake.getArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *Client) GetReturns(result1 error) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = nil
+	fake.getReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) GetReturnsOnCall(i int, result1 error) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = nil
+	if fake.getReturnsOnCall == nil {
+		fake.getReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.getReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) List(arg1 context.Context, arg2 client.ObjectList, arg3 ...client.ListOption) error {
+	fake.listMutex.Lock()
+	ret, specificReturn := fake.listReturnsOnCall[len(fake.listArgsForCall)]
+	fake.listArgsForCall = append(fake.listArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.ObjectList
+		arg3 []client.ListOption
+	}{arg1, arg2, arg3})
+	stub := fake.ListStub
+	fakeReturns := fake.listReturns
+	fake.recordInvocation("List", []interface{}{arg1, arg2, arg3})
+	fake.listMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) ListCallCount() int {
+	fake.listMutex.RLock()
+	defer fake.listMutex.RUnlock()
+	return len(fake.listArgsForCall)
+}
+
+func (fake *Client) ListCalls(stub func(context.Context, client.ObjectList, ...client.ListOption) error) {
+	fake.listMutex.Lock()
+	defer fake.listMutex.Unlock()
+	fake.ListStub = stub
+}
+
+func (fake *Client) ListArgsForCall(i int) (context.Context, client.ObjectList, []client.ListOption) {
+	fake.listMutex.RLock()
+	defer fake.listMutex.RUnlock()
+	argsForCall := fake.listArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *Client) ListReturns(result1 error) {
+	fake.listMutex.Lock()
+	defer fake.listMutex.Unlock()
+	fake.ListStub = nil
+	fake.listReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) ListReturnsOnCall(i int, result1 error) {
+	fake.listMutex.Lock()
+	defer fake.listMutex.Unlock()
+	fake.ListStub = nil
+	if fake.listReturnsOnCall == nil {
+		fake.listReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.listReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) Patch(arg1 context.Context, arg2 client.Object, arg3 client.Patch, arg4 ...client.PatchOption) error {
+	fake.patchMutex.Lock()
+	ret, specificReturn := fake.patchReturnsOnCall[len(fake.patchArgsForCall)]
+	fake.patchArgsForCall = append(fake.patchArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 client.Patch
+		arg4 []client.PatchOption
+	}{arg1, arg2, arg3, arg4})
+	stub := fake.PatchStub
+	fakeReturns := fake.patchReturns
+	fake.recordInvocation("Patch", []interface{}{arg1, arg2, arg3, arg4})
+	fake.patchMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3, arg4...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) PatchCallCount() int {
+	fake.patchMutex.RLock()
+	defer fake.patchMutex.RUnlock()
+	return len(fake.patchArgsForCall)
+}
+
+func (fake *Client) PatchCalls(stub func(context.Context, client.Object, client.Patch, ...client.PatchOption) error) {
+	fake.patchMutex.Lock()
+	defer fake.patchMutex.Unlock()
+	fake.PatchStub = stub
+}
+
+func (fake *Client) PatchArgsForCall(i int) (context.Context, client.Object, client.Patch, []client.PatchOption) {
+	fake.patchMutex.RLock()
+	defer fake.patchMutex.RUnlock()
+	argsForCall := fake.patchArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
+}
+
+func (fake *Client) PatchReturns(result1 error) {
+	fake.patchMutex.Lock()
+	defer fake.patchMutex.Unlock()
+	fake.PatchStub = nil
+	fake.patchReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) PatchReturnsOnCall(i int, result1 error) {
+	fake.patchMutex.Lock()
+	defer fake.patchMutex.Unlock()
+	fake.PatchStub = nil
+	if fake.patchReturnsOnCall == nil {
+		fake.patchReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.patchReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) RESTMapper() meta.RESTMapper {
+	fake.rESTMapperMutex.Lock()
+	ret, specificReturn := fake.rESTMapperReturnsOnCall[len(fake.rESTMapperArgsForCall)]
+	fake.rESTMapperArgsForCall = append(fake.rESTMapperArgsForCall, struct {
+	}{})
+	stub := fake.RESTMapperStub
+	fakeReturns := fake.rESTMapperReturns
+	fake.recordInvocation("RESTMapper", []interface{}{})
+	fake.rESTMapperMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) RESTMapperCallCount() int {
+	fake.rESTMapperMutex.RLock()
+	defer fake.rESTMapperMutex.RUnlock()
+	return len(fake.rESTMapperArgsForCall)
+}
+
+func (fake *Client) RESTMapperCalls(stub func() meta.RESTMapper) {
+	fake.rESTMapperMutex.Lock()
+	defer fake.rESTMapperMutex.Unlock()
+	fake.RESTMapperStub = stub
+}
+
+func (fake *Client) RESTMapperReturns(result1 meta.RESTMapper) {
+	fake.rESTMapperMutex.Lock()
+	defer fake.rESTMapperMutex.Unlock()
+	fake.RESTMapperStub = nil
+	fake.rESTMapperReturns = struct {
+		result1 meta.RESTMapper
+	}{result1}
+}
+
+func (fake *Client) RESTMapperReturnsOnCall(i int, result1 meta.RESTMapper) {
+	fake.rESTMapperMutex.Lock()
+	defer fake.rESTMapperMutex.Unlock()
+	fake.RESTMapperStub = nil
+	if fake.rESTMapperReturnsOnCall == nil {
+		fake.rESTMapperReturnsOnCall = make(map[int]struct {
+			result1 meta.RESTMapper
+		})
+	}
+	fake.rESTMapperReturnsOnCall[i] = struct {
+		result1 meta.RESTMapper
+	}{result1}
+}
+
+func (fake *Client) Scheme() *runtime.Scheme {
+	fake.schemeMutex.Lock()
+	ret, specificReturn := fake.schemeReturnsOnCall[len(fake.schemeArgsForCall)]
+	fake.schemeArgsForCall = append(fake.schemeArgsForCall, struct {
+	}{})
+	stub := fake.SchemeStub
+	fakeReturns := fake.schemeReturns
+	fake.recordInvocation("Scheme", []interface{}{})
+	fake.schemeMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) SchemeCallCount() int {
+	fake.schemeMutex.RLock()
+	defer fake.schemeMutex.RUnlock()
+	return len(fake.schemeArgsForCall)
+}
+
+func (fake *Client) SchemeCalls(stub func() *runtime.Scheme) {
+	fake.schemeMutex.Lock()
+	defer fake.schemeMutex.Unlock()
+	fake.SchemeStub = stub
+}
+
+func (fake *Client) SchemeReturns(result1 *runtime.Scheme) {
+	fake.schemeMutex.Lock()
+	defer fake.schemeMutex.Unlock()
+	fake.SchemeStub = nil
+	fake.schemeReturns = struct {
+		result1 *runtime.Scheme
+	}{result1}
+}
+
+func (fake *Client) SchemeReturnsOnCall(i int, result1 *runtime.Scheme) {
+	fake.schemeMutex.Lock()
+	defer fake.schemeMutex.Unlock()
+	fake.SchemeStub = nil
+	if fake.schemeReturnsOnCall == nil {
+		fake.schemeReturnsOnCall = make(map[int]struct {
+			result1 *runtime.Scheme
+		})
+	}
+	fake.schemeReturnsOnCall[i] = struct {
+		result1 *runtime.Scheme
+	}{result1}
+}
+
+func (fake *Client) Status() client.StatusWriter {
+	fake.statusMutex.Lock()
+	ret, specificReturn := fake.statusReturnsOnCall[len(fake.statusArgsForCall)]
+	fake.statusArgsForCall = append(fake.statusArgsForCall, struct {
+	}{})
+	stub := fake.StatusStub
+	fakeReturns := fake.statusReturns
+	fake.recordInvocation("Status", []interface{}{})
+	fake.statusMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) StatusCallCount() int {
+	fake.statusMutex.RLock()
+	defer fake.statusMutex.RUnlock()
+	return len(fake.statusArgsForCall)
+}
+
+func (fake *Client) StatusCalls(stub func() client.StatusWriter) {
+	fake.statusMutex.Lock()
+	defer fake.statusMutex.Unlock()
+	fake.StatusStub = stub
+}
+
+func (fake *Client) StatusReturns(result1 client.StatusWriter) {
+	fake.statusMutex.Lock()
+	defer fake.statusMutex.Unlock()
+	fake.StatusStub = nil
+	fake.statusReturns = struct {
+		result1 client.StatusWriter
+	}{result1}
+}
+
+func (fake *Client) StatusReturnsOnCall(i int, result1 client.StatusWriter) {
+	fake.statusMutex.Lock()
+	defer fake.statusMutex.Unlock()
+	fake.StatusStub = nil
+	if fake.statusReturnsOnCall == nil {
+		fake.statusReturnsOnCall = make(map[int]struct {
+			result1 client.StatusWriter
+		})
+	}
+	fake.statusReturnsOnCall[i] = struct {
+		result1 client.StatusWriter
+	}{result1}
+}
+
+func (fake *Client) Update(arg1 context.Context, arg2 client.Object, arg3 ...client.UpdateOption) error {
+	fake.updateMutex.Lock()
+	ret, specificReturn := fake.updateReturnsOnCall[len(fake.updateArgsForCall)]
+	fake.updateArgsForCall = append(fake.updateArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.UpdateOption
+	}{arg1, arg2, arg3})
+	stub := fake.UpdateStub
+	fakeReturns := fake.updateReturns
+	fake.recordInvocation("Update", []interface{}{arg1, arg2, arg3})
+	fake.updateMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Client) UpdateCallCount() int {
+	fake.updateMutex.RLock()
+	defer fake.updateMutex.RUnlock()
+	return len(fake.updateArgsForCall)
+}
+
+func (fake *Client) UpdateCalls(stub func(context.Context, client.Object, ...client.UpdateOption) error) {
+	fake.updateMutex.Lock()
+	defer fake.updateMutex.Unlock()
+	fake.UpdateStub = stub
+}
+
+func (fake *Client) UpdateArgsForCall(i int) (context.Context, client.Object, []client.UpdateOption) {
+	fake.updateMutex.RLock()
+	defer fake.updateMutex.RUnlock()
+	argsForCall := fake.updateArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *Client) UpdateReturns(result1 error) {
+	fake.updateMutex.Lock()
+	defer fake.updateMutex.Unlock()
+	fake.UpdateStub = nil
+	fake.updateReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) UpdateReturnsOnCall(i int, result1 error) {
+	fake.updateMutex.Lock()
+	defer fake.updateMutex.Unlock()
+	fake.UpdateStub = nil
+	if fake.updateReturnsOnCall == nil {
+		fake.updateReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.updateReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Client) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.createMutex.RLock()
+	defer fake.createMutex.RUnlock()
+	fake.deleteMutex.RLock()
+	defer fake.deleteMutex.RUnlock()
+	fake.deleteAllOfMutex.RLock()
+	defer fake.deleteAllOfMutex.RUnlock()
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	fake.listMutex.RLock()
+	defer fake.listMutex.RUnlock()
+	fake.patchMutex.RLock()
+	defer fake.patchMutex.RUnlock()
+	fake.rESTMapperMutex.RLock()
+	defer fake.rESTMapperMutex.RUnlock()
+	fake.schemeMutex.RLock()
+	defer fake.schemeMutex.RUnlock()
+	fake.statusMutex.RLock()
+	defer fake.statusMutex.RUnlock()
+	fake.updateMutex.RLock()
+	defer fake.updateMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *Client) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ client.Client = new(Client)

+ 1288 - 0
pkg/controllers/pushsecret/internal/fakes/manager.go

@@ -0,0 +1,1288 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package fakes
+
+import (
+	"context"
+	"net/http"
+	"sync"
+
+	"github.com/go-logr/logr"
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/record"
+	"sigs.k8s.io/controller-runtime/pkg/cache"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/config/v1alpha1"
+	"sigs.k8s.io/controller-runtime/pkg/healthz"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/webhook"
+)
+
+type Manager struct {
+	AddStub        func(manager.Runnable) error
+	addMutex       sync.RWMutex
+	addArgsForCall []struct {
+		arg1 manager.Runnable
+	}
+	addReturns struct {
+		result1 error
+	}
+	addReturnsOnCall map[int]struct {
+		result1 error
+	}
+	AddHealthzCheckStub        func(string, healthz.Checker) error
+	addHealthzCheckMutex       sync.RWMutex
+	addHealthzCheckArgsForCall []struct {
+		arg1 string
+		arg2 healthz.Checker
+	}
+	addHealthzCheckReturns struct {
+		result1 error
+	}
+	addHealthzCheckReturnsOnCall map[int]struct {
+		result1 error
+	}
+	AddMetricsExtraHandlerStub        func(string, http.Handler) error
+	addMetricsExtraHandlerMutex       sync.RWMutex
+	addMetricsExtraHandlerArgsForCall []struct {
+		arg1 string
+		arg2 http.Handler
+	}
+	addMetricsExtraHandlerReturns struct {
+		result1 error
+	}
+	addMetricsExtraHandlerReturnsOnCall map[int]struct {
+		result1 error
+	}
+	AddReadyzCheckStub        func(string, healthz.Checker) error
+	addReadyzCheckMutex       sync.RWMutex
+	addReadyzCheckArgsForCall []struct {
+		arg1 string
+		arg2 healthz.Checker
+	}
+	addReadyzCheckReturns struct {
+		result1 error
+	}
+	addReadyzCheckReturnsOnCall map[int]struct {
+		result1 error
+	}
+	ElectedStub        func() <-chan struct{}
+	electedMutex       sync.RWMutex
+	electedArgsForCall []struct {
+	}
+	electedReturns struct {
+		result1 <-chan struct{}
+	}
+	electedReturnsOnCall map[int]struct {
+		result1 <-chan struct{}
+	}
+	GetAPIReaderStub        func() client.Reader
+	getAPIReaderMutex       sync.RWMutex
+	getAPIReaderArgsForCall []struct {
+	}
+	getAPIReaderReturns struct {
+		result1 client.Reader
+	}
+	getAPIReaderReturnsOnCall map[int]struct {
+		result1 client.Reader
+	}
+	GetCacheStub        func() cache.Cache
+	getCacheMutex       sync.RWMutex
+	getCacheArgsForCall []struct {
+	}
+	getCacheReturns struct {
+		result1 cache.Cache
+	}
+	getCacheReturnsOnCall map[int]struct {
+		result1 cache.Cache
+	}
+	GetClientStub        func() client.Client
+	getClientMutex       sync.RWMutex
+	getClientArgsForCall []struct {
+	}
+	getClientReturns struct {
+		result1 client.Client
+	}
+	getClientReturnsOnCall map[int]struct {
+		result1 client.Client
+	}
+	GetConfigStub        func() *rest.Config
+	getConfigMutex       sync.RWMutex
+	getConfigArgsForCall []struct {
+	}
+	getConfigReturns struct {
+		result1 *rest.Config
+	}
+	getConfigReturnsOnCall map[int]struct {
+		result1 *rest.Config
+	}
+	GetControllerOptionsStub        func() v1alpha1.ControllerConfigurationSpec
+	getControllerOptionsMutex       sync.RWMutex
+	getControllerOptionsArgsForCall []struct {
+	}
+	getControllerOptionsReturns struct {
+		result1 v1alpha1.ControllerConfigurationSpec
+	}
+	getControllerOptionsReturnsOnCall map[int]struct {
+		result1 v1alpha1.ControllerConfigurationSpec
+	}
+	GetEventRecorderForStub        func(string) record.EventRecorder
+	getEventRecorderForMutex       sync.RWMutex
+	getEventRecorderForArgsForCall []struct {
+		arg1 string
+	}
+	getEventRecorderForReturns struct {
+		result1 record.EventRecorder
+	}
+	getEventRecorderForReturnsOnCall map[int]struct {
+		result1 record.EventRecorder
+	}
+	GetFieldIndexerStub        func() client.FieldIndexer
+	getFieldIndexerMutex       sync.RWMutex
+	getFieldIndexerArgsForCall []struct {
+	}
+	getFieldIndexerReturns struct {
+		result1 client.FieldIndexer
+	}
+	getFieldIndexerReturnsOnCall map[int]struct {
+		result1 client.FieldIndexer
+	}
+	GetLoggerStub        func() logr.Logger
+	getLoggerMutex       sync.RWMutex
+	getLoggerArgsForCall []struct {
+	}
+	getLoggerReturns struct {
+		result1 logr.Logger
+	}
+	getLoggerReturnsOnCall map[int]struct {
+		result1 logr.Logger
+	}
+	GetRESTMapperStub        func() meta.RESTMapper
+	getRESTMapperMutex       sync.RWMutex
+	getRESTMapperArgsForCall []struct {
+	}
+	getRESTMapperReturns struct {
+		result1 meta.RESTMapper
+	}
+	getRESTMapperReturnsOnCall map[int]struct {
+		result1 meta.RESTMapper
+	}
+	GetSchemeStub        func() *runtime.Scheme
+	getSchemeMutex       sync.RWMutex
+	getSchemeArgsForCall []struct {
+	}
+	getSchemeReturns struct {
+		result1 *runtime.Scheme
+	}
+	getSchemeReturnsOnCall map[int]struct {
+		result1 *runtime.Scheme
+	}
+	GetWebhookServerStub        func() *webhook.Server
+	getWebhookServerMutex       sync.RWMutex
+	getWebhookServerArgsForCall []struct {
+	}
+	getWebhookServerReturns struct {
+		result1 *webhook.Server
+	}
+	getWebhookServerReturnsOnCall map[int]struct {
+		result1 *webhook.Server
+	}
+	SetFieldsStub        func(interface{}) error
+	setFieldsMutex       sync.RWMutex
+	setFieldsArgsForCall []struct {
+		arg1 interface{}
+	}
+	setFieldsReturns struct {
+		result1 error
+	}
+	setFieldsReturnsOnCall map[int]struct {
+		result1 error
+	}
+	StartStub        func(context.Context) error
+	startMutex       sync.RWMutex
+	startArgsForCall []struct {
+		arg1 context.Context
+	}
+	startReturns struct {
+		result1 error
+	}
+	startReturnsOnCall map[int]struct {
+		result1 error
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *Manager) Add(arg1 manager.Runnable) error {
+	fake.addMutex.Lock()
+	ret, specificReturn := fake.addReturnsOnCall[len(fake.addArgsForCall)]
+	fake.addArgsForCall = append(fake.addArgsForCall, struct {
+		arg1 manager.Runnable
+	}{arg1})
+	stub := fake.AddStub
+	fakeReturns := fake.addReturns
+	fake.recordInvocation("Add", []interface{}{arg1})
+	fake.addMutex.Unlock()
+	if stub != nil {
+		return stub(arg1)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) AddCallCount() int {
+	fake.addMutex.RLock()
+	defer fake.addMutex.RUnlock()
+	return len(fake.addArgsForCall)
+}
+
+func (fake *Manager) AddCalls(stub func(manager.Runnable) error) {
+	fake.addMutex.Lock()
+	defer fake.addMutex.Unlock()
+	fake.AddStub = stub
+}
+
+func (fake *Manager) AddArgsForCall(i int) manager.Runnable {
+	fake.addMutex.RLock()
+	defer fake.addMutex.RUnlock()
+	argsForCall := fake.addArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *Manager) AddReturns(result1 error) {
+	fake.addMutex.Lock()
+	defer fake.addMutex.Unlock()
+	fake.AddStub = nil
+	fake.addReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) AddReturnsOnCall(i int, result1 error) {
+	fake.addMutex.Lock()
+	defer fake.addMutex.Unlock()
+	fake.AddStub = nil
+	if fake.addReturnsOnCall == nil {
+		fake.addReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.addReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) AddHealthzCheck(arg1 string, arg2 healthz.Checker) error {
+	fake.addHealthzCheckMutex.Lock()
+	ret, specificReturn := fake.addHealthzCheckReturnsOnCall[len(fake.addHealthzCheckArgsForCall)]
+	fake.addHealthzCheckArgsForCall = append(fake.addHealthzCheckArgsForCall, struct {
+		arg1 string
+		arg2 healthz.Checker
+	}{arg1, arg2})
+	stub := fake.AddHealthzCheckStub
+	fakeReturns := fake.addHealthzCheckReturns
+	fake.recordInvocation("AddHealthzCheck", []interface{}{arg1, arg2})
+	fake.addHealthzCheckMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) AddHealthzCheckCallCount() int {
+	fake.addHealthzCheckMutex.RLock()
+	defer fake.addHealthzCheckMutex.RUnlock()
+	return len(fake.addHealthzCheckArgsForCall)
+}
+
+func (fake *Manager) AddHealthzCheckCalls(stub func(string, healthz.Checker) error) {
+	fake.addHealthzCheckMutex.Lock()
+	defer fake.addHealthzCheckMutex.Unlock()
+	fake.AddHealthzCheckStub = stub
+}
+
+func (fake *Manager) AddHealthzCheckArgsForCall(i int) (string, healthz.Checker) {
+	fake.addHealthzCheckMutex.RLock()
+	defer fake.addHealthzCheckMutex.RUnlock()
+	argsForCall := fake.addHealthzCheckArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *Manager) AddHealthzCheckReturns(result1 error) {
+	fake.addHealthzCheckMutex.Lock()
+	defer fake.addHealthzCheckMutex.Unlock()
+	fake.AddHealthzCheckStub = nil
+	fake.addHealthzCheckReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) AddHealthzCheckReturnsOnCall(i int, result1 error) {
+	fake.addHealthzCheckMutex.Lock()
+	defer fake.addHealthzCheckMutex.Unlock()
+	fake.AddHealthzCheckStub = nil
+	if fake.addHealthzCheckReturnsOnCall == nil {
+		fake.addHealthzCheckReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.addHealthzCheckReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) AddMetricsExtraHandler(arg1 string, arg2 http.Handler) error {
+	fake.addMetricsExtraHandlerMutex.Lock()
+	ret, specificReturn := fake.addMetricsExtraHandlerReturnsOnCall[len(fake.addMetricsExtraHandlerArgsForCall)]
+	fake.addMetricsExtraHandlerArgsForCall = append(fake.addMetricsExtraHandlerArgsForCall, struct {
+		arg1 string
+		arg2 http.Handler
+	}{arg1, arg2})
+	stub := fake.AddMetricsExtraHandlerStub
+	fakeReturns := fake.addMetricsExtraHandlerReturns
+	fake.recordInvocation("AddMetricsExtraHandler", []interface{}{arg1, arg2})
+	fake.addMetricsExtraHandlerMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) AddMetricsExtraHandlerCallCount() int {
+	fake.addMetricsExtraHandlerMutex.RLock()
+	defer fake.addMetricsExtraHandlerMutex.RUnlock()
+	return len(fake.addMetricsExtraHandlerArgsForCall)
+}
+
+func (fake *Manager) AddMetricsExtraHandlerCalls(stub func(string, http.Handler) error) {
+	fake.addMetricsExtraHandlerMutex.Lock()
+	defer fake.addMetricsExtraHandlerMutex.Unlock()
+	fake.AddMetricsExtraHandlerStub = stub
+}
+
+func (fake *Manager) AddMetricsExtraHandlerArgsForCall(i int) (string, http.Handler) {
+	fake.addMetricsExtraHandlerMutex.RLock()
+	defer fake.addMetricsExtraHandlerMutex.RUnlock()
+	argsForCall := fake.addMetricsExtraHandlerArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *Manager) AddMetricsExtraHandlerReturns(result1 error) {
+	fake.addMetricsExtraHandlerMutex.Lock()
+	defer fake.addMetricsExtraHandlerMutex.Unlock()
+	fake.AddMetricsExtraHandlerStub = nil
+	fake.addMetricsExtraHandlerReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) AddMetricsExtraHandlerReturnsOnCall(i int, result1 error) {
+	fake.addMetricsExtraHandlerMutex.Lock()
+	defer fake.addMetricsExtraHandlerMutex.Unlock()
+	fake.AddMetricsExtraHandlerStub = nil
+	if fake.addMetricsExtraHandlerReturnsOnCall == nil {
+		fake.addMetricsExtraHandlerReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.addMetricsExtraHandlerReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) AddReadyzCheck(arg1 string, arg2 healthz.Checker) error {
+	fake.addReadyzCheckMutex.Lock()
+	ret, specificReturn := fake.addReadyzCheckReturnsOnCall[len(fake.addReadyzCheckArgsForCall)]
+	fake.addReadyzCheckArgsForCall = append(fake.addReadyzCheckArgsForCall, struct {
+		arg1 string
+		arg2 healthz.Checker
+	}{arg1, arg2})
+	stub := fake.AddReadyzCheckStub
+	fakeReturns := fake.addReadyzCheckReturns
+	fake.recordInvocation("AddReadyzCheck", []interface{}{arg1, arg2})
+	fake.addReadyzCheckMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) AddReadyzCheckCallCount() int {
+	fake.addReadyzCheckMutex.RLock()
+	defer fake.addReadyzCheckMutex.RUnlock()
+	return len(fake.addReadyzCheckArgsForCall)
+}
+
+func (fake *Manager) AddReadyzCheckCalls(stub func(string, healthz.Checker) error) {
+	fake.addReadyzCheckMutex.Lock()
+	defer fake.addReadyzCheckMutex.Unlock()
+	fake.AddReadyzCheckStub = stub
+}
+
+func (fake *Manager) AddReadyzCheckArgsForCall(i int) (string, healthz.Checker) {
+	fake.addReadyzCheckMutex.RLock()
+	defer fake.addReadyzCheckMutex.RUnlock()
+	argsForCall := fake.addReadyzCheckArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *Manager) AddReadyzCheckReturns(result1 error) {
+	fake.addReadyzCheckMutex.Lock()
+	defer fake.addReadyzCheckMutex.Unlock()
+	fake.AddReadyzCheckStub = nil
+	fake.addReadyzCheckReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) AddReadyzCheckReturnsOnCall(i int, result1 error) {
+	fake.addReadyzCheckMutex.Lock()
+	defer fake.addReadyzCheckMutex.Unlock()
+	fake.AddReadyzCheckStub = nil
+	if fake.addReadyzCheckReturnsOnCall == nil {
+		fake.addReadyzCheckReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.addReadyzCheckReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) Elected() <-chan struct{} {
+	fake.electedMutex.Lock()
+	ret, specificReturn := fake.electedReturnsOnCall[len(fake.electedArgsForCall)]
+	fake.electedArgsForCall = append(fake.electedArgsForCall, struct {
+	}{})
+	stub := fake.ElectedStub
+	fakeReturns := fake.electedReturns
+	fake.recordInvocation("Elected", []interface{}{})
+	fake.electedMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) ElectedCallCount() int {
+	fake.electedMutex.RLock()
+	defer fake.electedMutex.RUnlock()
+	return len(fake.electedArgsForCall)
+}
+
+func (fake *Manager) ElectedCalls(stub func() <-chan struct{}) {
+	fake.electedMutex.Lock()
+	defer fake.electedMutex.Unlock()
+	fake.ElectedStub = stub
+}
+
+func (fake *Manager) ElectedReturns(result1 <-chan struct{}) {
+	fake.electedMutex.Lock()
+	defer fake.electedMutex.Unlock()
+	fake.ElectedStub = nil
+	fake.electedReturns = struct {
+		result1 <-chan struct{}
+	}{result1}
+}
+
+func (fake *Manager) ElectedReturnsOnCall(i int, result1 <-chan struct{}) {
+	fake.electedMutex.Lock()
+	defer fake.electedMutex.Unlock()
+	fake.ElectedStub = nil
+	if fake.electedReturnsOnCall == nil {
+		fake.electedReturnsOnCall = make(map[int]struct {
+			result1 <-chan struct{}
+		})
+	}
+	fake.electedReturnsOnCall[i] = struct {
+		result1 <-chan struct{}
+	}{result1}
+}
+
+func (fake *Manager) GetAPIReader() client.Reader {
+	fake.getAPIReaderMutex.Lock()
+	ret, specificReturn := fake.getAPIReaderReturnsOnCall[len(fake.getAPIReaderArgsForCall)]
+	fake.getAPIReaderArgsForCall = append(fake.getAPIReaderArgsForCall, struct {
+	}{})
+	stub := fake.GetAPIReaderStub
+	fakeReturns := fake.getAPIReaderReturns
+	fake.recordInvocation("GetAPIReader", []interface{}{})
+	fake.getAPIReaderMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetAPIReaderCallCount() int {
+	fake.getAPIReaderMutex.RLock()
+	defer fake.getAPIReaderMutex.RUnlock()
+	return len(fake.getAPIReaderArgsForCall)
+}
+
+func (fake *Manager) GetAPIReaderCalls(stub func() client.Reader) {
+	fake.getAPIReaderMutex.Lock()
+	defer fake.getAPIReaderMutex.Unlock()
+	fake.GetAPIReaderStub = stub
+}
+
+func (fake *Manager) GetAPIReaderReturns(result1 client.Reader) {
+	fake.getAPIReaderMutex.Lock()
+	defer fake.getAPIReaderMutex.Unlock()
+	fake.GetAPIReaderStub = nil
+	fake.getAPIReaderReturns = struct {
+		result1 client.Reader
+	}{result1}
+}
+
+func (fake *Manager) GetAPIReaderReturnsOnCall(i int, result1 client.Reader) {
+	fake.getAPIReaderMutex.Lock()
+	defer fake.getAPIReaderMutex.Unlock()
+	fake.GetAPIReaderStub = nil
+	if fake.getAPIReaderReturnsOnCall == nil {
+		fake.getAPIReaderReturnsOnCall = make(map[int]struct {
+			result1 client.Reader
+		})
+	}
+	fake.getAPIReaderReturnsOnCall[i] = struct {
+		result1 client.Reader
+	}{result1}
+}
+
+func (fake *Manager) GetCache() cache.Cache {
+	fake.getCacheMutex.Lock()
+	ret, specificReturn := fake.getCacheReturnsOnCall[len(fake.getCacheArgsForCall)]
+	fake.getCacheArgsForCall = append(fake.getCacheArgsForCall, struct {
+	}{})
+	stub := fake.GetCacheStub
+	fakeReturns := fake.getCacheReturns
+	fake.recordInvocation("GetCache", []interface{}{})
+	fake.getCacheMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetCacheCallCount() int {
+	fake.getCacheMutex.RLock()
+	defer fake.getCacheMutex.RUnlock()
+	return len(fake.getCacheArgsForCall)
+}
+
+func (fake *Manager) GetCacheCalls(stub func() cache.Cache) {
+	fake.getCacheMutex.Lock()
+	defer fake.getCacheMutex.Unlock()
+	fake.GetCacheStub = stub
+}
+
+func (fake *Manager) GetCacheReturns(result1 cache.Cache) {
+	fake.getCacheMutex.Lock()
+	defer fake.getCacheMutex.Unlock()
+	fake.GetCacheStub = nil
+	fake.getCacheReturns = struct {
+		result1 cache.Cache
+	}{result1}
+}
+
+func (fake *Manager) GetCacheReturnsOnCall(i int, result1 cache.Cache) {
+	fake.getCacheMutex.Lock()
+	defer fake.getCacheMutex.Unlock()
+	fake.GetCacheStub = nil
+	if fake.getCacheReturnsOnCall == nil {
+		fake.getCacheReturnsOnCall = make(map[int]struct {
+			result1 cache.Cache
+		})
+	}
+	fake.getCacheReturnsOnCall[i] = struct {
+		result1 cache.Cache
+	}{result1}
+}
+
+func (fake *Manager) GetClient() client.Client {
+	fake.getClientMutex.Lock()
+	ret, specificReturn := fake.getClientReturnsOnCall[len(fake.getClientArgsForCall)]
+	fake.getClientArgsForCall = append(fake.getClientArgsForCall, struct {
+	}{})
+	stub := fake.GetClientStub
+	fakeReturns := fake.getClientReturns
+	fake.recordInvocation("GetClient", []interface{}{})
+	fake.getClientMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetClientCallCount() int {
+	fake.getClientMutex.RLock()
+	defer fake.getClientMutex.RUnlock()
+	return len(fake.getClientArgsForCall)
+}
+
+func (fake *Manager) GetClientCalls(stub func() client.Client) {
+	fake.getClientMutex.Lock()
+	defer fake.getClientMutex.Unlock()
+	fake.GetClientStub = stub
+}
+
+func (fake *Manager) GetClientReturns(result1 client.Client) {
+	fake.getClientMutex.Lock()
+	defer fake.getClientMutex.Unlock()
+	fake.GetClientStub = nil
+	fake.getClientReturns = struct {
+		result1 client.Client
+	}{result1}
+}
+
+func (fake *Manager) GetClientReturnsOnCall(i int, result1 client.Client) {
+	fake.getClientMutex.Lock()
+	defer fake.getClientMutex.Unlock()
+	fake.GetClientStub = nil
+	if fake.getClientReturnsOnCall == nil {
+		fake.getClientReturnsOnCall = make(map[int]struct {
+			result1 client.Client
+		})
+	}
+	fake.getClientReturnsOnCall[i] = struct {
+		result1 client.Client
+	}{result1}
+}
+
+func (fake *Manager) GetConfig() *rest.Config {
+	fake.getConfigMutex.Lock()
+	ret, specificReturn := fake.getConfigReturnsOnCall[len(fake.getConfigArgsForCall)]
+	fake.getConfigArgsForCall = append(fake.getConfigArgsForCall, struct {
+	}{})
+	stub := fake.GetConfigStub
+	fakeReturns := fake.getConfigReturns
+	fake.recordInvocation("GetConfig", []interface{}{})
+	fake.getConfigMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetConfigCallCount() int {
+	fake.getConfigMutex.RLock()
+	defer fake.getConfigMutex.RUnlock()
+	return len(fake.getConfigArgsForCall)
+}
+
+func (fake *Manager) GetConfigCalls(stub func() *rest.Config) {
+	fake.getConfigMutex.Lock()
+	defer fake.getConfigMutex.Unlock()
+	fake.GetConfigStub = stub
+}
+
+func (fake *Manager) GetConfigReturns(result1 *rest.Config) {
+	fake.getConfigMutex.Lock()
+	defer fake.getConfigMutex.Unlock()
+	fake.GetConfigStub = nil
+	fake.getConfigReturns = struct {
+		result1 *rest.Config
+	}{result1}
+}
+
+func (fake *Manager) GetConfigReturnsOnCall(i int, result1 *rest.Config) {
+	fake.getConfigMutex.Lock()
+	defer fake.getConfigMutex.Unlock()
+	fake.GetConfigStub = nil
+	if fake.getConfigReturnsOnCall == nil {
+		fake.getConfigReturnsOnCall = make(map[int]struct {
+			result1 *rest.Config
+		})
+	}
+	fake.getConfigReturnsOnCall[i] = struct {
+		result1 *rest.Config
+	}{result1}
+}
+
+func (fake *Manager) GetControllerOptions() v1alpha1.ControllerConfigurationSpec {
+	fake.getControllerOptionsMutex.Lock()
+	ret, specificReturn := fake.getControllerOptionsReturnsOnCall[len(fake.getControllerOptionsArgsForCall)]
+	fake.getControllerOptionsArgsForCall = append(fake.getControllerOptionsArgsForCall, struct {
+	}{})
+	stub := fake.GetControllerOptionsStub
+	fakeReturns := fake.getControllerOptionsReturns
+	fake.recordInvocation("GetControllerOptions", []interface{}{})
+	fake.getControllerOptionsMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetControllerOptionsCallCount() int {
+	fake.getControllerOptionsMutex.RLock()
+	defer fake.getControllerOptionsMutex.RUnlock()
+	return len(fake.getControllerOptionsArgsForCall)
+}
+
+func (fake *Manager) GetControllerOptionsCalls(stub func() v1alpha1.ControllerConfigurationSpec) {
+	fake.getControllerOptionsMutex.Lock()
+	defer fake.getControllerOptionsMutex.Unlock()
+	fake.GetControllerOptionsStub = stub
+}
+
+func (fake *Manager) GetControllerOptionsReturns(result1 v1alpha1.ControllerConfigurationSpec) {
+	fake.getControllerOptionsMutex.Lock()
+	defer fake.getControllerOptionsMutex.Unlock()
+	fake.GetControllerOptionsStub = nil
+	fake.getControllerOptionsReturns = struct {
+		result1 v1alpha1.ControllerConfigurationSpec
+	}{result1}
+}
+
+func (fake *Manager) GetControllerOptionsReturnsOnCall(i int, result1 v1alpha1.ControllerConfigurationSpec) {
+	fake.getControllerOptionsMutex.Lock()
+	defer fake.getControllerOptionsMutex.Unlock()
+	fake.GetControllerOptionsStub = nil
+	if fake.getControllerOptionsReturnsOnCall == nil {
+		fake.getControllerOptionsReturnsOnCall = make(map[int]struct {
+			result1 v1alpha1.ControllerConfigurationSpec
+		})
+	}
+	fake.getControllerOptionsReturnsOnCall[i] = struct {
+		result1 v1alpha1.ControllerConfigurationSpec
+	}{result1}
+}
+
+func (fake *Manager) GetEventRecorderFor(arg1 string) record.EventRecorder {
+	fake.getEventRecorderForMutex.Lock()
+	ret, specificReturn := fake.getEventRecorderForReturnsOnCall[len(fake.getEventRecorderForArgsForCall)]
+	fake.getEventRecorderForArgsForCall = append(fake.getEventRecorderForArgsForCall, struct {
+		arg1 string
+	}{arg1})
+	stub := fake.GetEventRecorderForStub
+	fakeReturns := fake.getEventRecorderForReturns
+	fake.recordInvocation("GetEventRecorderFor", []interface{}{arg1})
+	fake.getEventRecorderForMutex.Unlock()
+	if stub != nil {
+		return stub(arg1)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetEventRecorderForCallCount() int {
+	fake.getEventRecorderForMutex.RLock()
+	defer fake.getEventRecorderForMutex.RUnlock()
+	return len(fake.getEventRecorderForArgsForCall)
+}
+
+func (fake *Manager) GetEventRecorderForCalls(stub func(string) record.EventRecorder) {
+	fake.getEventRecorderForMutex.Lock()
+	defer fake.getEventRecorderForMutex.Unlock()
+	fake.GetEventRecorderForStub = stub
+}
+
+func (fake *Manager) GetEventRecorderForArgsForCall(i int) string {
+	fake.getEventRecorderForMutex.RLock()
+	defer fake.getEventRecorderForMutex.RUnlock()
+	argsForCall := fake.getEventRecorderForArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *Manager) GetEventRecorderForReturns(result1 record.EventRecorder) {
+	fake.getEventRecorderForMutex.Lock()
+	defer fake.getEventRecorderForMutex.Unlock()
+	fake.GetEventRecorderForStub = nil
+	fake.getEventRecorderForReturns = struct {
+		result1 record.EventRecorder
+	}{result1}
+}
+
+func (fake *Manager) GetEventRecorderForReturnsOnCall(i int, result1 record.EventRecorder) {
+	fake.getEventRecorderForMutex.Lock()
+	defer fake.getEventRecorderForMutex.Unlock()
+	fake.GetEventRecorderForStub = nil
+	if fake.getEventRecorderForReturnsOnCall == nil {
+		fake.getEventRecorderForReturnsOnCall = make(map[int]struct {
+			result1 record.EventRecorder
+		})
+	}
+	fake.getEventRecorderForReturnsOnCall[i] = struct {
+		result1 record.EventRecorder
+	}{result1}
+}
+
+func (fake *Manager) GetFieldIndexer() client.FieldIndexer {
+	fake.getFieldIndexerMutex.Lock()
+	ret, specificReturn := fake.getFieldIndexerReturnsOnCall[len(fake.getFieldIndexerArgsForCall)]
+	fake.getFieldIndexerArgsForCall = append(fake.getFieldIndexerArgsForCall, struct {
+	}{})
+	stub := fake.GetFieldIndexerStub
+	fakeReturns := fake.getFieldIndexerReturns
+	fake.recordInvocation("GetFieldIndexer", []interface{}{})
+	fake.getFieldIndexerMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetFieldIndexerCallCount() int {
+	fake.getFieldIndexerMutex.RLock()
+	defer fake.getFieldIndexerMutex.RUnlock()
+	return len(fake.getFieldIndexerArgsForCall)
+}
+
+func (fake *Manager) GetFieldIndexerCalls(stub func() client.FieldIndexer) {
+	fake.getFieldIndexerMutex.Lock()
+	defer fake.getFieldIndexerMutex.Unlock()
+	fake.GetFieldIndexerStub = stub
+}
+
+func (fake *Manager) GetFieldIndexerReturns(result1 client.FieldIndexer) {
+	fake.getFieldIndexerMutex.Lock()
+	defer fake.getFieldIndexerMutex.Unlock()
+	fake.GetFieldIndexerStub = nil
+	fake.getFieldIndexerReturns = struct {
+		result1 client.FieldIndexer
+	}{result1}
+}
+
+func (fake *Manager) GetFieldIndexerReturnsOnCall(i int, result1 client.FieldIndexer) {
+	fake.getFieldIndexerMutex.Lock()
+	defer fake.getFieldIndexerMutex.Unlock()
+	fake.GetFieldIndexerStub = nil
+	if fake.getFieldIndexerReturnsOnCall == nil {
+		fake.getFieldIndexerReturnsOnCall = make(map[int]struct {
+			result1 client.FieldIndexer
+		})
+	}
+	fake.getFieldIndexerReturnsOnCall[i] = struct {
+		result1 client.FieldIndexer
+	}{result1}
+}
+
+func (fake *Manager) GetLogger() logr.Logger {
+	fake.getLoggerMutex.Lock()
+	ret, specificReturn := fake.getLoggerReturnsOnCall[len(fake.getLoggerArgsForCall)]
+	fake.getLoggerArgsForCall = append(fake.getLoggerArgsForCall, struct {
+	}{})
+	stub := fake.GetLoggerStub
+	fakeReturns := fake.getLoggerReturns
+	fake.recordInvocation("GetLogger", []interface{}{})
+	fake.getLoggerMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetLoggerCallCount() int {
+	fake.getLoggerMutex.RLock()
+	defer fake.getLoggerMutex.RUnlock()
+	return len(fake.getLoggerArgsForCall)
+}
+
+func (fake *Manager) GetLoggerCalls(stub func() logr.Logger) {
+	fake.getLoggerMutex.Lock()
+	defer fake.getLoggerMutex.Unlock()
+	fake.GetLoggerStub = stub
+}
+
+func (fake *Manager) GetLoggerReturns(result1 logr.Logger) {
+	fake.getLoggerMutex.Lock()
+	defer fake.getLoggerMutex.Unlock()
+	fake.GetLoggerStub = nil
+	fake.getLoggerReturns = struct {
+		result1 logr.Logger
+	}{result1}
+}
+
+func (fake *Manager) GetLoggerReturnsOnCall(i int, result1 logr.Logger) {
+	fake.getLoggerMutex.Lock()
+	defer fake.getLoggerMutex.Unlock()
+	fake.GetLoggerStub = nil
+	if fake.getLoggerReturnsOnCall == nil {
+		fake.getLoggerReturnsOnCall = make(map[int]struct {
+			result1 logr.Logger
+		})
+	}
+	fake.getLoggerReturnsOnCall[i] = struct {
+		result1 logr.Logger
+	}{result1}
+}
+
+func (fake *Manager) GetRESTMapper() meta.RESTMapper {
+	fake.getRESTMapperMutex.Lock()
+	ret, specificReturn := fake.getRESTMapperReturnsOnCall[len(fake.getRESTMapperArgsForCall)]
+	fake.getRESTMapperArgsForCall = append(fake.getRESTMapperArgsForCall, struct {
+	}{})
+	stub := fake.GetRESTMapperStub
+	fakeReturns := fake.getRESTMapperReturns
+	fake.recordInvocation("GetRESTMapper", []interface{}{})
+	fake.getRESTMapperMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetRESTMapperCallCount() int {
+	fake.getRESTMapperMutex.RLock()
+	defer fake.getRESTMapperMutex.RUnlock()
+	return len(fake.getRESTMapperArgsForCall)
+}
+
+func (fake *Manager) GetRESTMapperCalls(stub func() meta.RESTMapper) {
+	fake.getRESTMapperMutex.Lock()
+	defer fake.getRESTMapperMutex.Unlock()
+	fake.GetRESTMapperStub = stub
+}
+
+func (fake *Manager) GetRESTMapperReturns(result1 meta.RESTMapper) {
+	fake.getRESTMapperMutex.Lock()
+	defer fake.getRESTMapperMutex.Unlock()
+	fake.GetRESTMapperStub = nil
+	fake.getRESTMapperReturns = struct {
+		result1 meta.RESTMapper
+	}{result1}
+}
+
+func (fake *Manager) GetRESTMapperReturnsOnCall(i int, result1 meta.RESTMapper) {
+	fake.getRESTMapperMutex.Lock()
+	defer fake.getRESTMapperMutex.Unlock()
+	fake.GetRESTMapperStub = nil
+	if fake.getRESTMapperReturnsOnCall == nil {
+		fake.getRESTMapperReturnsOnCall = make(map[int]struct {
+			result1 meta.RESTMapper
+		})
+	}
+	fake.getRESTMapperReturnsOnCall[i] = struct {
+		result1 meta.RESTMapper
+	}{result1}
+}
+
+func (fake *Manager) GetScheme() *runtime.Scheme {
+	fake.getSchemeMutex.Lock()
+	ret, specificReturn := fake.getSchemeReturnsOnCall[len(fake.getSchemeArgsForCall)]
+	fake.getSchemeArgsForCall = append(fake.getSchemeArgsForCall, struct {
+	}{})
+	stub := fake.GetSchemeStub
+	fakeReturns := fake.getSchemeReturns
+	fake.recordInvocation("GetScheme", []interface{}{})
+	fake.getSchemeMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetSchemeCallCount() int {
+	fake.getSchemeMutex.RLock()
+	defer fake.getSchemeMutex.RUnlock()
+	return len(fake.getSchemeArgsForCall)
+}
+
+func (fake *Manager) GetSchemeCalls(stub func() *runtime.Scheme) {
+	fake.getSchemeMutex.Lock()
+	defer fake.getSchemeMutex.Unlock()
+	fake.GetSchemeStub = stub
+}
+
+func (fake *Manager) GetSchemeReturns(result1 *runtime.Scheme) {
+	fake.getSchemeMutex.Lock()
+	defer fake.getSchemeMutex.Unlock()
+	fake.GetSchemeStub = nil
+	fake.getSchemeReturns = struct {
+		result1 *runtime.Scheme
+	}{result1}
+}
+
+func (fake *Manager) GetSchemeReturnsOnCall(i int, result1 *runtime.Scheme) {
+	fake.getSchemeMutex.Lock()
+	defer fake.getSchemeMutex.Unlock()
+	fake.GetSchemeStub = nil
+	if fake.getSchemeReturnsOnCall == nil {
+		fake.getSchemeReturnsOnCall = make(map[int]struct {
+			result1 *runtime.Scheme
+		})
+	}
+	fake.getSchemeReturnsOnCall[i] = struct {
+		result1 *runtime.Scheme
+	}{result1}
+}
+
+func (fake *Manager) GetWebhookServer() *webhook.Server {
+	fake.getWebhookServerMutex.Lock()
+	ret, specificReturn := fake.getWebhookServerReturnsOnCall[len(fake.getWebhookServerArgsForCall)]
+	fake.getWebhookServerArgsForCall = append(fake.getWebhookServerArgsForCall, struct {
+	}{})
+	stub := fake.GetWebhookServerStub
+	fakeReturns := fake.getWebhookServerReturns
+	fake.recordInvocation("GetWebhookServer", []interface{}{})
+	fake.getWebhookServerMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) GetWebhookServerCallCount() int {
+	fake.getWebhookServerMutex.RLock()
+	defer fake.getWebhookServerMutex.RUnlock()
+	return len(fake.getWebhookServerArgsForCall)
+}
+
+func (fake *Manager) GetWebhookServerCalls(stub func() *webhook.Server) {
+	fake.getWebhookServerMutex.Lock()
+	defer fake.getWebhookServerMutex.Unlock()
+	fake.GetWebhookServerStub = stub
+}
+
+func (fake *Manager) GetWebhookServerReturns(result1 *webhook.Server) {
+	fake.getWebhookServerMutex.Lock()
+	defer fake.getWebhookServerMutex.Unlock()
+	fake.GetWebhookServerStub = nil
+	fake.getWebhookServerReturns = struct {
+		result1 *webhook.Server
+	}{result1}
+}
+
+func (fake *Manager) GetWebhookServerReturnsOnCall(i int, result1 *webhook.Server) {
+	fake.getWebhookServerMutex.Lock()
+	defer fake.getWebhookServerMutex.Unlock()
+	fake.GetWebhookServerStub = nil
+	if fake.getWebhookServerReturnsOnCall == nil {
+		fake.getWebhookServerReturnsOnCall = make(map[int]struct {
+			result1 *webhook.Server
+		})
+	}
+	fake.getWebhookServerReturnsOnCall[i] = struct {
+		result1 *webhook.Server
+	}{result1}
+}
+
+func (fake *Manager) SetFields(arg1 interface{}) error {
+	fake.setFieldsMutex.Lock()
+	ret, specificReturn := fake.setFieldsReturnsOnCall[len(fake.setFieldsArgsForCall)]
+	fake.setFieldsArgsForCall = append(fake.setFieldsArgsForCall, struct {
+		arg1 interface{}
+	}{arg1})
+	stub := fake.SetFieldsStub
+	fakeReturns := fake.setFieldsReturns
+	fake.recordInvocation("SetFields", []interface{}{arg1})
+	fake.setFieldsMutex.Unlock()
+	if stub != nil {
+		return stub(arg1)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) SetFieldsCallCount() int {
+	fake.setFieldsMutex.RLock()
+	defer fake.setFieldsMutex.RUnlock()
+	return len(fake.setFieldsArgsForCall)
+}
+
+func (fake *Manager) SetFieldsCalls(stub func(interface{}) error) {
+	fake.setFieldsMutex.Lock()
+	defer fake.setFieldsMutex.Unlock()
+	fake.SetFieldsStub = stub
+}
+
+func (fake *Manager) SetFieldsArgsForCall(i int) interface{} {
+	fake.setFieldsMutex.RLock()
+	defer fake.setFieldsMutex.RUnlock()
+	argsForCall := fake.setFieldsArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *Manager) SetFieldsReturns(result1 error) {
+	fake.setFieldsMutex.Lock()
+	defer fake.setFieldsMutex.Unlock()
+	fake.SetFieldsStub = nil
+	fake.setFieldsReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) SetFieldsReturnsOnCall(i int, result1 error) {
+	fake.setFieldsMutex.Lock()
+	defer fake.setFieldsMutex.Unlock()
+	fake.SetFieldsStub = nil
+	if fake.setFieldsReturnsOnCall == nil {
+		fake.setFieldsReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.setFieldsReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) Start(arg1 context.Context) error {
+	fake.startMutex.Lock()
+	ret, specificReturn := fake.startReturnsOnCall[len(fake.startArgsForCall)]
+	fake.startArgsForCall = append(fake.startArgsForCall, struct {
+		arg1 context.Context
+	}{arg1})
+	stub := fake.StartStub
+	fakeReturns := fake.startReturns
+	fake.recordInvocation("Start", []interface{}{arg1})
+	fake.startMutex.Unlock()
+	if stub != nil {
+		return stub(arg1)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *Manager) StartCallCount() int {
+	fake.startMutex.RLock()
+	defer fake.startMutex.RUnlock()
+	return len(fake.startArgsForCall)
+}
+
+func (fake *Manager) StartCalls(stub func(context.Context) error) {
+	fake.startMutex.Lock()
+	defer fake.startMutex.Unlock()
+	fake.StartStub = stub
+}
+
+func (fake *Manager) StartArgsForCall(i int) context.Context {
+	fake.startMutex.RLock()
+	defer fake.startMutex.RUnlock()
+	argsForCall := fake.startArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *Manager) StartReturns(result1 error) {
+	fake.startMutex.Lock()
+	defer fake.startMutex.Unlock()
+	fake.StartStub = nil
+	fake.startReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) StartReturnsOnCall(i int, result1 error) {
+	fake.startMutex.Lock()
+	defer fake.startMutex.Unlock()
+	fake.StartStub = nil
+	if fake.startReturnsOnCall == nil {
+		fake.startReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.startReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *Manager) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.addMutex.RLock()
+	defer fake.addMutex.RUnlock()
+	fake.addHealthzCheckMutex.RLock()
+	defer fake.addHealthzCheckMutex.RUnlock()
+	fake.addMetricsExtraHandlerMutex.RLock()
+	defer fake.addMetricsExtraHandlerMutex.RUnlock()
+	fake.addReadyzCheckMutex.RLock()
+	defer fake.addReadyzCheckMutex.RUnlock()
+	fake.electedMutex.RLock()
+	defer fake.electedMutex.RUnlock()
+	fake.getAPIReaderMutex.RLock()
+	defer fake.getAPIReaderMutex.RUnlock()
+	fake.getCacheMutex.RLock()
+	defer fake.getCacheMutex.RUnlock()
+	fake.getClientMutex.RLock()
+	defer fake.getClientMutex.RUnlock()
+	fake.getConfigMutex.RLock()
+	defer fake.getConfigMutex.RUnlock()
+	fake.getControllerOptionsMutex.RLock()
+	defer fake.getControllerOptionsMutex.RUnlock()
+	fake.getEventRecorderForMutex.RLock()
+	defer fake.getEventRecorderForMutex.RUnlock()
+	fake.getFieldIndexerMutex.RLock()
+	defer fake.getFieldIndexerMutex.RUnlock()
+	fake.getLoggerMutex.RLock()
+	defer fake.getLoggerMutex.RUnlock()
+	fake.getRESTMapperMutex.RLock()
+	defer fake.getRESTMapperMutex.RUnlock()
+	fake.getSchemeMutex.RLock()
+	defer fake.getSchemeMutex.RUnlock()
+	fake.getWebhookServerMutex.RLock()
+	defer fake.getWebhookServerMutex.RUnlock()
+	fake.setFieldsMutex.RLock()
+	defer fake.setFieldsMutex.RUnlock()
+	fake.startMutex.RLock()
+	defer fake.startMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *Manager) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ manager.Manager = new(Manager)

+ 179 - 0
pkg/controllers/pushsecret/internal/fakes/recorder.go

@@ -0,0 +1,179 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package fakes
+
+import (
+	"sync"
+
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/tools/record"
+)
+
+type FakeEventRecorder struct {
+	AnnotatedEventfStub        func(runtime.Object, map[string]string, string, string, string, ...interface{})
+	annotatedEventfMutex       sync.RWMutex
+	annotatedEventfArgsForCall []struct {
+		arg1 runtime.Object
+		arg2 map[string]string
+		arg3 string
+		arg4 string
+		arg5 string
+		arg6 []interface{}
+	}
+	EventStub        func(runtime.Object, string, string, string)
+	eventMutex       sync.RWMutex
+	eventArgsForCall []struct {
+		arg1 runtime.Object
+		arg2 string
+		arg3 string
+		arg4 string
+	}
+	EventfStub        func(runtime.Object, string, string, string, ...interface{})
+	eventfMutex       sync.RWMutex
+	eventfArgsForCall []struct {
+		arg1 runtime.Object
+		arg2 string
+		arg3 string
+		arg4 string
+		arg5 []interface{}
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *FakeEventRecorder) AnnotatedEventf(arg1 runtime.Object, arg2 map[string]string, arg3 string, arg4 string, arg5 string, arg6 ...interface{}) {
+	fake.annotatedEventfMutex.Lock()
+	fake.annotatedEventfArgsForCall = append(fake.annotatedEventfArgsForCall, struct {
+		arg1 runtime.Object
+		arg2 map[string]string
+		arg3 string
+		arg4 string
+		arg5 string
+		arg6 []interface{}
+	}{arg1, arg2, arg3, arg4, arg5, arg6})
+	stub := fake.AnnotatedEventfStub
+	fake.recordInvocation("AnnotatedEventf", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6})
+	fake.annotatedEventfMutex.Unlock()
+	if stub != nil {
+		fake.AnnotatedEventfStub(arg1, arg2, arg3, arg4, arg5, arg6...)
+	}
+}
+
+func (fake *FakeEventRecorder) AnnotatedEventfCallCount() int {
+	fake.annotatedEventfMutex.RLock()
+	defer fake.annotatedEventfMutex.RUnlock()
+	return len(fake.annotatedEventfArgsForCall)
+}
+
+func (fake *FakeEventRecorder) AnnotatedEventfCalls(stub func(runtime.Object, map[string]string, string, string, string, ...interface{})) {
+	fake.annotatedEventfMutex.Lock()
+	defer fake.annotatedEventfMutex.Unlock()
+	fake.AnnotatedEventfStub = stub
+}
+
+func (fake *FakeEventRecorder) AnnotatedEventfArgsForCall(i int) (runtime.Object, map[string]string, string, string, string, []interface{}) {
+	fake.annotatedEventfMutex.RLock()
+	defer fake.annotatedEventfMutex.RUnlock()
+	argsForCall := fake.annotatedEventfArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6
+}
+
+func (fake *FakeEventRecorder) Event(arg1 runtime.Object, arg2 string, arg3 string, arg4 string) {
+	fake.eventMutex.Lock()
+	fake.eventArgsForCall = append(fake.eventArgsForCall, struct {
+		arg1 runtime.Object
+		arg2 string
+		arg3 string
+		arg4 string
+	}{arg1, arg2, arg3, arg4})
+	stub := fake.EventStub
+	fake.recordInvocation("Event", []interface{}{arg1, arg2, arg3, arg4})
+	fake.eventMutex.Unlock()
+	if stub != nil {
+		fake.EventStub(arg1, arg2, arg3, arg4)
+	}
+}
+
+func (fake *FakeEventRecorder) EventCallCount() int {
+	fake.eventMutex.RLock()
+	defer fake.eventMutex.RUnlock()
+	return len(fake.eventArgsForCall)
+}
+
+func (fake *FakeEventRecorder) EventCalls(stub func(runtime.Object, string, string, string)) {
+	fake.eventMutex.Lock()
+	defer fake.eventMutex.Unlock()
+	fake.EventStub = stub
+}
+
+func (fake *FakeEventRecorder) EventArgsForCall(i int) (runtime.Object, string, string, string) {
+	fake.eventMutex.RLock()
+	defer fake.eventMutex.RUnlock()
+	argsForCall := fake.eventArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
+}
+
+func (fake *FakeEventRecorder) Eventf(arg1 runtime.Object, arg2 string, arg3 string, arg4 string, arg5 ...interface{}) {
+	fake.eventfMutex.Lock()
+	fake.eventfArgsForCall = append(fake.eventfArgsForCall, struct {
+		arg1 runtime.Object
+		arg2 string
+		arg3 string
+		arg4 string
+		arg5 []interface{}
+	}{arg1, arg2, arg3, arg4, arg5})
+	stub := fake.EventfStub
+	fake.recordInvocation("Eventf", []interface{}{arg1, arg2, arg3, arg4, arg5})
+	fake.eventfMutex.Unlock()
+	if stub != nil {
+		fake.EventfStub(arg1, arg2, arg3, arg4, arg5...)
+	}
+}
+
+func (fake *FakeEventRecorder) EventfCallCount() int {
+	fake.eventfMutex.RLock()
+	defer fake.eventfMutex.RUnlock()
+	return len(fake.eventfArgsForCall)
+}
+
+func (fake *FakeEventRecorder) EventfCalls(stub func(runtime.Object, string, string, string, ...interface{})) {
+	fake.eventfMutex.Lock()
+	defer fake.eventfMutex.Unlock()
+	fake.EventfStub = stub
+}
+
+func (fake *FakeEventRecorder) EventfArgsForCall(i int) (runtime.Object, string, string, string, []interface{}) {
+	fake.eventfMutex.RLock()
+	defer fake.eventfMutex.RUnlock()
+	argsForCall := fake.eventfArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5
+}
+
+func (fake *FakeEventRecorder) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.annotatedEventfMutex.RLock()
+	defer fake.annotatedEventfMutex.RUnlock()
+	fake.eventMutex.RLock()
+	defer fake.eventMutex.RUnlock()
+	fake.eventfMutex.RLock()
+	defer fake.eventfMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *FakeEventRecorder) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ record.EventRecorder = new(FakeEventRecorder)

+ 196 - 0
pkg/controllers/pushsecret/internal/fakes/statuswriter.go

@@ -0,0 +1,196 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package fakes
+
+import (
+	"context"
+	"sync"
+
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type StatusWriter struct {
+	PatchStub        func(context.Context, client.Object, client.Patch, ...client.PatchOption) error
+	patchMutex       sync.RWMutex
+	patchArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 client.Patch
+		arg4 []client.PatchOption
+	}
+	patchReturns struct {
+		result1 error
+	}
+	patchReturnsOnCall map[int]struct {
+		result1 error
+	}
+	UpdateStub        func(context.Context, client.Object, ...client.UpdateOption) error
+	updateMutex       sync.RWMutex
+	updateArgsForCall []struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.UpdateOption
+	}
+	updateReturns struct {
+		result1 error
+	}
+	updateReturnsOnCall map[int]struct {
+		result1 error
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *StatusWriter) Patch(arg1 context.Context, arg2 client.Object, arg3 client.Patch, arg4 ...client.PatchOption) error {
+	fake.patchMutex.Lock()
+	ret, specificReturn := fake.patchReturnsOnCall[len(fake.patchArgsForCall)]
+	fake.patchArgsForCall = append(fake.patchArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 client.Patch
+		arg4 []client.PatchOption
+	}{arg1, arg2, arg3, arg4})
+	stub := fake.PatchStub
+	fakeReturns := fake.patchReturns
+	fake.recordInvocation("Patch", []interface{}{arg1, arg2, arg3, arg4})
+	fake.patchMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3, arg4...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *StatusWriter) PatchCallCount() int {
+	fake.patchMutex.RLock()
+	defer fake.patchMutex.RUnlock()
+	return len(fake.patchArgsForCall)
+}
+
+func (fake *StatusWriter) PatchCalls(stub func(context.Context, client.Object, client.Patch, ...client.PatchOption) error) {
+	fake.patchMutex.Lock()
+	defer fake.patchMutex.Unlock()
+	fake.PatchStub = stub
+}
+
+func (fake *StatusWriter) PatchArgsForCall(i int) (context.Context, client.Object, client.Patch, []client.PatchOption) {
+	fake.patchMutex.RLock()
+	defer fake.patchMutex.RUnlock()
+	argsForCall := fake.patchArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
+}
+
+func (fake *StatusWriter) PatchReturns(result1 error) {
+	fake.patchMutex.Lock()
+	defer fake.patchMutex.Unlock()
+	fake.PatchStub = nil
+	fake.patchReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *StatusWriter) PatchReturnsOnCall(i int, result1 error) {
+	fake.patchMutex.Lock()
+	defer fake.patchMutex.Unlock()
+	fake.PatchStub = nil
+	if fake.patchReturnsOnCall == nil {
+		fake.patchReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.patchReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *StatusWriter) Update(arg1 context.Context, arg2 client.Object, arg3 ...client.UpdateOption) error {
+	fake.updateMutex.Lock()
+	ret, specificReturn := fake.updateReturnsOnCall[len(fake.updateArgsForCall)]
+	fake.updateArgsForCall = append(fake.updateArgsForCall, struct {
+		arg1 context.Context
+		arg2 client.Object
+		arg3 []client.UpdateOption
+	}{arg1, arg2, arg3})
+	stub := fake.UpdateStub
+	fakeReturns := fake.updateReturns
+	fake.recordInvocation("Update", []interface{}{arg1, arg2, arg3})
+	fake.updateMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *StatusWriter) UpdateCallCount() int {
+	fake.updateMutex.RLock()
+	defer fake.updateMutex.RUnlock()
+	return len(fake.updateArgsForCall)
+}
+
+func (fake *StatusWriter) UpdateCalls(stub func(context.Context, client.Object, ...client.UpdateOption) error) {
+	fake.updateMutex.Lock()
+	defer fake.updateMutex.Unlock()
+	fake.UpdateStub = stub
+}
+
+func (fake *StatusWriter) UpdateArgsForCall(i int) (context.Context, client.Object, []client.UpdateOption) {
+	fake.updateMutex.RLock()
+	defer fake.updateMutex.RUnlock()
+	argsForCall := fake.updateArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *StatusWriter) UpdateReturns(result1 error) {
+	fake.updateMutex.Lock()
+	defer fake.updateMutex.Unlock()
+	fake.UpdateStub = nil
+	fake.updateReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *StatusWriter) UpdateReturnsOnCall(i int, result1 error) {
+	fake.updateMutex.Lock()
+	defer fake.updateMutex.Unlock()
+	fake.UpdateStub = nil
+	if fake.updateReturnsOnCall == nil {
+		fake.updateReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.updateReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *StatusWriter) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.patchMutex.RLock()
+	defer fake.patchMutex.RUnlock()
+	fake.updateMutex.RLock()
+	defer fake.updateMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *StatusWriter) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ client.StatusWriter = new(StatusWriter)

+ 247 - 0
pkg/controllers/pushsecret/pushsecret_controller.go

@@ -0,0 +1,247 @@
+/*
+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 pushsecret
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/go-logr/logr"
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/tools/record"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	v1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+const (
+	errFailedGetSecret        = "could not get source secret"
+	errPatchStatus            = "error merging"
+	errGetSecretStore         = "could not get SecretStore %q, %w"
+	errGetClusterSecretStore  = "could not get ClusterSecretStore %q, %w"
+	errGetProviderFailed      = "could not start provider"
+	errGetSecretsClientFailed = "could not start secrets client"
+	errCloseStoreClient       = "error when calling provider close method"
+	errSetSecretFailed        = "could not write remote ref %v to target secretstore %v: %v"
+	errFailedSetSecret        = "set secret failed: %v"
+)
+
+type Reconciler struct {
+	client.Client
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	recorder        record.EventRecorder
+	RequeueInterval time.Duration
+	ControllerClass string
+}
+
+func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	log := r.Log.WithValues("pushsecret", req.NamespacedName)
+	var ps esapi.PushSecret
+	err := r.Get(ctx, req.NamespacedName, &ps)
+	if apierrors.IsNotFound(err) {
+		return ctrl.Result{}, nil
+	} else if err != nil {
+		msg := "unable to get PushSecret"
+		r.recorder.Event(&ps, v1.EventTypeWarning, esapi.ReasonErrored, msg)
+		log.Error(err, msg)
+		return ctrl.Result{}, fmt.Errorf("get resource: %w", err)
+	}
+
+	refreshInt := r.RequeueInterval
+	if ps.Spec.RefreshInterval != nil {
+		refreshInt = ps.Spec.RefreshInterval.Duration
+	}
+
+	p := client.MergeFrom(ps.DeepCopy())
+	defer func() {
+		err := r.Client.Status().Patch(ctx, &ps, p)
+		if err != nil {
+			log.Error(err, errPatchStatus)
+		}
+	}()
+	secret, err := r.GetSecret(ctx, ps)
+	if err != nil {
+		cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionFalse, esapi.ReasonErrored, errFailedGetSecret)
+		ps = SetPushSecretCondition(ps, *cond)
+		r.recorder.Event(&ps, v1.EventTypeWarning, esapi.ReasonErrored, errFailedGetSecret)
+		return ctrl.Result{}, err
+	}
+	secretStores, err := r.GetSecretStores(ctx, ps)
+	if err != nil {
+		cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionFalse, esapi.ReasonErrored, err.Error())
+		ps = SetPushSecretCondition(ps, *cond)
+		r.recorder.Event(&ps, v1.EventTypeWarning, esapi.ReasonErrored, err.Error())
+		return ctrl.Result{}, err
+	}
+	err = r.SetSecretToProviders(ctx, secretStores, ps, secret)
+	if err != nil {
+		msg := fmt.Sprintf(errFailedSetSecret, err)
+		cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionFalse, esapi.ReasonErrored, msg)
+		ps = SetPushSecretCondition(ps, *cond)
+		r.recorder.Event(&ps, v1.EventTypeWarning, esapi.ReasonErrored, msg)
+		return ctrl.Result{}, err
+	}
+	msg := "PushSecret synced successfully"
+	cond := NewPushSecretCondition(esapi.PushSecretReady, v1.ConditionTrue, esapi.ReasonSynced, msg)
+	ps = SetPushSecretCondition(ps, *cond)
+	// Set status for PushSecret
+	r.recorder.Event(&ps, v1.EventTypeNormal, esapi.ReasonSynced, msg)
+
+	if refreshInt == 0 {
+		return ctrl.Result{
+			RequeueAfter: 0,
+			Requeue:      false,
+		}, nil
+	}
+
+	return ctrl.Result{RequeueAfter: refreshInt}, nil
+}
+
+func (r *Reconciler) SetSecretToProviders(ctx context.Context, stores []v1beta1.GenericStore, ps esapi.PushSecret, secret *v1.Secret) error {
+	for _, store := range stores {
+		provider, err := v1beta1.GetProvider(store)
+		if err != nil {
+			return fmt.Errorf(errGetProviderFailed)
+		}
+		client, err := provider.NewClient(ctx, store, r.Client, ps.Namespace)
+		if err != nil {
+			return fmt.Errorf(errGetSecretsClientFailed)
+		}
+		defer func() { //nolint
+			err := client.Close(ctx)
+			if err != nil {
+				r.Log.Error(err, errCloseStoreClient)
+			}
+		}()
+		for _, ref := range ps.Spec.Data {
+			secretValue, ok := secret.Data[ref.Match.SecretKey]
+			if !ok {
+				return fmt.Errorf("secret key %v does not exist", ref.Match.SecretKey)
+			}
+			for _, rK := range ref.Match.RemoteRefs {
+				err := client.SetSecret(ctx, secretValue, rK)
+				if err != nil {
+					return fmt.Errorf(errSetSecretFailed, ref.Match.SecretKey, store.GetName(), err)
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func (r *Reconciler) GetSecret(ctx context.Context, ps esapi.PushSecret) (*v1.Secret, error) {
+	secretName := types.NamespacedName{Name: ps.Spec.Selector.Secret.Name, Namespace: ps.Namespace}
+	secret := &v1.Secret{}
+	err := r.Client.Get(ctx, secretName, secret)
+	if err != nil {
+		return nil, err
+	}
+	return secret, nil
+}
+
+func (r *Reconciler) GetSecretStores(ctx context.Context, ps esapi.PushSecret) ([]v1beta1.GenericStore, error) {
+	stores := make([]v1beta1.GenericStore, 0)
+	for _, refStore := range ps.Spec.SecretStoreRefs {
+		ref := types.NamespacedName{
+			Name: refStore.Name,
+		}
+
+		if refStore.Kind == v1beta1.ClusterSecretStoreKind {
+			var store v1beta1.ClusterSecretStore
+			err := r.Get(ctx, ref, &store)
+			if err != nil {
+				return nil, fmt.Errorf(errGetClusterSecretStore, ref.Name, err)
+			}
+			stores = append(stores, &store)
+		} else {
+			ref.Namespace = ps.Namespace
+
+			var store v1beta1.SecretStore
+			err := r.Get(ctx, ref, &store)
+			if err != nil {
+				return nil, fmt.Errorf(errGetSecretStore, ref.Name, err)
+			}
+			stores = append(stores, &store)
+		}
+	}
+	return stores, nil
+}
+
+func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
+	r.recorder = mgr.GetEventRecorderFor("pushsecret")
+
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&esapi.PushSecret{}).
+		Complete(r)
+}
+
+func NewPushSecretCondition(condType esapi.PushSecretConditionType, status v1.ConditionStatus, reason, message string) *esapi.PushSecretStatusCondition {
+	return &esapi.PushSecretStatusCondition{
+		Type:               condType,
+		Status:             status,
+		LastTransitionTime: metav1.Now(),
+		Reason:             reason,
+		Message:            message,
+	}
+}
+
+func SetPushSecretCondition(gs esapi.PushSecret, condition esapi.PushSecretStatusCondition) esapi.PushSecret {
+	status := gs.Status
+	currentCond := GetPushSecretCondition(status, condition.Type)
+	if currentCond != nil && currentCond.Status == condition.Status &&
+		currentCond.Reason == condition.Reason && currentCond.Message == condition.Message {
+		return gs
+	}
+
+	// Do not update lastTransitionTime if the status of the condition doesn't change.
+	if currentCond != nil && currentCond.Status == condition.Status {
+		condition.LastTransitionTime = currentCond.LastTransitionTime
+	}
+
+	status.Conditions = append(filterOutCondition(status.Conditions, condition.Type), condition)
+	gs.Status = status
+	return gs
+}
+
+// filterOutCondition returns an empty set of conditions with the provided type.
+func filterOutCondition(conditions []esapi.PushSecretStatusCondition, condType esapi.PushSecretConditionType) []esapi.PushSecretStatusCondition {
+	newConditions := make([]esapi.PushSecretStatusCondition, 0, len(conditions))
+	for _, c := range conditions {
+		if c.Type == condType {
+			continue
+		}
+		newConditions = append(newConditions, c)
+	}
+	return newConditions
+}
+
+// GetSecretStoreCondition returns the condition with the provided type.
+func GetPushSecretCondition(status esapi.PushSecretStatus, condType esapi.PushSecretConditionType) *esapi.PushSecretStatusCondition {
+	for i := range status.Conditions {
+		c := status.Conditions[i]
+		if c.Type == condType {
+			return &c
+		}
+	}
+	return nil
+}

+ 440 - 0
pkg/controllers/pushsecret/pushsecret_controller_test.go

@@ -0,0 +1,440 @@
+/*
+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 pushsecret
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/go-logr/logr"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	ctrl "sigs.k8s.io/controller-runtime"
+	kubeclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	v1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/controllers/pushsecret/internal/fakes"
+	"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
+)
+
+var fakeProvider *fake.Client
+
+var _ = Describe("pushsecret", func() {
+	var (
+		reconciler *Reconciler
+		client     *fakes.Client
+		recorder   *fakes.FakeEventRecorder
+	)
+	BeforeEach(func() {
+		client = new(fakes.Client)
+		recorder = &fakes.FakeEventRecorder{}
+		reconciler = &Reconciler{client, logr.Discard(), nil, recorder, time.Minute, ""}
+	})
+
+	Describe("#Reconcile", func() {
+		var (
+			statusWriter *fakes.StatusWriter
+		)
+
+		BeforeEach(func() {
+			statusWriter = new(fakes.StatusWriter)
+			client.StatusReturns(statusWriter)
+		})
+
+		It("succeeds", func() {
+			namspacedName := types.NamespacedName{Namespace: "foo", Name: "Bar"}
+			result, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: namspacedName})
+			Expect(result).To(Equal(ctrl.Result{RequeueAfter: time.Minute}))
+			Expect(err).NotTo(HaveOccurred())
+			Expect(client.GetCallCount()).To(Equal(2))
+			Expect(client.StatusCallCount()).To(Equal(1))
+
+			_, gotNamespacedName, _ := client.GetArgsForCall(0)
+			Expect(gotNamespacedName).To(Equal(namspacedName))
+
+			Expect(statusWriter.PatchCallCount()).To(Equal(1))
+			_, _, patch, _ := statusWriter.PatchArgsForCall(0)
+			Expect(patch.Type()).To(Equal(types.MergePatchType))
+			Expect(recorder.EventCallCount()).To(Equal(1))
+			_, _, reason, message := recorder.EventArgsForCall(0)
+			Expect(reason).To(Equal(esapi.ReasonSynced))
+			Expect(message).To(Equal("PushSecret synced successfully"))
+		})
+		It("requeues after specified time", func() {
+			client.GetStub = func(context context.Context, name types.NamespacedName, obj kubeclient.Object) error {
+				myObj := obj
+				switch obj.(type) {
+				case *esapi.PushSecret:
+					t := myObj.(*esapi.PushSecret)
+					t.Spec.RefreshInterval = &metav1.Duration{Duration: 0 * time.Second}
+					return nil
+				default:
+					return nil
+				}
+			}
+
+			namspacedName := types.NamespacedName{Namespace: "foo", Name: "Bar"}
+			result, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: namspacedName})
+			Expect(result).To(Equal(ctrl.Result{RequeueAfter: 0, Requeue: false}))
+			Expect(err).NotTo(HaveOccurred())
+			Expect(client.GetCallCount()).To(Equal(2))
+			Expect(client.StatusCallCount()).To(Equal(1))
+
+			_, gotNamespacedName, _ := client.GetArgsForCall(0)
+			Expect(gotNamespacedName).To(Equal(namspacedName))
+
+			Expect(statusWriter.PatchCallCount()).To(Equal(1))
+			_, _, patch, _ := statusWriter.PatchArgsForCall(0)
+			Expect(patch.Type()).To(Equal(types.MergePatchType))
+			Expect(recorder.EventCallCount()).To(Equal(1))
+			_, _, reason, message := recorder.EventArgsForCall(0)
+			Expect(reason).To(Equal(esapi.ReasonSynced))
+			Expect(message).To(Equal("PushSecret synced successfully"))
+		})
+
+		When("an error returns in get", func() {
+			BeforeEach(func() {
+				client.GetReturns(errors.New("UnknownError"))
+			})
+
+			It("returns the error", func() {
+				namspacedName := types.NamespacedName{Namespace: "foo", Name: "Bar"}
+				_, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: namspacedName})
+
+				Expect(err).To(MatchError("get resource: UnknownError"))
+				Expect(client.GetCallCount()).To(Equal(1))
+				Expect(client.StatusCallCount()).To(Equal(0))
+				_, _, reason, message := recorder.EventArgsForCall(0)
+				Expect(reason).To(Equal(esapi.ReasonErrored))
+				Expect(message).To(Equal("unable to get PushSecret"))
+			})
+		})
+		When("an error returns in get secret", func() {
+			BeforeEach(func() {
+				client.GetStub = func(context context.Context, name types.NamespacedName, obj kubeclient.Object) error {
+					switch obj.(type) {
+					case *v1.Secret:
+						return fmt.Errorf("GetSecretError")
+					default:
+						return nil
+					}
+				}
+			})
+
+			It("returns the error", func() {
+				namspacedName := types.NamespacedName{Namespace: "foo", Name: "Bar"}
+				_, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: namspacedName})
+
+				Expect(err).To(MatchError("GetSecretError"))
+				_, _, reason, message := recorder.EventArgsForCall(0)
+				Expect(reason).To(Equal(esapi.ReasonErrored))
+				Expect(message).To(Equal(errFailedGetSecret))
+			})
+		})
+
+		When("an error returns in get secret store", func() {
+			BeforeEach(func() {
+				client.GetStub = func(context context.Context, name types.NamespacedName, obj kubeclient.Object) error {
+					switch v := obj.(type) {
+					case *esapi.PushSecret:
+						v.Spec.SecretStoreRefs = []esapi.PushSecretStoreRef{
+							{Name: "a", Kind: "secretstore"},
+						}
+					}
+					switch obj.(type) {
+					case *v1beta1.SecretStore:
+						return fmt.Errorf("BORK")
+					default:
+						return nil
+					}
+
+				}
+			})
+
+			It("returns the error", func() {
+				namspacedName := types.NamespacedName{Namespace: "foo", Name: "Bar"}
+				_, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: namspacedName})
+
+				Expect(err).To(MatchError("could not get SecretStore \"a\", BORK"))
+				_, _, reason, message := recorder.EventArgsForCall(0)
+				Expect(reason).To(Equal(esapi.ReasonErrored))
+				Expect(message).To(Equal("could not get SecretStore \"a\", BORK"))
+			})
+		})
+
+		When("an error returns in set secret to providers", func() {
+			BeforeEach(func() {
+				client.GetStub = func(context context.Context, name types.NamespacedName, obj kubeclient.Object) error {
+					switch v := obj.(type) {
+					case *esapi.PushSecret:
+						v.Spec.SecretStoreRefs = []esapi.PushSecretStoreRef{
+							{Name: "a", Kind: "secretstore"},
+						}
+					case *v1beta1.SecretStore:
+						v.Kind = "PotatoStore"
+					}
+					switch obj.(type) {
+					default:
+						return nil
+					}
+
+				}
+			})
+
+			It("returns the error", func() {
+				namspacedName := types.NamespacedName{Namespace: "foo", Name: "Bar"}
+				_, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: namspacedName})
+
+				Expect(err).To(MatchError("could not start provider"))
+				_, _, reason, message := recorder.EventArgsForCall(0)
+				Expect(reason).To(Equal(esapi.ReasonErrored))
+				Expect(message).To(Equal("set secret failed: could not start provider"))
+			})
+		})
+
+		When("an object is not found", func() {
+			BeforeEach(func() {
+				client.GetReturns(statusErrorNotFound{})
+			})
+
+			It("returns an empty result without error", func() {
+				namspacedName := types.NamespacedName{Namespace: "foo", Name: "Bar"}
+				_, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: namspacedName})
+
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
+	})
+
+	Describe("#GetPushSecretCondition", func() {
+		It("returns nil for empty secret sink status", func() {
+			pushSecretStatus := new(esapi.PushSecretStatus)
+			pushSecretConditionType := new(esapi.PushSecretConditionType)
+
+			Expect(GetPushSecretCondition(*pushSecretStatus, *pushSecretConditionType)).To(BeNil())
+		})
+
+		It("returns correct condition for secret sink status", func() {
+			pushSecretStatusCondition := esapi.PushSecretStatusCondition{Type: esapi.PushSecretReady}
+			pushSecretStatus := esapi.PushSecretStatus{Conditions: []esapi.PushSecretStatusCondition{pushSecretStatusCondition}}
+			pushSecretConditionType := esapi.PushSecretReady
+
+			Expect(GetPushSecretCondition(pushSecretStatus, pushSecretConditionType)).To(Equal(&pushSecretStatusCondition))
+		})
+	})
+
+	Describe("#SetPushSecretCondition", func() {
+		It("appends a condition", func() {
+			pushSecret := esapi.PushSecret{}
+
+			pushSecretStatusCondition := esapi.PushSecretStatusCondition{}
+			pushSecretStatus := esapi.PushSecretStatus{Conditions: []esapi.PushSecretStatusCondition{pushSecretStatusCondition}}
+			expected := esapi.PushSecret{Status: pushSecretStatus}
+			Expect(SetPushSecretCondition(pushSecret, pushSecretStatusCondition)).To(Equal(expected))
+		})
+
+		It("changes an existing condition", func() {
+			conditionStatusTrue := v1.ConditionTrue
+			pushSecretWithCondition := esapi.PushSecret{Status: esapi.PushSecretStatus{Conditions: []esapi.PushSecretStatusCondition{
+				{
+					Status: conditionStatusTrue,
+					Type:   esapi.PushSecretReady,
+				},
+			}},
+			}
+			pushSecretStatusConditionTrue := esapi.PushSecretStatusCondition{Status: conditionStatusTrue,
+				Type:    esapi.PushSecretReady,
+				Message: "Update status",
+			}
+
+			got := SetPushSecretCondition(pushSecretWithCondition, pushSecretStatusConditionTrue)
+			Expect(len(got.Status.Conditions)).To(Equal(1))
+			Expect(got.Status.Conditions[0]).To(Equal(pushSecretStatusConditionTrue))
+		})
+	})
+	Describe("#GetSecret", func() {
+		It("returns a secret if it exists", func() {
+			sink := esapi.PushSecret{
+				Spec: esapi.PushSecretSpec{
+					Selector: esapi.PushSecretSelector{
+						Secret: esapi.PushSecretSecret{
+							Name: "foo",
+						},
+					},
+				},
+			}
+			sink.Namespace = "foobar"
+			_, err := reconciler.GetSecret(context.TODO(), sink)
+			Expect(err).To(BeNil())
+			_, name, _ := client.GetArgsForCall(0)
+			Expect(name.Namespace).To(Equal("foobar"))
+			Expect(name.Name).To(Equal("foo"))
+
+		})
+
+		It("returns an error if it doesn't exist", func() {
+			client.GetReturns(errors.New("secret not found"))
+			_, err := reconciler.GetSecret(context.TODO(), esapi.PushSecret{})
+			Expect(err).To(HaveOccurred())
+		})
+	})
+
+	Describe("#GetSecretStore", func() {
+		sink := esapi.PushSecret{
+			Spec: esapi.PushSecretSpec{
+				SecretStoreRefs: []esapi.PushSecretStoreRef{
+					{
+						Name: "foo",
+					},
+				},
+			},
+		}
+		sink.Namespace = "bar"
+
+		clusterSink := esapi.PushSecret{
+			Spec: esapi.PushSecretSpec{
+				SecretStoreRefs: []esapi.PushSecretStoreRef{
+					{
+						Name: "foo",
+						Kind: "ClusterSecretStore",
+					},
+				},
+			},
+		}
+
+		It("returns a secretstore if it exists", func() {
+			_, err := reconciler.GetSecretStores(context.TODO(), sink)
+			Expect(err).To(BeNil())
+			Expect(client.GetCallCount()).To(Equal(1))
+			_, name, store := client.GetArgsForCall(0)
+			Expect(name.Namespace).To(Equal("bar"))
+			Expect(name.Name).To(Equal("foo"))
+			Expect(store).To(BeAssignableToTypeOf(&v1beta1.SecretStore{}))
+		})
+
+		It("returns an error if it doesn't exist", func() {
+			client.GetReturns(errors.New("secretstore not found"))
+			_, err := reconciler.GetSecretStores(context.TODO(), sink)
+			Expect(err).To(HaveOccurred())
+		})
+
+		It("returns a clustersecretstore if it exists", func() {
+			_, err := reconciler.GetSecretStores(context.TODO(), clusterSink)
+			Expect(err).To(BeNil())
+			Expect(client.GetCallCount()).To(Equal(1))
+			_, name, store := client.GetArgsForCall(0)
+			Expect(store).To(BeAssignableToTypeOf(&v1beta1.ClusterSecretStore{}))
+			Expect(name.Name).To(Equal("foo"))
+		})
+	})
+	Describe("#SetSecretToProviders", func() {
+		val := "supersecret"
+		secret := &v1.Secret{
+			Data: map[string][]byte{
+				"foo": []byte(val),
+			},
+		}
+		sink := esapi.PushSecret{
+			Spec: esapi.PushSecretSpec{
+				SecretStoreRefs: []esapi.PushSecretStoreRef{
+					{
+						Name: "foo",
+					},
+				},
+				Data: []esapi.PushSecretData{
+					{
+						Match: esapi.PushSecretMatch{
+							SecretKey: "foo",
+							RemoteRefs: []esapi.PushSecretRemoteRefs{
+								{
+									RemoteKey: "bar",
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		sink.Namespace = "bar"
+
+		secretStore := v1beta1.SecretStore{}
+		stores := make([]v1beta1.GenericStore, 0)
+		stores = append(stores, &secretStore)
+
+		It("gets the provider and client and then sets the secret", func() {
+
+			Expect(reconciler.SetSecretToProviders(context.TODO(), []v1beta1.GenericStore{}, sink, secret)).To(BeNil())
+		})
+
+		It("returns an error if it can't get a provider", func() {
+			err := reconciler.SetSecretToProviders(context.TODO(), stores, sink, secret)
+
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(Equal(errGetProviderFailed))
+		})
+
+		It("returns an if it can't get a client", func() {
+			specWithProvider := v1beta1.SecretStoreSpec{
+				Provider: &v1beta1.SecretStoreProvider{
+					Fake: &v1beta1.FakeProvider{},
+				},
+			}
+			fakeProvider.WithNew(func(context.Context, v1beta1.GenericStore, kubeclient.Client,
+				string) (v1beta1.SecretsClient, error) {
+				return nil, fmt.Errorf("Something went wrong")
+			})
+			secretStore = v1beta1.SecretStore{
+				Spec: specWithProvider,
+			}
+
+			stores[0] = &secretStore
+			err := reconciler.SetSecretToProviders(context.TODO(), stores, sink, secret)
+
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(Equal(errGetSecretsClientFailed))
+		})
+		It("returns an error if set secret fails", func() {
+			specWithProvider := v1beta1.SecretStoreSpec{
+				Provider: &v1beta1.SecretStoreProvider{
+					Fake: &v1beta1.FakeProvider{},
+				},
+			}
+			fakeProvider.Reset()
+			fakeProvider.WithSetSecret(fmt.Errorf("something went wrong"))
+			secretStore = v1beta1.SecretStore{
+				Spec: specWithProvider,
+			}
+
+			stores[0] = &secretStore
+			err := reconciler.SetSecretToProviders(context.TODO(), stores, sink, secret)
+
+			Expect(err).To(HaveOccurred())
+			Expect(err.Error()).To(Equal(fmt.Sprintf(errSetSecretFailed, "foo", "", "something went wrong")))
+		})
+	})
+})
+
+func init() {
+	fakeProvider = fake.New()
+	v1beta1.ForceRegister(fakeProvider, &v1beta1.SecretStoreProvider{
+		Fake: &v1beta1.FakeProvider{},
+	})
+}

+ 44 - 0
pkg/controllers/pushsecret/suite_test.go

@@ -0,0 +1,44 @@
+/*
+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 pushsecret
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestAPIs(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Controller Suite")
+}
+
+type statusErrorNotFound struct {
+}
+
+func (statusErrorNotFound) Status() metav1.Status {
+	return metav1.Status{
+		Reason: metav1.StatusReasonNotFound,
+	}
+}
+
+func (statusErrorNotFound) Error() string {
+	return "Blurb"
+}
+
+var _ errors.APIStatus = statusErrorNotFound{}

+ 9 - 0
pkg/controllers/secretstore/common.go

@@ -67,6 +67,15 @@ func reconcile(ctx context.Context, req ctrl.Request, ss esapi.GenericStore, cl
 		log.Error(err, "unable to validate store")
 		return ctrl.Result{}, err
 	}
+	storeProvider, err := esapi.GetProvider(ss)
+	if err != nil {
+		return ctrl.Result{}, err
+	}
+	capStatus := esapi.SecretStoreStatus{
+		Capabilities: storeProvider.Capabilities(),
+		Conditions:   ss.GetStatus().Conditions,
+	}
+	ss.SetStatus(capStatus)
 
 	recorder.Event(ss, v1.EventTypeNormal, esapi.ReasonStoreValid, msgStoreValidated)
 	cond := NewSecretStoreCondition(esapi.SecretStoreReady, v1.ConditionTrue, esapi.ReasonStoreValid, msgStoreValidated)

+ 33 - 0
pkg/controllers/secretstore/common_test.go

@@ -125,6 +125,37 @@ var _ = Describe("SecretStore reconcile", func() {
 
 	}
 
+	readWrite := func(tc *testCase) {
+		spc := tc.store.GetSpec()
+		spc.Provider.Vault = nil
+		spc.Provider.Fake = &esapi.FakeProvider{
+			Data: []esapi.FakeProviderData{},
+		}
+
+		tc.assert = func() {
+			Eventually(func() bool {
+				ss := tc.store.Copy()
+				err := k8sClient.Get(context.Background(), types.NamespacedName{
+					Name:      defaultStoreName,
+					Namespace: ss.GetNamespace(),
+				}, ss)
+				if err != nil {
+					return false
+				}
+
+				if ss.GetStatus().Capabilities != esapi.SecretStoreReadWrite {
+					return false
+				}
+
+				return true
+			}).
+				WithTimeout(time.Second * 10).
+				WithPolling(time.Second).
+				Should(BeTrue())
+		}
+
+	}
+
 	DescribeTable("Controller Reconcile logic", func(muts ...func(tc *testCase)) {
 		for _, mut := range muts {
 			mut(test)
@@ -137,11 +168,13 @@ var _ = Describe("SecretStore reconcile", func() {
 		Entry("[namespace] invalid provider with secretStore should set InvalidStore condition", invalidProvider),
 		Entry("[namespace] ignore stores with non-matching class", ignoreControllerClass),
 		Entry("[namespace] valid provider has status=ready", validProvider),
+		Entry("[namespace] valid provider has capabilities=ReadWrite", readWrite),
 
 		// cluster store
 		Entry("[cluster] invalid provider with secretStore should set InvalidStore condition", invalidProvider, useClusterStore),
 		Entry("[cluster] ignore stores with non-matching class", ignoreControllerClass, useClusterStore),
 		Entry("[cluster] valid provider has status=ready", validProvider, useClusterStore),
+		Entry("[cluster] valid provider has capabilities=ReadWrite", readWrite, useClusterStore),
 	)
 
 })

+ 9 - 0
pkg/provider/akeyless/akeyless.go

@@ -70,6 +70,11 @@ func init() {
 	})
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient constructs a new secrets client based on the provided store.
 func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	// controller-runtime/client does not support TokenRequest or other subresource APIs
@@ -204,6 +209,10 @@ func (a *Akeyless) Validate() (esv1beta1.ValidationResult, error) {
 	return esv1beta1.ValidationResultReady, nil
 }
 
+func (a *Akeyless) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // Implements store.Client.GetSecret Interface.
 // Retrieves a secret with the secret name defined in ref.Name.
 func (a *Akeyless) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {

+ 9 - 0
pkg/provider/alibaba/kms.go

@@ -114,6 +114,10 @@ func (c *Client) setAuth(ctx context.Context) error {
 	return nil
 }
 
+func (kms *KeyManagementService) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // Empty GetAllSecrets.
 func (kms *KeyManagementService) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented
@@ -168,6 +172,11 @@ func (kms *KeyManagementService) GetSecretMap(ctx context.Context, ref esv1beta1
 	return secretData, nil
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (kms *KeyManagementService) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient constructs a new secrets client based on the provided store.
 func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()

+ 49 - 6
pkg/provider/aws/parameterstore/fake/fake.go

@@ -14,27 +14,70 @@ limitations under the License.
 package fake
 
 import (
+	"context"
 	"fmt"
 
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/request"
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/google/go-cmp/cmp"
 )
 
 // Client implements the aws parameterstore interface.
 type Client struct {
-	valFn func(*ssm.GetParameterInput) (*ssm.GetParameterOutput, error)
+	GetParameterWithContextFn        GetParameterWithContextFn
+	PutParameterWithContextFn        PutParameterWithContextFn
+	DescribeParametersWithContextFn  DescribeParametersWithContextFn
+	ListTagsForResourceWithContextFn ListTagsForResourceWithContextFn
 }
 
-func (sm *Client) GetParameter(in *ssm.GetParameterInput) (*ssm.GetParameterOutput, error) {
-	return sm.valFn(in)
+type GetParameterWithContextFn func(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error)
+type PutParameterWithContextFn func(aws.Context, *ssm.PutParameterInput, ...request.Option) (*ssm.PutParameterOutput, error)
+type DescribeParametersWithContextFn func(aws.Context, *ssm.DescribeParametersInput, ...request.Option) (*ssm.DescribeParametersOutput, error)
+type ListTagsForResourceWithContextFn func(aws.Context, *ssm.ListTagsForResourceInput, ...request.Option) (*ssm.ListTagsForResourceOutput, error)
+
+func (sm *Client) ListTagsForResourceWithContext(ctx aws.Context, input *ssm.ListTagsForResourceInput, options ...request.Option) (*ssm.ListTagsForResourceOutput, error) {
+	return sm.ListTagsForResourceWithContextFn(ctx, input, options...)
+}
+
+func NewListTagsForResourceWithContextFn(output *ssm.ListTagsForResourceOutput, err error) ListTagsForResourceWithContextFn {
+	return func(aws.Context, *ssm.ListTagsForResourceInput, ...request.Option) (*ssm.ListTagsForResourceOutput, error) {
+		return output, err
+	}
+}
+
+func (sm *Client) GetParameterWithContext(ctx aws.Context, input *ssm.GetParameterInput, options ...request.Option) (*ssm.GetParameterOutput, error) {
+	return sm.GetParameterWithContextFn(ctx, input, options...)
+}
+
+func NewGetParameterWithContextFn(output *ssm.GetParameterOutput, err error) GetParameterWithContextFn {
+	return func(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error) {
+		return output, err
+	}
 }
 
-func (sm *Client) DescribeParameters(*ssm.DescribeParametersInput) (*ssm.DescribeParametersOutput, error) {
-	return nil, nil
+func (sm *Client) DescribeParametersWithContext(ctx context.Context, input *ssm.DescribeParametersInput, options ...request.Option) (*ssm.DescribeParametersOutput, error) {
+	return sm.DescribeParametersWithContextFn(ctx, input, options...)
+}
+
+func NewDescribeParametersWithContextFn(output *ssm.DescribeParametersOutput, err error) DescribeParametersWithContextFn {
+	return func(aws.Context, *ssm.DescribeParametersInput, ...request.Option) (*ssm.DescribeParametersOutput, error) {
+		return output, err
+	}
+}
+
+func (sm *Client) PutParameterWithContext(ctx aws.Context, input *ssm.PutParameterInput, options ...request.Option) (*ssm.PutParameterOutput, error) {
+	return sm.PutParameterWithContextFn(ctx, input, options...)
+}
+
+func NewPutParameterWithContextFn(output *ssm.PutParameterOutput, err error) PutParameterWithContextFn {
+	return func(aws.Context, *ssm.PutParameterInput, ...request.Option) (*ssm.PutParameterOutput, error) {
+		return output, err
+	}
 }
 
 func (sm *Client) WithValue(in *ssm.GetParameterInput, val *ssm.GetParameterOutput, err error) {
-	sm.valFn = func(paramIn *ssm.GetParameterInput) (*ssm.GetParameterOutput, error) {
+	sm.GetParameterWithContextFn = func(ctx aws.Context, paramIn *ssm.GetParameterInput, options ...request.Option) (*ssm.GetParameterOutput, error) {
 		if !cmp.Equal(paramIn, in) {
 			return nil, fmt.Errorf("unexpected test argument")
 		}

+ 138 - 22
pkg/provider/aws/parameterstore/parameterstore.go

@@ -21,6 +21,8 @@ import (
 	"strings"
 
 	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/awserr"
+	"github.com/aws/aws-sdk-go/aws/request"
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/tidwall/gjson"
@@ -32,7 +34,11 @@ import (
 )
 
 // https://github.com/external-secrets/external-secrets/issues/644
-var _ esv1beta1.SecretsClient = &ParameterStore{}
+var (
+	_               esv1beta1.SecretsClient = &ParameterStore{}
+	managedBy                               = "managed-by"
+	externalSecrets                         = "external-secrets"
+)
 
 // ParameterStore is a provider for AWS ParameterStore.
 type ParameterStore struct {
@@ -43,8 +49,10 @@ type ParameterStore struct {
 // PMInterface is a subset of the parameterstore api.
 // see: https://docs.aws.amazon.com/sdk-for-go/api/service/ssm/ssmiface/
 type PMInterface interface {
-	GetParameter(*ssm.GetParameterInput) (*ssm.GetParameterOutput, error)
-	DescribeParameters(*ssm.DescribeParametersInput) (*ssm.DescribeParametersOutput, error)
+	GetParameterWithContext(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error)
+	PutParameterWithContext(aws.Context, *ssm.PutParameterInput, ...request.Option) (*ssm.PutParameterOutput, error)
+	DescribeParametersWithContext(aws.Context, *ssm.DescribeParametersInput, ...request.Option) (*ssm.DescribeParametersOutput, error)
+	ListTagsForResourceWithContext(aws.Context, *ssm.ListTagsForResourceInput, ...request.Option) (*ssm.ListTagsForResourceOutput, error)
 }
 
 const (
@@ -59,18 +67,121 @@ func New(sess *session.Session, cfg *aws.Config) (*ParameterStore, error) {
 	}, nil
 }
 
-// Empty GetAllSecrets.
+func (pm *ParameterStore) getTagsByName(ctx aws.Context, ref *ssm.GetParameterOutput) ([]*ssm.Tag, error) {
+	parameterType := "Parameter"
+
+	parameterTags := ssm.ListTagsForResourceInput{
+		ResourceId:   ref.Parameter.Name,
+		ResourceType: &parameterType,
+	}
+
+	data, err := pm.client.ListTagsForResourceWithContext(ctx, &parameterTags)
+
+	if err != nil {
+		return nil, fmt.Errorf("error listing tags %w", err)
+	}
+
+	return data.TagList, nil
+}
+
+func (pm *ParameterStore) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	// TODO create tags outside of the flow of create parameter: so we can always create parameters
+	// and always create tags.
+	// TODO then create validation for secret versions so that we only have a new version on value change
+	// TODO Testing manually, unit tests, validation tests
+
+	parameterType := "String"
+	overwrite := true
+
+	stringValue := string(value)
+	secretName := remoteRef.GetRemoteKey()
+
+	secretRequest := ssm.PutParameterInput{
+		Name:      &secretName,
+		Value:     &stringValue,
+		Type:      &parameterType,
+		Overwrite: &overwrite,
+	}
+
+	secretValue := ssm.GetParameterInput{
+		Name: &secretName,
+	}
+
+	existing, err := pm.client.GetParameterWithContext(ctx, &secretValue)
+	var awsError awserr.Error
+	ok := errors.As(err, &awsError)
+	if err != nil && (!ok || awsError.Code() != ssm.ErrCodeParameterNotFound) {
+		return fmt.Errorf("unexpected error getting parameter %v: %w", secretName, err)
+	}
+
+	// If we have a valid parameter returned to us, check its tags
+	if existing != nil && existing.Parameter != nil {
+		fmt.Println("The existing value contains data:", existing.String())
+		tags, err := pm.getTagsByName(ctx, existing)
+		if err != nil {
+			return fmt.Errorf("error getting the existing tags for the parameter %v: %w", secretName, err)
+		}
+
+		isManaged := isManagedByESO(tags)
+
+		if !isManaged {
+			// TODO Can we refactor this error message to a higher scope to stop duplicates
+			return fmt.Errorf("secret not managed by external-secrets")
+		}
+
+		if existing.Parameter.Value != nil && *existing.Parameter.Value == string(value) {
+			return nil
+		}
+
+		return pm.setManagedRemoteParameter(ctx, secretRequest, false)
+	}
+
+	// let's set the secret
+	// Do we need to delete the existing parameter on the remote?
+	return pm.setManagedRemoteParameter(ctx, secretRequest, true)
+}
+
+func isManagedByESO(tags []*ssm.Tag) bool {
+	for _, tag := range tags {
+		if *tag.Key == managedBy && *tag.Value == externalSecrets {
+			return true
+		}
+	}
+	return false
+}
+
+func (pm *ParameterStore) setManagedRemoteParameter(ctx context.Context, secretRequest ssm.PutParameterInput, createManagedByTags bool) error {
+	externalSecretsTag := ssm.Tag{
+		Key:   &managedBy,
+		Value: &externalSecrets,
+	}
+
+	overwrite := true
+	secretRequest.Overwrite = &overwrite
+	if createManagedByTags {
+		secretRequest.Tags = append(secretRequest.Tags, &externalSecretsTag)
+		overwrite = false
+	}
+
+	_, err := pm.client.PutParameterWithContext(ctx, &secretRequest)
+	if err != nil {
+		return fmt.Errorf("unexpected error pushing parameter %v: %w", secretRequest.Name, err)
+	}
+	return nil
+}
+
+// GetAllSecrets fetches information from multiple secrets into a single kubernetes secret.
 func (pm *ParameterStore) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	if ref.Name != nil {
-		return pm.findByName(ref)
+		return pm.findByName(ctx, ref)
 	}
 	if ref.Tags != nil {
-		return pm.findByTags(ref)
+		return pm.findByTags(ctx, ref)
 	}
 	return nil, errors.New(errUnexpectedFindOperator)
 }
 
-func (pm *ParameterStore) findByName(ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+func (pm *ParameterStore) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	matcher, err := find.New(*ref.Name)
 	if err != nil {
 		return nil, err
@@ -86,10 +197,12 @@ func (pm *ParameterStore) findByName(ref esv1beta1.ExternalSecretFind) (map[stri
 	data := make(map[string][]byte)
 	var nextToken *string
 	for {
-		it, err := pm.client.DescribeParameters(&ssm.DescribeParametersInput{
-			NextToken:        nextToken,
-			ParameterFilters: pathFilter,
-		})
+		it, err := pm.client.DescribeParametersWithContext(
+			ctx,
+			&ssm.DescribeParametersInput{
+				NextToken:        nextToken,
+				ParameterFilters: pathFilter,
+			})
 		if err != nil {
 			return nil, err
 		}
@@ -97,7 +210,7 @@ func (pm *ParameterStore) findByName(ref esv1beta1.ExternalSecretFind) (map[stri
 			if !matcher.MatchName(*param.Name) {
 				continue
 			}
-			err = pm.fetchAndSet(data, *param.Name)
+			err = pm.fetchAndSet(ctx, data, *param.Name)
 			if err != nil {
 				return nil, err
 			}
@@ -111,7 +224,7 @@ func (pm *ParameterStore) findByName(ref esv1beta1.ExternalSecretFind) (map[stri
 	return data, nil
 }
 
-func (pm *ParameterStore) findByTags(ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+func (pm *ParameterStore) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	filters := make([]*ssm.ParameterStringFilter, 0)
 	for k, v := range ref.Tags {
 		filters = append(filters, &ssm.ParameterStringFilter{
@@ -132,15 +245,17 @@ func (pm *ParameterStore) findByTags(ref esv1beta1.ExternalSecretFind) (map[stri
 	data := make(map[string][]byte)
 	var nextToken *string
 	for {
-		it, err := pm.client.DescribeParameters(&ssm.DescribeParametersInput{
-			ParameterFilters: filters,
-			NextToken:        nextToken,
-		})
+		it, err := pm.client.DescribeParametersWithContext(
+			ctx,
+			&ssm.DescribeParametersInput{
+				ParameterFilters: filters,
+				NextToken:        nextToken,
+			})
 		if err != nil {
 			return nil, err
 		}
 		for _, param := range it.Parameters {
-			err = pm.fetchAndSet(data, *param.Name)
+			err = pm.fetchAndSet(ctx, data, *param.Name)
 			if err != nil {
 				return nil, err
 			}
@@ -154,8 +269,8 @@ func (pm *ParameterStore) findByTags(ref esv1beta1.ExternalSecretFind) (map[stri
 	return data, nil
 }
 
-func (pm *ParameterStore) fetchAndSet(data map[string][]byte, name string) error {
-	out, err := pm.client.GetParameter(&ssm.GetParameterInput{
+func (pm *ParameterStore) fetchAndSet(ctx context.Context, data map[string][]byte, name string) error {
+	out, err := pm.client.GetParameterWithContext(ctx, &ssm.GetParameterInput{
 		Name:           utilpointer.StringPtr(name),
 		WithDecryption: aws.Bool(true),
 	})
@@ -169,13 +284,14 @@ func (pm *ParameterStore) fetchAndSet(data map[string][]byte, name string) error
 
 // GetSecret returns a single secret from the provider.
 func (pm *ParameterStore) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	out, err := pm.client.GetParameter(&ssm.GetParameterInput{
+	out, err := pm.client.GetParameterWithContext(ctx, &ssm.GetParameterInput{
 		Name:           &ref.Key,
 		WithDecryption: aws.Bool(true),
 	})
 
+	nsf := esv1beta1.NoSecretError{}
 	var nf *ssm.ParameterNotFound
-	if errors.As(err, &nf) {
+	if errors.As(err, &nf) || errors.As(err, &nsf) {
 		return nil, esv1beta1.NoSecretErr
 	}
 	if err != nil {

+ 220 - 3
pkg/provider/aws/parameterstore/parameterstore_test.go

@@ -15,6 +15,7 @@ package parameterstore
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"strings"
 	"testing"
@@ -22,13 +23,14 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/ssm"
 	"github.com/google/go-cmp/cmp"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-	fake "github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore/fake"
+	fakeps "github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore/fake"
 )
 
 type parameterstoreTestCase struct {
-	fakeClient     *fake.Client
+	fakeClient     *fakeps.Client
 	apiInput       *ssm.GetParameterInput
 	apiOutput      *ssm.GetParameterOutput
 	remoteRef      *esv1beta1.ExternalSecretDataRemoteRef
@@ -38,9 +40,17 @@ type parameterstoreTestCase struct {
 	expectedData   map[string][]byte
 }
 
+type fakeRef struct {
+	key string
+}
+
+func (f fakeRef) GetRemoteKey() string {
+	return f.key
+}
+
 func makeValidParameterStoreTestCase() *parameterstoreTestCase {
 	return &parameterstoreTestCase{
-		fakeClient:     &fake.Client{},
+		fakeClient:     &fakeps.Client{},
 		apiInput:       makeValidAPIInput(),
 		apiOutput:      makeValidAPIOutput(),
 		remoteRef:      makeValidRemoteRef(),
@@ -81,6 +91,188 @@ func makeValidParameterStoreTestCaseCustom(tweaks ...func(pstc *parameterstoreTe
 	return pstc
 }
 
+func TestPushSecret(t *testing.T) {
+	invalidParameters := errors.New(ssm.ErrCodeInvalidParameters)
+	alreadyExistsError := errors.New(ssm.ErrCodeAlreadyExistsException)
+	fakeValue := "fakeValue"
+
+	managedByESO := ssm.Tag{
+		Key:   &managedBy,
+		Value: &externalSecrets,
+	}
+
+	putParameterOutput := &ssm.PutParameterOutput{}
+	getParameterOutput := &ssm.GetParameterOutput{}
+	describeParameterOutput := &ssm.DescribeParametersOutput{}
+	validListTagsForResourceOutput := &ssm.ListTagsForResourceOutput{
+		TagList: []*ssm.Tag{&managedByESO},
+	}
+	noTagsResourceOutput := &ssm.ListTagsForResourceOutput{}
+
+	validGetParameterOutput := &ssm.GetParameterOutput{
+		Parameter: &ssm.Parameter{
+			ARN:              nil,
+			DataType:         nil,
+			LastModifiedDate: nil,
+			Name:             nil,
+			Selector:         nil,
+			SourceResult:     nil,
+			Type:             nil,
+			Value:            nil,
+			Version:          nil,
+		},
+	}
+
+	sameGetParameterOutput := &ssm.GetParameterOutput{
+		Parameter: &ssm.Parameter{
+			Value: &fakeValue,
+		},
+	}
+
+	type args struct {
+		store  *esv1beta1.AWSProvider
+		client fakeps.Client
+	}
+
+	type want struct {
+		err error
+	}
+
+	tests := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"PutParameterSucceeds": {
+			reason: "a parameter can be successfully pushed to aws parameter store",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(getParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SetParameterFailsWhenNoNameProvided": {
+			reason: "test push secret with no name gives error",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(getParameterOutput, invalidParameters),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: invalidParameters,
+			},
+		},
+		"SetSecretWhenAlreadyExists": {
+			reason: "test push secret with secret that already exists gives error",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, alreadyExistsError),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(getParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: alreadyExistsError,
+			},
+		},
+		"GetSecretWithValidParameters": {
+			reason: "Get secret with valid parameters",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(validGetParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SetSecretNotManagedByESO": {
+			reason: "SetSecret to the parameter store but tags are not managed by ESO",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(validGetParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(noTagsResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: fmt.Errorf("secret not managed by external-secrets"),
+			},
+		},
+		"SetSecretGetTagsError": {
+			reason: "SetSecret to the parameter store returns error while obtaining tags",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(validGetParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(nil, fmt.Errorf("you shall not tag")),
+				},
+			},
+			want: want{
+				err: fmt.Errorf("you shall not tag"),
+			},
+		},
+		"SetSecretContentMatches": {
+			reason: "No ops",
+			args: args{
+				store: makeValidParameterStore().Spec.Provider.AWS,
+				client: fakeps.Client{
+					PutParameterWithContextFn:        fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
+					GetParameterWithContextFn:        fakeps.NewGetParameterWithContextFn(sameGetParameterOutput, nil),
+					DescribeParametersWithContextFn:  fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
+					ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			ref := fakeRef{key: "fake-key"}
+			ps := ParameterStore{
+				client: &tc.args.client,
+			}
+			err := ps.SetSecret(context.TODO(), []byte(fakeValue), ref)
+
+			// Error nil XOR tc.want.err nil
+			if ((err == nil) || (tc.want.err == nil)) && !((err == nil) && (tc.want.err == nil)) {
+				t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, err)
+			}
+
+			// if errors are the same type but their contents do not match
+			if err != nil && tc.want.err != nil {
+				if !strings.Contains(err.Error(), tc.want.err.Error()) {
+					t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error got nil", name, tc.reason, tc.want.err)
+				}
+			}
+		})
+	}
+}
+
 // test the ssm<->aws interface
 // make sure correct values are passed and errors are handled accordingly.
 func TestGetSecret(t *testing.T) {
@@ -110,6 +302,13 @@ func TestGetSecret(t *testing.T) {
 		pstc.expectError = "key INVALPROP does not exist in secret"
 	}
 
+	// bad case: parameter.Value not found
+	setParameterValueNotFound := func(pstc *parameterstoreTestCase) {
+		pstc.apiOutput.Parameter.Value = aws.String("NONEXISTENT")
+		pstc.apiErr = esv1beta1.NoSecretErr
+		pstc.expectError = "Secret does not exist"
+	}
+
 	// bad case: extract property failure due to invalid json
 	setPropertyFail := func(pstc *parameterstoreTestCase) {
 		pstc.apiOutput.Parameter.Value = aws.String(`------`)
@@ -138,6 +337,7 @@ func TestGetSecret(t *testing.T) {
 		makeValidParameterStoreTestCaseCustom(setParameterValueNil),
 		makeValidParameterStoreTestCaseCustom(setAPIError),
 		makeValidParameterStoreTestCaseCustom(setExtractPropertyWithDot),
+		makeValidParameterStoreTestCaseCustom(setParameterValueNotFound),
 	}
 
 	ps := ParameterStore{}
@@ -200,6 +400,23 @@ func TestGetSecretMap(t *testing.T) {
 	}
 }
 
+func makeValidParameterStore() *esv1beta1.SecretStore {
+	return &esv1beta1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "aws-parameterstore",
+			Namespace: "default",
+		},
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				AWS: &esv1beta1.AWSProvider{
+					Service: esv1beta1.AWSServiceParameterStore,
+					Region:  "us-east-1",
+				},
+			},
+		},
+	}
+}
+
 func ErrorContains(out error, want string) bool {
 	if out == nil {
 		return want == ""

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

@@ -46,6 +46,11 @@ const (
 	errInitAWSProvider        = "unable to initialize aws provider: %s"
 )
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadWrite
+}
+
 // NewClient constructs a new secrets client based on the provided store.
 func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	return newClient(ctx, store, kube, namespace, awsauth.DefaultSTSProvider)

+ 53 - 2
pkg/provider/aws/secretsmanager/fake/fake.go

@@ -17,14 +17,65 @@ package fake
 import (
 	"fmt"
 
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/request"
 	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
 	"github.com/google/go-cmp/cmp"
 )
 
 // Client implements the aws secretsmanager interface.
 type Client struct {
-	ExecutionCounter int
-	valFn            map[string]func(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
+	ExecutionCounter            int
+	valFn                       map[string]func(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
+	CreateSecretWithContextFn   CreateSecretWithContextFn
+	GetSecretValueWithContextFn GetSecretValueWithContextFn
+	PutSecretValueWithContextFn PutSecretValueWithContextFn
+	DescribeSecretWithContextFn DescribeSecretWithContextFn
+}
+
+type CreateSecretWithContextFn func(aws.Context, *awssm.CreateSecretInput, ...request.Option) (*awssm.CreateSecretOutput, error)
+type GetSecretValueWithContextFn func(aws.Context, *awssm.GetSecretValueInput, ...request.Option) (*awssm.GetSecretValueOutput, error)
+type PutSecretValueWithContextFn func(aws.Context, *awssm.PutSecretValueInput, ...request.Option) (*awssm.PutSecretValueOutput, error)
+type DescribeSecretWithContextFn func(aws.Context, *awssm.DescribeSecretInput, ...request.Option) (*awssm.DescribeSecretOutput, error)
+
+func (sm Client) CreateSecretWithContext(ctx aws.Context, input *awssm.CreateSecretInput, options ...request.Option) (*awssm.CreateSecretOutput, error) {
+	return sm.CreateSecretWithContextFn(ctx, input, options...)
+}
+
+func NewCreateSecretWithContextFn(output *awssm.CreateSecretOutput, err error) CreateSecretWithContextFn {
+	return func(ctx aws.Context, input *awssm.CreateSecretInput, options ...request.Option) (*awssm.CreateSecretOutput, error) {
+		return output, err
+	}
+}
+
+func (sm Client) GetSecretValueWithContext(ctx aws.Context, input *awssm.GetSecretValueInput, options ...request.Option) (*awssm.GetSecretValueOutput, error) {
+	return sm.GetSecretValueWithContextFn(ctx, input, options...)
+}
+
+func NewGetSecretValueWithContextFn(output *awssm.GetSecretValueOutput, err error) GetSecretValueWithContextFn {
+	return func(aws.Context, *awssm.GetSecretValueInput, ...request.Option) (*awssm.GetSecretValueOutput, error) {
+		return output, err
+	}
+}
+
+func (sm Client) PutSecretValueWithContext(ctx aws.Context, input *awssm.PutSecretValueInput, options ...request.Option) (*awssm.PutSecretValueOutput, error) {
+	return sm.PutSecretValueWithContextFn(ctx, input, options...)
+}
+
+func NewPutSecretValueWithContextFn(output *awssm.PutSecretValueOutput, err error) PutSecretValueWithContextFn {
+	return func(aws.Context, *awssm.PutSecretValueInput, ...request.Option) (*awssm.PutSecretValueOutput, error) {
+		return output, err
+	}
+}
+
+func (sm Client) DescribeSecretWithContext(ctx aws.Context, input *awssm.DescribeSecretInput, options ...request.Option) (*awssm.DescribeSecretOutput, error) {
+	return sm.DescribeSecretWithContextFn(ctx, input, options...)
+}
+
+func NewDescribeSecretWithContextFn(output *awssm.DescribeSecretOutput, err error) DescribeSecretWithContextFn {
+	return func(aws.Context, *awssm.DescribeSecretInput, ...request.Option) (*awssm.DescribeSecretOutput, error) {
+		return output, err
+	}
 }
 
 // NewClient init a new fake client.

+ 83 - 1
pkg/provider/aws/secretsmanager/secretsmanager.go

@@ -19,9 +19,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"reflect"
 	"strings"
 
 	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/awserr"
+	"github.com/aws/aws-sdk-go/aws/request"
 	"github.com/aws/aws-sdk-go/aws/session"
 	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
 	"github.com/tidwall/gjson"
@@ -46,8 +49,12 @@ type SecretsManager struct {
 // SMInterface is a subset of the smiface api.
 // see: https://docs.aws.amazon.com/sdk-for-go/api/service/secretsmanager/secretsmanageriface/
 type SMInterface interface {
-	GetSecretValue(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
 	ListSecrets(*awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error)
+	GetSecretValue(*awssm.GetSecretValueInput) (*awssm.GetSecretValueOutput, error)
+	CreateSecretWithContext(aws.Context, *awssm.CreateSecretInput, ...request.Option) (*awssm.CreateSecretOutput, error)
+	GetSecretValueWithContext(aws.Context, *awssm.GetSecretValueInput, ...request.Option) (*awssm.GetSecretValueOutput, error)
+	PutSecretValueWithContext(aws.Context, *awssm.PutSecretValueInput, ...request.Option) (*awssm.PutSecretValueOutput, error)
+	DescribeSecretWithContext(aws.Context, *awssm.DescribeSecretInput, ...request.Option) (*awssm.DescribeSecretOutput, error)
 }
 
 const (
@@ -104,6 +111,77 @@ func (sm *SecretsManager) fetch(_ context.Context, ref esv1beta1.ExternalSecretD
 	return secretOut, nil
 }
 
+func (sm *SecretsManager) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	secretName := remoteRef.GetRemoteKey()
+	managedBy := "managed-by"
+	externalSecrets := "external-secrets"
+	externalSecretsTag := []*awssm.Tag{
+		{
+			Key:   &managedBy,
+			Value: &externalSecrets,
+		},
+	}
+	secretRequest := awssm.CreateSecretInput{
+		Name:         &secretName,
+		SecretBinary: value,
+		Tags:         externalSecretsTag,
+	}
+
+	secretValue := awssm.GetSecretValueInput{
+		SecretId: &secretName,
+	}
+
+	secretInput := awssm.DescribeSecretInput{
+		SecretId: &secretName,
+	}
+
+	awsSecret, err := sm.client.GetSecretValueWithContext(ctx, &secretValue)
+
+	if err == nil {
+		data, err := sm.client.DescribeSecretWithContext(ctx, &secretInput)
+		if err != nil {
+			return err
+		}
+
+		for _, tag := range data.Tags {
+			if *tag.Key == managedBy && *tag.Value == externalSecrets {
+				goto TAGGED
+			} else {
+				return fmt.Errorf("secret not managed by external-secrets")
+			}
+		}
+	}
+TAGGED:
+	if awsSecret != nil && reflect.DeepEqual(awsSecret.SecretBinary, secretRequest.SecretBinary) {
+		return nil
+	} else if awsSecret.ARN != nil {
+		input := &awssm.PutSecretValueInput{
+			SecretId:     awsSecret.ARN,
+			SecretBinary: value,
+		}
+		_, err := sm.client.PutSecretValueWithContext(ctx, input)
+		if err != nil {
+			return err
+		}
+	}
+
+	var aerr awserr.Error
+	if ok := errors.As(err, &aerr); ok {
+		if aerr.Code() != awssm.ErrCodeResourceNotFoundException {
+			return err
+		}
+	} else if err != nil {
+		return err
+	}
+
+	_, err = sm.client.CreateSecretWithContext(ctx, &secretRequest)
+
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 // GetAllSecrets syncs multiple secrets from aws provider into a single Kubernetes Secret.
 func (sm *SecretsManager) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	if ref.Name != nil {
@@ -305,3 +383,7 @@ func (sm *SecretsManager) Validate() (esv1beta1.ValidationResult, error) {
 	}
 	return esv1beta1.ValidationResultReady, nil
 }
+
+func (sm *SecretsManager) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadWrite
+}

+ 230 - 0
pkg/provider/aws/secretsmanager/secretsmanager_test.go

@@ -16,13 +16,16 @@ package secretsmanager
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"strings"
 	"testing"
 
 	"github.com/aws/aws-sdk-go/aws"
 	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
+	"github.com/crossplane/crossplane-runtime/pkg/test"
 	"github.com/google/go-cmp/cmp"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	fakesm "github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager/fake"
@@ -316,3 +319,230 @@ func ErrorContains(out error, want string) bool {
 	}
 	return strings.Contains(out.Error(), want)
 }
+
+type fakeRef struct {
+	key string
+}
+
+func (f fakeRef) GetRemoteKey() string {
+	return f.key
+}
+
+func TestSetSecret(t *testing.T) {
+	managedBy := "managed-by"
+	notManagedBy := "not-managed-by"
+	secretValue := []byte("fake-value")
+	externalSecrets := "external-secrets"
+	noPermission := errors.New("no permission")
+	arn := "arn:aws:secretsmanager:us-east-1:702902267788:secret:foo-bar5-Robbgh"
+
+	getSecretCorrectErr := awssm.ResourceNotFoundException{}
+	getSecretWrongErr := awssm.InvalidRequestException{}
+
+	secretOutput := &awssm.CreateSecretOutput{
+		ARN: &arn,
+	}
+
+	externalSecretsTag := []*awssm.Tag{
+		{
+			Key:   &managedBy,
+			Value: &externalSecrets,
+		},
+	}
+
+	externalSecretsTagFaulty := []*awssm.Tag{
+		{
+			Key:   &notManagedBy,
+			Value: &externalSecrets,
+		},
+	}
+
+	tagSecretOutput := &awssm.DescribeSecretOutput{
+		ARN:  &arn,
+		Tags: externalSecretsTag,
+	}
+
+	tagSecretOutputFaulty := &awssm.DescribeSecretOutput{
+		ARN:  &arn,
+		Tags: externalSecretsTagFaulty,
+	}
+
+	secretValueOutput := &awssm.GetSecretValueOutput{
+		ARN: &arn,
+	}
+
+	secretValueOutput2 := &awssm.GetSecretValueOutput{
+		ARN:          &arn,
+		SecretBinary: secretValue,
+	}
+
+	blankSecretValueOutput := &awssm.GetSecretValueOutput{}
+
+	putSecretOutput := &awssm.PutSecretValueOutput{
+		ARN: &arn,
+	}
+
+	type args struct {
+		store  *esv1beta1.AWSProvider
+		client fakesm.Client
+	}
+
+	type want struct {
+		err error
+	}
+	tests := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"SetSecretSucceedsWithExistingSecret": {
+			reason: "a secret can be pushed to aws secrets manager when it already exists",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(secretValueOutput, nil),
+					CreateSecretWithContextFn:   fakesm.NewCreateSecretWithContextFn(secretOutput, nil),
+					PutSecretValueWithContextFn: fakesm.NewPutSecretValueWithContextFn(putSecretOutput, nil),
+					DescribeSecretWithContextFn: fakesm.NewDescribeSecretWithContextFn(tagSecretOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SetSecretSucceedsWithNewSecret": {
+			reason: "a secret can be pushed to aws secrets manager if it doesn't already exist",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(blankSecretValueOutput, &getSecretCorrectErr),
+					CreateSecretWithContextFn:   fakesm.NewCreateSecretWithContextFn(secretOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SetSecretCreateSecretFails": {
+			reason: "CreateSecretWithContext returns an error if it fails",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(blankSecretValueOutput, &getSecretCorrectErr),
+					CreateSecretWithContextFn:   fakesm.NewCreateSecretWithContextFn(nil, noPermission),
+				},
+			},
+			want: want{
+				err: noPermission,
+			},
+		},
+		"SetSecretGetSecretFails": {
+			reason: "GetSecretValueWithContext returns an error if it fails",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(blankSecretValueOutput, noPermission),
+				},
+			},
+			want: want{
+				err: noPermission,
+			},
+		},
+		"SetSecretWillNotPushSameSecret": {
+			reason: "secret with the same value will not be pushed",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(secretValueOutput2, nil),
+					DescribeSecretWithContextFn: fakesm.NewDescribeSecretWithContextFn(tagSecretOutput, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"SetSecretPutSecretValueFails": {
+			reason: "PutSecretValueWithContext returns an error if it fails",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(secretValueOutput, nil),
+					PutSecretValueWithContextFn: fakesm.NewPutSecretValueWithContextFn(nil, noPermission),
+					DescribeSecretWithContextFn: fakesm.NewDescribeSecretWithContextFn(tagSecretOutput, nil),
+				},
+			},
+			want: want{
+				err: noPermission,
+			},
+		},
+		"SetSecretWrongGetSecretErrFails": {
+			reason: "GetSecretValueWithContext errors out when anything except awssm.ErrCodeResourceNotFoundException",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(blankSecretValueOutput, &getSecretWrongErr),
+				},
+			},
+			want: want{
+				err: &getSecretWrongErr,
+			},
+		},
+		"SetSecretDescribeSecretFails": {
+			reason: "secret cannot be described",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(secretValueOutput, nil),
+					DescribeSecretWithContextFn: fakesm.NewDescribeSecretWithContextFn(nil, noPermission),
+				},
+			},
+			want: want{
+				err: noPermission,
+			},
+		},
+		"SetSecretDoesNotOverwriteUntaggedSecret": {
+			reason: "secret cannot be described",
+			args: args{
+				store: makeValidSecretStore().Spec.Provider.AWS,
+				client: fakesm.Client{
+					GetSecretValueWithContextFn: fakesm.NewGetSecretValueWithContextFn(secretValueOutput, nil),
+					DescribeSecretWithContextFn: fakesm.NewDescribeSecretWithContextFn(tagSecretOutputFaulty, nil),
+				},
+			},
+			want: want{
+				err: fmt.Errorf("secret not managed by external-secrets"),
+			},
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			ref := fakeRef{key: "fake-key"}
+			sm := SecretsManager{
+				client: &tc.args.client,
+			}
+			err := sm.SetSecret(context.Background(), []byte("fake-value"), ref)
+
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, diff)
+			}
+		})
+	}
+}
+
+func makeValidSecretStore() *esv1beta1.SecretStore {
+	return &esv1beta1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "aws-secret-store",
+			Namespace: "default",
+		},
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				AWS: &esv1beta1.AWSProvider{
+					Service: esv1beta1.AWSServiceSecretsManager,
+					Region:  "eu-west-2",
+				},
+			},
+		},
+	}
+}

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

@@ -107,6 +107,11 @@ func init() {
 	})
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (a *Azure) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient constructs a new secrets client based on the provided store.
 func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	return newClient(ctx, store, kube, namespace)
@@ -196,6 +201,11 @@ func (a *Azure) ValidateStore(store esv1beta1.GenericStore) error {
 	return nil
 }
 
+// Not Implemented SetSecret.
+func (a *Azure) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // Implements store.Client.GetAllSecrets Interface.
 // Retrieves a map[string][]byte with the secret names as key and the secret itself as the calue.
 func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {

+ 75 - 13
pkg/provider/fake/fake.go

@@ -30,15 +30,61 @@ var (
 	errMissingValueField   = "at least one of value or valueMap must be set in data %v"
 )
 
+type SourceOrigin string
+
+const (
+	FakeSecretStore SourceOrigin = "SecretStore"
+	FakeSetSecret   SourceOrigin = "SetSecret"
+)
+
+type Data struct {
+	Value    string
+	Version  string
+	ValueMap map[string]string
+	Origin   SourceOrigin
+}
+type Config map[string]*Data
 type Provider struct {
-	config *esv1beta1.FakeProvider
+	config   Config
+	database map[string]Config
+}
+
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadWrite
 }
 
 func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
-	cfg, err := getProvider(store)
+	if p.database == nil {
+		p.database = make(map[string]Config)
+	}
+	c, err := getProvider(store)
 	if err != nil {
 		return nil, err
 	}
+	cfg := p.database[store.GetName()]
+	if cfg == nil {
+		cfg = Config{}
+	}
+	// We want to remove any FakeSecretStore entry from memory
+	// this will ensure SecretStores can delete from memory.
+	for key, data := range cfg {
+		if data.Origin == FakeSecretStore {
+			delete(cfg, key)
+		}
+	}
+	for _, data := range c.Data {
+		mapKey := fmt.Sprintf("%v%v", data.Key, data.Version)
+		cfg[mapKey] = &Data{
+			Value:   data.Value,
+			Version: data.Version,
+			Origin:  FakeSecretStore,
+		}
+		if data.ValueMap != nil {
+			cfg[mapKey].ValueMap = data.ValueMap
+		}
+	}
+	p.database[store.GetName()] = cfg
 	return &Provider{
 		config: cfg,
 	}, nil
@@ -55,6 +101,23 @@ func getProvider(store esv1beta1.GenericStore) (*esv1beta1.FakeProvider, error)
 	return spc.Provider.Fake, nil
 }
 
+// Not Implemented SetSecret.
+func (p *Provider) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	currentData, ok := p.config[remoteRef.GetRemoteKey()]
+	if !ok {
+		p.config[remoteRef.GetRemoteKey()] = &Data{
+			Value:  string(value),
+			Origin: FakeSetSecret,
+		}
+		return nil
+	}
+	if currentData.Origin != FakeSetSecret {
+		return fmt.Errorf("key already exists")
+	}
+	currentData.Value = string(value)
+	return nil
+}
+
 // Empty GetAllSecrets.
 func (p *Provider) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented
@@ -63,23 +126,22 @@ func (p *Provider) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecr
 
 // GetSecret returns a single secret from the provider.
 func (p *Provider) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	for _, data := range p.config.Data {
-		if data.Key == ref.Key && data.Version == ref.Version {
-			return []byte(data.Value), nil
-		}
+	mapKey := fmt.Sprintf("%v%v", ref.Key, ref.Version)
+	data, ok := p.config[mapKey]
+	if !ok || data.Version != ref.Version {
+		return nil, esv1beta1.NoSecretErr
 	}
-	return nil, esv1beta1.NoSecretErr
+	return []byte(data.Value), nil
 }
 
 // GetSecretMap returns multiple k/v pairs from the provider.
 func (p *Provider) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	for _, data := range p.config.Data {
-		if data.Key != ref.Key || data.Version != ref.Version || data.ValueMap == nil {
-			continue
-		}
-		return convertMap(data.ValueMap), nil
+	mapKey := fmt.Sprintf("%v%v", ref.Key, ref.Version)
+	data, ok := p.config[mapKey]
+	if !ok || data.Version != ref.Version || data.ValueMap == nil {
+		return nil, esv1beta1.NoSecretErr
 	}
-	return nil, esv1beta1.NoSecretErr
+	return convertMap(data.ValueMap), nil
 }
 
 func convertMap(in map[string]string) map[string][]byte {

+ 74 - 2
pkg/provider/fake/fake_test.go

@@ -15,11 +15,14 @@ package fake
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"testing"
 
 	"github.com/onsi/gomega"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 )
 
@@ -123,9 +126,12 @@ func TestGetSecret(t *testing.T) {
 		},
 	}
 
-	for _, row := range tbl {
+	for i, row := range tbl {
 		t.Run(row.name, func(t *testing.T) {
 			cl, err := p.NewClient(context.Background(), &esv1beta1.SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: fmt.Sprintf("secret-store-%v", i),
+				},
 				Spec: esv1beta1.SecretStoreSpec{
 					Provider: &esv1beta1.SecretStoreProvider{
 						Fake: &esv1beta1.FakeProvider{
@@ -146,6 +152,69 @@ func TestGetSecret(t *testing.T) {
 	}
 }
 
+type setSecretTestCase struct {
+	name       string
+	input      []esv1beta1.FakeProviderData
+	requestKey string
+	expValue   string
+	expErr     string
+}
+
+func TestSetSecret(t *testing.T) {
+	gomega.RegisterTestingT(t)
+	p := &Provider{}
+	tbl := []setSecretTestCase{
+		{
+			name:       "return nil if no existing secret",
+			input:      []esv1beta1.FakeProviderData{},
+			requestKey: "/foo",
+			expValue:   "my-secret-value",
+		},
+		{
+			name: "return err if existing secret",
+			input: []esv1beta1.FakeProviderData{
+				{
+					Key:   "/foo",
+					Value: "bar2",
+				},
+			},
+			requestKey: "/foo",
+			expErr:     errors.New("key already exists").Error(),
+		},
+	}
+
+	for i, row := range tbl {
+		t.Run(row.name, func(t *testing.T) {
+			cl, err := p.NewClient(context.Background(), &esv1beta1.SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: fmt.Sprintf("secret-store-%v", i),
+				},
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Fake: &esv1beta1.FakeProvider{
+							Data: row.input,
+						},
+					},
+				},
+			}, nil, "")
+			gomega.Expect(err).ToNot(gomega.HaveOccurred())
+			err = cl.SetSecret(context.TODO(), []byte(row.expValue), esv1alpha1.PushSecretRemoteRefs{
+				RemoteKey: row.requestKey,
+			})
+			if row.expErr != "" {
+				gomega.Expect(err).To(gomega.MatchError(row.expErr))
+			} else {
+				gomega.Expect(err).ToNot(gomega.HaveOccurred())
+				out, err := cl.GetSecret(context.Background(), esv1beta1.ExternalSecretDataRemoteRef{
+					Key: row.requestKey,
+				})
+				gomega.Expect(err).ToNot(gomega.HaveOccurred())
+				gomega.Expect(string(out)).To(gomega.Equal(row.expValue))
+			}
+		})
+	}
+}
+
 type testMapCase struct {
 	name     string
 	input    []esv1beta1.FakeProviderData
@@ -204,9 +273,12 @@ func TestGetSecretMap(t *testing.T) {
 		},
 	}
 
-	for _, row := range tbl {
+	for i, row := range tbl {
 		t.Run(row.name, func(t *testing.T) {
 			cl, err := p.NewClient(context.Background(), &esv1beta1.SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: fmt.Sprintf("secret-store-%v", i),
+				},
 				Spec: esv1beta1.SecretStoreSpec{
 					Provider: &esv1beta1.SecretStoreProvider{
 						Fake: &esv1beta1.FakeProvider{

+ 85 - 2
pkg/provider/gcp/secretmanager/client.go

@@ -14,6 +14,7 @@ limitations under the License.
 package secretmanager
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"errors"
@@ -23,9 +24,11 @@ import (
 
 	secretmanager "cloud.google.com/go/secretmanager/apiv1"
 	"github.com/googleapis/gax-go/v2"
+	"github.com/googleapis/gax-go/v2/apierror"
 	"github.com/tidwall/gjson"
 	"google.golang.org/api/iterator"
 	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
+	"google.golang.org/grpc/codes"
 	ctrl "sigs.k8s.io/controller-runtime"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -73,11 +76,90 @@ type Client struct {
 type GoogleSecretManagerClient interface {
 	AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
 	ListSecrets(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator
+	AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
+	CreateSecret(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
 	Close() error
+	GetSecret(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
 }
 
 var log = ctrl.Log.WithName("provider").WithName("gcp").WithName("secretsmanager")
 
+// SetSecret pushes a kubernetes secret key into gcp provider Secret.
+// funcName(variable type_of_variable, ...)
+func (c *Client) SetSecret(ctx context.Context, payload []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	createSecretReq := &secretmanagerpb.CreateSecretRequest{
+		Parent:   fmt.Sprintf("projects/%s", c.store.ProjectID),
+		SecretId: remoteRef.GetRemoteKey(),
+		Secret: &secretmanagerpb.Secret{
+			Labels: map[string]string{
+				"managed-by": "external-secrets",
+			},
+			Replication: &secretmanagerpb.Replication{
+				Replication: &secretmanagerpb.Replication_Automatic_{
+					Automatic: &secretmanagerpb.Replication_Automatic{},
+				},
+			},
+		},
+	}
+
+	var gcpSecret *secretmanagerpb.Secret
+	var err error
+
+	gcpSecret, err = c.smClient.GetSecret(ctx, &secretmanagerpb.GetSecretRequest{
+		Name: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
+	})
+
+	var gErr *apierror.APIError
+
+	if err != nil && errors.As(err, &gErr) {
+		if gErr.GRPCStatus().Code() == codes.NotFound {
+			gcpSecret, err = c.smClient.CreateSecret(ctx, createSecretReq)
+			if err != nil {
+				return err
+			}
+		} else {
+			return err
+		}
+	}
+
+	manager, ok := gcpSecret.Labels["managed-by"]
+
+	if !ok || manager != "external-secrets" {
+		return fmt.Errorf("secret %v is not managed by external secrets", remoteRef.GetRemoteKey())
+	}
+
+	gcpVersion, err := c.smClient.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
+		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/latest", c.store.ProjectID, remoteRef.GetRemoteKey()),
+	})
+
+	if errors.As(err, &gErr) {
+		if err != nil && gErr.GRPCStatus().Code() != codes.NotFound {
+			return err
+		}
+	} else if err != nil {
+		return err
+	}
+
+	if gcpVersion != nil && gcpVersion.Payload != nil && bytes.Equal(payload, gcpVersion.Payload.Data) {
+		return nil
+	}
+
+	addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
+		Parent: fmt.Sprintf("projects/%s/secrets/%s", c.store.ProjectID, remoteRef.GetRemoteKey()),
+		Payload: &secretmanagerpb.SecretPayload{
+			Data: payload,
+		},
+	}
+
+	_, err = c.smClient.AddSecretVersion(ctx, addSecretVersionReq)
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // GetAllSecrets syncs multiple secrets from gcp provider into a single Kubernetes Secret.
 func (c *Client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	if ref.Name != nil {
@@ -86,6 +168,7 @@ func (c *Client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecret
 	if len(ref.Tags) > 0 {
 		return c.findByTags(ctx, ref)
 	}
+
 	return nil, errors.New(errUnexpectedFindOperator)
 }
 
@@ -194,8 +277,8 @@ func (c *Client) trimName(name string) string {
 // (and users would always use the name, while requests accept both).
 func (c *Client) extractProjectIDNumber(secretFullName string) string {
 	s := strings.Split(secretFullName, "/")
-	projectIDNumuber := s[1]
-	return projectIDNumuber
+	ProjectIDNumuber := s[1]
+	return ProjectIDNumuber
 }
 
 // GetSecret returns a single secret from the provider.

+ 219 - 1
pkg/provider/gcp/secretmanager/client_test.go

@@ -20,7 +20,12 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/crossplane/crossplane-runtime/pkg/test"
+	"github.com/google/go-cmp/cmp"
+	"github.com/googleapis/gax-go/v2/apierror"
 	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
 	"k8s.io/utils/pointer"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -97,7 +102,7 @@ var setAPIErr = func(smtc *secretManagerTestCase) {
 
 var setNilMockClient = func(smtc *secretManagerTestCase) {
 	smtc.mockClient = nil
-	smtc.expectError = errUninitalizedGCPProvider
+	smtc.expectError = "provider GCP is not initialized"
 }
 
 // test the sm<->gcp interface
@@ -181,6 +186,219 @@ func TestSecretManagerGetSecret(t *testing.T) {
 	}
 }
 
+type fakeRef struct {
+	key string
+}
+
+func (f fakeRef) GetRemoteKey() string {
+	return f.key
+}
+
+func TestSetSecret(t *testing.T) {
+	ref := fakeRef{key: "/baz"}
+
+	notFoundError := status.Error(codes.NotFound, "failed")
+	notFoundError, _ = apierror.FromError(notFoundError)
+
+	canceledError := status.Error(codes.Canceled, "canceled")
+	canceledError, _ = apierror.FromError(canceledError)
+
+	APIerror := fmt.Errorf("API Error")
+	labelError := fmt.Errorf("secret %v is not managed by external secrets", ref.GetRemoteKey())
+
+	secret := secretmanagerpb.Secret{
+		Name: "projects/default/secrets/baz",
+		Replication: &secretmanagerpb.Replication{
+			Replication: &secretmanagerpb.Replication_Automatic_{
+				Automatic: &secretmanagerpb.Replication_Automatic{},
+			},
+		},
+		Labels: map[string]string{
+			"managed-by": "external-secrets",
+		},
+	}
+	wrongLabelSecret := secretmanagerpb.Secret{
+		Name: "projects/default/secrets/foo-bar",
+		Replication: &secretmanagerpb.Replication{
+			Replication: &secretmanagerpb.Replication_Automatic_{
+				Automatic: &secretmanagerpb.Replication_Automatic{},
+			},
+		},
+		Labels: map[string]string{
+			"managed-by": "not-external-secrets",
+		},
+	}
+
+	smtc := secretManagerTestCase{
+		mockClient:     &fakesm.MockSMClient{},
+		apiInput:       makeValidAPIInput(),
+		ref:            makeValidRef(),
+		apiOutput:      makeValidAPIOutput(),
+		projectID:      "default",
+		apiErr:         nil,
+		expectError:    "",
+		expectedSecret: "",
+		expectedData:   map[string][]byte{},
+	}
+
+	var payload = secretmanagerpb.SecretPayload{
+		Data: []byte("payload"),
+	}
+
+	var payload2 = secretmanagerpb.SecretPayload{
+		Data: []byte("fake-value"),
+	}
+
+	var res = secretmanagerpb.AccessSecretVersionResponse{
+		Name:    "projects/default/secrets/foo-bar",
+		Payload: &payload,
+	}
+
+	var res2 = secretmanagerpb.AccessSecretVersionResponse{
+		Name:    "projects/default/secrets/baz",
+		Payload: &payload2,
+	}
+
+	var secretVersion = secretmanagerpb.SecretVersion{}
+
+	type args struct {
+		mock                          *fakesm.MockSMClient
+		GetSecretMockReturn           fakesm.GetSecretMockReturn
+		AccessSecretVersionMockReturn fakesm.AccessSecretVersionMockReturn
+		AddSecretVersionMockReturn    fakesm.AddSecretVersionMockReturn
+		CreateSecretMockReturn        fakesm.CreateSecretMockReturn
+	}
+
+	type want struct {
+		err error
+	}
+	tests := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"SetSecret": {
+			reason: "SetSecret successfully pushes a secret",
+			args: args{
+				mock:                          smtc.mockClient,
+				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res, Err: nil},
+				AddSecretVersionMockReturn:    fakesm.AddSecretVersionMockReturn{SecretVersion: &secretVersion, Err: nil}},
+			want: want{
+				err: nil,
+			},
+		},
+		"AddSecretVersion": {
+			reason: "secret not pushed if AddSecretVersion errors",
+			args: args{
+				mock:                          smtc.mockClient,
+				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res, Err: nil},
+				AddSecretVersionMockReturn:    fakesm.AddSecretVersionMockReturn{SecretVersion: nil, Err: APIerror},
+			},
+			want: want{
+				err: APIerror,
+			},
+		},
+		"AccessSecretVersion": {
+			reason: "secret not pushed if AccessSecretVersion errors",
+			args: args{
+				mock:                          smtc.mockClient,
+				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: APIerror},
+			},
+			want: want{
+				err: APIerror,
+			},
+		},
+		"NotManagedByESO": {
+			reason: "secret not pushed if not managed-by external-secrets",
+			args: args{
+				mock:                smtc.mockClient,
+				GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: &wrongLabelSecret, Err: nil},
+			},
+			want: want{
+				err: labelError,
+			},
+		},
+		"SecretAlreadyExists": {
+			reason: "don't push a secret with the same key and value",
+			args: args{
+				mock:                          smtc.mockClient,
+				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: &res2, Err: nil},
+				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"GetSecretNotFound": {
+			reason: "secret is created if one doesn't already exist",
+			args: args{
+				mock:                          smtc.mockClient,
+				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: nil, Err: notFoundError},
+				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: notFoundError},
+				AddSecretVersionMockReturn:    fakesm.AddSecretVersionMockReturn{SecretVersion: &secretVersion, Err: nil},
+				CreateSecretMockReturn:        fakesm.CreateSecretMockReturn{Secret: &secret, Err: nil},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+		"CreateSecretReturnsNotFoundError": {
+			reason: "secret not created if CreateSecret returns not found error",
+			args: args{
+				mock:                   smtc.mockClient,
+				GetSecretMockReturn:    fakesm.GetSecretMockReturn{Secret: nil, Err: notFoundError},
+				CreateSecretMockReturn: fakesm.CreateSecretMockReturn{Secret: &secret, Err: notFoundError},
+			},
+			want: want{
+				err: notFoundError,
+			},
+		},
+		"CreateSecretReturnsError": {
+			reason: "secret not created if CreateSecret returns error",
+			args: args{
+				mock:                smtc.mockClient,
+				GetSecretMockReturn: fakesm.GetSecretMockReturn{Secret: nil, Err: canceledError},
+			},
+			want: want{
+				err: canceledError,
+			},
+		},
+		"AccessSecretVersionReturnsError": {
+			reason: "access secret version for an existing secret returns error",
+			args: args{
+				mock:                          smtc.mockClient,
+				GetSecretMockReturn:           fakesm.GetSecretMockReturn{Secret: &secret, Err: nil},
+				AccessSecretVersionMockReturn: fakesm.AccessSecretVersionMockReturn{Res: nil, Err: canceledError},
+			},
+			want: want{
+				err: canceledError,
+			},
+		},
+	}
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			tc.args.mock.NewGetSecretFn(tc.args.GetSecretMockReturn)
+			tc.args.mock.NewCreateSecretFn(tc.args.CreateSecretMockReturn)
+			tc.args.mock.NewAccessSecretVersionFn(tc.args.AccessSecretVersionMockReturn)
+			tc.args.mock.NewAddSecretVersionFn(tc.args.AddSecretVersionMockReturn)
+
+			c := Client{
+				smClient: tc.args.mock,
+				store: &esv1beta1.GCPSMProvider{
+					ProjectID: smtc.projectID,
+				},
+			}
+			err := c.SetSecret(context.Background(), []byte("fake-value"), ref)
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, diff)
+			}
+		})
+	}
+}
+
 func TestGetSecretMap(t *testing.T) {
 	// good case: default version & deserialization
 	setDeserialization := func(smtc *secretManagerTestCase) {

+ 123 - 0
pkg/provider/gcp/secretmanager/fake/fake.go

@@ -15,6 +15,7 @@ package fake
 
 import (
 	"context"
+	"errors"
 	"fmt"
 
 	secretmanager "cloud.google.com/go/secretmanager/apiv1"
@@ -27,13 +28,52 @@ import (
 type MockSMClient struct {
 	accessSecretFn func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
 	ListSecretsFn  func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator
+	addSecretFn    func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error)
+	createSecretFn func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
 	closeFn        func() error
+	GetSecretFn    func(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error)
+}
+
+type AccessSecretVersionMockReturn struct {
+	Res *secretmanagerpb.AccessSecretVersionResponse
+	Err error
+}
+
+type AddSecretVersionMockReturn struct {
+	SecretVersion *secretmanagerpb.SecretVersion
+	Err           error
+}
+
+type GetSecretMockReturn struct {
+	Secret *secretmanagerpb.Secret
+	Err    error
+}
+
+type CreateSecretMockReturn struct {
+	Secret *secretmanagerpb.Secret
+	Err    error
+}
+
+func (mc *MockSMClient) GetSecret(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+	return mc.GetSecretFn(ctx, req)
+}
+
+func (mc *MockSMClient) NewGetSecretFn(mock GetSecretMockReturn) {
+	mc.GetSecretFn = func(ctx context.Context, req *secretmanagerpb.GetSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+		return mock.Secret, mock.Err
+	}
 }
 
 func (mc *MockSMClient) AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
 	return mc.accessSecretFn(ctx, req)
 }
 
+func (mc *MockSMClient) NewAccessSecretVersionFn(mock AccessSecretVersionMockReturn) {
+	mc.accessSecretFn = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
+		return mock.Res, mock.Err
+	}
+}
+
 func (mc *MockSMClient) ListSecrets(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) *secretmanager.SecretIterator {
 	return mc.ListSecretsFn(ctx, req)
 }
@@ -41,12 +81,95 @@ func (mc *MockSMClient) Close() error {
 	return mc.closeFn()
 }
 
+func (mc *MockSMClient) AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
+	return mc.addSecretFn(ctx, req)
+}
+
+func (mc *MockSMClient) NewAddSecretVersionFn(mock AddSecretVersionMockReturn) {
+	mc.addSecretFn = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
+		return mock.SecretVersion, mock.Err
+	}
+}
+
+func (mc *MockSMClient) CreateSecret(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+	return mc.createSecretFn(ctx, req)
+}
+
+func (mc *MockSMClient) NewCreateSecretFn(mock CreateSecretMockReturn) {
+	mc.createSecretFn = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+		return mock.Secret, mock.Err
+	}
+}
+
 func (mc *MockSMClient) NilClose() {
 	mc.closeFn = func() error {
 		return nil
 	}
 }
 
+func (mc *MockSMClient) CreateSecretError() {
+	mc.createSecretFn = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+		return nil, errors.New("something went wrong")
+	}
+}
+
+func (mc *MockSMClient) CreateSecretGetError() {
+	mc.createSecretFn = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+		mc.accessSecretFn = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
+			return nil, errors.New("no, this broke")
+		}
+		return nil, nil
+	}
+}
+
+func (mc *MockSMClient) DefaultCreateSecret(wantedSecretID, wantedParent string) {
+	mc.createSecretFn = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) {
+		if req.SecretId != wantedSecretID {
+			return nil, fmt.Errorf("create secret req wrong key: got %v want %v", req.SecretId, wantedSecretID)
+		}
+		if req.Parent != wantedParent {
+			return nil, fmt.Errorf("create secret req wrong parent: got %v want %v", req.Parent, wantedParent)
+		}
+		return &secretmanagerpb.Secret{
+			Name: fmt.Sprintf("%s/%s", req.Parent, req.SecretId),
+		}, nil
+	}
+}
+
+func (mc *MockSMClient) DefaultAddSecretVersion(wantedData, wantedParent, versionName string) {
+	mc.addSecretFn = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) {
+		if string(req.Payload.Data) != wantedData {
+			return nil, fmt.Errorf("add version req wrong data got: %v want %v ", req.Payload.Data, wantedData)
+		}
+		if req.Parent != wantedParent {
+			return nil, fmt.Errorf("add version req has wrong parent: got %v want %v", req.Parent, wantedParent)
+		}
+		return &secretmanagerpb.SecretVersion{
+			Name: versionName,
+		}, nil
+	}
+}
+
+func (mc *MockSMClient) DefaultAccessSecretVersion(wantedVersionName string) {
+	mc.accessSecretFn = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
+		if req.Name != wantedVersionName {
+			return nil, fmt.Errorf("access req has wrong version name: got %v want %v", req.Name, wantedVersionName)
+		}
+		return &secretmanagerpb.AccessSecretVersionResponse{
+			Name:    req.Name,
+			Payload: &secretmanagerpb.SecretPayload{Data: []byte("bar")},
+		}, nil
+	}
+}
+
+func (mc *MockSMClient) AccessSecretVersionWithError(err error) {
+	mc.accessSecretFn = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {
+		return nil, err
+	}
+}
+
+// TODO: func (mc...) DefaultAccessSecretVersion (similar to above)
+
 func (mc *MockSMClient) WithValue(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, val *secretmanagerpb.AccessSecretVersionResponse, err error) {
 	if mc != nil {
 		mc.accessSecretFn = func(paramCtx context.Context, paramReq *secretmanagerpb.AccessSecretVersionRequest, paramOpts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) {

+ 516 - 0
pkg/provider/gcp/secretmanager/internal/fakes/client.go

@@ -0,0 +1,516 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package fakes
+
+import (
+	"context"
+	"sync"
+
+	secretmanagerb "cloud.google.com/go/secretmanager/apiv1"
+	"github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
+	gax "github.com/googleapis/gax-go/v2"
+	secretmanagera "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
+)
+
+type GoogleSecretManagerClient struct {
+	AccessSecretVersionStub        func(context.Context, *secretmanagera.AccessSecretVersionRequest, ...gax.CallOption) (*secretmanagera.AccessSecretVersionResponse, error)
+	accessSecretVersionMutex       sync.RWMutex
+	accessSecretVersionArgsForCall []struct {
+		arg1 context.Context
+		arg2 *secretmanagera.AccessSecretVersionRequest
+		arg3 []gax.CallOption
+	}
+	accessSecretVersionReturns struct {
+		result1 *secretmanagera.AccessSecretVersionResponse
+		result2 error
+	}
+	accessSecretVersionReturnsOnCall map[int]struct {
+		result1 *secretmanagera.AccessSecretVersionResponse
+		result2 error
+	}
+	AddSecretVersionStub        func(context.Context, *secretmanagera.AddSecretVersionRequest, ...gax.CallOption) (*secretmanagera.SecretVersion, error)
+	addSecretVersionMutex       sync.RWMutex
+	addSecretVersionArgsForCall []struct {
+		arg1 context.Context
+		arg2 *secretmanagera.AddSecretVersionRequest
+		arg3 []gax.CallOption
+	}
+	addSecretVersionReturns struct {
+		result1 *secretmanagera.SecretVersion
+		result2 error
+	}
+	addSecretVersionReturnsOnCall map[int]struct {
+		result1 *secretmanagera.SecretVersion
+		result2 error
+	}
+	CloseStub        func() error
+	closeMutex       sync.RWMutex
+	closeArgsForCall []struct {
+	}
+	closeReturns struct {
+		result1 error
+	}
+	closeReturnsOnCall map[int]struct {
+		result1 error
+	}
+	CreateSecretStub        func(context.Context, *secretmanagera.CreateSecretRequest, ...gax.CallOption) (*secretmanagera.Secret, error)
+	createSecretMutex       sync.RWMutex
+	createSecretArgsForCall []struct {
+		arg1 context.Context
+		arg2 *secretmanagera.CreateSecretRequest
+		arg3 []gax.CallOption
+	}
+	createSecretReturns struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}
+	createSecretReturnsOnCall map[int]struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}
+	GetSecretStub        func(context.Context, *secretmanagera.GetSecretRequest, ...gax.CallOption) (*secretmanagera.Secret, error)
+	getSecretMutex       sync.RWMutex
+	getSecretArgsForCall []struct {
+		arg1 context.Context
+		arg2 *secretmanagera.GetSecretRequest
+		arg3 []gax.CallOption
+	}
+	getSecretReturns struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}
+	getSecretReturnsOnCall map[int]struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}
+	ListSecretsStub        func(context.Context, *secretmanagera.ListSecretsRequest, ...gax.CallOption) *secretmanagerb.SecretIterator
+	listSecretsMutex       sync.RWMutex
+	listSecretsArgsForCall []struct {
+		arg1 context.Context
+		arg2 *secretmanagera.ListSecretsRequest
+		arg3 []gax.CallOption
+	}
+	listSecretsReturns struct {
+		result1 *secretmanagerb.SecretIterator
+	}
+	listSecretsReturnsOnCall map[int]struct {
+		result1 *secretmanagerb.SecretIterator
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *GoogleSecretManagerClient) AccessSecretVersion(arg1 context.Context, arg2 *secretmanagera.AccessSecretVersionRequest, arg3 ...gax.CallOption) (*secretmanagera.AccessSecretVersionResponse, error) {
+	fake.accessSecretVersionMutex.Lock()
+	ret, specificReturn := fake.accessSecretVersionReturnsOnCall[len(fake.accessSecretVersionArgsForCall)]
+	fake.accessSecretVersionArgsForCall = append(fake.accessSecretVersionArgsForCall, struct {
+		arg1 context.Context
+		arg2 *secretmanagera.AccessSecretVersionRequest
+		arg3 []gax.CallOption
+	}{arg1, arg2, arg3})
+	stub := fake.AccessSecretVersionStub
+	fakeReturns := fake.accessSecretVersionReturns
+	fake.recordInvocation("AccessSecretVersion", []interface{}{arg1, arg2, arg3})
+	fake.accessSecretVersionMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *GoogleSecretManagerClient) AccessSecretVersionCallCount() int {
+	fake.accessSecretVersionMutex.RLock()
+	defer fake.accessSecretVersionMutex.RUnlock()
+	return len(fake.accessSecretVersionArgsForCall)
+}
+
+func (fake *GoogleSecretManagerClient) AccessSecretVersionCalls(stub func(context.Context, *secretmanagera.AccessSecretVersionRequest, ...gax.CallOption) (*secretmanagera.AccessSecretVersionResponse, error)) {
+	fake.accessSecretVersionMutex.Lock()
+	defer fake.accessSecretVersionMutex.Unlock()
+	fake.AccessSecretVersionStub = stub
+}
+
+func (fake *GoogleSecretManagerClient) AccessSecretVersionArgsForCall(i int) (context.Context, *secretmanagera.AccessSecretVersionRequest, []gax.CallOption) {
+	fake.accessSecretVersionMutex.RLock()
+	defer fake.accessSecretVersionMutex.RUnlock()
+	argsForCall := fake.accessSecretVersionArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *GoogleSecretManagerClient) AccessSecretVersionReturns(result1 *secretmanagera.AccessSecretVersionResponse, result2 error) {
+	fake.accessSecretVersionMutex.Lock()
+	defer fake.accessSecretVersionMutex.Unlock()
+	fake.AccessSecretVersionStub = nil
+	fake.accessSecretVersionReturns = struct {
+		result1 *secretmanagera.AccessSecretVersionResponse
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) AccessSecretVersionReturnsOnCall(i int, result1 *secretmanagera.AccessSecretVersionResponse, result2 error) {
+	fake.accessSecretVersionMutex.Lock()
+	defer fake.accessSecretVersionMutex.Unlock()
+	fake.AccessSecretVersionStub = nil
+	if fake.accessSecretVersionReturnsOnCall == nil {
+		fake.accessSecretVersionReturnsOnCall = make(map[int]struct {
+			result1 *secretmanagera.AccessSecretVersionResponse
+			result2 error
+		})
+	}
+	fake.accessSecretVersionReturnsOnCall[i] = struct {
+		result1 *secretmanagera.AccessSecretVersionResponse
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) AddSecretVersion(arg1 context.Context, arg2 *secretmanagera.AddSecretVersionRequest, arg3 ...gax.CallOption) (*secretmanagera.SecretVersion, error) {
+	fake.addSecretVersionMutex.Lock()
+	ret, specificReturn := fake.addSecretVersionReturnsOnCall[len(fake.addSecretVersionArgsForCall)]
+	fake.addSecretVersionArgsForCall = append(fake.addSecretVersionArgsForCall, struct {
+		arg1 context.Context
+		arg2 *secretmanagera.AddSecretVersionRequest
+		arg3 []gax.CallOption
+	}{arg1, arg2, arg3})
+	stub := fake.AddSecretVersionStub
+	fakeReturns := fake.addSecretVersionReturns
+	fake.recordInvocation("AddSecretVersion", []interface{}{arg1, arg2, arg3})
+	fake.addSecretVersionMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *GoogleSecretManagerClient) AddSecretVersionCallCount() int {
+	fake.addSecretVersionMutex.RLock()
+	defer fake.addSecretVersionMutex.RUnlock()
+	return len(fake.addSecretVersionArgsForCall)
+}
+
+func (fake *GoogleSecretManagerClient) AddSecretVersionCalls(stub func(context.Context, *secretmanagera.AddSecretVersionRequest, ...gax.CallOption) (*secretmanagera.SecretVersion, error)) {
+	fake.addSecretVersionMutex.Lock()
+	defer fake.addSecretVersionMutex.Unlock()
+	fake.AddSecretVersionStub = stub
+}
+
+func (fake *GoogleSecretManagerClient) AddSecretVersionArgsForCall(i int) (context.Context, *secretmanagera.AddSecretVersionRequest, []gax.CallOption) {
+	fake.addSecretVersionMutex.RLock()
+	defer fake.addSecretVersionMutex.RUnlock()
+	argsForCall := fake.addSecretVersionArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *GoogleSecretManagerClient) AddSecretVersionReturns(result1 *secretmanagera.SecretVersion, result2 error) {
+	fake.addSecretVersionMutex.Lock()
+	defer fake.addSecretVersionMutex.Unlock()
+	fake.AddSecretVersionStub = nil
+	fake.addSecretVersionReturns = struct {
+		result1 *secretmanagera.SecretVersion
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) AddSecretVersionReturnsOnCall(i int, result1 *secretmanagera.SecretVersion, result2 error) {
+	fake.addSecretVersionMutex.Lock()
+	defer fake.addSecretVersionMutex.Unlock()
+	fake.AddSecretVersionStub = nil
+	if fake.addSecretVersionReturnsOnCall == nil {
+		fake.addSecretVersionReturnsOnCall = make(map[int]struct {
+			result1 *secretmanagera.SecretVersion
+			result2 error
+		})
+	}
+	fake.addSecretVersionReturnsOnCall[i] = struct {
+		result1 *secretmanagera.SecretVersion
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) Close() error {
+	fake.closeMutex.Lock()
+	ret, specificReturn := fake.closeReturnsOnCall[len(fake.closeArgsForCall)]
+	fake.closeArgsForCall = append(fake.closeArgsForCall, struct {
+	}{})
+	stub := fake.CloseStub
+	fakeReturns := fake.closeReturns
+	fake.recordInvocation("Close", []interface{}{})
+	fake.closeMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *GoogleSecretManagerClient) CloseCallCount() int {
+	fake.closeMutex.RLock()
+	defer fake.closeMutex.RUnlock()
+	return len(fake.closeArgsForCall)
+}
+
+func (fake *GoogleSecretManagerClient) CloseCalls(stub func() error) {
+	fake.closeMutex.Lock()
+	defer fake.closeMutex.Unlock()
+	fake.CloseStub = stub
+}
+
+func (fake *GoogleSecretManagerClient) CloseReturns(result1 error) {
+	fake.closeMutex.Lock()
+	defer fake.closeMutex.Unlock()
+	fake.CloseStub = nil
+	fake.closeReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *GoogleSecretManagerClient) CloseReturnsOnCall(i int, result1 error) {
+	fake.closeMutex.Lock()
+	defer fake.closeMutex.Unlock()
+	fake.CloseStub = nil
+	if fake.closeReturnsOnCall == nil {
+		fake.closeReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.closeReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *GoogleSecretManagerClient) CreateSecret(arg1 context.Context, arg2 *secretmanagera.CreateSecretRequest, arg3 ...gax.CallOption) (*secretmanagera.Secret, error) {
+	fake.createSecretMutex.Lock()
+	ret, specificReturn := fake.createSecretReturnsOnCall[len(fake.createSecretArgsForCall)]
+	fake.createSecretArgsForCall = append(fake.createSecretArgsForCall, struct {
+		arg1 context.Context
+		arg2 *secretmanagera.CreateSecretRequest
+		arg3 []gax.CallOption
+	}{arg1, arg2, arg3})
+	stub := fake.CreateSecretStub
+	fakeReturns := fake.createSecretReturns
+	fake.recordInvocation("CreateSecret", []interface{}{arg1, arg2, arg3})
+	fake.createSecretMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *GoogleSecretManagerClient) CreateSecretCallCount() int {
+	fake.createSecretMutex.RLock()
+	defer fake.createSecretMutex.RUnlock()
+	return len(fake.createSecretArgsForCall)
+}
+
+func (fake *GoogleSecretManagerClient) CreateSecretCalls(stub func(context.Context, *secretmanagera.CreateSecretRequest, ...gax.CallOption) (*secretmanagera.Secret, error)) {
+	fake.createSecretMutex.Lock()
+	defer fake.createSecretMutex.Unlock()
+	fake.CreateSecretStub = stub
+}
+
+func (fake *GoogleSecretManagerClient) CreateSecretArgsForCall(i int) (context.Context, *secretmanagera.CreateSecretRequest, []gax.CallOption) {
+	fake.createSecretMutex.RLock()
+	defer fake.createSecretMutex.RUnlock()
+	argsForCall := fake.createSecretArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *GoogleSecretManagerClient) CreateSecretReturns(result1 *secretmanagera.Secret, result2 error) {
+	fake.createSecretMutex.Lock()
+	defer fake.createSecretMutex.Unlock()
+	fake.CreateSecretStub = nil
+	fake.createSecretReturns = struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) CreateSecretReturnsOnCall(i int, result1 *secretmanagera.Secret, result2 error) {
+	fake.createSecretMutex.Lock()
+	defer fake.createSecretMutex.Unlock()
+	fake.CreateSecretStub = nil
+	if fake.createSecretReturnsOnCall == nil {
+		fake.createSecretReturnsOnCall = make(map[int]struct {
+			result1 *secretmanagera.Secret
+			result2 error
+		})
+	}
+	fake.createSecretReturnsOnCall[i] = struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) GetSecret(arg1 context.Context, arg2 *secretmanagera.GetSecretRequest, arg3 ...gax.CallOption) (*secretmanagera.Secret, error) {
+	fake.getSecretMutex.Lock()
+	ret, specificReturn := fake.getSecretReturnsOnCall[len(fake.getSecretArgsForCall)]
+	fake.getSecretArgsForCall = append(fake.getSecretArgsForCall, struct {
+		arg1 context.Context
+		arg2 *secretmanagera.GetSecretRequest
+		arg3 []gax.CallOption
+	}{arg1, arg2, arg3})
+	stub := fake.GetSecretStub
+	fakeReturns := fake.getSecretReturns
+	fake.recordInvocation("GetSecret", []interface{}{arg1, arg2, arg3})
+	fake.getSecretMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *GoogleSecretManagerClient) GetSecretCallCount() int {
+	fake.getSecretMutex.RLock()
+	defer fake.getSecretMutex.RUnlock()
+	return len(fake.getSecretArgsForCall)
+}
+
+func (fake *GoogleSecretManagerClient) GetSecretCalls(stub func(context.Context, *secretmanagera.GetSecretRequest, ...gax.CallOption) (*secretmanagera.Secret, error)) {
+	fake.getSecretMutex.Lock()
+	defer fake.getSecretMutex.Unlock()
+	fake.GetSecretStub = stub
+}
+
+func (fake *GoogleSecretManagerClient) GetSecretArgsForCall(i int) (context.Context, *secretmanagera.GetSecretRequest, []gax.CallOption) {
+	fake.getSecretMutex.RLock()
+	defer fake.getSecretMutex.RUnlock()
+	argsForCall := fake.getSecretArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *GoogleSecretManagerClient) GetSecretReturns(result1 *secretmanagera.Secret, result2 error) {
+	fake.getSecretMutex.Lock()
+	defer fake.getSecretMutex.Unlock()
+	fake.GetSecretStub = nil
+	fake.getSecretReturns = struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) GetSecretReturnsOnCall(i int, result1 *secretmanagera.Secret, result2 error) {
+	fake.getSecretMutex.Lock()
+	defer fake.getSecretMutex.Unlock()
+	fake.GetSecretStub = nil
+	if fake.getSecretReturnsOnCall == nil {
+		fake.getSecretReturnsOnCall = make(map[int]struct {
+			result1 *secretmanagera.Secret
+			result2 error
+		})
+	}
+	fake.getSecretReturnsOnCall[i] = struct {
+		result1 *secretmanagera.Secret
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *GoogleSecretManagerClient) ListSecrets(arg1 context.Context, arg2 *secretmanagera.ListSecretsRequest, arg3 ...gax.CallOption) *secretmanagerb.SecretIterator {
+	fake.listSecretsMutex.Lock()
+	ret, specificReturn := fake.listSecretsReturnsOnCall[len(fake.listSecretsArgsForCall)]
+	fake.listSecretsArgsForCall = append(fake.listSecretsArgsForCall, struct {
+		arg1 context.Context
+		arg2 *secretmanagera.ListSecretsRequest
+		arg3 []gax.CallOption
+	}{arg1, arg2, arg3})
+	stub := fake.ListSecretsStub
+	fakeReturns := fake.listSecretsReturns
+	fake.recordInvocation("ListSecrets", []interface{}{arg1, arg2, arg3})
+	fake.listSecretsMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3...)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *GoogleSecretManagerClient) ListSecretsCallCount() int {
+	fake.listSecretsMutex.RLock()
+	defer fake.listSecretsMutex.RUnlock()
+	return len(fake.listSecretsArgsForCall)
+}
+
+func (fake *GoogleSecretManagerClient) ListSecretsCalls(stub func(context.Context, *secretmanagera.ListSecretsRequest, ...gax.CallOption) *secretmanagerb.SecretIterator) {
+	fake.listSecretsMutex.Lock()
+	defer fake.listSecretsMutex.Unlock()
+	fake.ListSecretsStub = stub
+}
+
+func (fake *GoogleSecretManagerClient) ListSecretsArgsForCall(i int) (context.Context, *secretmanagera.ListSecretsRequest, []gax.CallOption) {
+	fake.listSecretsMutex.RLock()
+	defer fake.listSecretsMutex.RUnlock()
+	argsForCall := fake.listSecretsArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
+}
+
+func (fake *GoogleSecretManagerClient) ListSecretsReturns(result1 *secretmanagerb.SecretIterator) {
+	fake.listSecretsMutex.Lock()
+	defer fake.listSecretsMutex.Unlock()
+	fake.ListSecretsStub = nil
+	fake.listSecretsReturns = struct {
+		result1 *secretmanagerb.SecretIterator
+	}{result1}
+}
+
+func (fake *GoogleSecretManagerClient) ListSecretsReturnsOnCall(i int, result1 *secretmanagerb.SecretIterator) {
+	fake.listSecretsMutex.Lock()
+	defer fake.listSecretsMutex.Unlock()
+	fake.ListSecretsStub = nil
+	if fake.listSecretsReturnsOnCall == nil {
+		fake.listSecretsReturnsOnCall = make(map[int]struct {
+			result1 *secretmanagerb.SecretIterator
+		})
+	}
+	fake.listSecretsReturnsOnCall[i] = struct {
+		result1 *secretmanagerb.SecretIterator
+	}{result1}
+}
+
+func (fake *GoogleSecretManagerClient) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.accessSecretVersionMutex.RLock()
+	defer fake.accessSecretVersionMutex.RUnlock()
+	fake.addSecretVersionMutex.RLock()
+	defer fake.addSecretVersionMutex.RUnlock()
+	fake.closeMutex.RLock()
+	defer fake.closeMutex.RUnlock()
+	fake.createSecretMutex.RLock()
+	defer fake.createSecretMutex.RUnlock()
+	fake.getSecretMutex.RLock()
+	defer fake.getSecretMutex.RUnlock()
+	fake.listSecretsMutex.RLock()
+	defer fake.listSecretsMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *GoogleSecretManagerClient) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ secretmanager.GoogleSecretManagerClient = new(GoogleSecretManagerClient)

+ 4 - 0
pkg/provider/gcp/secretmanager/provider.go

@@ -49,6 +49,10 @@ A Mutex was implemented to make sure only one connection can be in place at a ti
 */
 var useMu = sync.Mutex{}
 
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadWrite
+}
+
 // NewClient constructs a GCP Provider.
 func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()

+ 10 - 0
pkg/provider/gitlab/gitlab.go

@@ -113,6 +113,11 @@ func NewGitlabProvider() *Gitlab {
 	return &Gitlab{}
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (g *Gitlab) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // Method on Gitlab Provider to set up client with credentials and populate projectID.
 func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
@@ -155,6 +160,11 @@ func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, ku
 	return g, nil
 }
 
+// Not Implemented SetSecret.
+func (g *Gitlab) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // Empty GetAllSecrets.
 func (g *Gitlab) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented

+ 0 - 1
pkg/provider/gitlab/gitlab_test.go

@@ -184,7 +184,6 @@ func TestValidate(t *testing.T) {
 	sm := Gitlab{}
 	for k, v := range successCases {
 		sm.client = v.mockClient
-		t.Logf("%+v", v)
 		validationResult, err := sm.Validate()
 		if !ErrorContains(err, v.expectError) {
 			t.Errorf("[%d], unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)

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

@@ -101,6 +101,11 @@ func (c *client) setAuth(ctx context.Context) error {
 	return nil
 }
 
+// Not Implemented SetSecret.
+func (ibm *providerIBM) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // Empty GetAllSecrets.
 func (ibm *providerIBM) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented
@@ -578,6 +583,11 @@ func (ibm *providerIBM) ValidateStore(store esv1beta1.GenericStore) error {
 	return nil
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (ibm *providerIBM) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	ibmSpec := storeSpec.Provider.IBM

+ 5 - 0
pkg/provider/kubernetes/client.go

@@ -49,6 +49,11 @@ func (c *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
 	return jsonStr, nil
 }
 
+// Not Implemented SetSecret.
+func (c *Client) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 func (c *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	secret, err := c.userSecretClient.Get(ctx, ref.Key, metav1.GetOptions{})
 	if err != nil {

+ 4 - 0
pkg/provider/kubernetes/provider.go

@@ -83,6 +83,10 @@ func init() {
 	})
 }
 
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient constructs a Kubernetes Provider.
 func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	restCfg, err := ctrlcfg.GetConfig()

+ 10 - 0
pkg/provider/onepassword/onepassword.go

@@ -68,6 +68,11 @@ type ProviderOnePassword struct {
 var _ esv1beta1.SecretsClient = &ProviderOnePassword{}
 var _ esv1beta1.Provider = &ProviderOnePassword{}
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (provider *ProviderOnePassword) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient constructs a 1Password Provider.
 func (provider *ProviderOnePassword) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	config := store.GetSpec().Provider.OnePassword
@@ -147,6 +152,11 @@ func validateStore(store esv1beta1.GenericStore) error {
 	return nil
 }
 
+// Not Implemented SetSecret.
+func (provider *ProviderOnePassword) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // GetSecret returns a single secret from the provider.
 func (provider *ProviderOnePassword) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	if ref.Version != "" {

+ 10 - 0
pkg/provider/oracle/oracle.go

@@ -67,6 +67,11 @@ type VMInterface interface {
 	GetSecretBundleByName(ctx context.Context, request secrets.GetSecretBundleByNameRequest) (secrets.GetSecretBundleByNameResponse, error)
 }
 
+// Not Implemented SetSecret.
+func (vms *VaultManagementService) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // Empty GetAllSecrets.
 func (vms *VaultManagementService) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented
@@ -127,6 +132,11 @@ func (vms *VaultManagementService) GetSecretMap(ctx context.Context, ref esv1bet
 	return secretData, nil
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (vms *VaultManagementService) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient constructs a new secrets client based on the provided store.
 func (vms *VaultManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()

+ 5 - 0
pkg/provider/senhasegura/dsm/dsm.go

@@ -90,6 +90,11 @@ func New(isoSession *senhaseguraAuth.SenhaseguraIsoSession) (*DSM, error) {
 	}, nil
 }
 
+// Not Implemented SetSecret.
+func (dsm *DSM) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 /*
 GetSecret implements ESO interface and get a single secret from senhasegura provider with DSM service.
 */

+ 5 - 0
pkg/provider/senhasegura/provider.go

@@ -43,6 +43,11 @@ const (
 	errMissingClientID            = "missing senhasegura authentication Client ID"
 )
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 /*
 Construct a new secrets client based on provided store.
 */

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

@@ -30,6 +30,7 @@ type Client struct {
 	GetSecretFn     func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error)
 	GetSecretMapFn  func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
 	GetAllSecretsFn func(context.Context, esv1beta1.ExternalSecretFind) (map[string][]byte, error)
+	SetSecretFn     func() error
 }
 
 // New returns a fake provider/client.
@@ -44,6 +45,9 @@ func New() *Client {
 		GetAllSecretsFn: func(context.Context, esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 			return nil, nil
 		},
+		SetSecretFn: func() error {
+			return nil
+		},
 	}
 
 	v.NewFn = func(context.Context, esv1beta1.GenericStore, client.Client, string) (esv1beta1.SecretsClient, error) {
@@ -63,6 +67,11 @@ func (v *Client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecret
 	return v.GetAllSecretsFn(ctx, ref)
 }
 
+// Not Implemented SetSecret.
+func (v *Client) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return v.SetSecretFn()
+}
+
 // GetSecret implements the provider.Provider interface.
 func (v *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	return v.GetSecretFn(ctx, ref)
@@ -109,6 +118,14 @@ func (v *Client) WithGetAllSecrets(secData map[string][]byte, err error) *Client
 	return v
 }
 
+// WithSetSecret wraps the secret response to the fake provider.
+func (v *Client) WithSetSecret(err error) *Client {
+	v.SetSecretFn = func() error {
+		return err
+	}
+	return v
+}
+
 // WithNew wraps the fake provider factory function.
 func (v *Client) WithNew(f func(context.Context, esv1beta1.GenericStore, client.Client,
 	string) (esv1beta1.SecretsClient, error)) *Client {
@@ -116,6 +133,11 @@ func (v *Client) WithNew(f func(context.Context, esv1beta1.GenericStore, client.
 	return v
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (v *Client) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient returns a new fake provider.
 func (v *Client) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	c, err := v.NewFn(ctx, store, kube, namespace)

+ 21 - 0
pkg/provider/vault/fake/vault.go

@@ -48,6 +48,27 @@ func NewReadWithContextFn(secret map[string]interface{}, err error) ReadWithData
 	}
 }
 
+func NewWriteWithContextFn(secret map[string]interface{}, err error) WriteWithContextFn {
+	return func(ctx context.Context, path string, data map[string]interface{}) (*vault.Secret, error) {
+		vault := &vault.Secret{
+			Data: secret,
+		}
+		return vault, err
+	}
+}
+
+func WriteChangingReadContext(secret map[string]interface{}, l Logical) WriteWithContextFn {
+	v := &vault.Secret{
+		Data: secret,
+	}
+	return func(ctx context.Context, path string, data map[string]interface{}) (*vault.Secret, error) {
+		l.ReadWithDataWithContextFn = func(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) {
+			return v, nil
+		}
+		return v, nil
+	}
+}
+
 func (f Logical) ReadWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*vault.Secret, error) {
 	return f.ReadWithDataWithContextFn(ctx, path, data)
 }

+ 446 - 0
pkg/provider/vault/internal/fakes/client.go

@@ -0,0 +1,446 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package fakes
+
+import (
+	"sync"
+
+	"github.com/external-secrets/external-secrets/pkg/provider/vault"
+)
+
+type VaultClient struct {
+	AddHeaderStub        func(string, string)
+	addHeaderMutex       sync.RWMutex
+	addHeaderArgsForCall []struct {
+		arg1 string
+		arg2 string
+	}
+	AuthStub        func() vault.Auth
+	authMutex       sync.RWMutex
+	authArgsForCall []struct {
+	}
+	authReturns struct {
+		result1 vault.Auth
+	}
+	authReturnsOnCall map[int]struct {
+		result1 vault.Auth
+	}
+	AuthTokenStub        func() vault.Token
+	authTokenMutex       sync.RWMutex
+	authTokenArgsForCall []struct {
+	}
+	authTokenReturns struct {
+		result1 vault.Token
+	}
+	authTokenReturnsOnCall map[int]struct {
+		result1 vault.Token
+	}
+	ClearTokenStub        func()
+	clearTokenMutex       sync.RWMutex
+	clearTokenArgsForCall []struct {
+	}
+	LogicalStub        func() vault.Logical
+	logicalMutex       sync.RWMutex
+	logicalArgsForCall []struct {
+	}
+	logicalReturns struct {
+		result1 vault.Logical
+	}
+	logicalReturnsOnCall map[int]struct {
+		result1 vault.Logical
+	}
+	SetNamespaceStub        func(string)
+	setNamespaceMutex       sync.RWMutex
+	setNamespaceArgsForCall []struct {
+		arg1 string
+	}
+	SetTokenStub        func(string)
+	setTokenMutex       sync.RWMutex
+	setTokenArgsForCall []struct {
+		arg1 string
+	}
+	TokenStub        func() string
+	tokenMutex       sync.RWMutex
+	tokenArgsForCall []struct {
+	}
+	tokenReturns struct {
+		result1 string
+	}
+	tokenReturnsOnCall map[int]struct {
+		result1 string
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *VaultClient) AddHeader(arg1 string, arg2 string) {
+	fake.addHeaderMutex.Lock()
+	fake.addHeaderArgsForCall = append(fake.addHeaderArgsForCall, struct {
+		arg1 string
+		arg2 string
+	}{arg1, arg2})
+	stub := fake.AddHeaderStub
+	fake.recordInvocation("AddHeader", []interface{}{arg1, arg2})
+	fake.addHeaderMutex.Unlock()
+	if stub != nil {
+		fake.AddHeaderStub(arg1, arg2)
+	}
+}
+
+func (fake *VaultClient) AddHeaderCallCount() int {
+	fake.addHeaderMutex.RLock()
+	defer fake.addHeaderMutex.RUnlock()
+	return len(fake.addHeaderArgsForCall)
+}
+
+func (fake *VaultClient) AddHeaderCalls(stub func(string, string)) {
+	fake.addHeaderMutex.Lock()
+	defer fake.addHeaderMutex.Unlock()
+	fake.AddHeaderStub = stub
+}
+
+func (fake *VaultClient) AddHeaderArgsForCall(i int) (string, string) {
+	fake.addHeaderMutex.RLock()
+	defer fake.addHeaderMutex.RUnlock()
+	argsForCall := fake.addHeaderArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *VaultClient) Auth() vault.Auth {
+	fake.authMutex.Lock()
+	ret, specificReturn := fake.authReturnsOnCall[len(fake.authArgsForCall)]
+	fake.authArgsForCall = append(fake.authArgsForCall, struct {
+	}{})
+	stub := fake.AuthStub
+	fakeReturns := fake.authReturns
+	fake.recordInvocation("Auth", []interface{}{})
+	fake.authMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *VaultClient) AuthCallCount() int {
+	fake.authMutex.RLock()
+	defer fake.authMutex.RUnlock()
+	return len(fake.authArgsForCall)
+}
+
+func (fake *VaultClient) AuthCalls(stub func() vault.Auth) {
+	fake.authMutex.Lock()
+	defer fake.authMutex.Unlock()
+	fake.AuthStub = stub
+}
+
+func (fake *VaultClient) AuthReturns(result1 vault.Auth) {
+	fake.authMutex.Lock()
+	defer fake.authMutex.Unlock()
+	fake.AuthStub = nil
+	fake.authReturns = struct {
+		result1 vault.Auth
+	}{result1}
+}
+
+func (fake *VaultClient) AuthReturnsOnCall(i int, result1 vault.Auth) {
+	fake.authMutex.Lock()
+	defer fake.authMutex.Unlock()
+	fake.AuthStub = nil
+	if fake.authReturnsOnCall == nil {
+		fake.authReturnsOnCall = make(map[int]struct {
+			result1 vault.Auth
+		})
+	}
+	fake.authReturnsOnCall[i] = struct {
+		result1 vault.Auth
+	}{result1}
+}
+
+func (fake *VaultClient) AuthToken() vault.Token {
+	fake.authTokenMutex.Lock()
+	ret, specificReturn := fake.authTokenReturnsOnCall[len(fake.authTokenArgsForCall)]
+	fake.authTokenArgsForCall = append(fake.authTokenArgsForCall, struct {
+	}{})
+	stub := fake.AuthTokenStub
+	fakeReturns := fake.authTokenReturns
+	fake.recordInvocation("AuthToken", []interface{}{})
+	fake.authTokenMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *VaultClient) AuthTokenCallCount() int {
+	fake.authTokenMutex.RLock()
+	defer fake.authTokenMutex.RUnlock()
+	return len(fake.authTokenArgsForCall)
+}
+
+func (fake *VaultClient) AuthTokenCalls(stub func() vault.Token) {
+	fake.authTokenMutex.Lock()
+	defer fake.authTokenMutex.Unlock()
+	fake.AuthTokenStub = stub
+}
+
+func (fake *VaultClient) AuthTokenReturns(result1 vault.Token) {
+	fake.authTokenMutex.Lock()
+	defer fake.authTokenMutex.Unlock()
+	fake.AuthTokenStub = nil
+	fake.authTokenReturns = struct {
+		result1 vault.Token
+	}{result1}
+}
+
+func (fake *VaultClient) AuthTokenReturnsOnCall(i int, result1 vault.Token) {
+	fake.authTokenMutex.Lock()
+	defer fake.authTokenMutex.Unlock()
+	fake.AuthTokenStub = nil
+	if fake.authTokenReturnsOnCall == nil {
+		fake.authTokenReturnsOnCall = make(map[int]struct {
+			result1 vault.Token
+		})
+	}
+	fake.authTokenReturnsOnCall[i] = struct {
+		result1 vault.Token
+	}{result1}
+}
+
+func (fake *VaultClient) ClearToken() {
+	fake.clearTokenMutex.Lock()
+	fake.clearTokenArgsForCall = append(fake.clearTokenArgsForCall, struct {
+	}{})
+	stub := fake.ClearTokenStub
+	fake.recordInvocation("ClearToken", []interface{}{})
+	fake.clearTokenMutex.Unlock()
+	if stub != nil {
+		fake.ClearTokenStub()
+	}
+}
+
+func (fake *VaultClient) ClearTokenCallCount() int {
+	fake.clearTokenMutex.RLock()
+	defer fake.clearTokenMutex.RUnlock()
+	return len(fake.clearTokenArgsForCall)
+}
+
+func (fake *VaultClient) ClearTokenCalls(stub func()) {
+	fake.clearTokenMutex.Lock()
+	defer fake.clearTokenMutex.Unlock()
+	fake.ClearTokenStub = stub
+}
+
+func (fake *VaultClient) Logical() vault.Logical {
+	fake.logicalMutex.Lock()
+	ret, specificReturn := fake.logicalReturnsOnCall[len(fake.logicalArgsForCall)]
+	fake.logicalArgsForCall = append(fake.logicalArgsForCall, struct {
+	}{})
+	stub := fake.LogicalStub
+	fakeReturns := fake.logicalReturns
+	fake.recordInvocation("Logical", []interface{}{})
+	fake.logicalMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *VaultClient) LogicalCallCount() int {
+	fake.logicalMutex.RLock()
+	defer fake.logicalMutex.RUnlock()
+	return len(fake.logicalArgsForCall)
+}
+
+func (fake *VaultClient) LogicalCalls(stub func() vault.Logical) {
+	fake.logicalMutex.Lock()
+	defer fake.logicalMutex.Unlock()
+	fake.LogicalStub = stub
+}
+
+func (fake *VaultClient) LogicalReturns(result1 vault.Logical) {
+	fake.logicalMutex.Lock()
+	defer fake.logicalMutex.Unlock()
+	fake.LogicalStub = nil
+	fake.logicalReturns = struct {
+		result1 vault.Logical
+	}{result1}
+}
+
+func (fake *VaultClient) LogicalReturnsOnCall(i int, result1 vault.Logical) {
+	fake.logicalMutex.Lock()
+	defer fake.logicalMutex.Unlock()
+	fake.LogicalStub = nil
+	if fake.logicalReturnsOnCall == nil {
+		fake.logicalReturnsOnCall = make(map[int]struct {
+			result1 vault.Logical
+		})
+	}
+	fake.logicalReturnsOnCall[i] = struct {
+		result1 vault.Logical
+	}{result1}
+}
+
+func (fake *VaultClient) SetNamespace(arg1 string) {
+	fake.setNamespaceMutex.Lock()
+	fake.setNamespaceArgsForCall = append(fake.setNamespaceArgsForCall, struct {
+		arg1 string
+	}{arg1})
+	stub := fake.SetNamespaceStub
+	fake.recordInvocation("SetNamespace", []interface{}{arg1})
+	fake.setNamespaceMutex.Unlock()
+	if stub != nil {
+		fake.SetNamespaceStub(arg1)
+	}
+}
+
+func (fake *VaultClient) SetNamespaceCallCount() int {
+	fake.setNamespaceMutex.RLock()
+	defer fake.setNamespaceMutex.RUnlock()
+	return len(fake.setNamespaceArgsForCall)
+}
+
+func (fake *VaultClient) SetNamespaceCalls(stub func(string)) {
+	fake.setNamespaceMutex.Lock()
+	defer fake.setNamespaceMutex.Unlock()
+	fake.SetNamespaceStub = stub
+}
+
+func (fake *VaultClient) SetNamespaceArgsForCall(i int) string {
+	fake.setNamespaceMutex.RLock()
+	defer fake.setNamespaceMutex.RUnlock()
+	argsForCall := fake.setNamespaceArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *VaultClient) SetToken(arg1 string) {
+	fake.setTokenMutex.Lock()
+	fake.setTokenArgsForCall = append(fake.setTokenArgsForCall, struct {
+		arg1 string
+	}{arg1})
+	stub := fake.SetTokenStub
+	fake.recordInvocation("SetToken", []interface{}{arg1})
+	fake.setTokenMutex.Unlock()
+	if stub != nil {
+		fake.SetTokenStub(arg1)
+	}
+}
+
+func (fake *VaultClient) SetTokenCallCount() int {
+	fake.setTokenMutex.RLock()
+	defer fake.setTokenMutex.RUnlock()
+	return len(fake.setTokenArgsForCall)
+}
+
+func (fake *VaultClient) SetTokenCalls(stub func(string)) {
+	fake.setTokenMutex.Lock()
+	defer fake.setTokenMutex.Unlock()
+	fake.SetTokenStub = stub
+}
+
+func (fake *VaultClient) SetTokenArgsForCall(i int) string {
+	fake.setTokenMutex.RLock()
+	defer fake.setTokenMutex.RUnlock()
+	argsForCall := fake.setTokenArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *VaultClient) Token() string {
+	fake.tokenMutex.Lock()
+	ret, specificReturn := fake.tokenReturnsOnCall[len(fake.tokenArgsForCall)]
+	fake.tokenArgsForCall = append(fake.tokenArgsForCall, struct {
+	}{})
+	stub := fake.TokenStub
+	fakeReturns := fake.tokenReturns
+	fake.recordInvocation("Token", []interface{}{})
+	fake.tokenMutex.Unlock()
+	if stub != nil {
+		return stub()
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *VaultClient) TokenCallCount() int {
+	fake.tokenMutex.RLock()
+	defer fake.tokenMutex.RUnlock()
+	return len(fake.tokenArgsForCall)
+}
+
+func (fake *VaultClient) TokenCalls(stub func() string) {
+	fake.tokenMutex.Lock()
+	defer fake.tokenMutex.Unlock()
+	fake.TokenStub = stub
+}
+
+func (fake *VaultClient) TokenReturns(result1 string) {
+	fake.tokenMutex.Lock()
+	defer fake.tokenMutex.Unlock()
+	fake.TokenStub = nil
+	fake.tokenReturns = struct {
+		result1 string
+	}{result1}
+}
+
+func (fake *VaultClient) TokenReturnsOnCall(i int, result1 string) {
+	fake.tokenMutex.Lock()
+	defer fake.tokenMutex.Unlock()
+	fake.TokenStub = nil
+	if fake.tokenReturnsOnCall == nil {
+		fake.tokenReturnsOnCall = make(map[int]struct {
+			result1 string
+		})
+	}
+	fake.tokenReturnsOnCall[i] = struct {
+		result1 string
+	}{result1}
+}
+
+func (fake *VaultClient) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.addHeaderMutex.RLock()
+	defer fake.addHeaderMutex.RUnlock()
+	fake.authMutex.RLock()
+	defer fake.authMutex.RUnlock()
+	fake.authTokenMutex.RLock()
+	defer fake.authTokenMutex.RUnlock()
+	fake.clearTokenMutex.RLock()
+	defer fake.clearTokenMutex.RUnlock()
+	fake.logicalMutex.RLock()
+	defer fake.logicalMutex.RUnlock()
+	fake.setNamespaceMutex.RLock()
+	defer fake.setNamespaceMutex.RUnlock()
+	fake.setTokenMutex.RLock()
+	defer fake.setTokenMutex.RUnlock()
+	fake.tokenMutex.RLock()
+	defer fake.tokenMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *VaultClient) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ vault.Client = new(VaultClient)

+ 60 - 4
pkg/provider/vault/vault.go

@@ -224,6 +224,11 @@ type connector struct {
 	newVaultClient func(c *vault.Config) (Client, error)
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (c *connector) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadWrite
+}
+
 func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	// controller-runtime/client does not support TokenRequest or other subresource APIs
 	// so we need to construct our own client and use it to fetch tokens
@@ -236,6 +241,7 @@ func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore,
 	if err != nil {
 		return nil, err
 	}
+
 	return c.newClient(ctx, store, kube, clientset.CoreV1(), namespace)
 }
 
@@ -355,9 +361,60 @@ func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
 	return nil
 }
 
-// Empty GetAllSecrets.
-// GetAllSecrets
-// First load all secrets from secretStore path configuration.
+func (v *client) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	label := map[string]interface{}{
+		"custom_metadata": map[string]string{
+			"managed-by": "external-secrets",
+		},
+	}
+	secretToPush := map[string]interface{}{
+		"data": map[string]string{
+			remoteRef.GetRemoteKey(): string(value),
+		},
+	}
+	path := v.buildPath(remoteRef.GetRemoteKey())
+	metaPath, err := v.buildMetadataPath(remoteRef.GetRemoteKey())
+	if err != nil {
+		return err
+	}
+
+	// Retrieve the secret map from vault and convert the secret value in string form.
+	vaultSecret, err := v.GetSecretMap(ctx, esv1beta1.ExternalSecretDataRemoteRef{Key: path})
+	vaultSecretValue := string(vaultSecret[remoteRef.GetRemoteKey()])
+	// If error is not of type secret not found, we should error
+	if err != nil && !strings.Contains(err.Error(), "secret not found") {
+		return err
+	}
+
+	// Retrieve the secret value to be pushed and convert it to string form.
+	pushSecretValue := string(value)
+
+	if vaultSecretValue == pushSecretValue {
+		return nil
+	}
+
+	// If the secret exists (err == nil), we should check if it is managed by external-secrets
+	if err == nil {
+		metadata, err := v.readSecretMetadata(ctx, remoteRef.GetRemoteKey())
+		if err != nil {
+			return err
+		}
+		manager, ok := metadata["managed-by"]
+		if !ok || manager != "external-secrets" {
+			return fmt.Errorf("secret not managed by external-secrets")
+		}
+	}
+	_, err = v.logical.WriteWithContext(ctx, metaPath, label)
+	if err != nil {
+		return err
+	}
+	// Otherwise, create or update the version.
+	_, err = v.logical.WriteWithContext(ctx, path, secretToPush)
+	return err
+}
+
+// GetAllSecrets gets multiple secrets from the provider and loads into a kubernetes secret.
+// First load all secrets from secretStore path configuration
 // Then, gets secrets from a matching name or matching custom_metadata.
 func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	if v.store.Version == esv1beta1.VaultKVStoreV1 {
@@ -701,7 +758,6 @@ func (v *client) readSecret(ctx context.Context, path, version string) (map[stri
 		// Vault KV2 has data embedded within sub-field
 		// reference - https://www.vaultproject.io/api/secret/kv/kv-v2#read-secret-version
 		dataInt, ok := vaultSecret.Data["data"]
-
 		if !ok {
 			return nil, errors.New(errDataField)
 		}

+ 120 - 0
pkg/provider/vault/vault_test.go

@@ -1388,3 +1388,123 @@ func TestValidateStore(t *testing.T) {
 		})
 	}
 }
+
+type fakeRef struct {
+	key string
+}
+
+func (f fakeRef) GetRemoteKey() string {
+	return f.key
+}
+
+func TestSetSecret(t *testing.T) {
+	noPermission := errors.New("no permission")
+	secretNotFound := errors.New("secret not found")
+
+	type args struct {
+		store    *esv1beta1.VaultProvider
+		vLogical Logical
+	}
+
+	type want struct {
+		err error
+	}
+	tests := map[string]struct {
+		reason string
+		args   args
+		want   want
+	}{
+		"SetSecret": {
+			reason: "secret is successfully set, with no existing vault secret",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, secretNotFound),
+					WriteWithContextFn:        fake.NewWriteWithContextFn(nil, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+
+		"SetSecretWithWriteError": {
+			reason: "secret cannot be pushed if write fails",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, secretNotFound),
+					WriteWithContextFn:        fake.NewWriteWithContextFn(nil, noPermission),
+				},
+			},
+			want: want{
+				err: noPermission,
+			},
+		},
+
+		"SetSecretEqualsPushSecret": {
+			reason: "vault secret kv equals secret to push kv",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{
+						"data": map[string]interface{}{
+							"fake-key": "fake-value",
+						},
+					}, nil),
+				},
+			},
+			want: want{
+				err: nil,
+			},
+		},
+
+		"SetSecretErrorReadingSecret": {
+			reason: "error occurs if secret cannot be read",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadWithContextFn(nil, noPermission),
+				},
+			},
+			want: want{
+				err: fmt.Errorf(errReadSecret, noPermission),
+			},
+		},
+
+		"SetSecretNotManagedByESO": {
+			reason: "a secret not managed by ESO cannot be updated",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadWithContextFn(map[string]interface{}{
+						"data": map[string]interface{}{
+							"fake-key": "fake-value2",
+							"custom_metadata": map[string]interface{}{
+								"managed-by": "not-external-secrets",
+							},
+						},
+					}, nil),
+				},
+			},
+			want: want{
+				err: errors.New("secret not managed by external-secrets"),
+			},
+		},
+	}
+
+	for name, tc := range tests {
+		t.Run(name, func(t *testing.T) {
+			ref := fakeRef{key: "fake-key"}
+			client := &client{
+				logical: tc.args.vLogical,
+				store:   tc.args.store,
+			}
+			err := client.SetSecret(context.Background(), []byte("fake-value"), ref)
+
+			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
+				t.Errorf("\nTesting SetSecret:\nName: %v\nReason: %v\nWant error: %v\nGot error: %v", name, tc.reason, tc.want.err, diff)
+			}
+		})
+	}
+}

+ 10 - 0
pkg/provider/webhook/webhook.go

@@ -61,6 +61,11 @@ func init() {
 	})
 }
 
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	whClient := &WebHook{
 		kube:      kube,
@@ -111,6 +116,11 @@ func (w *WebHook) getStoreSecret(ctx context.Context, ref esmeta.SecretKeySelect
 	return secret, nil
 }
 
+// Not Implemented SetSecret.
+func (w *WebHook) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
 // Empty GetAllSecrets.
 func (w *WebHook) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
 	// TO be implemented

+ 6 - 1
pkg/provider/yandex/common/provider.go

@@ -88,6 +88,7 @@ func InitYandexCloudProvider(
 	return provider
 }
 
+type NewSecretSetterFunc func()
 type AdaptInputFunc func(store esv1beta1.GenericStore) (*SecretsClientInput, error)
 type NewSecretGetterFunc func(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error)
 type NewIamTokenFunc func(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (*IamToken, error)
@@ -103,6 +104,10 @@ type SecretsClientInput struct {
 	CACertificate *esmeta.SecretKeySelector
 }
 
+func (p *YandexCloudProvider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
 // NewClient constructs a Yandex.Cloud Provider.
 func (p *YandexCloudProvider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	input, err := p.adaptInputFunc(store)
@@ -177,7 +182,7 @@ func (p *YandexCloudProvider) NewClient(ctx context.Context, store esv1beta1.Gen
 		return nil, fmt.Errorf("failed to create IAM token: %w", err)
 	}
 
-	return &yandexCloudSecretsClient{secretGetter, iamToken.Token}, nil
+	return &yandexCloudSecretsClient{secretGetter, nil, iamToken.Token}, nil
 }
 
 func (p *YandexCloudProvider) getOrCreateSecretGetter(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error) {

+ 14 - 9
pkg/provider/yandex/common/secretsclient.go

@@ -26,26 +26,31 @@ var _ esv1beta1.SecretsClient = &yandexCloudSecretsClient{}
 // Implementation of v1beta1.SecretsClient.
 type yandexCloudSecretsClient struct {
 	secretGetter SecretGetter
+	secretSetter SecretSetter
 	iamToken     string
 }
 
-func (c *yandexCloudSecretsClient) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
-	// TO be implemented
-	return nil, fmt.Errorf("GetAllSecrets not supported")
-}
-
 func (c *yandexCloudSecretsClient) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	return c.secretGetter.GetSecret(ctx, c.iamToken, ref.Key, ref.Version, ref.Property)
 }
 
+func (c *yandexCloudSecretsClient) SetSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
+	return fmt.Errorf("not implemented")
+}
+
+func (c *yandexCloudSecretsClient) Validate() (esv1beta1.ValidationResult, error) {
+	return esv1beta1.ValidationResultReady, nil
+}
+
 func (c *yandexCloudSecretsClient) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	return c.secretGetter.GetSecretMap(ctx, c.iamToken, ref.Key, ref.Version)
 }
 
-func (c *yandexCloudSecretsClient) Close(ctx context.Context) error {
-	return nil
+func (c *yandexCloudSecretsClient) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	// TO be implemented
+	return nil, fmt.Errorf("GetAllSecrets not supported")
 }
 
-func (c *yandexCloudSecretsClient) Validate() (esv1beta1.ValidationResult, error) {
-	return esv1beta1.ValidationResultReady, nil
+func (c *yandexCloudSecretsClient) Close(ctx context.Context) error {
+	return nil
 }

+ 18 - 0
pkg/provider/yandex/common/secretsetter.go

@@ -0,0 +1,18 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package common
+
+type SecretSetter interface {
+	SetSecret() error
+}

+ 0 - 1
pkg/utils/utils.go

@@ -15,7 +15,6 @@ limitations under the License.
 package utils
 
 import (
-
 	//nolint:gosec
 	"crypto/md5"
 	"encoding/base64"

+ 1 - 0
tools.go

@@ -5,6 +5,7 @@ package tools
 
 import (
 	_ "github.com/ahmetb/gen-crd-api-reference-docs"
+	_ "github.com/maxbrunsfeld/counterfeiter/v6"
 	_ "github.com/onsi/ginkgo/v2/ginkgo"
 	_ "sigs.k8s.io/controller-tools/cmd/controller-gen"
 )