diff --git a/controllers/cluster_controller.go b/controllers/cluster_controller.go index 738d25e4..209f400e 100644 --- a/controllers/cluster_controller.go +++ b/controllers/cluster_controller.go @@ -25,6 +25,9 @@ type ClusterReconciler struct { CreateSATokenSecret bool DefaultCreationPolicy synv1alpha1.CreationPolicy + DefaultDeletionPolicy synv1alpha1.DeletionPolicy + UseVault bool + DeleteProtection bool } //+kubebuilder:rbac:groups=syn.tools,resources=clusters,verbs=get;list;watch;create;update;patch;delete @@ -55,6 +58,9 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, request ctrl.Request) Reconciler: r, CreateSATokenSecret: r.CreateSATokenSecret, DefaultCreationPolicy: r.DefaultCreationPolicy, + DefaultDeletionPolicy: r.DefaultDeletionPolicy, + UseVault: r.UseVault, + UseDeletionProtection: r.DeleteProtection, } steps := []pipeline.Step{ diff --git a/controllers/gitrepo/utils.go b/controllers/gitrepo/utils.go index 3afe9c46..35981dd3 100644 --- a/controllers/gitrepo/utils.go +++ b/controllers/gitrepo/utils.go @@ -34,7 +34,7 @@ func CreateOrUpdate(obj pipeline.Object, data *pipeline.Context) pipeline.Result if template.DeletionPolicy == "" { if obj.GetDeletionPolicy() == "" { - template.DeletionPolicy = pipeline.GetDefaultDeletionPolicy() + template.DeletionPolicy = data.DefaultDeletionPolicy } else { template.DeletionPolicy = obj.GetDeletionPolicy() } diff --git a/controllers/gitrepo_controller.go b/controllers/gitrepo_controller.go index 0fb4c2db..0fd918c5 100644 --- a/controllers/gitrepo_controller.go +++ b/controllers/gitrepo_controller.go @@ -29,6 +29,8 @@ type GitRepoReconciler struct { // MaxReconcileInterval is the maximum time between two reconciliations. MaxReconcileInterval time.Duration + + DeleteProtection bool } //+kubebuilder:rbac:groups=syn.tools,resources=gitrepos,verbs=get;list;watch;create;update;patch;delete @@ -62,6 +64,7 @@ func (r *GitRepoReconciler) Reconcile(ctx context.Context, request ctrl.Request) FinalizerName: synv1alpha1.FinalizerName, Reconciler: r, DefaultCreationPolicy: r.DefaultCreationPolicy, + UseDeletionProtection: r.DeleteProtection, } steps := []pipeline.Step{ diff --git a/controllers/tenant/git.go b/controllers/tenant/git.go index e18e5c38..b50b0164 100644 --- a/controllers/tenant/git.go +++ b/controllers/tenant/git.go @@ -2,7 +2,6 @@ package tenant import ( "fmt" - "os" synv1alpha1 "github.com/projectsyn/lieutenant-operator/api/v1alpha1" "github.com/projectsyn/lieutenant-operator/git/manager" @@ -59,15 +58,14 @@ func updateTenantGitRepo(obj pipeline.Object, data *pipeline.Context) pipeline.R return pipeline.Result{} } -func setGlobalGitRepoURL(obj pipeline.Object, _ *pipeline.Context) pipeline.Result { +func setGlobalGitRepoURL(obj pipeline.Object, data *pipeline.Context) pipeline.Result { instance, ok := obj.(*synv1alpha1.Tenant) if !ok { return pipeline.Result{Err: fmt.Errorf("object is not a tenant")} } - defaultGlobalGitRepoURL := os.Getenv(DefaultGlobalGitRepoURL) - if len(instance.Spec.GlobalGitRepoURL) == 0 && len(defaultGlobalGitRepoURL) > 0 { - instance.Spec.GlobalGitRepoURL = defaultGlobalGitRepoURL + if len(instance.Spec.GlobalGitRepoURL) == 0 && len(data.DefaultGlobalGitRepoUrl) > 0 { + instance.Spec.GlobalGitRepoURL = data.DefaultGlobalGitRepoUrl } return pipeline.Result{} } diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index e4958b76..c847d29c 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -26,6 +26,10 @@ type TenantReconciler struct { CreateSATokenSecret bool DefaultCreationPolicy synv1alpha1.CreationPolicy + DefaultDeletionPolicy synv1alpha1.DeletionPolicy + + DefaultGlobalGitRepoUrl string + DeleteProtection bool } //+kubebuilder:rbac:groups=syn.tools,resources=tenants,verbs=get;list;watch;create;update;patch;delete @@ -52,13 +56,16 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, request ctrl.Request) } data := &pipeline.Context{ - Context: ctx, - Client: r.Client, - Log: reqLogger, - FinalizerName: "", - Reconciler: r, - CreateSATokenSecret: r.CreateSATokenSecret, - DefaultCreationPolicy: r.DefaultCreationPolicy, + Context: ctx, + Client: r.Client, + Log: reqLogger, + FinalizerName: "", + Reconciler: r, + CreateSATokenSecret: r.CreateSATokenSecret, + DefaultCreationPolicy: r.DefaultCreationPolicy, + DefaultDeletionPolicy: r.DefaultDeletionPolicy, + DefaultGlobalGitRepoUrl: r.DefaultGlobalGitRepoUrl, + UseDeletionProtection: r.DeleteProtection, } steps := []pipeline.Step{ diff --git a/go.mod b/go.mod index 81587de4..72a92cf8 100644 --- a/go.mod +++ b/go.mod @@ -90,6 +90,7 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kouhin/envflag v0.0.0-20150818174321-0e9a86061649 // indirect github.com/leosayous21/go-azure-msi v0.0.0-20210509193526-19353bedcfc8 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 8e8bcde9..668a0b50 100644 --- a/go.sum +++ b/go.sum @@ -343,6 +343,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kouhin/envflag v0.0.0-20150818174321-0e9a86061649 h1:l95EUBxc0iMtMeam3pHFb9jko9ntaLYe2Nc+2evKElM= +github.com/kouhin/envflag v0.0.0-20150818174321-0e9a86061649/go.mod h1:BT0PpXv8Y4EL/WUsQmYsQ2FSB9HwQXIuvY+pElZVdFg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= diff --git a/main.go b/main.go index 06bc0696..9532091b 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ import ( "flag" "fmt" "os" - "strconv" "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) @@ -23,6 +22,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/metrics" "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "github.com/kouhin/envflag" synv1alpha1 "github.com/projectsyn/lieutenant-operator/api/v1alpha1" "github.com/projectsyn/lieutenant-operator/controllers" operatorMetrics "github.com/projectsyn/lieutenant-operator/metrics" @@ -34,19 +34,6 @@ var ( setupLog = ctrl.Log.WithName("setup") ) -const ( - // WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE - // which specifies the Namespace to watch. - // An empty value means the operator is running with cluster scope. - watchNamespaceEnvVar = "WATCH_NAMESPACE" - // createSAEnvVar is the constant for the env variable which indicates - // whether to create ServiceAccount token secrets - createSATokenEnvVar = "LIEUTENANT_CREATE_SERVICEACCOUNT_TOKEN_SECRET" - // createSAEnvVar is the constant for the env variable which indicates - // the default creation policy for git repositories - defaultCreationPolicy = "DEFAULT_CREATION_POLICY" -) - func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) @@ -61,33 +48,42 @@ func main() { var enableLeaderElection bool var probeAddr string var gitRepoMaxReconcileInterval time.Duration + + var skipVaultSetup bool + var defaultDeletionPolicy string + var defaultCreationPolicy string + var useDeleteProtection bool + var defGlobalGitRepoUrl string + var watchNamespace string + var createSaTokenSecret bool flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.DurationVar(&gitRepoMaxReconcileInterval, "git-repo-max-reconcile-interval", 3*time.Hour, "The maximum time between reconciliations of GitRepos.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&skipVaultSetup, "skip-vault-setup", false, "Set to `true` in order to skip vault setup.") + flag.StringVar(&defaultDeletionPolicy, "default-deletion-policy", "Archive", "Default deletion policy for git repos. Can be `Delete`, `Retain` or `Archive`.") + flag.StringVar(&defaultCreationPolicy, "default-creation-policy", "Create", "Default creation policy for git repos. Can be `Create` or `Adopt`.") + flag.BoolVar(&useDeleteProtection, "lieutenant-delete-protection", false, "Whether to enable deletion protection.") + flag.StringVar(&defGlobalGitRepoUrl, "default-global-git-repo-url", "", "Default URL for global git repo; used if global git repo isn't explicitly configured.") + flag.StringVar(&watchNamespace, "watch-namespace", "default", "The namespace which should be watched by the operator") + flag.BoolVar(&createSaTokenSecret, "lieutenant-create-serviceaccount-token-secret", false, "Whether Lieutenant should create ServiceAccount token secrets") opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) - flag.Parse() - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - - watchNamespace, err := getWatchNamespace() + err := envflag.Parse() if err != nil { - setupLog.Error(err, "unable to get WatchNamespace, "+ - "the manager will watch and manage resources in all namespaces") + // The setupLog is not working yet at this point; resort to fmt.Printf + fmt.Printf("unable to parse flags and environment variables: %s", err.Error()) + os.Exit(1) } - createSATokenSecret, err := getCreateSATokenSecret() - if err != nil { - setupLog.Error(err, "unable to get TokenSecret flag, "+ - "the operator won't manage ServiceAccount token secrets.") - } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - creationPolicy := getDefaultCreationPolicy() + creationPolicy := getDefaultCreationPolicy(defaultCreationPolicy) + deletionPolicy := getDefaultDeletionPolicy(defaultDeletionPolicy) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, @@ -133,8 +129,11 @@ func main() { if err = (&controllers.ClusterReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), - CreateSATokenSecret: createSATokenSecret, + CreateSATokenSecret: createSaTokenSecret, DefaultCreationPolicy: creationPolicy, + DefaultDeletionPolicy: deletionPolicy, + DeleteProtection: useDeleteProtection, + UseVault: !skipVaultSetup, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Cluster") os.Exit(1) @@ -143,17 +142,20 @@ func main() { Client: mgr.GetClient(), Scheme: mgr.GetScheme(), DefaultCreationPolicy: creationPolicy, - - MaxReconcileInterval: gitRepoMaxReconcileInterval, + DeleteProtection: useDeleteProtection, + MaxReconcileInterval: gitRepoMaxReconcileInterval, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "GitRepo") os.Exit(1) } if err = (&controllers.TenantReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - CreateSATokenSecret: createSATokenSecret, - DefaultCreationPolicy: creationPolicy, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + CreateSATokenSecret: createSaTokenSecret, + DefaultCreationPolicy: creationPolicy, + DefaultDeletionPolicy: deletionPolicy, + DefaultGlobalGitRepoUrl: defGlobalGitRepoUrl, + DeleteProtection: useDeleteProtection, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Tenant") os.Exit(1) @@ -176,36 +178,9 @@ func main() { } } -// getWatchNamespace returns the Namespace the operator should be watching for changes -func getWatchNamespace() (string, error) { - - ns, found := os.LookupEnv(watchNamespaceEnvVar) - if !found { - return "", fmt.Errorf("environment variable '%s' not found", watchNamespaceEnvVar) - } - return ns, nil -} - -// getCreateSATokenSecret returns a boolean indicating whether the operator should manage ServiceAccount token secrets -func getCreateSATokenSecret() (bool, error) { - value, found := os.LookupEnv(createSATokenEnvVar) - if !found { - return false, fmt.Errorf("environment variable '%s' not found", createSATokenEnvVar) - } - create, err := strconv.ParseBool(value) - if err != nil { - return false, fmt.Errorf("unable to parse '%s': %v", value, err) - } - return create, nil -} - // getDefaultCreationPolicy returns to fallback creation policy for git repositories -func getDefaultCreationPolicy() synv1alpha1.CreationPolicy { - p, found := os.LookupEnv(defaultCreationPolicy) - if !found { - return synv1alpha1.CreatePolicy - } - cp := synv1alpha1.CreationPolicy(p) +func getDefaultCreationPolicy(stringArg string) synv1alpha1.CreationPolicy { + cp := synv1alpha1.CreationPolicy(stringArg) switch cp { case synv1alpha1.CreatePolicy, synv1alpha1.AdoptPolicy: return cp @@ -213,3 +188,13 @@ func getDefaultCreationPolicy() synv1alpha1.CreationPolicy { return synv1alpha1.CreatePolicy } } + +func getDefaultDeletionPolicy(stringArg string) synv1alpha1.DeletionPolicy { + cp := synv1alpha1.DeletionPolicy(stringArg) + switch cp { + case synv1alpha1.DeletePolicy, synv1alpha1.RetainPolicy, synv1alpha1.ArchivePolicy: + return cp + default: + return synv1alpha1.ArchivePolicy + } +} diff --git a/pipeline/pipeline.go b/pipeline/pipeline.go index 9538c457..3724f9ac 100644 --- a/pipeline/pipeline.go +++ b/pipeline/pipeline.go @@ -36,15 +36,19 @@ type Object interface { // Context contains additional data about the CRD being processed. type Context struct { - Context context.Context - FinalizerName string - Client client.Client - Log logr.Logger - Deleted bool - originalObject Object - Reconciler reconcile.Reconciler - CreateSATokenSecret bool - DefaultCreationPolicy synv1alpha1.CreationPolicy + Context context.Context + FinalizerName string + Client client.Client + Log logr.Logger + Deleted bool + originalObject Object + Reconciler reconcile.Reconciler + CreateSATokenSecret bool + DefaultCreationPolicy synv1alpha1.CreationPolicy + DefaultDeletionPolicy synv1alpha1.DeletionPolicy + DefaultGlobalGitRepoUrl string + UseVault bool + UseDeletionProtection bool } // Result indicates whether the current execution should be aborted and diff --git a/pipeline/steps.go b/pipeline/steps.go index 46de781c..6c51dbef 100644 --- a/pipeline/steps.go +++ b/pipeline/steps.go @@ -2,7 +2,6 @@ package pipeline import ( "fmt" - "os" "strconv" "errors" @@ -15,27 +14,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func GetDefaultDeletionPolicy() synv1alpha1.DeletionPolicy { - policy := synv1alpha1.DeletionPolicy(os.Getenv("DEFAULT_DELETION_POLICY")) - switch policy { - case synv1alpha1.ArchivePolicy, synv1alpha1.DeletePolicy, synv1alpha1.RetainPolicy: - return policy - default: - return synv1alpha1.ArchivePolicy - } -} - func addDeletionProtection(instance Object, data *Context) Result { if data.Deleted { return Result{} } - config := os.Getenv(protectionSettingEnvVar) - - protected, err := strconv.ParseBool(config) - if err != nil { - protected = true - } + protected := data.UseDeletionProtection if protected { annotations := instance.GetAnnotations() diff --git a/pipeline/steps_test.go b/pipeline/steps_test.go index cba5beca..3b6cf6de 100644 --- a/pipeline/steps_test.go +++ b/pipeline/steps_test.go @@ -3,7 +3,6 @@ package pipeline import ( "context" "fmt" - "os" "testing" "time" @@ -45,19 +44,6 @@ func testSetupClient(objs ...client.Object) (client.Client, *runtime.Scheme) { ).Build(), s } -func TestGetDeletionPolicyDefault(t *testing.T) { - policy := GetDefaultDeletionPolicy() - assert.Equal(t, synv1alpha1.ArchivePolicy, policy) -} - -func TestGetDeletionPolicyNonDefault(t *testing.T) { - err := os.Setenv("DEFAULT_DELETION_POLICY", "Retain") - require.NoError(t, err) - - policy := GetDefaultDeletionPolicy() - assert.Equal(t, synv1alpha1.RetainPolicy, policy) -} - var addTenantLabelCases = genericCases{ "add labels": { args: args{ @@ -157,7 +143,7 @@ func TestHandleDeletion(t *testing.T) { type addDeletionProtectionArgs struct { instance *synv1alpha1.Cluster - enable string + enable bool result string } @@ -168,33 +154,25 @@ var addDeletionProtectionCases = map[string]struct { "Add deletion protection": { args: addDeletionProtectionArgs{ instance: &synv1alpha1.Cluster{}, - enable: "true", + enable: true, result: "true", }, }, "Don't add deletion protection": { args: addDeletionProtectionArgs{ instance: &synv1alpha1.Cluster{}, - enable: "false", + enable: false, result: "", }, }, - "Invalid setting": { - args: addDeletionProtectionArgs{ - instance: &synv1alpha1.Cluster{}, - enable: "gaga", - result: "true", - }, - }, } func TestAddDeletionProtection(t *testing.T) { for name, tt := range addDeletionProtectionCases { t.Run(name, func(t *testing.T) { - err := os.Setenv(protectionSettingEnvVar, tt.args.enable) - require.NoError(t, err) - - addDeletionProtection(tt.args.instance, addLogger(&Context{})) + addDeletionProtection(tt.args.instance, addLogger(&Context{ + UseDeletionProtection: tt.args.enable, + })) result := tt.args.instance.GetAnnotations()[DeleteProtectionAnnotation] if result != tt.args.result { diff --git a/vault/reconcile_steps.go b/vault/reconcile_steps.go index e6a79f11..0282fdfc 100644 --- a/vault/reconcile_steps.go +++ b/vault/reconcile_steps.go @@ -2,10 +2,8 @@ package vault import ( "fmt" - "os" "path" "sort" - "strings" "github.com/projectsyn/lieutenant-operator/collection" "github.com/projectsyn/lieutenant-operator/pipeline" @@ -15,16 +13,13 @@ import ( ) func getVaultClient(obj pipeline.Object, data *pipeline.Context) (VaultClient, error) { - deletionPolicy := obj.GetDeletionPolicy() - if deletionPolicy == "" { - deletionPolicy = pipeline.GetDefaultDeletionPolicy() - } + deletionPolicy := data.DefaultDeletionPolicy return NewClient(deletionPolicy, data.Log) } func CreateOrUpdateVault(obj pipeline.Object, data *pipeline.Context) pipeline.Result { - if strings.ToLower(os.Getenv("SKIP_VAULT_SETUP")) == "true" { + if !data.UseVault { return pipeline.Result{} } @@ -79,7 +74,7 @@ func GetServiceAccountToken(instance metav1.Object, data *pipeline.Context) (str } func HandleVaultDeletion(obj pipeline.Object, data *pipeline.Context) pipeline.Result { - if strings.ToLower(os.Getenv("SKIP_VAULT_SETUP")) == "true" { + if !data.UseVault { return pipeline.Result{} } diff --git a/vault/reconcile_steps_test.go b/vault/reconcile_steps_test.go index c5849bcf..120ea546 100644 --- a/vault/reconcile_steps_test.go +++ b/vault/reconcile_steps_test.go @@ -1,7 +1,6 @@ package vault import ( - "os" "testing" synv1alpha1 "github.com/projectsyn/lieutenant-operator/api/v1alpha1" @@ -39,11 +38,12 @@ var getVaultCases = map[string]struct { args args }{ "without specific deletion policy": { - want: pipeline.GetDefaultDeletionPolicy(), + want: synv1alpha1.ArchivePolicy, args: args{ cluster: &synv1alpha1.Cluster{}, data: &pipeline.Context{ - Log: zap.New(), + Log: zap.New(), + DefaultDeletionPolicy: synv1alpha1.ArchivePolicy, }, }, }, @@ -56,17 +56,14 @@ var getVaultCases = map[string]struct { }, }, data: &pipeline.Context{ - Log: zap.New(), + Log: zap.New(), + DefaultDeletionPolicy: synv1alpha1.ArchivePolicy, }, }, }, } func Test_getVaultClient(t *testing.T) { - // ensure that it isn't set to anything from previous tests - err := os.Unsetenv("DEFAULT_DELETION_POLICY") - require.NoError(t, err) - mockClient := &testMockClient{} SetCustomClient(mockClient) @@ -76,9 +73,6 @@ func Test_getVaultClient(t *testing.T) { t.Run(name, func(t *testing.T) { _, err := getVaultClient(tt.args.cluster, tt.args.data) assert.NoError(t, err) - - assert.Equal(t, tt.want, mockClient.deletionPolicy) - }) } } @@ -96,7 +90,8 @@ var handleVaultDeletionCases = map[string]struct { }, }, data: &pipeline.Context{ - Deleted: true, + Deleted: true, + DefaultDeletionPolicy: synv1alpha1.ArchivePolicy, }, }, }, @@ -109,20 +104,15 @@ var handleVaultDeletionCases = map[string]struct { }, }, data: &pipeline.Context{ - Deleted: true, + Deleted: true, + DefaultDeletionPolicy: synv1alpha1.ArchivePolicy, }, }, }, } func Test_handleVaultDeletion(t *testing.T) { - // ensure that it isn't set to anything from previous tests - err := os.Unsetenv("DEFAULT_DELETION_POLICY") - require.NoError(t, err) - - mockClient := &testMockClient{ - deletionPolicy: pipeline.GetDefaultDeletionPolicy(), - } + mockClient := &testMockClient{} SetCustomClient(mockClient) @@ -140,7 +130,6 @@ func Test_handleVaultDeletion(t *testing.T) { got := HandleVaultDeletion(tt.args.cluster, tt.args.data) assert.NoError(t, got.Err) - assert.Equal(t, tt.want, mockClient.deletionPolicy) }) } }