From 38451b7086823d61cf137db93f1608b2fcd8a10a Mon Sep 17 00:00:00 2001 From: hasheddan Date: Mon, 19 Sep 2022 15:23:57 -0400 Subject: [PATCH 1/5] Pass Crossplane ServiceAccount name to env var via downward API Updates the core Crossplane chart to pass its ServiceAccount name as an environment variable via the downward API. Signed-off-by: hasheddan (cherry picked from commit ed0f58ae80b3a31d3610984d4978421cd4b6d3f1) --- cluster/charts/crossplane/templates/deployment.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cluster/charts/crossplane/templates/deployment.yaml b/cluster/charts/crossplane/templates/deployment.yaml index afa9724c8..97a7c200e 100644 --- a/cluster/charts/crossplane/templates/deployment.yaml +++ b/cluster/charts/crossplane/templates/deployment.yaml @@ -65,6 +65,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: POD_SERVICE_ACCOUNT + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName {{- if .Values.webhooks.enabled }} - name: "WEBHOOK_TLS_SECRET_NAME" value: webhook-tls-secret From 7b2826723683637629dc23bd044d6b0b5bd46f11 Mon Sep 17 00:00:00 2001 From: hasheddan Date: Mon, 19 Sep 2022 15:25:49 -0400 Subject: [PATCH 2/5] Add flag and pkg controller options support for ServiceAccount Adds a flag and uses it to set the ServiceAccount name in the pkg controllers options. Signed-off-by: hasheddan (cherry picked from commit c2a6242bba1aa72813a2fc1b8c10455ffb2dd9cc) --- cmd/crossplane/core/core.go | 2 ++ internal/controller/pkg/controller/options.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/cmd/crossplane/core/core.go b/cmd/crossplane/core/core.go index 8f2861a9c..7bd37f353 100644 --- a/cmd/crossplane/core/core.go +++ b/cmd/crossplane/core/core.go @@ -61,6 +61,7 @@ func (c *Command) Run() error { type startCommand struct { Namespace string `short:"n" help:"Namespace used to unpack and run packages." default:"crossplane-system" env:"POD_NAMESPACE"` + ServiceAccount string `help:"Name of the Crossplane Service Account." default:"crossplane" env:"POD_SERVICE_ACCOUNT"` CacheDir string `short:"c" help:"Directory used for caching package images." default:"/cache" env:"CACHE_DIR"` LeaderElection bool `short:"l" help:"Use leader election for the controller manager." default:"false" env:"LEADER_ELECTION"` Registry string `short:"r" help:"Default registry used to fetch packages when not specified in tag." default:"${default_registry}" env:"REGISTRY"` @@ -130,6 +131,7 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli Options: o, Cache: xpkg.NewFsPackageCache(c.CacheDir, afero.NewOsFs()), Namespace: c.Namespace, + ServiceAccount: c.ServiceAccount, DefaultRegistry: c.Registry, Features: feats, WebhookTLSSecretName: c.WebhookTLSSecretName, diff --git a/internal/controller/pkg/controller/options.go b/internal/controller/pkg/controller/options.go index 81b9016fe..aa570b0f6 100644 --- a/internal/controller/pkg/controller/options.go +++ b/internal/controller/pkg/controller/options.go @@ -34,6 +34,9 @@ type Options struct { // Namespace used to unpack and run packages. Namespace string + // ServiceAccount is the core Crossplane ServiceAccount name. + ServiceAccount string + // DefaultRegistry used to pull packages. DefaultRegistry string From b6032d5bba2b2ab3b54ee44efe001aadd2db53cf Mon Sep 17 00:00:00 2001 From: hasheddan Date: Mon, 19 Sep 2022 15:31:09 -0400 Subject: [PATCH 3/5] Update fetcher to store ServiceAccount and use options Updates the fetcher to store the ServiceAccount used for package pull secrets and moves to passing Namespace and ServiceAccount via options. Signed-off-by: hasheddan (cherry picked from commit 3552aaab3f1bb209a2250d588917fd693baed8c6) --- internal/xpkg/fetch.go | 43 +++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/internal/xpkg/fetch.go b/internal/xpkg/fetch.go index 3bb0887a5..9815ce68f 100644 --- a/internal/xpkg/fetch.go +++ b/internal/xpkg/fetch.go @@ -54,9 +54,10 @@ type Fetcher interface { // K8sFetcher uses kubernetes credentials to fetch package images. type K8sFetcher struct { - client kubernetes.Interface - namespace string - transport http.RoundTripper + client kubernetes.Interface + namespace string + serviceAccount string + transport http.RoundTripper } // FetcherOpt can be used to add optional parameters to NewK8sFetcher @@ -98,11 +99,28 @@ func WithCustomCA(rootCAs *x509.CertPool) FetcherOpt { } } +// WithNamespace is a FetcherOpt that sets the Namespace for fetching package +// pull secrets. +func WithNamespace(ns string) FetcherOpt { + return func(k *K8sFetcher) error { + k.namespace = ns + return nil + } +} + +// WithServiceAccount is a FetcherOpt that sets the ServiceAccount name for +// fetching package pull secrets. +func WithServiceAccount(sa string) FetcherOpt { + return func(k *K8sFetcher) error { + k.serviceAccount = sa + return nil + } +} + // NewK8sFetcher creates a new K8sFetcher. -func NewK8sFetcher(client kubernetes.Interface, namespace string, opts ...FetcherOpt) (*K8sFetcher, error) { +func NewK8sFetcher(client kubernetes.Interface, opts ...FetcherOpt) (*K8sFetcher, error) { k := &K8sFetcher{ client: client, - namespace: namespace, transport: remote.DefaultTransport.Clone(), } @@ -118,8 +136,9 @@ func NewK8sFetcher(client kubernetes.Interface, namespace string, opts ...Fetche // Fetch fetches a package image. func (i *K8sFetcher) Fetch(ctx context.Context, ref name.Reference, secrets ...string) (v1.Image, error) { auth, err := k8schain.New(ctx, i.client, k8schain.Options{ - Namespace: i.namespace, - ImagePullSecrets: secrets, + Namespace: i.namespace, + ServiceAccountName: i.serviceAccount, + ImagePullSecrets: secrets, }) if err != nil { return nil, err @@ -130,8 +149,9 @@ func (i *K8sFetcher) Fetch(ctx context.Context, ref name.Reference, secrets ...s // Head fetches a package descriptor. func (i *K8sFetcher) Head(ctx context.Context, ref name.Reference, secrets ...string) (*v1.Descriptor, error) { auth, err := k8schain.New(ctx, i.client, k8schain.Options{ - Namespace: i.namespace, - ImagePullSecrets: secrets, + Namespace: i.namespace, + ServiceAccountName: i.serviceAccount, + ImagePullSecrets: secrets, }) if err != nil { return nil, err @@ -150,8 +170,9 @@ func (i *K8sFetcher) Head(ctx context.Context, ref name.Reference, secrets ...st // Tags fetches a package's tags. func (i *K8sFetcher) Tags(ctx context.Context, ref name.Reference, secrets ...string) ([]string, error) { auth, err := k8schain.New(ctx, i.client, k8schain.Options{ - Namespace: i.namespace, - ImagePullSecrets: secrets, + Namespace: i.namespace, + ServiceAccountName: i.serviceAccount, + ImagePullSecrets: secrets, }) if err != nil { return nil, err From f07f918947d5f8a106c61fc7914f5ed32b24767e Mon Sep 17 00:00:00 2001 From: hasheddan Date: Mon, 19 Sep 2022 21:56:57 -0400 Subject: [PATCH 4/5] Pass namespace and service account to all fetchers Updates all package reconciler fetchters to take a namespace and service account. Signed-off-by: hasheddan (cherry picked from commit b700c723ae9e7d8b5d56b3f51c44f45c8184613f) --- internal/controller/pkg/manager/reconciler.go | 4 ++-- internal/controller/pkg/resolver/reconciler.go | 2 +- internal/controller/pkg/revision/reconciler.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/controller/pkg/manager/reconciler.go b/internal/controller/pkg/manager/reconciler.go index 995ac8329..95de3e858 100644 --- a/internal/controller/pkg/manager/reconciler.go +++ b/internal/controller/pkg/manager/reconciler.go @@ -156,7 +156,7 @@ func SetupProvider(mgr ctrl.Manager, o controller.Options) error { if err != nil { return errors.Wrap(err, errCreateK8sClient) } - f, err := xpkg.NewK8sFetcher(cs, o.Namespace, o.FetcherOptions...) + f, err := xpkg.NewK8sFetcher(cs, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) if err != nil { return errors.Wrap(err, errBuildFetcher) } @@ -191,7 +191,7 @@ func SetupConfiguration(mgr ctrl.Manager, o controller.Options) error { if err != nil { return errors.Wrap(err, "failed to initialize clientset") } - fetcher, err := xpkg.NewK8sFetcher(clientset, o.Namespace, o.FetcherOptions...) + fetcher, err := xpkg.NewK8sFetcher(clientset, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) if err != nil { return errors.Wrap(err, "cannot build fetcher") } diff --git a/internal/controller/pkg/resolver/reconciler.go b/internal/controller/pkg/resolver/reconciler.go index 1e846c15b..e658bdad8 100644 --- a/internal/controller/pkg/resolver/reconciler.go +++ b/internal/controller/pkg/resolver/reconciler.go @@ -115,7 +115,7 @@ func Setup(mgr ctrl.Manager, o controller.Options) error { if err != nil { return errors.Wrap(err, "failed to initialize clientset") } - f, err := xpkg.NewK8sFetcher(cs, o.Namespace, o.FetcherOptions...) + f, err := xpkg.NewK8sFetcher(cs, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) if err != nil { return errors.Wrap(err, "cannot build fetcher") } diff --git a/internal/controller/pkg/revision/reconciler.go b/internal/controller/pkg/revision/reconciler.go index a445130a6..741b23111 100644 --- a/internal/controller/pkg/revision/reconciler.go +++ b/internal/controller/pkg/revision/reconciler.go @@ -226,7 +226,7 @@ func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error { if err != nil { return errors.New("cannot build object scheme for package parser") } - fetcher, err := xpkg.NewK8sFetcher(clientset, o.Namespace, o.FetcherOptions...) + fetcher, err := xpkg.NewK8sFetcher(clientset, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) if err != nil { return errors.Wrap(err, "cannot build fetcher for package parser") } @@ -237,7 +237,7 @@ func SetupProviderRevision(mgr ctrl.Manager, o controller.Options) error { WithHooks(NewProviderHooks(resource.ClientApplicator{ Client: mgr.GetClient(), Applicator: resource.NewAPIPatchingApplicator(mgr.GetClient()), - }, o.Namespace)), + }, o.Namespace, o.ServiceAccount)), WithEstablisher(NewAPIEstablisher(mgr.GetClient(), o.Namespace)), WithNewPackageRevisionFn(nr), WithParser(parser.New(metaScheme, objScheme)), @@ -276,7 +276,7 @@ func SetupConfigurationRevision(mgr ctrl.Manager, o controller.Options) error { if err != nil { return errors.New("cannot build object scheme for package parser") } - f, err := xpkg.NewK8sFetcher(cs, o.Namespace, o.FetcherOptions...) + f, err := xpkg.NewK8sFetcher(cs, append(o.FetcherOptions, xpkg.WithNamespace(o.Namespace), xpkg.WithServiceAccount(o.ServiceAccount))...) if err != nil { return errors.Wrap(err, "cannot build fetcher for package parser") } From 76f99080c27eea45d39b8e9019d081eff61f6550 Mon Sep 17 00:00:00 2001 From: hasheddan Date: Mon, 19 Sep 2022 21:57:44 -0400 Subject: [PATCH 5/5] Pass revision and service account pull secrets to deployment Passes all revision and service account pull secrets to Provider deployments. This means that every Provider deployment gets the core Crossplane secrets and the secrets of its parent revision. Signed-off-by: hasheddan (cherry picked from commit ab7ebe9c23981066e539eb9291d218eb3f7f58d0) --- .../controller/pkg/revision/deployment.go | 3 +- .../pkg/revision/deployment_test.go | 2 +- internal/controller/pkg/revision/hook.go | 55 ++++--- internal/controller/pkg/revision/hook_test.go | 153 +++++++++++++++++- 4 files changed, 189 insertions(+), 24 deletions(-) diff --git a/internal/controller/pkg/revision/deployment.go b/internal/controller/pkg/revision/deployment.go index 11dde6dd5..495cbb3de 100644 --- a/internal/controller/pkg/revision/deployment.go +++ b/internal/controller/pkg/revision/deployment.go @@ -54,13 +54,14 @@ const ( upboundCTXValue = "uxp" ) -func buildProviderDeployment(provider *pkgmetav1.Provider, revision v1.PackageRevision, cc *v1alpha1.ControllerConfig, namespace string) (*corev1.ServiceAccount, *appsv1.Deployment, *corev1.Service) { // nolint:gocyclo +func buildProviderDeployment(provider *pkgmetav1.Provider, revision v1.PackageRevision, cc *v1alpha1.ControllerConfig, namespace string, pullSecrets []corev1.LocalObjectReference) (*corev1.ServiceAccount, *appsv1.Deployment, *corev1.Service) { // nolint:gocyclo s := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: revision.GetName(), Namespace: namespace, OwnerReferences: []metav1.OwnerReference{meta.AsController(meta.TypedReferenceTo(revision, v1.ProviderRevisionGroupVersionKind))}, }, + ImagePullSecrets: pullSecrets, } pullPolicy := corev1.PullIfNotPresent if revision.GetPackagePullPolicy() != nil { diff --git a/internal/controller/pkg/revision/deployment_test.go b/internal/controller/pkg/revision/deployment_test.go index d21900c49..418cd6996 100644 --- a/internal/controller/pkg/revision/deployment_test.go +++ b/internal/controller/pkg/revision/deployment_test.go @@ -340,7 +340,7 @@ func TestBuildProviderDeployment(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - sa, d, svc := buildProviderDeployment(tc.fields.provider, tc.fields.revision, tc.fields.cc, namespace) + sa, d, svc := buildProviderDeployment(tc.fields.provider, tc.fields.revision, tc.fields.cc, namespace, nil) if diff := cmp.Diff(tc.want.sa, sa, cmpopts.IgnoreTypes([]metav1.OwnerReference{})); diff != "" { t.Errorf("-want, +got:\n%s\n", diff) diff --git a/internal/controller/pkg/revision/hook.go b/internal/controller/pkg/revision/hook.go index e855c43c7..369ba37fb 100644 --- a/internal/controller/pkg/revision/hook.go +++ b/internal/controller/pkg/revision/hook.go @@ -37,7 +37,8 @@ import ( const ( errNotProvider = "not a provider package" errNotProviderRevision = "not a provider revision" - errControllerConfig = "cannot get referenced controller config" + errGetControllerConfig = "cannot get referenced controller config" + errGetServiceAccount = "cannot get Crossplane service account" errDeleteProviderDeployment = "cannot delete provider package deployment" errDeleteProviderSA = "cannot delete provider package service account" errDeleteProviderService = "cannot delete provider package service" @@ -59,15 +60,17 @@ type Hooks interface { // ProviderHooks performs operations for a provider package that requires a // controller before and after the revision establishes objects. type ProviderHooks struct { - client resource.ClientApplicator - namespace string + client resource.ClientApplicator + namespace string + serviceAccount string } // NewProviderHooks creates a new ProviderHooks. -func NewProviderHooks(client resource.ClientApplicator, namespace string) *ProviderHooks { +func NewProviderHooks(client resource.ClientApplicator, namespace, serviceAccount string) *ProviderHooks { return &ProviderHooks{ - client: client, - namespace: namespace, + client: client, + namespace: namespace, + serviceAccount: serviceAccount, } } @@ -93,11 +96,10 @@ func (h *ProviderHooks) Pre(ctx context.Context, pkg runtime.Object, pr v1.Packa if pr.GetDesiredState() != v1.PackageRevisionInactive { return nil } - cc, err := h.getControllerConfig(ctx, pr) - if err != nil { - return errors.Wrap(err, errControllerConfig) - } - s, d, svc := buildProviderDeployment(pkgProvider, pr, cc, h.namespace) + + // NOTE(hasheddan): we avoid fetching pull secrets and controller config as + // they aren't needed to delete Deployment, ServiceAccount, and Service. + s, d, svc := buildProviderDeployment(pkgProvider, pr, nil, h.namespace, []corev1.LocalObjectReference{}) if err := h.client.Delete(ctx, d); resource.IgnoreNotFound(err) != nil { return errors.Wrap(err, errDeleteProviderDeployment) } @@ -123,9 +125,13 @@ func (h *ProviderHooks) Post(ctx context.Context, pkg runtime.Object, pr v1.Pack } cc, err := h.getControllerConfig(ctx, pr) if err != nil { - return errors.Wrap(err, errControllerConfig) + return err + } + ps, err := h.getSAPullSecrets(ctx) + if err != nil { + return err } - s, d, svc := buildProviderDeployment(pkgProvider, pr, cc, h.namespace) + s, d, svc := buildProviderDeployment(pkgProvider, pr, cc, h.namespace, append(pr.GetPackagePullSecrets(), ps...)) if err := h.client.Apply(ctx, s); err != nil { return errors.Wrap(err, errApplyProviderSA) } @@ -150,15 +156,24 @@ func (h *ProviderHooks) Post(ctx context.Context, pkg runtime.Object, pr v1.Pack return nil } +func (h *ProviderHooks) getSAPullSecrets(ctx context.Context) ([]corev1.LocalObjectReference, error) { + sa := &corev1.ServiceAccount{} + if err := h.client.Get(ctx, types.NamespacedName{ + Namespace: h.namespace, + Name: h.serviceAccount, + }, sa); err != nil { + return []corev1.LocalObjectReference{}, errors.Wrap(err, errGetServiceAccount) + } + return sa.ImagePullSecrets, nil +} + func (h *ProviderHooks) getControllerConfig(ctx context.Context, pr v1.PackageRevision) (*v1alpha1.ControllerConfig, error) { - var cc *v1alpha1.ControllerConfig - if pr.GetControllerConfigRef() != nil { - cc = &v1alpha1.ControllerConfig{} - if err := h.client.Get(ctx, types.NamespacedName{Name: pr.GetControllerConfigRef().Name}, cc); err != nil { - return nil, errors.Wrap(err, errControllerConfig) - } + if pr.GetControllerConfigRef() == nil { + return nil, nil } - return cc, nil + cc := &v1alpha1.ControllerConfig{} + err := h.client.Get(ctx, types.NamespacedName{Name: pr.GetControllerConfigRef().Name}, cc) + return cc, errors.Wrap(err, errGetControllerConfig) } // ConfigurationHooks performs operations for a configuration package before and diff --git a/internal/controller/pkg/revision/hook_test.go b/internal/controller/pkg/revision/hook_test.go index 73e73c932..e5040e218 100644 --- a/internal/controller/pkg/revision/hook_test.go +++ b/internal/controller/pkg/revision/hook_test.go @@ -33,6 +33,7 @@ import ( pkgmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1" v1 "github.com/crossplane/crossplane/apis/pkg/v1" + "github.com/crossplane/crossplane/apis/pkg/v1alpha1" ) var ( @@ -290,6 +291,8 @@ func TestHookPre(t *testing.T) { func TestHookPost(t *testing.T) { errBoom := errors.New("boom") + saName := "crossplane" + saNamespace := "crossplane-system" type args struct { hook Hooks @@ -335,10 +338,62 @@ func TestHookPost(t *testing.T) { }, }, }, + "ErrGetSA": { + reason: "Should return error if we fail to get core Crossplane ServiceAccount.", + args: args{ + hook: &ProviderHooks{ + namespace: saNamespace, + serviceAccount: saName, + client: resource.ClientApplicator{ + Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { + switch o.(type) { + case *appsv1.Deployment: + return nil + case *corev1.ServiceAccount: + return errBoom + } + return nil + }), + Client: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch obj.(type) { + case *corev1.ServiceAccount: + if key.Name != saName { + t.Errorf("unexpected ServiceAccount name: %s", key.Name) + } + if key.Namespace != saNamespace { + t.Errorf("unexpected ServiceAccount Namespace: %s", key.Namespace) + } + return errBoom + default: + return nil + } + }, + }, + }, + }, + pkg: &pkgmetav1.Provider{}, + rev: &v1.ProviderRevision{ + Spec: v1.PackageRevisionSpec{ + DesiredState: v1.PackageRevisionActive, + }, + }, + }, + want: want{ + rev: &v1.ProviderRevision{ + Spec: v1.PackageRevisionSpec{ + DesiredState: v1.PackageRevisionActive, + }, + }, + err: errors.Wrap(errBoom, errGetServiceAccount), + }, + }, "ErrProviderApplySA": { reason: "Should return error if we fail to apply service account for active providerrevision.", args: args{ hook: &ProviderHooks{ + namespace: saNamespace, + serviceAccount: saName, client: resource.ClientApplicator{ Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { switch o.(type) { @@ -349,6 +404,25 @@ func TestHookPost(t *testing.T) { } return nil }), + Client: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch o := obj.(type) { + case *corev1.ServiceAccount: + if key.Name != saName { + t.Errorf("unexpected ServiceAccount name: %s", key.Name) + } + if key.Namespace != saNamespace { + t.Errorf("unexpected ServiceAccount Namespace: %s", key.Namespace) + } + *o = corev1.ServiceAccount{ + ImagePullSecrets: []corev1.LocalObjectReference{{}}, + } + return nil + default: + return errBoom + } + }, + }, }, }, pkg: &pkgmetav1.Provider{}, @@ -371,12 +445,24 @@ func TestHookPost(t *testing.T) { reason: "Should return error if we fail to get controller config for active provider revision.", args: args{ hook: &ProviderHooks{ + namespace: saNamespace, + serviceAccount: saName, client: resource.ClientApplicator{ Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { return nil }), Client: &test.MockClient{ - MockGet: test.NewMockGetFn(errBoom), + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch obj.(type) { + case *v1alpha1.ControllerConfig: + if key.Name != "custom-config" { + t.Errorf("unexpected Controller Config name: %s", key.Name) + } + return errBoom + default: + return nil + } + }, }, }, }, @@ -399,13 +485,15 @@ func TestHookPost(t *testing.T) { }, }, }, - err: errors.Wrap(errors.Wrap(errBoom, errControllerConfig), errControllerConfig), + err: errors.Wrap(errBoom, errGetControllerConfig), }, }, "ErrProviderApplyDeployment": { reason: "Should return error if we fail to apply deployment for active provider revision.", args: args{ hook: &ProviderHooks{ + namespace: saNamespace, + serviceAccount: saName, client: resource.ClientApplicator{ Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { switch o.(type) { @@ -416,6 +504,25 @@ func TestHookPost(t *testing.T) { } return nil }), + Client: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch o := obj.(type) { + case *corev1.ServiceAccount: + if key.Name != saName { + t.Errorf("unexpected ServiceAccount name: %s", key.Name) + } + if key.Namespace != saNamespace { + t.Errorf("unexpected ServiceAccount Namespace: %s", key.Namespace) + } + *o = corev1.ServiceAccount{ + ImagePullSecrets: []corev1.LocalObjectReference{{}}, + } + return nil + default: + return errBoom + } + }, + }, }, }, pkg: &pkgmetav1.Provider{}, @@ -438,6 +545,8 @@ func TestHookPost(t *testing.T) { reason: "Should return error if deployment is unavailable for provider revision.", args: args{ hook: &ProviderHooks{ + namespace: saNamespace, + serviceAccount: saName, client: resource.ClientApplicator{ Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { d, ok := o.(*appsv1.Deployment) @@ -451,6 +560,25 @@ func TestHookPost(t *testing.T) { }} return nil }), + Client: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch o := obj.(type) { + case *corev1.ServiceAccount: + if key.Name != saName { + t.Errorf("unexpected ServiceAccount name: %s", key.Name) + } + if key.Namespace != saNamespace { + t.Errorf("unexpected ServiceAccount Namespace: %s", key.Namespace) + } + *o = corev1.ServiceAccount{ + ImagePullSecrets: []corev1.LocalObjectReference{{}}, + } + return nil + default: + return errBoom + } + }, + }, }, }, pkg: &pkgmetav1.Provider{}, @@ -473,10 +601,31 @@ func TestHookPost(t *testing.T) { reason: "Should not return error if successfully applied service account and deployment for active provider revision.", args: args{ hook: &ProviderHooks{ + namespace: saNamespace, + serviceAccount: saName, client: resource.ClientApplicator{ Applicator: resource.ApplyFn(func(_ context.Context, o client.Object, _ ...resource.ApplyOption) error { return nil }), + Client: &test.MockClient{ + MockGet: func(_ context.Context, key client.ObjectKey, obj client.Object) error { + switch o := obj.(type) { + case *corev1.ServiceAccount: + if key.Name != saName { + t.Errorf("unexpected ServiceAccount name: %s", key.Name) + } + if key.Namespace != saNamespace { + t.Errorf("unexpected ServiceAccount Namespace: %s", key.Namespace) + } + *o = corev1.ServiceAccount{ + ImagePullSecrets: []corev1.LocalObjectReference{{}}, + } + return nil + default: + return errBoom + } + }, + }, }, }, pkg: &pkgmetav1.Provider{},