diff --git a/config/crd/bases/carto.run_deliverables.yaml b/config/crd/bases/carto.run_deliverables.yaml index 4a9f5ac8a..21674fb36 100644 --- a/config/crd/bases/carto.run_deliverables.yaml +++ b/config/crd/bases/carto.run_deliverables.yaml @@ -60,6 +60,8 @@ spec: - value type: object type: array + serviceAccountName: + type: string source: properties: git: diff --git a/config/crd/bases/carto.run_runnables.yaml b/config/crd/bases/carto.run_runnables.yaml index d7815bb28..c56dd3fd4 100644 --- a/config/crd/bases/carto.run_runnables.yaml +++ b/config/crd/bases/carto.run_runnables.yaml @@ -79,6 +79,8 @@ spec: - matchingLabels - resource type: object + serviceAccountName: + type: string required: - runTemplateRef type: object diff --git a/config/crd/bases/carto.run_workloads.yaml b/config/crd/bases/carto.run_workloads.yaml index 29eed53e7..7292fcc61 100644 --- a/config/crd/bases/carto.run_workloads.yaml +++ b/config/crd/bases/carto.run_workloads.yaml @@ -306,6 +306,8 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + serviceAccountName: + type: string serviceClaims: items: properties: diff --git a/config/rbac/rbac.yaml b/config/rbac/rbac.yaml index de1b87c7e..d2fcd6a70 100644 --- a/config/rbac/rbac.yaml +++ b/config/rbac/rbac.yaml @@ -19,10 +19,6 @@ metadata: namespace: cartographer-system --- -#! -#! TODO make use of granular roles rather than a catch-all "cluster-admin" -#! grants it more privileges than it needs. -#! apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -30,8 +26,37 @@ metadata: roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: cluster-admin + name: cartographer-controller-admin subjects: - kind: ServiceAccount name: cartographer-controller namespace: cartographer-system + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cartographer-controller-admin +rules: + - apiGroups: + - carto.run + resources: + - workloads/status + - clustersupplychains/status + - runnables/status + - clusterdeliveries/status + - deliverables/status + verbs: + - create + - update + - delete + - patch + + - apiGroups: + - '*' + resources: + - '*' + verbs: + - watch + - get + - list diff --git a/examples/source-to-knative-service/app-operator/runtemplate-tekton-pipelinerun.yaml b/examples/source-to-knative-service/app-operator/runtemplate-tekton-pipelinerun.yaml index f9a78bf97..5a4e670bb 100644 --- a/examples/source-to-knative-service/app-operator/runtemplate-tekton-pipelinerun.yaml +++ b/examples/source-to-knative-service/app-operator/runtemplate-tekton-pipelinerun.yaml @@ -17,6 +17,9 @@ kind: ClusterRunTemplate metadata: name: tekton-pipelinerun spec: + outputs: + url: spec.params[?(@.name=="blob-url")].value + revision: spec.params[?(@.name=="blob-revision")].value template: apiVersion: tekton.dev/v1beta1 kind: TaskRun diff --git a/examples/source-to-knative-service/app-operator/supply-chain-templates.yaml b/examples/source-to-knative-service/app-operator/supply-chain-templates.yaml index e024f0b3b..31b283664 100644 --- a/examples/source-to-knative-service/app-operator/supply-chain-templates.yaml +++ b/examples/source-to-knative-service/app-operator/supply-chain-templates.yaml @@ -61,8 +61,8 @@ kind: ClusterSourceTemplate metadata: name: test spec: - urlPath: .spec.inputs.source.url - revisionPath: .spec.inputs.source.revision + urlPath: .status.outputs.url + revisionPath: .status.outputs.revision template: apiVersion: carto.run/v1alpha1 @@ -70,6 +70,8 @@ spec: metadata: name: $(workload.metadata.name)$ spec: + serviceAccountName: $(workload.spec.serviceAccountName)$ + runTemplateRef: name: tekton-pipelinerun @@ -85,6 +87,8 @@ spec: params: - name: blob-url value: $(source.url)$ + - name: blob-revision + value: $(source.revision)$ --- diff --git a/examples/source-to-knative-service/developer/serviceaccount.yaml b/examples/source-to-knative-service/developer/serviceaccount.yaml new file mode 100644 index 000000000..104e7a3d6 --- /dev/null +++ b/examples/source-to-knative-service/developer/serviceaccount.yaml @@ -0,0 +1,64 @@ +# Copyright 2021 VMware +# +# 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. + +#@ load("@ytt:data", "data") +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: #@ data.values.service_account_name + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: workload-role-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: workload-role +subjects: + - kind: ServiceAccount + name: #@ data.values.service_account_name + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: workload-role +rules: + - apiGroups: + - source.toolkit.fluxcd.io + - kpack.io + - kapp.k14s.io/v1alpha1 + - kappctrl.k14s.io + - serving.knative.dev/v1 + - carto.run + - tekton.dev + resources: + - gitrepositories + - images + - configs + - apps + - services + - runnables + - tasks + - taskruns + verbs: + - list + - create + - update + - delete + - patch + - watch diff --git a/examples/source-to-knative-service/developer/tests-task.yaml b/examples/source-to-knative-service/developer/tests-task.yaml index f5edc1220..10394527a 100644 --- a/examples/source-to-knative-service/developer/tests-task.yaml +++ b/examples/source-to-knative-service/developer/tests-task.yaml @@ -21,6 +21,7 @@ metadata: spec: params: - name: blob-url + - name: blob-revision steps: - name: test image: golang @@ -30,6 +31,8 @@ spec: - |- set -o pipefail + echo $(params.blob-revision) + cd `mktemp -d` curl -SL $(params.blob-url) | tar xvzf - go test -v ./... diff --git a/examples/source-to-knative-service/developer/workload.yaml b/examples/source-to-knative-service/developer/workload.yaml index d245390a6..818aea423 100644 --- a/examples/source-to-knative-service/developer/workload.yaml +++ b/examples/source-to-knative-service/developer/workload.yaml @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +#@ load("@ytt:data", "data") +--- apiVersion: carto.run/v1alpha1 kind: Workload metadata: @@ -19,6 +21,7 @@ metadata: labels: app.tanzu.vmware.com/workload-type: web spec: + serviceAccountName: #@ data.values.service_account_name source: git: url: https://github.com/kontinue/hello-world diff --git a/examples/source-to-knative-service/values.yaml b/examples/source-to-knative-service/values.yaml index 6aaf9f717..06a289232 100644 --- a/examples/source-to-knative-service/values.yaml +++ b/examples/source-to-knative-service/values.yaml @@ -14,6 +14,7 @@ #@data/values --- +service_account_name: workload-service-account image_prefix: projectcartographer/demo- registry: server: https://index.docker.io/v1/ diff --git a/go.mod b/go.mod index 053149d7a..c3df81e30 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,14 @@ go 1.17 require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/go-logr/logr v0.4.0 + github.com/go-yaml/yaml v2.1.0+incompatible github.com/golangci/golangci-lint v1.43.0 github.com/google/addlicense v1.0.0 github.com/maxbrunsfeld/counterfeiter/v6 v6.4.1 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.17.0 github.com/valyala/fasttemplate v1.2.1 + go.uber.org/zap v1.19.1 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect golang.org/x/text v0.3.7 // indirect k8s.io/api v0.22.4 @@ -25,8 +27,6 @@ require ( sigs.k8s.io/yaml v1.3.0 ) -require go.uber.org/zap v1.19.1 - require ( 4d63.com/gochecknoglobals v0.1.0 // indirect cloud.google.com/go v0.93.3 // indirect diff --git a/go.sum b/go.sum index 72d523564..7358dc7a4 100644 --- a/go.sum +++ b/go.sum @@ -301,6 +301,8 @@ github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYw github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobuffalo/flect v0.2.3 h1:f/ZukRnSNA/DUpSNDadko7Qc0PhGvsew35p/2tu+CRY= github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= diff --git a/hack/setup.sh b/hack/setup.sh index 31b21707e..73d51d4c2 100755 --- a/hack/setup.sh +++ b/hack/setup.sh @@ -265,20 +265,23 @@ teardown_example() { test_example() { log "testing" - for i in {15..1}; do - echo "- attempt $i" - - local deployed_pods - deployed_pods=$(kubectl get pods \ - -l 'serving.knative.dev/configuration=dev' \ - -o name) - - if [[ -n "$deployed_pods" ]]; then - log 'SUCCEEDED! sweet' - exit 0 - fi - - sleep "$i" + for _ in {1..5}; do + for sleep_duration in {15..1}; do + local deployed_pods + deployed_pods=$(kubectl get pods \ + -l 'serving.knative.dev/configuration=dev' \ + -o name) + + if [[ -n "$deployed_pods" ]]; then + log 'SUCCEEDED! sweet' + exit 0 + fi + + echo "- waiting $sleep_duration seconds" + sleep "$sleep_duration" + done + + kubectl tree workload dev done log 'FAILED :(' diff --git a/pkg/apis/v1alpha1/deliverable.go b/pkg/apis/v1alpha1/deliverable.go index 69080b1d2..9198603c5 100644 --- a/pkg/apis/v1alpha1/deliverable.go +++ b/pkg/apis/v1alpha1/deliverable.go @@ -53,8 +53,9 @@ type Deliverable struct { } type DeliverableSpec struct { - Params []Param `json:"params,omitempty"` - Source *Source `json:"source,omitempty"` + Params []Param `json:"params,omitempty"` + Source *Source `json:"source,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` } type DeliverableStatus struct { diff --git a/pkg/apis/v1alpha1/runnable.go b/pkg/apis/v1alpha1/runnable.go index 31b230b1b..665e952ad 100644 --- a/pkg/apis/v1alpha1/runnable.go +++ b/pkg/apis/v1alpha1/runnable.go @@ -36,6 +36,7 @@ const ( TemplateStampFailureRunTemplateReason = "TemplateStampFailure" FailedToListCreatedObjectsReason = "FailedToListCreatedObjects" UnknownErrorReason = "UnknownError" + ClientBuilderErrorResourcesSubmittedReason = "ClientBuilderError" ) // +kubebuilder:object:root=true @@ -56,9 +57,10 @@ type RunnableStatus struct { type RunnableSpec struct { // +kubebuilder:validation:Required - RunTemplateRef TemplateReference `json:"runTemplateRef"` - Selector *ResourceSelector `json:"selector,omitempty"` - Inputs map[string]apiextensionsv1.JSON `json:"inputs,omitempty"` + RunTemplateRef TemplateReference `json:"runTemplateRef"` + Selector *ResourceSelector `json:"selector,omitempty"` + Inputs map[string]apiextensionsv1.JSON `json:"inputs,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` } type ResourceSelector struct { diff --git a/pkg/apis/v1alpha1/workload.go b/pkg/apis/v1alpha1/workload.go index 5347f9868..f8fc3c50e 100644 --- a/pkg/apis/v1alpha1/workload.go +++ b/pkg/apis/v1alpha1/workload.go @@ -30,10 +30,12 @@ const ( ) const ( - ReadySupplyChainReason = "Ready" - WorkloadLabelsMissingSupplyChainReason = "WorkloadLabelsMissing" - NotFoundSupplyChainReadyReason = "SupplyChainNotFound" - MultipleMatchesSupplyChainReadyReason = "MultipleSupplyChainMatches" + ReadySupplyChainReason = "Ready" + WorkloadLabelsMissingSupplyChainReason = "WorkloadLabelsMissing" + NotFoundSupplyChainReadyReason = "SupplyChainNotFound" + MultipleMatchesSupplyChainReadyReason = "MultipleSupplyChainMatches" + ServiceAccountSecretErrorResourcesSubmittedReason = "ServiceAccountSecretError" + ResourceRealizerBuilderErrorResourcesSubmittedReason = "ResourceRealizerBuilderError" ) // +kubebuilder:object:root=true @@ -58,15 +60,16 @@ type WorkloadServiceClaimReference struct { } type WorkloadSpec struct { - Params []Param `json:"params,omitempty"` - Source *Source `json:"source,omitempty"` + Params []Param `json:"params,omitempty"` + Source *Source `json:"source,omitempty"` + Build WorkloadBuild `json:"build,omitempty"` + Env []corev1.EnvVar `json:"env,omitempty"` // Image is a pre-built image in a registry. It is an alternative to defining source // code. - Image *string `json:"image,omitempty"` - ServiceClaims []WorkloadServiceClaim `json:"serviceClaims,omitempty"` - Env []corev1.EnvVar `json:"env,omitempty"` - Build WorkloadBuild `json:"build,omitempty"` - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` + Image *string `json:"image,omitempty"` + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` + ServiceClaims []WorkloadServiceClaim `json:"serviceClaims,omitempty"` } type WorkloadBuild struct { diff --git a/pkg/apis/v1alpha1/workload_test.go b/pkg/apis/v1alpha1/workload_test.go index c0ae326d3..cb267284d 100644 --- a/pkg/apis/v1alpha1/workload_test.go +++ b/pkg/apis/v1alpha1/workload_test.go @@ -34,6 +34,14 @@ var _ = Describe("Workload", func() { workloadSpecType = reflect.TypeOf(workloadSpec) }) + It("allows but does not require service account name", func() { + metadataField, found := workloadSpecType.FieldByName("ServiceAccountName") + Expect(found).To(BeTrue()) + jsonValue := metadataField.Tag.Get("json") + Expect(jsonValue).To(ContainSubstring("serviceAccountName")) + Expect(jsonValue).To(ContainSubstring("omitempty")) + }) + It("allows but does not require params", func() { metadataField, found := workloadSpecType.FieldByName("Params") Expect(found).To(BeTrue()) diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go index d50d6ea68..0e0aaa2b3 100644 --- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go @@ -1557,18 +1557,7 @@ func (in *WorkloadSpec) DeepCopyInto(out *WorkloadSpec) { *out = new(Source) (*in).DeepCopyInto(*out) } - if in.Image != nil { - in, out := &in.Image, &out.Image - *out = new(string) - **out = **in - } - if in.ServiceClaims != nil { - in, out := &in.ServiceClaims, &out.ServiceClaims - *out = make([]WorkloadServiceClaim, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + in.Build.DeepCopyInto(&out.Build) if in.Env != nil { in, out := &in.Env, &out.Env *out = make([]corev1.EnvVar, len(*in)) @@ -1576,12 +1565,23 @@ func (in *WorkloadSpec) DeepCopyInto(out *WorkloadSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - in.Build.DeepCopyInto(&out.Build) + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(string) + **out = **in + } if in.Resources != nil { in, out := &in.Resources, &out.Resources *out = new(corev1.ResourceRequirements) (*in).DeepCopyInto(*out) } + if in.ServiceClaims != nil { + in, out := &in.ServiceClaims, &out.ServiceClaims + *out = make([]WorkloadServiceClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadSpec. diff --git a/pkg/controller/deliverable/conditions.go b/pkg/controller/deliverable/conditions.go index c81a51595..63daef2da 100644 --- a/pkg/controller/deliverable/conditions.go +++ b/pkg/controller/deliverable/conditions.go @@ -150,3 +150,21 @@ func UnknownResourceErrorCondition(err error) metav1.Condition { Message: err.Error(), } } + +func ServiceAccountSecretNotFoundCondition(err error) metav1.Condition { + return metav1.Condition{ + Type: v1alpha1.DeliverableResourcesSubmitted, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ServiceAccountSecretErrorResourcesSubmittedReason, + Message: err.Error(), + } +} + +func ResourceRealizerBuilderErrorCondition(err error) metav1.Condition { + return metav1.Condition{ + Type: v1alpha1.DeliverableResourcesSubmitted, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ResourceRealizerBuilderErrorResourcesSubmittedReason, + Message: err.Error(), + } +} diff --git a/pkg/controller/deliverable/reconciler.go b/pkg/controller/deliverable/reconciler.go index 5ea97d4b9..52ffde357 100644 --- a/pkg/controller/deliverable/reconciler.go +++ b/pkg/controller/deliverable/reconciler.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/go-logr/logr" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -39,6 +40,7 @@ import ( type Reconciler struct { Repo repository.Repository ConditionManagerBuilder conditions.ConditionManagerBuilder + ResourceRealizerBuilder realizer.ResourceRealizerBuilder Realizer realizer.Realizer DynamicTracker tracker.DynamicTracker conditionManager conditions.ConditionManager @@ -90,7 +92,24 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } r.conditionManager.AddPositive(DeliveryReadyCondition()) - stampedObjects, err := r.Realizer.Realize(ctx, realizer.NewResourceRealizer(deliverable, r.Repo, delivery.Spec.Params), delivery) + serviceAccountName := "default" + if deliverable.Spec.ServiceAccountName != "" { + serviceAccountName = deliverable.Spec.ServiceAccountName + } + + secret, err := r.Repo.GetServiceAccountSecret(ctx, serviceAccountName, deliverable.Namespace) + if err != nil { + r.conditionManager.AddPositive(ServiceAccountSecretNotFoundCondition(err)) + return r.completeReconciliation(ctx, deliverable, fmt.Errorf("get secret for service account '%s': %w", deliverable.Spec.ServiceAccountName, err)) + } + + resourceRealizer, err := r.ResourceRealizerBuilder(secret, deliverable, r.Repo, delivery.Spec.Params) + if err != nil { + r.conditionManager.AddPositive(ResourceRealizerBuilderErrorCondition(err)) + return r.completeReconciliation(ctx, deliverable, controller.NewUnhandledError(fmt.Errorf("build resource realizer: %w", err))) + } + + stampedObjects, err := r.Realizer.Realize(ctx, resourceRealizer, delivery) if err != nil { log.V(logger.DEBUG).Info("failed to realize") switch typedErr := err.(type) { @@ -101,7 +120,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.conditionManager.AddPositive(TemplateStampFailureCondition(typedErr)) case realizer.ApplyStampedObjectError: r.conditionManager.AddPositive(TemplateRejectedByAPIServerCondition(typedErr)) - err = controller.NewUnhandledError(err) + if !kerrors.IsForbidden(typedErr.Err) { + err = controller.NewUnhandledError(err) + } case realizer.RetrieveOutputError: switch typedErr.Err.(type) { case templates.ObservedGenerationError: diff --git a/pkg/controller/deliverable/reconciler_test.go b/pkg/controller/deliverable/reconciler_test.go index adb1b48e2..20773e130 100644 --- a/pkg/controller/deliverable/reconciler_test.go +++ b/pkg/controller/deliverable/reconciler_test.go @@ -23,6 +23,8 @@ import ( . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" . "github.com/onsi/gomega/gstruct" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -39,6 +41,7 @@ import ( realizer "github.com/vmware-tanzu/cartographer/pkg/realizer/deliverable" "github.com/vmware-tanzu/cartographer/pkg/realizer/deliverable/deliverablefakes" "github.com/vmware-tanzu/cartographer/pkg/registrar" + "github.com/vmware-tanzu/cartographer/pkg/repository" "github.com/vmware-tanzu/cartographer/pkg/repository/repositoryfakes" "github.com/vmware-tanzu/cartographer/pkg/templates" "github.com/vmware-tanzu/cartographer/pkg/tracker/trackerfakes" @@ -56,6 +59,12 @@ var _ = Describe("Reconciler", func() { dl *v1alpha1.Deliverable deliverableLabels map[string]string dynamicTracker *trackerfakes.FakeDynamicTracker + + builtResourceRealizer *deliverablefakes.FakeResourceRealizer + resourceRealizerSecret *corev1.Secret + serviceAccountSecret *corev1.Secret + serviceAccountName string + resourceRealizerBuilderError error ) BeforeEach(func() { @@ -80,9 +89,25 @@ var _ = Describe("Reconciler", func() { Expect(err).NotTo(HaveOccurred()) repo.GetSchemeReturns(scheme) + serviceAccountSecret = &corev1.Secret{ + StringData: map[string]string{"foo": "bar"}, + } + repo.GetServiceAccountSecretReturns(serviceAccountSecret, nil) + + resourceRealizerBuilderError = nil + resourceRealizerBuilder := func(secret *corev1.Secret, deliverable *v1alpha1.Deliverable, systemRepo repository.Repository, deliveryParams []v1alpha1.DelegatableParam) (realizer.ResourceRealizer, error) { + if resourceRealizerBuilderError != nil { + return nil, resourceRealizerBuilderError + } + resourceRealizerSecret = secret + builtResourceRealizer = &deliverablefakes.FakeResourceRealizer{} + return builtResourceRealizer, nil + } + reconciler = deliverable.Reconciler{ Repo: repo, ConditionManagerBuilder: fakeConditionManagerBuilder, + ResourceRealizerBuilder: resourceRealizerBuilder, Realizer: rlzr, DynamicTracker: dynamicTracker, } @@ -93,6 +118,8 @@ var _ = Describe("Reconciler", func() { deliverableLabels = map[string]string{"some-key": "some-val"} + serviceAccountName = "service-account-name-for-deliverable" + dl = &v1alpha1.Deliverable{ ObjectMeta: metav1.ObjectMeta{ Name: "my-deliverable", @@ -100,6 +127,9 @@ var _ = Describe("Reconciler", func() { Generation: 1, Labels: deliverableLabels, }, + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: serviceAccountName, + }, } repo.GetDeliverableReturns(dl, nil) }) @@ -217,6 +247,35 @@ var _ = Describe("Reconciler", func() { rlzr.RealizeReturns([]*unstructured.Unstructured{stampedObject1, stampedObject2}, nil) }) + It("uses the service account specified by the workload for realizing resources", func() { + _, _ = reconciler.Reconcile(ctx, req) + + Expect(repo.GetServiceAccountSecretCallCount()).To(Equal(1)) + _, serviceAccountNameArg, serviceAccountNS := repo.GetServiceAccountSecretArgsForCall(0) + Expect(serviceAccountNameArg).To(Equal(serviceAccountName)) + Expect(serviceAccountNS).To(Equal("my-ns")) + Expect(resourceRealizerSecret).To(Equal(serviceAccountSecret)) + + Expect(rlzr.RealizeCallCount()).To(Equal(1)) + _, resourceRealizer, _ := rlzr.RealizeArgsForCall(0) + Expect(resourceRealizer).To(Equal(builtResourceRealizer)) + }) + + It("uses the default service account in the deliverables namespace if there is no service account specified", func() { + dl.Spec.ServiceAccountName = "" + _, _ = reconciler.Reconcile(ctx, req) + + Expect(repo.GetServiceAccountSecretCallCount()).To(Equal(1)) + _, serviceAccountNameArg, serviceAccountNS := repo.GetServiceAccountSecretArgsForCall(0) + Expect(serviceAccountNameArg).To(Equal("default")) + Expect(serviceAccountNS).To(Equal("my-ns")) + Expect(resourceRealizerSecret).To(Equal(serviceAccountSecret)) + + Expect(rlzr.RealizeCallCount()).To(Equal(1)) + _, resourceRealizer, _ := rlzr.RealizeArgsForCall(0) + Expect(resourceRealizer).To(Equal(builtResourceRealizer)) + }) + It("sets the DeliveryRef", func() { _, _ = reconciler.Reconcile(ctx, req) @@ -383,6 +442,40 @@ var _ = Describe("Reconciler", func() { }) }) + Context("of type ApplyStampedObjectError where the user did not have proper permissions", func() { + var stampedObjectError realizer.ApplyStampedObjectError + BeforeEach(func() { + status := &metav1.Status{ + Message: "fantastic error", + Reason: metav1.StatusReasonForbidden, + Code: 403, + } + stampedObject1 = &unstructured.Unstructured{} + stampedObject1.SetNamespace("a-namespace") + stampedObject1.SetName("a-name") + + stampedObjectError = realizer.ApplyStampedObjectError{ + Err: kerrors.FromObject(status), + StampedObject: stampedObject1, + } + + rlzr.RealizeReturns(nil, stampedObjectError) + }) + + It("calls the condition manager to report", func() { + _, _ = reconciler.Reconcile(ctx, req) + Expect(conditionManager.AddPositiveArgsForCall(1)).To(Equal(deliverable.TemplateRejectedByAPIServerCondition(stampedObjectError))) + }) + + It("handles the error and logs it", func() { + _, err := reconciler.Reconcile(ctx, req) + Expect(err).NotTo(HaveOccurred()) + + Expect(out).To(Say(`"level":"info"`)) + Expect(out).To(Say(`"handled error":"unable to apply object \[a-namespace/a-name\]: fantastic error"`)) + }) + }) + Context("of type RetrieveOutputError", func() { var retrieveError realizer.RetrieveOutputError var wrappedError error @@ -568,6 +661,45 @@ var _ = Describe("Reconciler", func() { Expect(err.Error()).To(ContainSubstring("could not watch")) }) }) + + Context("but the repo returns an error when requesting the service account secret", func() { + var repoError error + BeforeEach(func() { + repoError = errors.New("some error") + repo.GetServiceAccountSecretReturns(nil, repoError) + }) + + It("calls the condition manager to add a service account secret not found condition", func() { + _, _ = reconciler.Reconcile(ctx, req) + Expect(conditionManager.AddPositiveArgsForCall(1)).To(Equal(deliverable.ServiceAccountSecretNotFoundCondition(repoError))) + }) + + It("handles the error and logs it", func() { + _, err := reconciler.Reconcile(ctx, req) + Expect(err).NotTo(HaveOccurred()) + + Expect(out).To(Say(`"level":"info"`)) + Expect(out).To(Say(`"handled error":"get secret for service account 'service-account-name-for-deliverable': some error"`)) + }) + }) + + Context("but the resource realizer builder fails", func() { + BeforeEach(func() { + resourceRealizerBuilderError = errors.New("some error") + }) + + It("calls the condition manager to add a resource realizer builder error condition", func() { + _, _ = reconciler.Reconcile(ctx, req) + Expect(conditionManager.AddPositiveArgsForCall(1)).To(Equal(deliverable.ResourceRealizerBuilderErrorCondition(resourceRealizerBuilderError))) + }) + + It("returns an unhandled error", func() { + _, err := reconciler.Reconcile(ctx, req) + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring("build resource realizer: some error")) + }) + }) }) Context("but the deliverable has no label to match with the delivery", func() { diff --git a/pkg/controller/runnable/conditions.go b/pkg/controller/runnable/conditions.go index 73c9d0fcd..90429932e 100644 --- a/pkg/controller/runnable/conditions.go +++ b/pkg/controller/runnable/conditions.go @@ -83,3 +83,21 @@ func UnknownErrorCondition(err error) metav1.Condition { Message: err.Error(), } } + +func ServiceAccountSecretNotFoundCondition(err error) metav1.Condition { + return metav1.Condition{ + Type: v1alpha1.RunTemplateReady, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ServiceAccountSecretErrorResourcesSubmittedReason, + Message: err.Error(), + } +} + +func ClientBuilderErrorCondition(err error) metav1.Condition { + return metav1.Condition{ + Type: v1alpha1.RunTemplateReady, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ClientBuilderErrorResourcesSubmittedReason, + Message: err.Error(), + } +} diff --git a/pkg/controller/runnable/reconciler.go b/pkg/controller/runnable/reconciler.go index 26accf12a..e03c3e2d6 100644 --- a/pkg/controller/runnable/reconciler.go +++ b/pkg/controller/runnable/reconciler.go @@ -21,12 +21,15 @@ import ( "fmt" "github.com/go-logr/logr" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/handler" "github.com/vmware-tanzu/cartographer/pkg/apis/v1alpha1" "github.com/vmware-tanzu/cartographer/pkg/conditions" "github.com/vmware-tanzu/cartographer/pkg/controller" + realizerclient "github.com/vmware-tanzu/cartographer/pkg/realizer/client" realizer "github.com/vmware-tanzu/cartographer/pkg/realizer/runnable" "github.com/vmware-tanzu/cartographer/pkg/repository" "github.com/vmware-tanzu/cartographer/pkg/tracker" @@ -38,12 +41,16 @@ type Reconciler struct { DynamicTracker tracker.DynamicTracker ConditionManagerBuilder conditions.ConditionManagerBuilder conditionManager conditions.ConditionManager + RepositoryBuilder repository.RepositoryBuilder + ClientBuilder realizerclient.ClientBuilder + RunnableCache repository.RepoCache + logger logr.Logger } func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { - logger := logr.FromContext(ctx) - logger.Info("started") - defer logger.Info("finished") + r.logger = logr.FromContext(ctx) + r.logger.Info("started") + defer r.logger.Info("finished") runnable, err := r.Repo.GetRunnable(ctx, request.Name, request.Namespace) if err != nil { @@ -51,13 +58,30 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl. } if runnable == nil { - logger.Info("runnable no longer exists") + r.logger.Info("runnable no longer exists") return ctrl.Result{}, nil } r.conditionManager = r.ConditionManagerBuilder(v1alpha1.RunnableReady, runnable.Status.Conditions) - stampedObject, outputs, err := r.Realizer.Realize(ctx, runnable, r.Repo) + serviceAccountName := "default" + if runnable.Spec.ServiceAccountName != "" { + serviceAccountName = runnable.Spec.ServiceAccountName + } + + secret, err := r.Repo.GetServiceAccountSecret(ctx, serviceAccountName, request.Namespace) + if err != nil { + r.conditionManager.AddPositive(ServiceAccountSecretNotFoundCondition(err)) + return r.completeReconciliation(ctx, runnable, nil, fmt.Errorf("get secret for service account '%s': %w", serviceAccountName, err)) + } + + runnableClient, err := r.ClientBuilder(secret) + if err != nil { + r.conditionManager.AddPositive(ClientBuilderErrorCondition(err)) + return r.completeReconciliation(ctx, runnable, nil, controller.NewUnhandledError(fmt.Errorf("build resource realizer: %w", err))) + } + + stampedObject, outputs, err := r.Realizer.Realize(ctx, runnable, r.Repo, r.RepositoryBuilder(runnableClient, r.RunnableCache)) if err != nil { switch typedErr := err.(type) { case realizer.GetRunTemplateError: @@ -69,7 +93,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl. r.conditionManager.AddPositive(TemplateStampFailureCondition(typedErr)) case realizer.ApplyStampedObjectError: r.conditionManager.AddPositive(StampedObjectRejectedByAPIServerCondition(typedErr)) - err = controller.NewUnhandledError(err) + if !kerrors.IsForbidden(typedErr.Err) { + err = controller.NewUnhandledError(err) + } case realizer.ListCreatedObjectsError: r.conditionManager.AddPositive(FailedToListCreatedObjectsCondition(typedErr)) err = controller.NewUnhandledError(err) @@ -85,13 +111,17 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl. var trackingError error if stampedObject != nil { - trackingError = r.DynamicTracker.Watch(logger, stampedObject, &handler.EnqueueRequestForOwner{OwnerType: &v1alpha1.Runnable{}}) + trackingError = r.DynamicTracker.Watch(r.logger, stampedObject, &handler.EnqueueRequestForOwner{OwnerType: &v1alpha1.Runnable{}}) if trackingError != nil { - logger.Error(err, "dynamic tracker watch") + r.logger.Error(err, "dynamic tracker watch") err = controller.NewUnhandledError(trackingError) } } + return r.completeReconciliation(ctx, runnable, outputs, err) +} + +func (r *Reconciler) completeReconciliation(ctx context.Context, runnable *v1alpha1.Runnable, outputs map[string]apiextensionsv1.JSON, err error) (ctrl.Result, error) { var changed bool runnable.Status.Conditions, changed = r.conditionManager.Finalize() @@ -107,7 +137,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl. if controller.IsUnhandledError(err) { return ctrl.Result{}, err } - logger.Info("handled error", "error", err) + r.logger.Info("handled error", "error", err) } return ctrl.Result{}, nil diff --git a/pkg/controller/runnable/reconciler_test.go b/pkg/controller/runnable/reconciler_test.go index 35174c1cc..53912d1d5 100644 --- a/pkg/controller/runnable/reconciler_test.go +++ b/pkg/controller/runnable/reconciler_test.go @@ -23,12 +23,15 @@ import ( . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" . "github.com/onsi/gomega/gstruct" + corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -38,6 +41,7 @@ import ( "github.com/vmware-tanzu/cartographer/pkg/controller/runnable" realizer "github.com/vmware-tanzu/cartographer/pkg/realizer/runnable" "github.com/vmware-tanzu/cartographer/pkg/realizer/runnable/runnablefakes" + "github.com/vmware-tanzu/cartographer/pkg/repository" "github.com/vmware-tanzu/cartographer/pkg/repository/repositoryfakes" "github.com/vmware-tanzu/cartographer/pkg/templates" "github.com/vmware-tanzu/cartographer/pkg/tracker/trackerfakes" @@ -45,34 +49,62 @@ import ( var _ = Describe("Reconcile", func() { var ( - out *Buffer - ctx context.Context - reconciler runnable.Reconciler - request controllerruntime.Request - repository *repositoryfakes.FakeRepository - rlzr *runnablefakes.FakeRealizer - dynamicTracker *trackerfakes.FakeDynamicTracker - conditionManager *conditionsfakes.FakeConditionManager + out *Buffer + ctx context.Context + reconciler runnable.Reconciler + request controllerruntime.Request + repo *repositoryfakes.FakeRepository + rlzr *runnablefakes.FakeRealizer + dynamicTracker *trackerfakes.FakeDynamicTracker + conditionManager *conditionsfakes.FakeConditionManager + builtClient *repositoryfakes.FakeClient + clientForBuiltRepository *client.Client + cacheForBuiltRepository *repository.RepoCache + fakeCache *repositoryfakes.FakeRepoCache + fakeRunnabeRepo *repositoryfakes.FakeRepository + serviceAccountSecret *corev1.Secret + secretForBuiltClient *corev1.Secret + serviceAccountName string ) BeforeEach(func() { out = NewBuffer() logger := zap.New(zap.WriteTo(out)) ctx = logr.NewContext(context.Background(), logger) - repository = &repositoryfakes.FakeRepository{} + repo = &repositoryfakes.FakeRepository{} rlzr = &runnablefakes.FakeRealizer{} dynamicTracker = &trackerfakes.FakeDynamicTracker{} conditionManager = &conditionsfakes.FakeConditionManager{} + fakeCache = &repositoryfakes.FakeRepoCache{} + + serviceAccountName = "alternate-service-account-name" + + repo.GetServiceAccountSecretReturns(serviceAccountSecret, nil) fakeConditionManagerBuilder := func(string, []metav1.Condition) conditions.ConditionManager { return conditionManager } + repositoryBuilder := func(client client.Client, repoCache repository.RepoCache) repository.Repository { + clientForBuiltRepository = &client + cacheForBuiltRepository = &repoCache + return fakeRunnabeRepo + } + + builtClient = &repositoryfakes.FakeClient{} + clientBuilder := func(secret *corev1.Secret) (client.Client, error) { + secretForBuiltClient = secret + return builtClient, nil + } + reconciler = runnable.Reconciler{ - Repo: repository, + Repo: repo, Realizer: rlzr, DynamicTracker: dynamicTracker, ConditionManagerBuilder: fakeConditionManagerBuilder, + RunnableCache: fakeCache, + ClientBuilder: clientBuilder, + RepositoryBuilder: repositoryBuilder, } request = controllerruntime.Request{ @@ -84,8 +116,9 @@ var _ = Describe("Reconcile", func() { }) Context("reconcile a new valid Runnable", func() { + var rb *v1alpha1.Runnable BeforeEach(func() { - repository.GetRunnableReturns(&v1alpha1.Runnable{ + rb = &v1alpha1.Runnable{ TypeMeta: metav1.TypeMeta{ Kind: "Runnable", APIVersion: "carto.run/v1alpha1", @@ -100,9 +133,10 @@ var _ = Describe("Reconcile", func() { Kind: "RunTemplateRef", Name: "my-run-template", }, + ServiceAccountName: serviceAccountName, }, - }, nil) - + } + repo.GetRunnableReturns(rb, nil) }) It("updates the conditions based on the condition manager", func() { @@ -127,7 +161,7 @@ var _ = Describe("Reconcile", func() { _, _ = reconciler.Reconcile(ctx, request) - actualCtx, updatedRunnable := repository.StatusUpdateArgsForCall(0) + actualCtx, updatedRunnable := repo.StatusUpdateArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) Expect(*updatedRunnable.(*v1alpha1.Runnable)).To(MatchFields(IgnoreExtras, Fields{ @@ -137,6 +171,24 @@ var _ = Describe("Reconcile", func() { })) }) + It("uses the service account specified by the workload for realizing resources", func() { + _, _ = reconciler.Reconcile(ctx, request) + + Expect(repo.GetServiceAccountSecretCallCount()).To(Equal(1)) + _, serviceAccountName, serviceAccountNS := repo.GetServiceAccountSecretArgsForCall(0) + Expect(serviceAccountName).To(Equal(serviceAccountName)) + Expect(serviceAccountNS).To(Equal("my-namespace")) + + Expect(*clientForBuiltRepository).To(Equal(builtClient)) + Expect(secretForBuiltClient).To(Equal(serviceAccountSecret)) + Expect(*cacheForBuiltRepository).To(Equal(reconciler.RunnableCache)) + + Expect(rlzr.RealizeCallCount()).To(Equal(1)) + _, _, systemRepo, runnableRepo := rlzr.RealizeArgsForCall(0) + Expect(systemRepo).To(Equal(repo)) + Expect(runnableRepo).To(Equal(fakeRunnabeRepo)) + }) + Context("watching does not cause an error", func() { It("watches the stampedObject's kind", func() { stampedObject := &unstructured.Unstructured{} @@ -186,8 +238,8 @@ var _ = Describe("Reconcile", func() { It("fetches the runnable", func() { _, _ = reconciler.Reconcile(ctx, request) - Expect(repository.GetRunnableCallCount()).To(Equal(1)) - actualCtx, actualName, actualNamespace := repository.GetRunnableArgsForCall(0) + Expect(repo.GetRunnableCallCount()).To(Equal(1)) + actualCtx, actualName, actualNamespace := repo.GetRunnableArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) Expect(actualName).To(Equal("my-runnable")) Expect(actualNamespace).To(Equal("my-namespace")) @@ -205,8 +257,8 @@ var _ = Describe("Reconcile", func() { _, err := reconciler.Reconcile(ctx, request) Expect(err).NotTo(HaveOccurred()) - Expect(repository.StatusUpdateCallCount()).To(Equal(1)) - actualCtx, obj := repository.StatusUpdateArgsForCall(0) + Expect(repo.StatusUpdateCallCount()).To(Equal(1)) + actualCtx, obj := repo.StatusUpdateArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) statusObject, ok := obj.(*v1alpha1.Runnable) Expect(ok).To(BeTrue()) @@ -226,8 +278,8 @@ var _ = Describe("Reconcile", func() { _, err := reconciler.Reconcile(ctx, request) Expect(err).NotTo(HaveOccurred()) - Expect(repository.StatusUpdateCallCount()).To(Equal(1)) - actualCtx, obj := repository.StatusUpdateArgsForCall(0) + Expect(repo.StatusUpdateCallCount()).To(Equal(1)) + actualCtx, obj := repo.StatusUpdateArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) statusObject, ok := obj.(*v1alpha1.Runnable) Expect(ok).To(BeTrue()) @@ -241,7 +293,7 @@ var _ = Describe("Reconcile", func() { Context("updating the status fails", func() { BeforeEach(func() { rlzr.RealizeReturns(nil, nil, nil) - repository.StatusUpdateReturns(errors.New("bad status update error")) + repo.StatusUpdateReturns(errors.New("bad status update error")) }) It("Starts and Finishes cleanly", func() { @@ -393,6 +445,45 @@ var _ = Describe("Reconcile", func() { }) }) + Context("of type ApplyStampedObjectError where the user did not have proper permissions", func() { + var stampedObjectError realizer.ApplyStampedObjectError + BeforeEach(func() { + status := &metav1.Status{ + Message: "fantastic error", + Reason: metav1.StatusReasonForbidden, + Code: 403, + } + stampedObject := &unstructured.Unstructured{} + stampedObject.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "thing.io", + Version: "alphabeta1", + Kind: "MyThing", + }) + stampedObject.SetNamespace("a-namespace") + stampedObject.SetName("a-name") + + stampedObjectError = realizer.ApplyStampedObjectError{ + Err: kerrors.FromObject(status), + StampedObject: stampedObject, + } + + rlzr.RealizeReturns(nil, nil, stampedObjectError) + }) + + It("calls the condition manager to report", func() { + _, _ = reconciler.Reconcile(ctx, request) + Expect(conditionManager.AddPositiveArgsForCall(0)).To(Equal(runnable.StampedObjectRejectedByAPIServerCondition(stampedObjectError))) + }) + + It("handles the error and logs it", func() { + _, err := reconciler.Reconcile(ctx, request) + Expect(err).NotTo(HaveOccurred()) + + Expect(out).To(Say(`"level":"info"`)) + Expect(out).To(Say(`"error":"unable to apply stamped object 'a-namespace/a-name': 'fantastic error'"`)) + }) + }) + Context("of type ListCreatedObjectsError", func() { var err error BeforeEach(func() { @@ -464,11 +555,26 @@ var _ = Describe("Reconcile", func() { }) }) }) + + Context("the runnable does not specify a service account", func() { + BeforeEach(func() { + rb.Spec.ServiceAccountName = "" + repo.GetRunnableReturns(rb, nil) + }) + It("uses the default service account in the namespace", func() { + _, _ = reconciler.Reconcile(ctx, request) + + Expect(repo.GetServiceAccountSecretCallCount()).To(Equal(1)) + _, serviceAccountName, serviceAccountNS := repo.GetServiceAccountSecretArgsForCall(0) + Expect(serviceAccountName).To(Equal("default")) + Expect(serviceAccountNS).To(Equal("my-namespace")) + }) + }) }) Context("the runnable goes away", func() { BeforeEach(func() { - repository.GetRunnableReturns(nil, nil) + repo.GetRunnableReturns(nil, nil) }) It("considers the reconcile complete", func() { @@ -495,7 +601,7 @@ var _ = Describe("Reconcile", func() { Context("the runnable fetch is in error", func() { BeforeEach(func() { - repository.GetRunnableReturns(nil, errors.New("very bad runnable")) + repo.GetRunnableReturns(nil, errors.New("very bad runnable")) }) It("returns an error and requeues", func() { diff --git a/pkg/controller/workload/conditions.go b/pkg/controller/workload/conditions.go index 890411b18..0ba23823e 100644 --- a/pkg/controller/workload/conditions.go +++ b/pkg/controller/workload/conditions.go @@ -137,3 +137,21 @@ func UnknownResourceErrorCondition(err error) metav1.Condition { Message: err.Error(), } } + +func ServiceAccountSecretNotFoundCondition(err error) metav1.Condition { + return metav1.Condition{ + Type: v1alpha1.WorkloadResourceSubmitted, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ServiceAccountSecretErrorResourcesSubmittedReason, + Message: err.Error(), + } +} + +func ResourceRealizerBuilderErrorCondition(err error) metav1.Condition { + return metav1.Condition{ + Type: v1alpha1.WorkloadResourceSubmitted, + Status: metav1.ConditionFalse, + Reason: v1alpha1.ResourceRealizerBuilderErrorResourcesSubmittedReason, + Message: err.Error(), + } +} diff --git a/pkg/controller/workload/reconciler.go b/pkg/controller/workload/reconciler.go index f7382b61f..ba3ae75d0 100644 --- a/pkg/controller/workload/reconciler.go +++ b/pkg/controller/workload/reconciler.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/go-logr/logr" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -37,6 +38,7 @@ import ( type Reconciler struct { Repo repository.Repository ConditionManagerBuilder conditions.ConditionManagerBuilder + ResourceRealizerBuilder realizer.ResourceRealizerBuilder Realizer realizer.Realizer DynamicTracker tracker.DynamicTracker conditionManager conditions.ConditionManager @@ -79,7 +81,24 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } r.conditionManager.AddPositive(SupplyChainReadyCondition()) - stampedObjects, err := r.Realizer.Realize(ctx, realizer.NewResourceRealizer(workload, r.Repo, supplyChain.Spec.Params), supplyChain) + serviceAccountName := "default" + if workload.Spec.ServiceAccountName != "" { + serviceAccountName = workload.Spec.ServiceAccountName + } + + secret, err := r.Repo.GetServiceAccountSecret(ctx, serviceAccountName, workload.Namespace) + if err != nil { + r.conditionManager.AddPositive(ServiceAccountSecretNotFoundCondition(err)) + return r.completeReconciliation(ctx, workload, fmt.Errorf("get secret for service account '%s': %w", workload.Spec.ServiceAccountName, err)) + } + + resourceRealizer, err := r.ResourceRealizerBuilder(secret, workload, r.Repo, supplyChain.Spec.Params) + if err != nil { + r.conditionManager.AddPositive(ResourceRealizerBuilderErrorCondition(err)) + return r.completeReconciliation(ctx, workload, controller.NewUnhandledError(fmt.Errorf("build resource realizer: %w", err))) + } + + stampedObjects, err := r.Realizer.Realize(ctx, resourceRealizer, supplyChain) if err != nil { switch typedErr := err.(type) { case realizer.GetClusterTemplateError: @@ -89,7 +108,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu r.conditionManager.AddPositive(TemplateStampFailureCondition(typedErr)) case realizer.ApplyStampedObjectError: r.conditionManager.AddPositive(TemplateRejectedByAPIServerCondition(typedErr)) - err = controller.NewUnhandledError(err) + if !kerrors.IsForbidden(typedErr.Err) { + err = controller.NewUnhandledError(err) + } case realizer.RetrieveOutputError: r.conditionManager.AddPositive(MissingValueAtPathCondition(typedErr.StampedObject, typedErr.JsonPathExpression())) default: diff --git a/pkg/controller/workload/reconciler_test.go b/pkg/controller/workload/reconciler_test.go index bd3d15b35..8061efd0a 100644 --- a/pkg/controller/workload/reconciler_test.go +++ b/pkg/controller/workload/reconciler_test.go @@ -23,6 +23,8 @@ import ( . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" . "github.com/onsi/gomega/gstruct" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -39,6 +41,7 @@ import ( realizer "github.com/vmware-tanzu/cartographer/pkg/realizer/workload" "github.com/vmware-tanzu/cartographer/pkg/realizer/workload/workloadfakes" "github.com/vmware-tanzu/cartographer/pkg/registrar" + "github.com/vmware-tanzu/cartographer/pkg/repository" "github.com/vmware-tanzu/cartographer/pkg/repository/repositoryfakes" "github.com/vmware-tanzu/cartographer/pkg/templates" "github.com/vmware-tanzu/cartographer/pkg/tracker/trackerfakes" @@ -46,16 +49,21 @@ import ( var _ = Describe("Reconciler", func() { var ( - out *Buffer - reconciler workload.Reconciler - ctx context.Context - req ctrl.Request - repo *repositoryfakes.FakeRepository - conditionManager *conditionsfakes.FakeConditionManager - rlzr *workloadfakes.FakeRealizer - wl *v1alpha1.Workload - workloadLabels map[string]string - dynamicTracker *trackerfakes.FakeDynamicTracker + out *Buffer + reconciler workload.Reconciler + ctx context.Context + req ctrl.Request + repo *repositoryfakes.FakeRepository + conditionManager *conditionsfakes.FakeConditionManager + rlzr *workloadfakes.FakeRealizer + wl *v1alpha1.Workload + workloadLabels map[string]string + dynamicTracker *trackerfakes.FakeDynamicTracker + builtResourceRealizer *workloadfakes.FakeResourceRealizer + resourceRealizerSecret *corev1.Secret + serviceAccountSecret *corev1.Secret + serviceAccountName string + resourceRealizerBuilderError error ) BeforeEach(func() { @@ -80,9 +88,23 @@ var _ = Describe("Reconciler", func() { Expect(err).NotTo(HaveOccurred()) repo.GetSchemeReturns(scheme) + serviceAccountSecret = &corev1.Secret{Data: map[string][]byte{"token": []byte(`blahblah`)}} + repo.GetServiceAccountSecretReturns(serviceAccountSecret, nil) + + resourceRealizerBuilderError = nil + resourceRealizerBuilder := func(secret *corev1.Secret, workload *v1alpha1.Workload, systemRepo repository.Repository, supplyChainParams []v1alpha1.DelegatableParam) (realizer.ResourceRealizer, error) { + if resourceRealizerBuilderError != nil { + return nil, resourceRealizerBuilderError + } + resourceRealizerSecret = secret + builtResourceRealizer = &workloadfakes.FakeResourceRealizer{} + return builtResourceRealizer, nil + } + reconciler = workload.Reconciler{ Repo: repo, ConditionManagerBuilder: fakeConditionManagerBuilder, + ResourceRealizerBuilder: resourceRealizerBuilder, Realizer: rlzr, DynamicTracker: dynamicTracker, } @@ -93,10 +115,17 @@ var _ = Describe("Reconciler", func() { workloadLabels = map[string]string{"some-key": "some-val"} + serviceAccountName = "workload-service-account-name" + wl = &v1alpha1.Workload{ ObjectMeta: metav1.ObjectMeta{ Generation: 1, Labels: workloadLabels, + Name: "my-workload-name", + Namespace: "my-namespace", + }, + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: serviceAccountName, }, } repo.GetWorkloadReturns(wl, nil) @@ -214,6 +243,35 @@ var _ = Describe("Reconciler", func() { rlzr.RealizeReturns([]*unstructured.Unstructured{stampedObject1, stampedObject2}, nil) }) + It("uses the service account specified by the workload for realizing resources", func() { + _, _ = reconciler.Reconcile(ctx, req) + + Expect(repo.GetServiceAccountSecretCallCount()).To(Equal(1)) + _, serviceAccountNameArg, serviceAccountNS := repo.GetServiceAccountSecretArgsForCall(0) + Expect(serviceAccountNameArg).To(Equal(serviceAccountName)) + Expect(serviceAccountNS).To(Equal("my-namespace")) + Expect(resourceRealizerSecret).To(Equal(serviceAccountSecret)) + + Expect(rlzr.RealizeCallCount()).To(Equal(1)) + _, resourceRealizer, _ := rlzr.RealizeArgsForCall(0) + Expect(resourceRealizer).To(Equal(builtResourceRealizer)) + }) + + It("uses the default service account in the workloads namespace if there is no service account specified", func() { + wl.Spec.ServiceAccountName = "" + _, _ = reconciler.Reconcile(ctx, req) + + Expect(repo.GetServiceAccountSecretCallCount()).To(Equal(1)) + _, serviceAccountNameArg, serviceAccountNS := repo.GetServiceAccountSecretArgsForCall(0) + Expect(serviceAccountNameArg).To(Equal("default")) + Expect(serviceAccountNS).To(Equal("my-namespace")) + Expect(resourceRealizerSecret).To(Equal(serviceAccountSecret)) + + Expect(rlzr.RealizeCallCount()).To(Equal(1)) + _, resourceRealizer, _ := rlzr.RealizeArgsForCall(0) + Expect(resourceRealizer).To(Equal(builtResourceRealizer)) + }) + It("sets the SupplyChainRef", func() { _, _ = reconciler.Reconcile(ctx, req) @@ -376,6 +434,40 @@ var _ = Describe("Reconciler", func() { }) }) + Context("of type ApplyStampedObjectError where the user did not have proper permissions", func() { + var stampedObjectError realizer.ApplyStampedObjectError + BeforeEach(func() { + status := &metav1.Status{ + Message: "fantastic error", + Reason: metav1.StatusReasonForbidden, + Code: 403, + } + stampedObject1 = &unstructured.Unstructured{} + stampedObject1.SetNamespace("a-namespace") + stampedObject1.SetName("a-name") + + stampedObjectError = realizer.ApplyStampedObjectError{ + Err: kerrors.FromObject(status), + StampedObject: stampedObject1, + } + + rlzr.RealizeReturns(nil, stampedObjectError) + }) + + It("calls the condition manager to report", func() { + _, _ = reconciler.Reconcile(ctx, req) + Expect(conditionManager.AddPositiveArgsForCall(1)).To(Equal(workload.TemplateRejectedByAPIServerCondition(stampedObjectError))) + }) + + It("handles the error and logs it", func() { + _, err := reconciler.Reconcile(ctx, req) + Expect(err).NotTo(HaveOccurred()) + + Expect(out).To(Say(`"level":"info"`)) + Expect(out).To(Say(`"error":"unable to apply object 'a-namespace/a-name': fantastic error"`)) + }) + }) + Context("of type RetrieveOutputError", func() { var retrieveError realizer.RetrieveOutputError var stampedObject *unstructured.Unstructured @@ -455,6 +547,45 @@ var _ = Describe("Reconciler", func() { Expect(err.Error()).To(ContainSubstring("could not watch")) }) }) + + Context("but the repo returns an error when requesting the service account secret", func() { + var repoError error + BeforeEach(func() { + repoError = errors.New("some error") + repo.GetServiceAccountSecretReturns(nil, repoError) + }) + + It("calls the condition manager to add a service account secret not found condition", func() { + _, _ = reconciler.Reconcile(ctx, req) + Expect(conditionManager.AddPositiveArgsForCall(1)).To(Equal(workload.ServiceAccountSecretNotFoundCondition(repoError))) + }) + + It("handles the error and logs it", func() { + _, err := reconciler.Reconcile(ctx, req) + Expect(err).NotTo(HaveOccurred()) + + Expect(out).To(Say(`"level":"info"`)) + Expect(out).To(Say(`"error":"get secret for service account 'workload-service-account-name': some error"`)) + }) + }) + + Context("but the resource realizer builder fails", func() { + BeforeEach(func() { + resourceRealizerBuilderError = errors.New("some error") + }) + + It("calls the condition manager to add a resource realizer builder error condition", func() { + _, _ = reconciler.Reconcile(ctx, req) + Expect(conditionManager.AddPositiveArgsForCall(1)).To(Equal(workload.ResourceRealizerBuilderErrorCondition(resourceRealizerBuilderError))) + }) + + It("returns an unhandled error", func() { + _, err := reconciler.Reconcile(ctx, req) + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring("build resource realizer: some error")) + }) + }) }) Context("but the workload has no label to match with the supply chain", func() { diff --git a/pkg/realizer/client/cartographer_suite_test.go b/pkg/realizer/client/cartographer_suite_test.go new file mode 100644 index 000000000..0cfaf6acd --- /dev/null +++ b/pkg/realizer/client/cartographer_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2021 VMware +// +// 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 client_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestCartographer(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Realizer Client Suite") +} diff --git a/pkg/realizer/client/client.go b/pkg/realizer/client/client.go new file mode 100644 index 000000000..0c89912c7 --- /dev/null +++ b/pkg/realizer/client/client.go @@ -0,0 +1,54 @@ +// Copyright 2021 VMware +// +// 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 client + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ClientBuilder func(secret *corev1.Secret) (client.Client, error) + +func NewClientBuilder(restConfig *rest.Config) ClientBuilder { + return func(secret *corev1.Secret) (client.Client, error) { + config, err := AddBearerToken(secret, restConfig) + if err != nil { + return nil, fmt.Errorf("adding bearer token: %w", err) + } + + cl, err := client.New(config, client.Options{}) + if err != nil { + return nil, fmt.Errorf("creating client: %w", err) + } + + return cl, nil + } +} + +func AddBearerToken(secret *corev1.Secret, restConfig *rest.Config) (*rest.Config, error) { + tokenBytes, found := secret.Data[corev1.ServiceAccountTokenKey] + if !found { + return nil, fmt.Errorf("couldn't find service account token value") + } + + newConfig := *restConfig + newConfig.BearerToken = string(tokenBytes) + newConfig.BearerTokenFile = "" + + return &newConfig, nil +} diff --git a/pkg/realizer/client/client_test.go b/pkg/realizer/client/client_test.go new file mode 100644 index 000000000..500719705 --- /dev/null +++ b/pkg/realizer/client/client_test.go @@ -0,0 +1,72 @@ +// Copyright 2021 VMware +// +// 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 client_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + + realizerclient "github.com/vmware-tanzu/cartographer/pkg/realizer/client" +) + +var _ = Describe("Pkg/Realizer/Client/Client", func() { + Describe("AddBearerToken", func() { + var ( + oldConfig *rest.Config + secret *corev1.Secret + newToken string + oldToken string + tokenFile string + ) + + BeforeEach(func() { + oldToken = "some-old-token" + tokenFile = "some-file-path" + oldConfig = &rest.Config{ + Host: "some-host", + BearerToken: oldToken, + BearerTokenFile: tokenFile, + } + + newToken = "some-new-token" + + secret = &corev1.Secret{ + Data: map[string][]byte{ + corev1.ServiceAccountTokenKey: []byte(newToken), + }, + } + }) + + It("overwrites the BearerToken in the config and removes the BearerTokenFile (because it supersedes BearerToken)", func() { + newConfig, err := realizerclient.AddBearerToken(secret, oldConfig) + Expect(err).NotTo(HaveOccurred()) + + Expect(newConfig.BearerToken).To(Equal(newToken)) + Expect(newConfig.BearerTokenFile).To(Equal("")) + }) + + It("preserves the rest of the config", func() { + newConfig, err := realizerclient.AddBearerToken(secret, oldConfig) + Expect(err).NotTo(HaveOccurred()) + + newConfig.BearerToken = oldToken + newConfig.BearerTokenFile = tokenFile + + Expect(newConfig).To(Equal(oldConfig)) + }) + }) +}) diff --git a/pkg/realizer/deliverable/component.go b/pkg/realizer/deliverable/component.go index 73e6492ad..3f428d21f 100644 --- a/pkg/realizer/deliverable/component.go +++ b/pkg/realizer/deliverable/component.go @@ -19,9 +19,11 @@ import ( "fmt" "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/vmware-tanzu/cartographer/pkg/apis/v1alpha1" + realizerclient "github.com/vmware-tanzu/cartographer/pkg/realizer/client" "github.com/vmware-tanzu/cartographer/pkg/repository" "github.com/vmware-tanzu/cartographer/pkg/templates" ) @@ -34,16 +36,27 @@ type ResourceRealizer interface { } type resourceRealizer struct { - deliverable *v1alpha1.Deliverable - repo repository.Repository - deliveryParams []v1alpha1.DelegatableParam + deliverable *v1alpha1.Deliverable + systemRepo repository.Repository + deliverableRepo repository.Repository + deliveryParams []v1alpha1.DelegatableParam } -func NewResourceRealizer(deliverable *v1alpha1.Deliverable, repo repository.Repository, deliveryParams []v1alpha1.DelegatableParam) ResourceRealizer { - return &resourceRealizer{ - deliverable: deliverable, - repo: repo, - deliveryParams: deliveryParams, +type ResourceRealizerBuilder func(secret *corev1.Secret, deliverable *v1alpha1.Deliverable, repo repository.Repository, deliveryParams []v1alpha1.DelegatableParam) (ResourceRealizer, error) + +func NewResourceRealizerBuilder(repositoryBuilder repository.RepositoryBuilder, clientBuilder realizerclient.ClientBuilder, cache repository.RepoCache) ResourceRealizerBuilder { + return func(secret *corev1.Secret, deliverable *v1alpha1.Deliverable, systemRepo repository.Repository, deliveryParams []v1alpha1.DelegatableParam) (ResourceRealizer, error) { + client, err := clientBuilder(secret) + if err != nil { + return nil, fmt.Errorf("can't build client: %w", err) + } + deliverableRepo := repositoryBuilder(client, cache) + return &resourceRealizer{ + deliverable: deliverable, + systemRepo: systemRepo, + deliverableRepo: deliverableRepo, + deliveryParams: deliveryParams, + }, nil } } @@ -51,7 +64,7 @@ func (r *resourceRealizer) Do(ctx context.Context, resource *v1alpha1.ClusterDel log := logr.FromContextOrDiscard(ctx).WithValues("template", resource.TemplateRef) ctx = logr.NewContext(ctx, log) - apiTemplate, err := r.repo.GetDeliveryClusterTemplate(ctx, resource.TemplateRef) + apiTemplate, err := r.systemRepo.GetDeliveryClusterTemplate(ctx, resource.TemplateRef) if err != nil { log.Error(err, "failed to get delivery cluster template") return nil, nil, GetDeliveryClusterTemplateError{ @@ -102,7 +115,7 @@ func (r *resourceRealizer) Do(ctx context.Context, resource *v1alpha1.ClusterDel } } - err = r.repo.EnsureObjectExistsOnCluster(ctx, stampedObject, true) + err = r.deliverableRepo.EnsureObjectExistsOnCluster(ctx, stampedObject, true) if err != nil { log.Error(err, "failed to ensure object exists on cluster", "object", stampedObject) return nil, nil, ApplyStampedObjectError{ diff --git a/pkg/realizer/deliverable/component_test.go b/pkg/realizer/deliverable/component_test.go index 508deada3..8ae8d7fc1 100644 --- a/pkg/realizer/deliverable/component_test.go +++ b/pkg/realizer/deliverable/component_test.go @@ -25,6 +25,9 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/vmware-tanzu/cartographer/pkg/repository" "github.com/vmware-tanzu/cartographer/pkg/apis/v1alpha1" realizer "github.com/vmware-tanzu/cartographer/pkg/realizer/deliverable" @@ -35,14 +38,21 @@ import ( var _ = Describe("Resource", func() { var ( - ctx context.Context - resource v1alpha1.ClusterDeliveryResource - deliverable v1alpha1.Deliverable - outputs realizer.Outputs - deliveryName string - deliveryParams []v1alpha1.DelegatableParam - fakeRepo repositoryfakes.FakeRepository - r realizer.ResourceRealizer + ctx context.Context + resource v1alpha1.ClusterDeliveryResource + deliverable v1alpha1.Deliverable + outputs realizer.Outputs + deliveryName string + fakeSystemRepo repositoryfakes.FakeRepository + fakeDeliverableRepo repositoryfakes.FakeRepository + clientForBuiltRepository client.Client + cacheForBuiltRepository repository.RepoCache + repoCache repository.RepoCache + builtClient client.Client + theSecret *corev1.Secret + secretForBuiltClient *corev1.Secret + r realizer.ResourceRealizer + deliveryParams []v1alpha1.DelegatableParam ) BeforeEach(func() { @@ -61,9 +71,40 @@ var _ = Describe("Resource", func() { outputs = realizer.NewOutputs() - fakeRepo = repositoryfakes.FakeRepository{} + fakeSystemRepo = repositoryfakes.FakeRepository{} + fakeDeliverableRepo = repositoryfakes.FakeRepository{} + + repositoryBuilder := func(client client.Client, repoCache repository.RepoCache) repository.Repository { + clientForBuiltRepository = client + cacheForBuiltRepository = repoCache + return &fakeDeliverableRepo + } + + builtClient = &repositoryfakes.FakeClient{} + clientBuilder := func(secret *corev1.Secret) (client.Client, error) { + secretForBuiltClient = secret + return builtClient, nil + } + + repoCache = &repositoryfakes.FakeRepoCache{} //TODO: can we verify right cache used? + resourceRealizerBuilder := realizer.NewResourceRealizerBuilder(repositoryBuilder, clientBuilder, repoCache) + deliverable = v1alpha1.Deliverable{} - r = realizer.NewResourceRealizer(&deliverable, &fakeRepo, deliveryParams) + + theSecret = &corev1.Secret{StringData: map[string]string{"blah": "blah"}} + + var err error + r, err = resourceRealizerBuilder(theSecret, &deliverable, &fakeSystemRepo, deliveryParams) + Expect(err).NotTo(HaveOccurred()) + }) + + It("creates a resource realizer with the existing client, as well as one with the the supplied secret mixed in", func() { + Expect(secretForBuiltClient).To(Equal(theSecret)) + Expect(clientForBuiltRepository).To(Equal(builtClient)) + }) + + It("creates a resource realizer with the existing cache", func() { + Expect(cacheForBuiltRepository).To(Equal(repoCache)) }) Describe("Do", func() { @@ -117,15 +158,15 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) - fakeRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeSystemRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) + fakeDeliverableRepo.EnsureObjectExistsOnClusterReturns(nil) }) It("creates a stamped object and returns the outputs and stampedObjects", func() { returnedStampedObject, out, err := r.Do(ctx, &resource, deliveryName, outputs) Expect(err).ToNot(HaveOccurred()) - _, stampedObject, allowUpdate := fakeRepo.EnsureObjectExistsOnClusterArgsForCall(0) + _, stampedObject, allowUpdate := fakeDeliverableRepo.EnsureObjectExistsOnClusterArgsForCall(0) Expect(returnedStampedObject).To(Equal(stampedObject)) Expect(allowUpdate).To(BeTrue()) @@ -160,9 +201,9 @@ var _ = Describe("Resource", func() { }) }) - When("unable to get the template ref from repo", func() { + When("unable to get the template ref from systemRepo", func() { BeforeEach(func() { - fakeRepo.GetDeliveryClusterTemplateReturns(nil, errors.New("bad template")) + fakeSystemRepo.GetDeliveryClusterTemplateReturns(nil, errors.New("bad template")) }) It("returns GetDeliveryClusterTemplateError", func() { @@ -187,7 +228,7 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetClusterTemplateReturns(templateAPI, nil) + fakeSystemRepo.GetClusterTemplateReturns(templateAPI, nil) }) It("returns a helpful error", func() { @@ -215,7 +256,7 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) + fakeSystemRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) }) It("returns StampError", func() { @@ -263,8 +304,8 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) - fakeRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeSystemRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) + fakeDeliverableRepo.EnsureObjectExistsOnClusterReturns(nil) }) It("returns RetrieveOutputError", func() { @@ -324,8 +365,8 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) - fakeRepo.EnsureObjectExistsOnClusterReturns(errors.New("bad object")) + fakeSystemRepo.GetDeliveryClusterTemplateReturns(templateAPI, nil) + fakeDeliverableRepo.EnsureObjectExistsOnClusterReturns(errors.New("bad object")) }) It("returns ApplyStampedObjectError", func() { diff --git a/pkg/realizer/runnable/realizer.go b/pkg/realizer/runnable/realizer.go index c014cfb2e..0c77208a3 100644 --- a/pkg/realizer/runnable/realizer.go +++ b/pkg/realizer/runnable/realizer.go @@ -30,7 +30,7 @@ import ( //counterfeiter:generate . Realizer type Realizer interface { - Realize(ctx context.Context, runnable *v1alpha1.Runnable, repository repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) + Realize(ctx context.Context, runnable *v1alpha1.Runnable, systemRepo repository.Repository, runnableRepo repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) } func NewRealizer() Realizer { @@ -44,9 +44,9 @@ type TemplatingContext struct { Selected map[string]interface{} `json:"selected"` } -func (p *runnableRealizer) Realize(ctx context.Context, runnable *v1alpha1.Runnable, repository repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) { +func (p *runnableRealizer) Realize(ctx context.Context, runnable *v1alpha1.Runnable, systemRepo repository.Repository, runnableRepo repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) { runnable.Spec.RunTemplateRef.Kind = "ClusterRunTemplate" - apiRunTemplate, err := repository.GetRunTemplate(ctx, runnable.Spec.RunTemplateRef) + apiRunTemplate, err := systemRepo.GetRunTemplate(ctx, runnable.Spec.RunTemplateRef) if err != nil { return nil, nil, GetRunTemplateError{ @@ -62,7 +62,7 @@ func (p *runnableRealizer) Realize(ctx context.Context, runnable *v1alpha1.Runna "carto.run/run-template-name": template.GetName(), } - selected, err := resolveSelector(ctx, runnable.Spec.Selector, repository, runnable.GetNamespace()) + selected, err := resolveSelector(ctx, runnable.Spec.Selector, runnableRepo, runnable.GetNamespace()) if err != nil { return nil, nil, ResolveSelectorError{ Err: err, @@ -87,7 +87,7 @@ func (p *runnableRealizer) Realize(ctx context.Context, runnable *v1alpha1.Runna } } - err = repository.EnsureObjectExistsOnCluster(ctx, stampedObject.DeepCopy(), false) + err = runnableRepo.EnsureObjectExistsOnCluster(ctx, stampedObject.DeepCopy(), false) if err != nil { return nil, nil, ApplyStampedObjectError{ Err: err, @@ -98,7 +98,7 @@ func (p *runnableRealizer) Realize(ctx context.Context, runnable *v1alpha1.Runna objectForListCall := stampedObject.DeepCopy() objectForListCall.SetLabels(labels) - allRunnableStampedObjects, err := repository.ListUnstructured(ctx, objectForListCall) + allRunnableStampedObjects, err := runnableRepo.ListUnstructured(ctx, objectForListCall) if err != nil { return stampedObject, nil, ListCreatedObjectsError{ Err: err, diff --git a/pkg/realizer/runnable/realizer_test.go b/pkg/realizer/runnable/realizer_test.go index ee5c731f5..cc6dac921 100644 --- a/pkg/realizer/runnable/realizer_test.go +++ b/pkg/realizer/runnable/realizer_test.go @@ -39,7 +39,8 @@ import ( var _ = Describe("Realizer", func() { var ( ctx context.Context - repository *repositoryfakes.FakeRepository + systemRepo *repositoryfakes.FakeRepository + runnableRepo *repositoryfakes.FakeRepository rlzr realizer.Realizer runnable *v1alpha1.Runnable createdUnstructured *unstructured.Unstructured @@ -47,7 +48,8 @@ var _ = Describe("Realizer", func() { BeforeEach(func() { ctx = context.Background() - repository = &repositoryfakes.FakeRepository{} + systemRepo = &repositoryfakes.FakeRepository{} + runnableRepo = &repositoryfakes.FakeRepository{} rlzr = realizer.NewRealizer() runnable = &v1alpha1.Runnable{ @@ -102,23 +104,23 @@ var _ = Describe("Realizer", func() { }, } - repository.GetRunTemplateReturns(templateAPI, nil) + systemRepo.GetRunTemplateReturns(templateAPI, nil) createdUnstructured = &unstructured.Unstructured{} - repository.EnsureObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error { + runnableRepo.EnsureObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error { createdUnstructured.Object = obj.Object return nil } - repository.ListUnstructuredReturns([]*unstructured.Unstructured{createdUnstructured}, nil) + runnableRepo.ListUnstructuredReturns([]*unstructured.Unstructured{createdUnstructured}, nil) }) It("stamps out the resource from the template", func() { - _, _, _ = rlzr.Realize(ctx, runnable, repository) + _, _, _ = rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) - Expect(repository.GetRunTemplateCallCount()).To(Equal(1)) - actualCtx, actualTemplate := repository.GetRunTemplateArgsForCall(0) + Expect(systemRepo.GetRunTemplateCallCount()).To(Equal(1)) + actualCtx, actualTemplate := systemRepo.GetRunTemplateArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) Expect(actualTemplate).To(MatchFields(IgnoreExtras, Fields{ @@ -127,8 +129,8 @@ var _ = Describe("Realizer", func() { }, )) - Expect(repository.EnsureObjectExistsOnClusterCallCount()).To(Equal(1)) - actualCtx, stamped, allowUpdate := repository.EnsureObjectExistsOnClusterArgsForCall(0) + Expect(runnableRepo.EnsureObjectExistsOnClusterCallCount()).To(Equal(1)) + actualCtx, stamped, allowUpdate := runnableRepo.EnsureObjectExistsOnClusterArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) Expect(allowUpdate).To(BeFalse()) Expect(stamped.Object).To( @@ -146,17 +148,17 @@ var _ = Describe("Realizer", func() { }) It("does not return an error", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).ToNot(HaveOccurred()) }) It("returns the outputs", func() { - _, outputs, _ := rlzr.Realize(ctx, runnable, repository) + _, outputs, _ := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(outputs["myout"]).To(Equal(apiextensionsv1.JSON{Raw: []byte(`"is a string"`)})) }) It("returns the stampedObject", func() { - stampedObject, _, _ := rlzr.Realize(ctx, runnable, repository) + stampedObject, _, _ := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(stampedObject.Object["spec"]).To(Equal(map[string]interface{}{ "foo": "is a string", "value": nil, @@ -167,11 +169,11 @@ var _ = Describe("Realizer", func() { Context("error on EnsureObjectExistsOnCluster", func() { BeforeEach(func() { - repository.EnsureObjectExistsOnClusterReturns(errors.New("some bad error")) + runnableRepo.EnsureObjectExistsOnClusterReturns(errors.New("some bad error")) }) It("returns ApplyStampedObjectError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("some bad error")) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.ApplyStampedObjectError")) @@ -180,11 +182,11 @@ var _ = Describe("Realizer", func() { Context("listing previously created objects fails", func() { BeforeEach(func() { - repository.ListUnstructuredReturns(nil, errors.New("some list error")) + runnableRepo.ListUnstructuredReturns(nil, errors.New("some list error")) }) It("returns ListCreatedObjectsError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("some list error")) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.ListCreatedObjectsError")) @@ -200,22 +202,22 @@ var _ = Describe("Realizer", func() { }, MatchingLabels: map[string]string{"expected-label": "expected-value"}, } - repository.ListUnstructuredReturns([]*unstructured.Unstructured{{map[string]interface{}{"useful-value": "from-selected-object"}}}, nil) + runnableRepo.ListUnstructuredReturns([]*unstructured.Unstructured{{map[string]interface{}{"useful-value": "from-selected-object"}}}, nil) }) It("makes the selected object available in the templating context", func() { - _, _, _ = rlzr.Realize(ctx, runnable, repository) + _, _, _ = rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) - Expect(repository.ListUnstructuredCallCount()).To(Equal(2)) - actualCtx, clientQueryObjectForSelector := repository.ListUnstructuredArgsForCall(0) + Expect(runnableRepo.ListUnstructuredCallCount()).To(Equal(2)) + actualCtx, clientQueryObjectForSelector := runnableRepo.ListUnstructuredArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) Expect(clientQueryObjectForSelector.GetAPIVersion()).To(Equal("apiversion-to-be-selected")) Expect(clientQueryObjectForSelector.GetKind()).To(Equal("kind-to-be-selected")) Expect(clientQueryObjectForSelector.GetLabels()).To(Equal(map[string]string{"expected-label": "expected-value"})) - Expect(repository.EnsureObjectExistsOnClusterCallCount()).To(Equal(1)) - actualCtx, stamped, allowUpdate := repository.EnsureObjectExistsOnClusterArgsForCall(0) + Expect(runnableRepo.EnsureObjectExistsOnClusterCallCount()).To(Equal(1)) + actualCtx, stamped, allowUpdate := runnableRepo.EnsureObjectExistsOnClusterArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) Expect(allowUpdate).To(BeFalse()) Expect(stamped.Object).To( @@ -244,11 +246,11 @@ var _ = Describe("Realizer", func() { }, MatchingLabels: map[string]string{"expected-label": "expected-value"}, } - repository.ListUnstructuredReturns([]*unstructured.Unstructured{{map[string]interface{}{}}, {map[string]interface{}{}}}, nil) + runnableRepo.ListUnstructuredReturns([]*unstructured.Unstructured{{map[string]interface{}{}}, {map[string]interface{}{}}}, nil) }) It("returns ResolveSelectorError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(`unable to resolve selector '(apiVersion:apiversion-to-be-selected kind:kind-to-be-selected labels:map[expected-label:expected-value])': 'selector matched multiple objects'`)) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.ResolveSelectorError")) @@ -264,11 +266,11 @@ var _ = Describe("Realizer", func() { }, MatchingLabels: map[string]string{"expected-label": "expected-value"}, } - repository.ListUnstructuredReturns([]*unstructured.Unstructured{}, nil) + runnableRepo.ListUnstructuredReturns([]*unstructured.Unstructured{}, nil) }) It("returns ResolveSelectorError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(`unable to resolve selector '(apiVersion:apiversion-to-be-selected kind:kind-to-be-selected labels:map[expected-label:expected-value])': 'selector did not match any objects'`)) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.ResolveSelectorError")) @@ -284,11 +286,11 @@ var _ = Describe("Realizer", func() { }, MatchingLabels: map[string]string{"expected-label": "expected-value"}, } - repository.ListUnstructuredReturns(nil, fmt.Errorf("listing unstructured is hard")) + runnableRepo.ListUnstructuredReturns(nil, fmt.Errorf("listing unstructured is hard")) }) It("returns ResolveSelectorError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(`unable to resolve selector '(apiVersion:apiversion-to-be-selected kind:kind-to-be-selected labels:map[expected-label:expected-value])': 'could not list objects matching selector: listing unstructured is hard'`)) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.ResolveSelectorError")) @@ -315,20 +317,20 @@ var _ = Describe("Realizer", func() { }, } - repository.GetRunTemplateReturns(templateAPI, nil) + systemRepo.GetRunTemplateReturns(templateAPI, nil) createdUnstructured = &unstructured.Unstructured{} - repository.EnsureObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error { + runnableRepo.EnsureObjectExistsOnClusterStub = func(ctx context.Context, obj *unstructured.Unstructured, allowUpdate bool) error { createdUnstructured.Object = obj.Object return nil } - repository.ListUnstructuredReturns([]*unstructured.Unstructured{createdUnstructured}, nil) + runnableRepo.ListUnstructuredReturns([]*unstructured.Unstructured{createdUnstructured}, nil) }) It("returns RetrieveOutputError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(`unable to retrieve outputs from stamped object for runnable 'my-important-ns/my-runnable': failed to evaluate path [data.hasnot]: evaluate: find results: hasnot is not found`)) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.RetrieveOutputError")) @@ -342,11 +344,11 @@ var _ = Describe("Realizer", func() { Template: runtime.RawExtension{}, }, } - repository.GetRunTemplateReturns(templateAPI, nil) + systemRepo.GetRunTemplateReturns(templateAPI, nil) }) It("returns StampError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(`unable to stamp object 'my-important-ns/my-runnable': 'failed to unmarshal json resource template: unexpected end of JSON input'`)) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.StampError")) @@ -355,7 +357,7 @@ var _ = Describe("Realizer", func() { Context("the ClusterRunTemplate cannot be fetched", func() { BeforeEach(func() { - repository.GetRunTemplateReturns(nil, errors.New("Errol mcErrorFace")) + systemRepo.GetRunTemplateReturns(nil, errors.New("Errol mcErrorFace")) runnable.Spec = v1alpha1.RunnableSpec{ RunTemplateRef: v1alpha1.TemplateReference{ @@ -366,7 +368,7 @@ var _ = Describe("Realizer", func() { }) It("returns GetRunTemplateError", func() { - _, _, err := rlzr.Realize(ctx, runnable, repository) + _, _, err := rlzr.Realize(ctx, runnable, systemRepo, runnableRepo) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(`unable to get runnable 'my-important-ns/my-runnable': 'Errol mcErrorFace'`)) Expect(reflect.TypeOf(err).String()).To(Equal("runnable.GetRunTemplateError")) diff --git a/pkg/realizer/runnable/runnablefakes/fake_realizer.go b/pkg/realizer/runnable/runnablefakes/fake_realizer.go index c7485400f..9150b40f1 100644 --- a/pkg/realizer/runnable/runnablefakes/fake_realizer.go +++ b/pkg/realizer/runnable/runnablefakes/fake_realizer.go @@ -13,12 +13,13 @@ import ( ) type FakeRealizer struct { - RealizeStub func(context.Context, *v1alpha1.Runnable, repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) + RealizeStub func(context.Context, *v1alpha1.Runnable, repository.Repository, repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) realizeMutex sync.RWMutex realizeArgsForCall []struct { arg1 context.Context arg2 *v1alpha1.Runnable arg3 repository.Repository + arg4 repository.Repository } realizeReturns struct { result1 *unstructured.Unstructured @@ -34,20 +35,21 @@ type FakeRealizer struct { invocationsMutex sync.RWMutex } -func (fake *FakeRealizer) Realize(arg1 context.Context, arg2 *v1alpha1.Runnable, arg3 repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) { +func (fake *FakeRealizer) Realize(arg1 context.Context, arg2 *v1alpha1.Runnable, arg3 repository.Repository, arg4 repository.Repository) (*unstructured.Unstructured, templates.Outputs, error) { fake.realizeMutex.Lock() ret, specificReturn := fake.realizeReturnsOnCall[len(fake.realizeArgsForCall)] fake.realizeArgsForCall = append(fake.realizeArgsForCall, struct { arg1 context.Context arg2 *v1alpha1.Runnable arg3 repository.Repository - }{arg1, arg2, arg3}) + arg4 repository.Repository + }{arg1, arg2, arg3, arg4}) stub := fake.RealizeStub fakeReturns := fake.realizeReturns - fake.recordInvocation("Realize", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("Realize", []interface{}{arg1, arg2, arg3, arg4}) fake.realizeMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1, ret.result2, ret.result3 @@ -61,17 +63,17 @@ func (fake *FakeRealizer) RealizeCallCount() int { return len(fake.realizeArgsForCall) } -func (fake *FakeRealizer) RealizeCalls(stub func(context.Context, *v1alpha1.Runnable, repository.Repository) (*unstructured.Unstructured, templates.Outputs, error)) { +func (fake *FakeRealizer) RealizeCalls(stub func(context.Context, *v1alpha1.Runnable, repository.Repository, repository.Repository) (*unstructured.Unstructured, templates.Outputs, error)) { fake.realizeMutex.Lock() defer fake.realizeMutex.Unlock() fake.RealizeStub = stub } -func (fake *FakeRealizer) RealizeArgsForCall(i int) (context.Context, *v1alpha1.Runnable, repository.Repository) { +func (fake *FakeRealizer) RealizeArgsForCall(i int) (context.Context, *v1alpha1.Runnable, repository.Repository, repository.Repository) { fake.realizeMutex.RLock() defer fake.realizeMutex.RUnlock() argsForCall := fake.realizeArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } func (fake *FakeRealizer) RealizeReturns(result1 *unstructured.Unstructured, result2 templates.Outputs, result3 error) { diff --git a/pkg/realizer/workload/component.go b/pkg/realizer/workload/component.go index e8ca73b6a..c5bc1548a 100644 --- a/pkg/realizer/workload/component.go +++ b/pkg/realizer/workload/component.go @@ -18,9 +18,11 @@ import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/vmware-tanzu/cartographer/pkg/apis/v1alpha1" + realizerclient "github.com/vmware-tanzu/cartographer/pkg/realizer/client" "github.com/vmware-tanzu/cartographer/pkg/repository" "github.com/vmware-tanzu/cartographer/pkg/templates" ) @@ -34,20 +36,34 @@ type ResourceRealizer interface { type resourceRealizer struct { workload *v1alpha1.Workload - repo repository.Repository + systemRepo repository.Repository + workloadRepo repository.Repository supplyChainParams []v1alpha1.DelegatableParam } -func NewResourceRealizer(workload *v1alpha1.Workload, repo repository.Repository, supplyChainParams []v1alpha1.DelegatableParam) ResourceRealizer { - return &resourceRealizer{ - workload: workload, - repo: repo, - supplyChainParams: supplyChainParams, +type ResourceRealizerBuilder func(secret *corev1.Secret, workload *v1alpha1.Workload, systemRepo repository.Repository, supplyChainParams []v1alpha1.DelegatableParam) (ResourceRealizer, error) + +//counterfeiter:generate sigs.k8s.io/controller-runtime/pkg/client.Client +func NewResourceRealizerBuilder(repositoryBuilder repository.RepositoryBuilder, clientBuilder realizerclient.ClientBuilder, cache repository.RepoCache) ResourceRealizerBuilder { + return func(secret *corev1.Secret, workload *v1alpha1.Workload, systemRepo repository.Repository, supplyChainParams []v1alpha1.DelegatableParam) (ResourceRealizer, error) { + workloadClient, err := clientBuilder(secret) + if err != nil { + return nil, fmt.Errorf("can't build client: %w", err) + } + + workloadRepo := repositoryBuilder(workloadClient, cache) + + return &resourceRealizer{ + workload: workload, + systemRepo: systemRepo, + workloadRepo: workloadRepo, + supplyChainParams: supplyChainParams, + }, nil } } func (r *resourceRealizer) Do(ctx context.Context, resource *v1alpha1.SupplyChainResource, supplyChainName string, outputs Outputs) (*unstructured.Unstructured, *templates.Output, error) { - apiTemplate, err := r.repo.GetClusterTemplate(ctx, resource.TemplateRef) + apiTemplate, err := r.systemRepo.GetClusterTemplate(ctx, resource.TemplateRef) if err != nil { return nil, nil, GetClusterTemplateError{ Err: err, @@ -98,7 +114,7 @@ func (r *resourceRealizer) Do(ctx context.Context, resource *v1alpha1.SupplyChai } } - err = r.repo.EnsureObjectExistsOnCluster(ctx, stampedObject, true) + err = r.workloadRepo.EnsureObjectExistsOnCluster(ctx, stampedObject, true) if err != nil { return nil, nil, ApplyStampedObjectError{ Err: err, diff --git a/pkg/realizer/workload/component_test.go b/pkg/realizer/workload/component_test.go index 6fd5dd03c..e616a4fbc 100644 --- a/pkg/realizer/workload/component_test.go +++ b/pkg/realizer/workload/component_test.go @@ -20,8 +20,14 @@ import ( "errors" "reflect" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "github.com/vmware-tanzu/cartographer/pkg/repository" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -35,17 +41,25 @@ import ( var _ = Describe("Resource", func() { var ( - ctx context.Context - resource v1alpha1.SupplyChainResource - workload v1alpha1.Workload - outputs realizer.Outputs - supplyChainName string - supplyChainParams []v1alpha1.DelegatableParam - fakeRepo repositoryfakes.FakeRepository - r realizer.ResourceRealizer + ctx context.Context + resource v1alpha1.SupplyChainResource + workload v1alpha1.Workload + outputs realizer.Outputs + supplyChainName string + fakeSystemRepo repositoryfakes.FakeRepository + fakeWorkloadRepo repositoryfakes.FakeRepository + clientForBuiltRepository client.Client + cacheForBuiltRepository repository.RepoCache + theSecret, secretForBuiltClient *corev1.Secret + r realizer.ResourceRealizer + out *Buffer + repoCache repository.RepoCache + supplyChainParams []v1alpha1.DelegatableParam ) BeforeEach(func() { + var err error + ctx = context.Background() resource = v1alpha1.SupplyChainResource{ Name: "resource-1", @@ -60,9 +74,41 @@ var _ = Describe("Resource", func() { outputs = realizer.NewOutputs() - fakeRepo = repositoryfakes.FakeRepository{} + fakeSystemRepo = repositoryfakes.FakeRepository{} + fakeWorkloadRepo = repositoryfakes.FakeRepository{} workload = v1alpha1.Workload{} - r = realizer.NewResourceRealizer(&workload, &fakeRepo, supplyChainParams) + + repositoryBuilder := func(client client.Client, repoCache repository.RepoCache) repository.Repository { + clientForBuiltRepository = client + cacheForBuiltRepository = repoCache + return &fakeWorkloadRepo + } + + builtClient := &repositoryfakes.FakeClient{} + clientBuilder := func(secret *corev1.Secret) (client.Client, error) { + secretForBuiltClient = secret + return builtClient, nil + } + out = NewBuffer() + logger := zap.New(zap.WriteTo(out)) + + repoCache = repository.NewCache(logger) + resourceRealizerBuilder := realizer.NewResourceRealizerBuilder(repositoryBuilder, clientBuilder, repoCache) + + theSecret = &corev1.Secret{StringData: map[string]string{"blah": "blah"}} + + r, err = resourceRealizerBuilder(theSecret, &workload, &fakeSystemRepo, supplyChainParams) + + Expect(err).NotTo(HaveOccurred()) + }) + + It("creates a resource realizer with the existing client, as well as one with the the supplied secret mixed in", func() { + Expect(secretForBuiltClient).To(Equal(theSecret)) + Expect(clientForBuiltRepository).To(Equal(clientForBuiltRepository)) + }) + + It("creates a resource realizer with the existing cache", func() { + Expect(cacheForBuiltRepository).To(Equal(repoCache)) }) Describe("Do", func() { @@ -115,15 +161,15 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetClusterTemplateReturns(templateAPI, nil) - fakeRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeSystemRepo.GetClusterTemplateReturns(templateAPI, nil) + fakeWorkloadRepo.EnsureObjectExistsOnClusterReturns(nil) }) - It("creates a stamped object and returns the outputs and stampedObjects", func() { + It("creates a stamped object using the workload repository and returns the outputs and stampedObjects", func() { returnedStampedObject, out, err := r.Do(ctx, &resource, supplyChainName, outputs) Expect(err).ToNot(HaveOccurred()) - actualCtx, stampedObject, allowUpdate := fakeRepo.EnsureObjectExistsOnClusterArgsForCall(0) + actualCtx, stampedObject, allowUpdate := fakeWorkloadRepo.EnsureObjectExistsOnClusterArgsForCall(0) Expect(actualCtx).To(Equal(ctx)) Expect(returnedStampedObject).To(Equal(stampedObject)) Expect(allowUpdate).To(BeTrue()) @@ -159,7 +205,7 @@ var _ = Describe("Resource", func() { When("unable to get the template ref from repo", func() { BeforeEach(func() { - fakeRepo.GetClusterTemplateReturns(nil, errors.New("bad template")) + fakeSystemRepo.GetClusterTemplateReturns(nil, errors.New("bad template")) }) It("returns GetClusterTemplateError", func() { @@ -184,7 +230,7 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetClusterTemplateReturns(templateAPI, nil) + fakeWorkloadRepo.GetClusterTemplateReturns(templateAPI, nil) }) It("returns a helpful error", func() { @@ -212,7 +258,7 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetClusterTemplateReturns(templateAPI, nil) + fakeSystemRepo.GetClusterTemplateReturns(templateAPI, nil) }) It("returns StampError", func() { @@ -260,8 +306,8 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetClusterTemplateReturns(templateAPI, nil) - fakeRepo.EnsureObjectExistsOnClusterReturns(nil) + fakeSystemRepo.GetClusterTemplateReturns(templateAPI, nil) + fakeWorkloadRepo.EnsureObjectExistsOnClusterReturns(nil) }) It("returns RetrieveOutputError", func() { @@ -321,8 +367,8 @@ var _ = Describe("Resource", func() { }, } - fakeRepo.GetClusterTemplateReturns(templateAPI, nil) - fakeRepo.EnsureObjectExistsOnClusterReturns(errors.New("bad object")) + fakeSystemRepo.GetClusterTemplateReturns(templateAPI, nil) + fakeWorkloadRepo.EnsureObjectExistsOnClusterReturns(errors.New("bad object")) }) It("returns ApplyStampedObjectError", func() { _, _, err := r.Do(ctx, &resource, supplyChainName, outputs) diff --git a/pkg/realizer/workload/workloadfakes/fake_client.go b/pkg/realizer/workload/workloadfakes/fake_client.go new file mode 100644 index 000000000..648bd57b8 --- /dev/null +++ b/pkg/realizer/workload/workloadfakes/fake_client.go @@ -0,0 +1,784 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package workloadfakes + +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 FakeClient 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 *FakeClient) 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 *FakeClient) CreateCallCount() int { + fake.createMutex.RLock() + defer fake.createMutex.RUnlock() + return len(fake.createArgsForCall) +} + +func (fake *FakeClient) CreateCalls(stub func(context.Context, client.Object, ...client.CreateOption) error) { + fake.createMutex.Lock() + defer fake.createMutex.Unlock() + fake.CreateStub = stub +} + +func (fake *FakeClient) 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 *FakeClient) CreateReturns(result1 error) { + fake.createMutex.Lock() + defer fake.createMutex.Unlock() + fake.CreateStub = nil + fake.createReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) DeleteCallCount() int { + fake.deleteMutex.RLock() + defer fake.deleteMutex.RUnlock() + return len(fake.deleteArgsForCall) +} + +func (fake *FakeClient) DeleteCalls(stub func(context.Context, client.Object, ...client.DeleteOption) error) { + fake.deleteMutex.Lock() + defer fake.deleteMutex.Unlock() + fake.DeleteStub = stub +} + +func (fake *FakeClient) 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 *FakeClient) DeleteReturns(result1 error) { + fake.deleteMutex.Lock() + defer fake.deleteMutex.Unlock() + fake.DeleteStub = nil + fake.deleteReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) DeleteAllOfCallCount() int { + fake.deleteAllOfMutex.RLock() + defer fake.deleteAllOfMutex.RUnlock() + return len(fake.deleteAllOfArgsForCall) +} + +func (fake *FakeClient) DeleteAllOfCalls(stub func(context.Context, client.Object, ...client.DeleteAllOfOption) error) { + fake.deleteAllOfMutex.Lock() + defer fake.deleteAllOfMutex.Unlock() + fake.DeleteAllOfStub = stub +} + +func (fake *FakeClient) 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 *FakeClient) DeleteAllOfReturns(result1 error) { + fake.deleteAllOfMutex.Lock() + defer fake.deleteAllOfMutex.Unlock() + fake.DeleteAllOfStub = nil + fake.deleteAllOfReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) GetCallCount() int { + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + return len(fake.getArgsForCall) +} + +func (fake *FakeClient) GetCalls(stub func(context.Context, types.NamespacedName, client.Object) error) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = stub +} + +func (fake *FakeClient) 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 *FakeClient) GetReturns(result1 error) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = nil + fake.getReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) ListCallCount() int { + fake.listMutex.RLock() + defer fake.listMutex.RUnlock() + return len(fake.listArgsForCall) +} + +func (fake *FakeClient) ListCalls(stub func(context.Context, client.ObjectList, ...client.ListOption) error) { + fake.listMutex.Lock() + defer fake.listMutex.Unlock() + fake.ListStub = stub +} + +func (fake *FakeClient) 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 *FakeClient) ListReturns(result1 error) { + fake.listMutex.Lock() + defer fake.listMutex.Unlock() + fake.ListStub = nil + fake.listReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) PatchCallCount() int { + fake.patchMutex.RLock() + defer fake.patchMutex.RUnlock() + return len(fake.patchArgsForCall) +} + +func (fake *FakeClient) PatchCalls(stub func(context.Context, client.Object, client.Patch, ...client.PatchOption) error) { + fake.patchMutex.Lock() + defer fake.patchMutex.Unlock() + fake.PatchStub = stub +} + +func (fake *FakeClient) 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 *FakeClient) PatchReturns(result1 error) { + fake.patchMutex.Lock() + defer fake.patchMutex.Unlock() + fake.PatchStub = nil + fake.patchReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) RESTMapperCallCount() int { + fake.rESTMapperMutex.RLock() + defer fake.rESTMapperMutex.RUnlock() + return len(fake.rESTMapperArgsForCall) +} + +func (fake *FakeClient) RESTMapperCalls(stub func() meta.RESTMapper) { + fake.rESTMapperMutex.Lock() + defer fake.rESTMapperMutex.Unlock() + fake.RESTMapperStub = stub +} + +func (fake *FakeClient) RESTMapperReturns(result1 meta.RESTMapper) { + fake.rESTMapperMutex.Lock() + defer fake.rESTMapperMutex.Unlock() + fake.RESTMapperStub = nil + fake.rESTMapperReturns = struct { + result1 meta.RESTMapper + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) SchemeCallCount() int { + fake.schemeMutex.RLock() + defer fake.schemeMutex.RUnlock() + return len(fake.schemeArgsForCall) +} + +func (fake *FakeClient) SchemeCalls(stub func() *runtime.Scheme) { + fake.schemeMutex.Lock() + defer fake.schemeMutex.Unlock() + fake.SchemeStub = stub +} + +func (fake *FakeClient) SchemeReturns(result1 *runtime.Scheme) { + fake.schemeMutex.Lock() + defer fake.schemeMutex.Unlock() + fake.SchemeStub = nil + fake.schemeReturns = struct { + result1 *runtime.Scheme + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) StatusCallCount() int { + fake.statusMutex.RLock() + defer fake.statusMutex.RUnlock() + return len(fake.statusArgsForCall) +} + +func (fake *FakeClient) StatusCalls(stub func() client.StatusWriter) { + fake.statusMutex.Lock() + defer fake.statusMutex.Unlock() + fake.StatusStub = stub +} + +func (fake *FakeClient) StatusReturns(result1 client.StatusWriter) { + fake.statusMutex.Lock() + defer fake.statusMutex.Unlock() + fake.StatusStub = nil + fake.statusReturns = struct { + result1 client.StatusWriter + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) UpdateCallCount() int { + fake.updateMutex.RLock() + defer fake.updateMutex.RUnlock() + return len(fake.updateArgsForCall) +} + +func (fake *FakeClient) UpdateCalls(stub func(context.Context, client.Object, ...client.UpdateOption) error) { + fake.updateMutex.Lock() + defer fake.updateMutex.Unlock() + fake.UpdateStub = stub +} + +func (fake *FakeClient) 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 *FakeClient) UpdateReturns(result1 error) { + fake.updateMutex.Lock() + defer fake.updateMutex.Unlock() + fake.UpdateStub = nil + fake.updateReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClient) 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 *FakeClient) 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 *FakeClient) 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(FakeClient) diff --git a/pkg/registrar/map_functions.go b/pkg/registrar/map_functions.go index 918bc2abd..bcb039f4d 100644 --- a/pkg/registrar/map_functions.go +++ b/pkg/registrar/map_functions.go @@ -20,6 +20,8 @@ import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -286,3 +288,424 @@ func runTemplateRefMatch(ref v1alpha1.TemplateReference, runTemplate *v1alpha1.C return ref.Kind == "ClusterRunTemplate" || ref.Kind == "" } + +func (mapper *Mapper) ServiceAccountToWorkloadRequests(serviceAccountObject client.Object) []reconcile.Request { + list := &v1alpha1.WorkloadList{} + + err := mapper.Client.List(context.TODO(), list) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "service account to workload requests: list workloads") + return nil + } + + var requests []reconcile.Request + for _, workload := range list.Items { + if workload.Namespace == serviceAccountObject.GetNamespace() && workload.Spec.ServiceAccountName == serviceAccountObject.GetName() { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: workload.Name, + Namespace: workload.Namespace, + }, + }) + } + } + + return requests +} + +func (mapper *Mapper) RoleBindingToWorkloadRequests(roleBindingObject client.Object) []reconcile.Request { + roleBinding, ok := roleBindingObject.(*rbacv1.RoleBinding) + if !ok { + mapper.Logger.Error(nil, "role binding to workload requests: cast to RoleBinding failed") + return nil + } + + for _, subject := range roleBinding.Subjects { + if subject.APIGroup == "" && subject.Kind == "ServiceAccount" { + serviceAccountObject := &corev1.ServiceAccount{} + serviceAccountKey := client.ObjectKey{ + Namespace: subject.Name, + Name: subject.Namespace, + } + err := mapper.Client.Get(context.TODO(), serviceAccountKey, serviceAccountObject) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client get: %w", err), "role binding to workload requests: get service account") + } + return mapper.ServiceAccountToWorkloadRequests(serviceAccountObject) + } + } + + return []reconcile.Request{} +} + +func (mapper *Mapper) ClusterRoleBindingToWorkloadRequests(clusterRoleBindingObject client.Object) []reconcile.Request { + clusterRoleBinding, ok := clusterRoleBindingObject.(*rbacv1.ClusterRoleBinding) + if !ok { + mapper.Logger.Error(nil, "cluster role binding to workload requests: cast to ClusterRoleBinding failed") + return nil + } + + for _, subject := range clusterRoleBinding.Subjects { + if subject.APIGroup == "" && subject.Kind == "ServiceAccount" { + serviceAccountObject := &corev1.ServiceAccount{} + serviceAccountKey := client.ObjectKey{ + Namespace: subject.Name, + Name: subject.Namespace, + } + err := mapper.Client.Get(context.TODO(), serviceAccountKey, serviceAccountObject) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client get: %w", err), "cluster role binding to workload requests: get service account") + return []reconcile.Request{} + } + return mapper.ServiceAccountToWorkloadRequests(serviceAccountObject) + } + } + + return []reconcile.Request{} +} + +func (mapper *Mapper) RoleToWorkloadRequests(roleObject client.Object) []reconcile.Request { + role, ok := roleObject.(*rbacv1.Role) + if !ok { + mapper.Logger.Error(nil, "role to workload requests: cast to Role failed") + return nil + } + + list := &rbacv1.RoleBindingList{} + + err := mapper.Client.List(context.TODO(), list) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "role to workload requests: list role bindings") + return nil + } + + var requests []reconcile.Request + for _, roleBinding := range list.Items { + if roleBinding.RoleRef.APIGroup == "" && roleBinding.RoleRef.Kind == "Role" && roleBinding.RoleRef.Name == role.Name && roleBinding.Namespace == role.Namespace { + requests = append(requests, mapper.RoleBindingToWorkloadRequests(&roleBinding)...) + } + } + + return requests +} + +func (mapper *Mapper) ClusterRoleToWorkloadRequests(clusterRoleObject client.Object) []reconcile.Request { + clusterRole, ok := clusterRoleObject.(*rbacv1.ClusterRole) + if !ok { + mapper.Logger.Error(nil, "cluster role to workload requests: cast to ClusterRole failed") + return nil + } + + clusterRoleBindingList := &rbacv1.ClusterRoleBindingList{} + + err := mapper.Client.List(context.TODO(), clusterRoleBindingList) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "cluster role to workload requests: list cluster role bindings") + return nil + } + + var requests []reconcile.Request + + for _, clusterRoleBinding := range clusterRoleBindingList.Items { + if clusterRoleBinding.RoleRef.APIGroup == "" && clusterRoleBinding.RoleRef.Kind == "ClusterRole" && clusterRoleBinding.RoleRef.Name == clusterRole.Name { + requests = append(requests, mapper.ClusterRoleBindingToWorkloadRequests(&clusterRoleBinding)...) + } + } + + roleBindingList := &rbacv1.RoleBindingList{} + + err = mapper.Client.List(context.TODO(), roleBindingList) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "cluster role role to workload requests: list role bindings") + return nil + } + + for _, roleBinding := range roleBindingList.Items { + if roleBinding.RoleRef.APIGroup == "" && roleBinding.RoleRef.Kind == "ClusterRole" && roleBinding.RoleRef.Name == clusterRole.Name { + requests = append(requests, mapper.RoleBindingToWorkloadRequests(&roleBinding)...) + } + } + + return requests +} + +func (mapper *Mapper) ServiceAccountToDeliverableRequests(serviceAccountObject client.Object) []reconcile.Request { + list := &v1alpha1.DeliverableList{} + + err := mapper.Client.List(context.TODO(), list) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "service account to deliverable requests: list deliverables") + return nil + } + + var requests []reconcile.Request + for _, deliverable := range list.Items { + if deliverable.Namespace == serviceAccountObject.GetNamespace() && deliverable.Spec.ServiceAccountName == serviceAccountObject.GetName() { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: deliverable.Name, + Namespace: deliverable.Namespace, + }, + }) + } + } + + return requests +} + +func (mapper *Mapper) RoleBindingToDeliverableRequests(roleBindingObject client.Object) []reconcile.Request { + roleBinding, ok := roleBindingObject.(*rbacv1.RoleBinding) + if !ok { + mapper.Logger.Error(nil, "role binding to deliverable requests: cast to RoleBinding failed") + return nil + } + + for _, subject := range roleBinding.Subjects { + if subject.APIGroup == "" && subject.Kind == "ServiceAccount" { + serviceAccountObject := &corev1.ServiceAccount{} + serviceAccountKey := client.ObjectKey{ + Namespace: subject.Name, + Name: subject.Namespace, + } + err := mapper.Client.Get(context.TODO(), serviceAccountKey, serviceAccountObject) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client get: %w", err), "role binding to deliverable requests: get service account") + } + return mapper.ServiceAccountToDeliverableRequests(serviceAccountObject) + } + } + + return []reconcile.Request{} +} + +func (mapper *Mapper) ClusterRoleBindingToDeliverableRequests(clusterRoleBindingObject client.Object) []reconcile.Request { + clusterRoleBinding, ok := clusterRoleBindingObject.(*rbacv1.ClusterRoleBinding) + if !ok { + mapper.Logger.Error(nil, "cluster role binding to deliverable requests: cast to ClusterRoleBinding failed") + return nil + } + + for _, subject := range clusterRoleBinding.Subjects { + if subject.APIGroup == "" && subject.Kind == "ServiceAccount" { + serviceAccountObject := &corev1.ServiceAccount{} + serviceAccountKey := client.ObjectKey{ + Namespace: subject.Name, + Name: subject.Namespace, + } + err := mapper.Client.Get(context.TODO(), serviceAccountKey, serviceAccountObject) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client get: %w", err), "cluster role binding to deliverable requests: get service account") + return []reconcile.Request{} + } + return mapper.ServiceAccountToDeliverableRequests(serviceAccountObject) + } + } + + return []reconcile.Request{} +} + +func (mapper *Mapper) RoleToDeliverableRequests(roleObject client.Object) []reconcile.Request { + role, ok := roleObject.(*rbacv1.Role) + if !ok { + mapper.Logger.Error(nil, "role to deliverable requests: cast to Role failed") + return nil + } + + list := &rbacv1.RoleBindingList{} + + err := mapper.Client.List(context.TODO(), list) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "role to deliverable requests: list role bindings") + return nil + } + + var requests []reconcile.Request + for _, roleBinding := range list.Items { + if roleBinding.RoleRef.APIGroup == "" && roleBinding.RoleRef.Kind == "Role" && roleBinding.RoleRef.Name == role.Name && roleBinding.Namespace == role.Namespace { + requests = append(requests, mapper.RoleBindingToDeliverableRequests(&roleBinding)...) + } + } + + return requests +} + +func (mapper *Mapper) ClusterRoleToDeliverableRequests(clusterRoleObject client.Object) []reconcile.Request { + clusterRole, ok := clusterRoleObject.(*rbacv1.ClusterRole) + if !ok { + mapper.Logger.Error(nil, "cluster role to deliverable requests: cast to ClusterRole failed") + return nil + } + + clusterRoleBindingList := &rbacv1.ClusterRoleBindingList{} + + err := mapper.Client.List(context.TODO(), clusterRoleBindingList) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "cluster role to deliverable requests: list cluster role bindings") + return nil + } + + var requests []reconcile.Request + + for _, clusterRoleBinding := range clusterRoleBindingList.Items { + if clusterRoleBinding.RoleRef.APIGroup == "" && clusterRoleBinding.RoleRef.Kind == "ClusterRole" && clusterRoleBinding.RoleRef.Name == clusterRole.Name { + requests = append(requests, mapper.ClusterRoleBindingToDeliverableRequests(&clusterRoleBinding)...) + } + } + + roleBindingList := &rbacv1.RoleBindingList{} + + err = mapper.Client.List(context.TODO(), roleBindingList) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "cluster role role to deliverable requests: list role bindings") + return nil + } + + for _, roleBinding := range roleBindingList.Items { + if roleBinding.RoleRef.APIGroup == "" && roleBinding.RoleRef.Kind == "ClusterRole" && roleBinding.RoleRef.Name == clusterRole.Name { + requests = append(requests, mapper.RoleBindingToDeliverableRequests(&roleBinding)...) + } + } + + return requests +} + +func (mapper *Mapper) ServiceAccountToRunnableRequests(serviceAccountObject client.Object) []reconcile.Request { + list := &v1alpha1.RunnableList{} + + err := mapper.Client.List(context.TODO(), list) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "service account to runnable requests: list runnables") + return nil + } + + var requests []reconcile.Request + for _, runnable := range list.Items { + if runnable.Namespace == serviceAccountObject.GetNamespace() && runnable.Spec.ServiceAccountName == serviceAccountObject.GetName() { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: runnable.Name, + Namespace: runnable.Namespace, + }, + }) + } + } + + return requests +} + +func (mapper *Mapper) RoleBindingToRunnableRequests(roleBindingObject client.Object) []reconcile.Request { + roleBinding, ok := roleBindingObject.(*rbacv1.RoleBinding) + if !ok { + mapper.Logger.Error(nil, "role binding to runnable requests: cast to RoleBinding failed") + return nil + } + + for _, subject := range roleBinding.Subjects { + if subject.APIGroup == "" && subject.Kind == "ServiceAccount" { + serviceAccountObject := &corev1.ServiceAccount{} + + serviceAccountKey := client.ObjectKey{ + Namespace: subject.Name, + Name: subject.Namespace, + } + err := mapper.Client.Get(context.TODO(), serviceAccountKey, serviceAccountObject) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client get: %w", err), "role binding to runnable requests: get service account") + } + return mapper.ServiceAccountToRunnableRequests(serviceAccountObject) + } + } + + return []reconcile.Request{} +} + +func (mapper *Mapper) ClusterRoleBindingToRunnableRequests(clusterRoleBindingObject client.Object) []reconcile.Request { + clusterRoleBinding, ok := clusterRoleBindingObject.(*rbacv1.ClusterRoleBinding) + if !ok { + mapper.Logger.Error(nil, "cluster role binding to runnable requests: cast to ClusterRoleBinding failed") + return nil + } + + for _, subject := range clusterRoleBinding.Subjects { + if subject.APIGroup == "" && subject.Kind == "ServiceAccount" { + serviceAccountObject := &corev1.ServiceAccount{} + serviceAccountKey := client.ObjectKey{ + Namespace: subject.Name, + Name: subject.Namespace, + } + err := mapper.Client.Get(context.TODO(), serviceAccountKey, serviceAccountObject) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client get: %w", err), "cluster role binding to runnable requests: get service account") + return []reconcile.Request{} + } + return mapper.ServiceAccountToRunnableRequests(serviceAccountObject) + } + } + + return []reconcile.Request{} +} + +func (mapper *Mapper) RoleToRunnableRequests(roleObject client.Object) []reconcile.Request { + role, ok := roleObject.(*rbacv1.Role) + if !ok { + mapper.Logger.Error(nil, "role to runnable requests: cast to Role failed") + return nil + } + + list := &rbacv1.RoleBindingList{} + + err := mapper.Client.List(context.TODO(), list) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "role to runnable requests: list role bindings") + return nil + } + + var requests []reconcile.Request + for _, roleBinding := range list.Items { + if roleBinding.RoleRef.APIGroup == "" && roleBinding.RoleRef.Kind == "Role" && roleBinding.RoleRef.Name == role.Name && roleBinding.Namespace == role.Namespace { + requests = append(requests, mapper.RoleBindingToRunnableRequests(&roleBinding)...) + } + } + + return requests +} + +func (mapper *Mapper) ClusterRoleToRunnableRequests(clusterRoleObject client.Object) []reconcile.Request { + clusterRole, ok := clusterRoleObject.(*rbacv1.ClusterRole) + if !ok { + mapper.Logger.Error(nil, "cluster role to runnable requests: cast to ClusterRole failed") + return nil + } + + clusterRoleBindingList := &rbacv1.ClusterRoleBindingList{} + + err := mapper.Client.List(context.TODO(), clusterRoleBindingList) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "cluster role to runnable requests: list cluster role bindings") + return nil + } + + var requests []reconcile.Request + + for _, clusterRoleBinding := range clusterRoleBindingList.Items { + if clusterRoleBinding.RoleRef.APIGroup == "" && clusterRoleBinding.RoleRef.Kind == "ClusterRole" && clusterRoleBinding.RoleRef.Name == clusterRole.Name { + requests = append(requests, mapper.ClusterRoleBindingToRunnableRequests(&clusterRoleBinding)...) + } + } + + roleBindingList := &rbacv1.RoleBindingList{} + + err = mapper.Client.List(context.TODO(), roleBindingList) + if err != nil { + mapper.Logger.Error(fmt.Errorf("client list: %w", err), "cluster role role to runnable requests: list role bindings") + return nil + } + + for _, roleBinding := range roleBindingList.Items { + if roleBinding.RoleRef.APIGroup == "" && roleBinding.RoleRef.Kind == "ClusterRole" && roleBinding.RoleRef.Name == clusterRole.Name { + requests = append(requests, mapper.RoleBindingToRunnableRequests(&roleBinding)...) + } + } + + return requests +} diff --git a/pkg/registrar/map_functions_test.go b/pkg/registrar/map_functions_test.go index 163b2195a..e3dba288e 100644 --- a/pkg/registrar/map_functions_test.go +++ b/pkg/registrar/map_functions_test.go @@ -21,6 +21,8 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -1149,4 +1151,2779 @@ var _ = Describe("MapFunctions", func() { }) }) }) + + Describe("ServiceAccountToWorkloadRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no workloads", func() { + BeforeEach(func() { + existingList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + sa := &corev1.ServiceAccount{} + reqs := m.ServiceAccountToWorkloadRequests(sa) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple workloads", func() { + BeforeEach(func() { + existingWorkload1 := &v1alpha1.Workload{ + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingWorkload2 := &v1alpha1.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-workload", + Namespace: "some-namespace", + }, + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{*existingWorkload1, *existingWorkload2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + Context("there is a matching workload", func() { + + It("returns requests for only the matching workload", func() { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + reqs := m.ServiceAccountToWorkloadRequests(sa) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-workload")) + }) + }) + + Context("there is no matching workload", func() { + It("returns an empty request list", func() { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-service-account", + Namespace: "some-other-namespace", + }, + } + reqs := m.ServiceAccountToWorkloadRequests(sa) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + sa := &corev1.ServiceAccount{} + reqs := m.ServiceAccountToWorkloadRequests(sa) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("service account to workload requests: list workloads")) + }) + }) + }) + + Describe("RoleBindingToWorkloadRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no workloads", func() { + BeforeEach(func() { + existingList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + rb := &rbacv1.RoleBinding{} + reqs := m.RoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple workloads", func() { + BeforeEach(func() { + existingWorkload1 := &v1alpha1.Workload{ + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingWorkload2 := &v1alpha1.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-workload", + Namespace: "some-namespace", + }, + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{*existingWorkload1, *existingWorkload2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching workload", func() { + + It("returns requests for only the matching workload", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-workload")) + }) + }) + + Context("there is no matching workload", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + getErr error + ) + BeforeEach(func() { + getErr = fmt.Errorf("some error") + + fakeClient.GetReturns(getErr) + }) + + It("returns the error", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(getErr)) + Expect(msg).To(Equal("role binding to workload requests: get service account")) + }) + }) + }) + + Describe("ClusterRoleBindingToWorkloadRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no workloads", func() { + BeforeEach(func() { + existingList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + rb := &rbacv1.ClusterRoleBinding{} + reqs := m.ClusterRoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple workloads", func() { + BeforeEach(func() { + existingWorkload1 := &v1alpha1.Workload{ + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingWorkload2 := &v1alpha1.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-workload", + Namespace: "some-namespace", + }, + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{*existingWorkload1, *existingWorkload2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching workload", func() { + + It("returns requests for only the matching workload", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-workload")) + }) + }) + + Context("there is no matching workload", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + getErr error + ) + BeforeEach(func() { + getErr = fmt.Errorf("some error") + + fakeClient.GetReturns(getErr) + }) + + It("returns the error", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToWorkloadRequests(rb) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(getErr)) + Expect(msg).To(Equal("cluster role binding to workload requests: get service account")) + }) + }) + }) + + Describe("RoleToWorkloadRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no workloads", func() { + BeforeEach(func() { + existingList := rbacv1.RoleBindingList{} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + r := &rbacv1.Role{} + reqs := m.RoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple workloads", func() { + BeforeEach(func() { + existingWorkload1 := &v1alpha1.Workload{ + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingWorkload2 := &v1alpha1.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-workload", + Namespace: "some-namespace", + }, + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingWorkloadList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{*existingWorkload1, *existingWorkload2}, + } + + roleBinding := &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "Role", + Name: "some-role", + }, + } + + roleBindingList := rbacv1.RoleBindingList{Items: []rbacv1.RoleBinding{*roleBinding}} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isWorkloadList := list.(*v1alpha1.WorkloadList) + if isWorkloadList { + existingVal = reflect.ValueOf(existingWorkloadList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching workload", func() { + + It("returns requests for only the matching workload", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-workload")) + }) + }) + + Context("there is no matching workload", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("role to workload requests: list role bindings")) + }) + }) + }) + + Describe("ClusterRoleToWorkloadRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no workloads", func() { + BeforeEach(func() { + clusterRoleBindingList := rbacv1.ClusterRoleBindingList{} + roleBindingList := rbacv1.RoleBindingList{} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isClusterRoleBindingList := list.(*rbacv1.ClusterRoleBindingList) + if isClusterRoleBindingList { + existingVal = reflect.ValueOf(clusterRoleBindingList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + r := &rbacv1.ClusterRole{} + reqs := m.ClusterRoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple runnables", func() { + BeforeEach(func() { + existingWorkload1 := &v1alpha1.Workload{ + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingWorkload2 := &v1alpha1.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-workload", + Namespace: "some-namespace", + }, + Spec: v1alpha1.WorkloadSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingWorkloadList := v1alpha1.WorkloadList{ + Items: []v1alpha1.Workload{*existingWorkload1, *existingWorkload2}, + } + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "some-role", + }, + } + + clusterRoleBindingList := rbacv1.ClusterRoleBindingList{Items: []rbacv1.ClusterRoleBinding{*clusterRoleBinding}} + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "some-unused-role", + }, + } + + roleBindingList := rbacv1.RoleBindingList{Items: []rbacv1.RoleBinding{*roleBinding}} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isWorkloadList := list.(*v1alpha1.WorkloadList) + if isWorkloadList { + existingVal = reflect.ValueOf(existingWorkloadList) + } else if _, isClusterRoleBindingList := list.(*rbacv1.ClusterRoleBindingList); isClusterRoleBindingList { + existingVal = reflect.ValueOf(clusterRoleBindingList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching workload", func() { + + It("returns requests for only the matching workload", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + }, + } + reqs := m.ClusterRoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-workload")) + }) + }) + + Context("there is no matching workload", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + }, + } + reqs := m.ClusterRoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + } + reqs := m.ClusterRoleToWorkloadRequests(r) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("cluster role to workload requests: list cluster role bindings")) + }) + }) + }) + + Describe("ServiceAccountToRunnableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no runnables", func() { + BeforeEach(func() { + existingList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + sa := &corev1.ServiceAccount{} + reqs := m.ServiceAccountToRunnableRequests(sa) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple runnables", func() { + BeforeEach(func() { + existingRunnable1 := &v1alpha1.Runnable{ + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingRunnable2 := &v1alpha1.Runnable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-runnable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{*existingRunnable1, *existingRunnable2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + Context("there is a matching runnable", func() { + + It("returns requests for only the matching runnable", func() { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + reqs := m.ServiceAccountToRunnableRequests(sa) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-runnable")) + }) + }) + + Context("there is no matching runnable", func() { + It("returns an empty request list", func() { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-service-account", + Namespace: "some-other-namespace", + }, + } + reqs := m.ServiceAccountToRunnableRequests(sa) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + sa := &corev1.ServiceAccount{} + reqs := m.ServiceAccountToRunnableRequests(sa) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("service account to runnable requests: list runnables")) + }) + }) + }) + + Describe("RoleBindingToRunnableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no runnables", func() { + BeforeEach(func() { + existingList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + rb := &rbacv1.RoleBinding{} + reqs := m.RoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple runnables", func() { + BeforeEach(func() { + existingRunnable1 := &v1alpha1.Runnable{ + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingRunnable2 := &v1alpha1.Runnable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-runnable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{*existingRunnable1, *existingRunnable2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching runnable", func() { + + It("returns requests for only the matching runnable", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-runnable")) + }) + }) + + Context("there is no matching runnable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + getErr error + ) + BeforeEach(func() { + getErr = fmt.Errorf("some error") + + fakeClient.GetReturns(getErr) + }) + + It("returns the error", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(getErr)) + Expect(msg).To(Equal("role binding to runnable requests: get service account")) + }) + }) + }) + + Describe("ClusterRoleBindingToRunnableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no runnables", func() { + BeforeEach(func() { + existingList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + rb := &rbacv1.ClusterRoleBinding{} + reqs := m.ClusterRoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple runnables", func() { + BeforeEach(func() { + existingRunnable1 := &v1alpha1.Runnable{ + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingRunnable2 := &v1alpha1.Runnable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-runnable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{*existingRunnable1, *existingRunnable2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching runnable", func() { + + It("returns requests for only the matching runnable", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-runnable")) + }) + }) + + Context("there is no matching runnable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + getErr error + ) + BeforeEach(func() { + getErr = fmt.Errorf("some error") + + fakeClient.GetReturns(getErr) + }) + + It("returns the error", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(getErr)) + Expect(msg).To(Equal("cluster role binding to runnable requests: get service account")) + }) + }) + }) + + Describe("RoleToRunnableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no runnables", func() { + BeforeEach(func() { + existingList := rbacv1.RoleBindingList{} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + r := &rbacv1.Role{} + reqs := m.RoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple runnables", func() { + BeforeEach(func() { + existingRunnable1 := &v1alpha1.Runnable{ + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingRunnable2 := &v1alpha1.Runnable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-runnable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingRunnableList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{*existingRunnable1, *existingRunnable2}, + } + + roleBinding := &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "Role", + Name: "some-role", + }, + } + + roleBindingList := rbacv1.RoleBindingList{Items: []rbacv1.RoleBinding{*roleBinding}} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isRunnableList := list.(*v1alpha1.RunnableList) + if isRunnableList { + existingVal = reflect.ValueOf(existingRunnableList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching runnable", func() { + + It("returns requests for only the matching runnable", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-runnable")) + }) + }) + + Context("there is no matching runnable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("role to runnable requests: list role bindings")) + }) + }) + }) + + Describe("ClusterRoleToRunnableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no runnables", func() { + BeforeEach(func() { + clusterRoleBindingList := rbacv1.ClusterRoleBindingList{} + roleBindingList := rbacv1.RoleBindingList{} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isClusterRoleBindingList := list.(*rbacv1.ClusterRoleBindingList) + if isClusterRoleBindingList { + existingVal = reflect.ValueOf(clusterRoleBindingList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + r := &rbacv1.ClusterRole{} + reqs := m.ClusterRoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple runnables", func() { + BeforeEach(func() { + existingRunnable1 := &v1alpha1.Runnable{ + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingRunnable2 := &v1alpha1.Runnable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-runnable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.RunnableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingRunnableList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{*existingRunnable1, *existingRunnable2}, + } + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "some-role", + }, + } + + clusterRoleBindingList := rbacv1.ClusterRoleBindingList{Items: []rbacv1.ClusterRoleBinding{*clusterRoleBinding}} + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "some-unused-role", + }, + } + + roleBindingList := rbacv1.RoleBindingList{Items: []rbacv1.RoleBinding{*roleBinding}} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isRunnableList := list.(*v1alpha1.RunnableList) + if isRunnableList { + existingVal = reflect.ValueOf(existingRunnableList) + } else if _, isClusterRoleBindingList := list.(*rbacv1.ClusterRoleBindingList); isClusterRoleBindingList { + existingVal = reflect.ValueOf(clusterRoleBindingList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching runnable", func() { + + It("returns requests for only the matching runnable", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + }, + } + reqs := m.ClusterRoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-runnable")) + }) + }) + + Context("there is no matching runnable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + }, + } + reqs := m.ClusterRoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + } + reqs := m.ClusterRoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("cluster role to runnable requests: list cluster role bindings")) + }) + }) + }) + + Describe("ServiceAccountToDeliverableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no deliverables", func() { + BeforeEach(func() { + existingList := v1alpha1.DeliverableList{ + Items: []v1alpha1.Deliverable{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + sa := &corev1.ServiceAccount{} + reqs := m.ServiceAccountToDeliverableRequests(sa) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple deliverables", func() { + BeforeEach(func() { + existingDeliverable1 := &v1alpha1.Deliverable{ + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingDeliverable2 := &v1alpha1.Deliverable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-deliverable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.DeliverableList{ + Items: []v1alpha1.Deliverable{*existingDeliverable1, *existingDeliverable2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + Context("there is a matching deliverable", func() { + + It("returns requests for only the matching deliverable", func() { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + reqs := m.ServiceAccountToDeliverableRequests(sa) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-deliverable")) + }) + }) + + Context("there is no matching deliverable", func() { + It("returns an empty request list", func() { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-service-account", + Namespace: "some-other-namespace", + }, + } + reqs := m.ServiceAccountToDeliverableRequests(sa) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + sa := &corev1.ServiceAccount{} + reqs := m.ServiceAccountToDeliverableRequests(sa) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("service account to deliverable requests: list deliverables")) + }) + }) + }) + + Describe("RoleBindingToDeliverableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no deliverables", func() { + BeforeEach(func() { + existingList := v1alpha1.DeliverableList{ + Items: []v1alpha1.Deliverable{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + rb := &rbacv1.RoleBinding{} + reqs := m.RoleBindingToDeliverableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple deliverables", func() { + BeforeEach(func() { + existingDeliverable1 := &v1alpha1.Deliverable{ + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingDeliverable2 := &v1alpha1.Deliverable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-deliverable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.DeliverableList{ + Items: []v1alpha1.Deliverable{*existingDeliverable1, *existingDeliverable2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching deliverable", func() { + + It("returns requests for only the matching deliverable", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToDeliverableRequests(rb) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-deliverable")) + }) + }) + + Context("there is no matching deliverable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToDeliverableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + getErr error + ) + BeforeEach(func() { + getErr = fmt.Errorf("some error") + + fakeClient.GetReturns(getErr) + }) + + It("returns the error", func() { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.RoleBindingToDeliverableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(getErr)) + Expect(msg).To(Equal("role binding to deliverable requests: get service account")) + }) + }) + }) + + Describe("ClusterRoleBindingToDeliverableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no deliverables", func() { + BeforeEach(func() { + existingList := v1alpha1.RunnableList{ + Items: []v1alpha1.Runnable{}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + rb := &rbacv1.ClusterRoleBinding{} + reqs := m.ClusterRoleBindingToRunnableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple deliverables", func() { + BeforeEach(func() { + existingDeliverable1 := &v1alpha1.Deliverable{ + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingDeliverable2 := &v1alpha1.Deliverable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-deliverable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingList := v1alpha1.DeliverableList{ + Items: []v1alpha1.Deliverable{*existingDeliverable1, *existingDeliverable2}, + } + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching deliverable", func() { + + It("returns requests for only the matching deliverable", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToDeliverableRequests(rb) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-deliverable")) + }) + }) + + Context("there is no matching deliverable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToDeliverableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + getErr error + ) + BeforeEach(func() { + getErr = fmt.Errorf("some error") + + fakeClient.GetReturns(getErr) + }) + + It("returns the error", func() { + rb := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + } + reqs := m.ClusterRoleBindingToDeliverableRequests(rb) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(getErr)) + Expect(msg).To(Equal("cluster role binding to deliverable requests: get service account")) + }) + }) + }) + + Describe("RoleToDeliverableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no deliverables", func() { + BeforeEach(func() { + existingList := rbacv1.RoleBindingList{} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + existingVal := reflect.ValueOf(existingList) + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + r := &rbacv1.Role{} + reqs := m.RoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple deliverables", func() { + BeforeEach(func() { + existingDeliverable1 := &v1alpha1.Deliverable{ + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingDeliverable2 := &v1alpha1.Deliverable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-deliverable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingDeliverableList := v1alpha1.DeliverableList{ + Items: []v1alpha1.Deliverable{*existingDeliverable1, *existingDeliverable2}, + } + + roleBinding := &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "Role", + Name: "some-role", + }, + } + + roleBindingList := rbacv1.RoleBindingList{Items: []rbacv1.RoleBinding{*roleBinding}} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isDeliverableList := list.(*v1alpha1.DeliverableList) + if isDeliverableList { + existingVal = reflect.ValueOf(existingDeliverableList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching deliverable", func() { + + It("returns requests for only the matching deliverable", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToDeliverableRequests(r) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-deliverable")) + }) + }) + + Context("there is no matching deliverable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToDeliverableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + r := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + } + reqs := m.RoleToDeliverableRequests(r) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("role to deliverable requests: list role bindings")) + }) + }) + }) + + Describe("ClusterRoleToDeliverableRequests", func() { + var ( + m *registrar.Mapper + fakeLogger *registrarfakes.FakeLogger + fakeClient *registrarfakes.FakeClient + ) + + BeforeEach(func() { + fakeLogger = ®istrarfakes.FakeLogger{} + fakeClient = ®istrarfakes.FakeClient{} + + m = ®istrar.Mapper{ + Client: fakeClient, + Logger: fakeLogger, + } + + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + fakeClient.SchemeReturns(scheme) + }) + + Context("client.list does not return errors", func() { + Context("there are no deliverables", func() { + BeforeEach(func() { + clusterRoleBindingList := rbacv1.ClusterRoleBindingList{} + roleBindingList := rbacv1.RoleBindingList{} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isClusterRoleBindingList := list.(*rbacv1.ClusterRoleBindingList) + if isClusterRoleBindingList { + existingVal = reflect.ValueOf(clusterRoleBindingList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + }) + + It("returns an empty request list", func() { + r := &rbacv1.ClusterRole{} + reqs := m.ClusterRoleToRunnableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + Context("there are multiple deliverables", func() { + BeforeEach(func() { + existingDeliverable1 := &v1alpha1.Deliverable{ + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-other-service-account", + }, + } + existingDeliverable2 := &v1alpha1.Deliverable{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-deliverable", + Namespace: "some-namespace", + }, + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "some-service-account", + }, + } + existingDeliverableList := v1alpha1.DeliverableList{ + Items: []v1alpha1.Deliverable{*existingDeliverable1, *existingDeliverable2}, + } + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "some-role", + }, + } + + clusterRoleBindingList := rbacv1.ClusterRoleBindingList{Items: []rbacv1.ClusterRoleBinding{*clusterRoleBinding}} + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + Namespace: "some-namespace", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: "some-unused-role", + }, + } + + roleBindingList := rbacv1.RoleBindingList{Items: []rbacv1.RoleBinding{*roleBinding}} + + fakeClient.ListStub = func(ctx context.Context, list client.ObjectList, option ...client.ListOption) error { + listVal := reflect.ValueOf(list) + var existingVal reflect.Value + + _, isDeliverableList := list.(*v1alpha1.DeliverableList) + if isDeliverableList { + existingVal = reflect.ValueOf(existingDeliverableList) + } else if _, isClusterRoleBindingList := list.(*rbacv1.ClusterRoleBindingList); isClusterRoleBindingList { + existingVal = reflect.ValueOf(clusterRoleBindingList) + } else { + existingVal = reflect.ValueOf(roleBindingList) + } + + reflect.Indirect(listVal).Set(reflect.Indirect(existingVal)) + return nil + } + + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + + Context("there is a matching deliverable", func() { + + It("returns requests for only the matching deliverable", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + }, + } + reqs := m.ClusterRoleToDeliverableRequests(r) + + Expect(reqs).To(HaveLen(1)) + Expect(reqs[0].Name).To(Equal("some-deliverable")) + }) + }) + + Context("there is no matching deliverable", func() { + BeforeEach(func() { + sa := corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-unused-service-account", + Namespace: "some-namespace", + }, + } + + fakeClient.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error { + objectVal := reflect.ValueOf(object) + saVal := reflect.ValueOf(sa) + + reflect.Indirect(objectVal).Set(reflect.Indirect(saVal)) + return nil + } + }) + It("returns an empty request list", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role", + }, + } + reqs := m.ClusterRoleToDeliverableRequests(r) + + Expect(reqs).To(HaveLen(0)) + }) + }) + + }) + }) + + Context("client.list errors", func() { + var ( + listErr error + ) + BeforeEach(func() { + listErr = fmt.Errorf("some error") + + fakeClient.ListReturns(listErr) + }) + + It("returns the error", func() { + r := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-role-binding", + }, + } + reqs := m.ClusterRoleToDeliverableRequests(r) + + Expect(reqs).To(HaveLen(0)) + Expect(fakeLogger.ErrorCallCount()).To(Equal(1)) + + err, msg, _ := fakeLogger.ErrorArgsForCall(0) + Expect(err).To(MatchError(listErr)) + Expect(msg).To(Equal("cluster role to deliverable requests: list cluster role bindings")) + }) + }) + }) }) diff --git a/pkg/registrar/registrar.go b/pkg/registrar/registrar.go index 789425d69..ff98cb5ca 100644 --- a/pkg/registrar/registrar.go +++ b/pkg/registrar/registrar.go @@ -20,6 +20,8 @@ import ( "context" "fmt" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/cluster-api/controllers/external" @@ -36,6 +38,7 @@ import ( "github.com/vmware-tanzu/cartographer/pkg/controller/runnable" "github.com/vmware-tanzu/cartographer/pkg/controller/supplychain" "github.com/vmware-tanzu/cartographer/pkg/controller/workload" + realizerclient "github.com/vmware-tanzu/cartographer/pkg/realizer/client" realizerdeliverable "github.com/vmware-tanzu/cartographer/pkg/realizer/deliverable" realizerrunnable "github.com/vmware-tanzu/cartographer/pkg/realizer/runnable" realizerworkload "github.com/vmware-tanzu/cartographer/pkg/realizer/workload" @@ -53,6 +56,14 @@ func AddToScheme(scheme *runtime.Scheme) error { return fmt.Errorf("cartographer v1alpha1 add to scheme: %w", err) } + if err := corev1.AddToScheme(scheme); err != nil { + return fmt.Errorf("core v1 add to scheme: %w", err) + } + + if err := rbacv1.AddToScheme(scheme); err != nil { + return fmt.Errorf("rbac v1 add to scheme: %w", err) + } + return nil } @@ -89,6 +100,7 @@ func registerWorkloadController(mgr manager.Manager) error { reconciler := &workload.Reconciler{ Repo: repo, ConditionManagerBuilder: conditions.NewConditionManager, + ResourceRealizerBuilder: realizerworkload.NewResourceRealizerBuilder(repository.NewRepository, realizerclient.NewClientBuilder(mgr.GetConfig()), repository.NewCache(mgr.GetLogger().WithName("workload-stamping-repo-cache"))), Realizer: realizerworkload.NewRealizer(), } @@ -113,19 +125,23 @@ func registerWorkloadController(mgr manager.Manager) error { Logger: mgr.GetLogger().WithName("workload"), } - if err := ctrl.Watch( - &source.Kind{Type: &v1alpha1.ClusterSupplyChain{}}, - handler.EnqueueRequestsFromMapFunc(mapper.ClusterSupplyChainToWorkloadRequests), - ); err != nil { - return fmt.Errorf("watch: %w", err) + watches := map[client.Object]handler.MapFunc{ + &v1alpha1.ClusterSupplyChain{}: mapper.ClusterSupplyChainToWorkloadRequests, + &corev1.ServiceAccount{}: mapper.ServiceAccountToWorkloadRequests, + &rbacv1.Role{}: mapper.RoleToWorkloadRequests, + &rbacv1.RoleBinding{}: mapper.RoleBindingToWorkloadRequests, + &rbacv1.ClusterRole{}: mapper.ClusterRoleToWorkloadRequests, + &rbacv1.ClusterRoleBinding{}: mapper.ClusterRoleBindingToWorkloadRequests, } - for _, template := range v1alpha1.ValidSupplyChainTemplates { + watches[template] = mapper.TemplateToWorkloadRequests + } + for kindType, mapFunc := range watches { if err := ctrl.Watch( - &source.Kind{Type: template}, - handler.EnqueueRequestsFromMapFunc(mapper.TemplateToWorkloadRequests), + &source.Kind{Type: kindType}, + handler.EnqueueRequestsFromMapFunc(mapFunc), ); err != nil { - return fmt.Errorf("watch template: %w", err) + return fmt.Errorf("watch %T: %w", kindType, err) } } @@ -222,7 +238,12 @@ func registerDeliverableController(mgr manager.Manager) error { reconciler := &deliverable.Reconciler{ Repo: repo, ConditionManagerBuilder: conditions.NewConditionManager, - Realizer: realizerdeliverable.NewRealizer(), + ResourceRealizerBuilder: realizerdeliverable.NewResourceRealizerBuilder( + repository.NewRepository, + realizerclient.NewClientBuilder(mgr.GetConfig()), + repository.NewCache(mgr.GetLogger().WithName("deliverable-stamping-repo-cache")), + ), + Realizer: realizerdeliverable.NewRealizer(), } ctrl, err := pkgcontroller.New("deliverable", mgr, pkgcontroller.Options{ @@ -246,19 +267,23 @@ func registerDeliverableController(mgr manager.Manager) error { Logger: mgr.GetLogger().WithName("deliverable"), } - if err := ctrl.Watch( - &source.Kind{Type: &v1alpha1.ClusterDelivery{}}, - handler.EnqueueRequestsFromMapFunc(mapper.ClusterDeliveryToDeliverableRequests), - ); err != nil { - return fmt.Errorf("watch: %w", err) + watches := map[client.Object]handler.MapFunc{ + &v1alpha1.ClusterDelivery{}: mapper.ClusterDeliveryToDeliverableRequests, + &corev1.ServiceAccount{}: mapper.ServiceAccountToDeliverableRequests, + &rbacv1.Role{}: mapper.RoleToDeliverableRequests, + &rbacv1.RoleBinding{}: mapper.RoleBindingToDeliverableRequests, + &rbacv1.ClusterRole{}: mapper.ClusterRoleToDeliverableRequests, + &rbacv1.ClusterRoleBinding{}: mapper.ClusterRoleBindingToDeliverableRequests, } - for _, template := range v1alpha1.ValidDeliveryTemplates { + watches[template] = mapper.TemplateToDeliverableRequests + } + for kindType, mapFunc := range watches { if err := ctrl.Watch( - &source.Kind{Type: template}, - handler.EnqueueRequestsFromMapFunc(mapper.TemplateToDeliverableRequests), + &source.Kind{Type: kindType}, + handler.EnqueueRequestsFromMapFunc(mapFunc), ); err != nil { - return fmt.Errorf("watch template: %w", err) + return fmt.Errorf("watch %T: %w", kindType, err) } } @@ -274,6 +299,9 @@ func registerRunnableController(mgr manager.Manager) error { reconciler := &runnable.Reconciler{ Repo: repo, Realizer: realizerrunnable.NewRealizer(), + RunnableCache: repository.NewCache(mgr.GetLogger().WithName("runnable-stamping-repo-cache")), + RepositoryBuilder: repository.NewRepository, + ClientBuilder: realizerclient.NewClientBuilder(mgr.GetConfig()), ConditionManagerBuilder: conditions.NewConditionManager, } ctrl, err := pkgcontroller.New("runnable-service", mgr, pkgcontroller.Options{ @@ -297,11 +325,22 @@ func registerRunnableController(mgr manager.Manager) error { Logger: mgr.GetLogger().WithName("runnable"), } - if err := ctrl.Watch( - &source.Kind{Type: &v1alpha1.ClusterRunTemplate{}}, - handler.EnqueueRequestsFromMapFunc(mapper.RunTemplateToRunnableRequests), - ); err != nil { - return fmt.Errorf("watch: %w", err) + watches := map[client.Object]handler.MapFunc{ + &v1alpha1.ClusterRunTemplate{}: mapper.RunTemplateToRunnableRequests, + &corev1.ServiceAccount{}: mapper.ServiceAccountToRunnableRequests, + &rbacv1.Role{}: mapper.RoleToRunnableRequests, + &rbacv1.RoleBinding{}: mapper.RoleBindingToRunnableRequests, + &rbacv1.ClusterRole{}: mapper.ClusterRoleToRunnableRequests, + &rbacv1.ClusterRoleBinding{}: mapper.ClusterRoleBindingToRunnableRequests, + } + + for kindType, mapFunc := range watches { + if err := ctrl.Watch( + &source.Kind{Type: kindType}, + handler.EnqueueRequestsFromMapFunc(mapFunc), + ); err != nil { + return fmt.Errorf("watch %T: %w", kindType, err) + } } return nil diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 2e1499cba..0149960ee 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -19,7 +19,9 @@ import ( "fmt" "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -48,8 +50,11 @@ type Repository interface { ListUnstructured(ctx context.Context, obj *unstructured.Unstructured) ([]*unstructured.Unstructured, error) GetDelivery(ctx context.Context, name string) (*v1alpha1.ClusterDelivery, error) GetScheme() *runtime.Scheme + GetServiceAccountSecret(ctx context.Context, serviceAccountName, ns string) (*corev1.Secret, error) } +type RepositoryBuilder func(client client.Client, repoCache RepoCache) Repository + type repository struct { rc RepoCache cl client.Client @@ -62,6 +67,45 @@ func NewRepository(client client.Client, repoCache RepoCache) Repository { } } +func (r *repository) GetServiceAccountSecret(ctx context.Context, serviceAccountName, ns string) (*corev1.Secret, error) { + serviceAccount := &corev1.ServiceAccount{} + + key := client.ObjectKey{ + Name: serviceAccountName, + Namespace: ns, + } + + err := r.cl.Get(ctx, key, serviceAccount) + + if err != nil { + return nil, fmt.Errorf("getting service account: %w", err) + } + + if len(serviceAccount.Secrets) == 0 { + return nil, fmt.Errorf("service account '%s' does not have any secrets", serviceAccountName) + } + + for _, secretRef := range serviceAccount.Secrets { + secret := &corev1.Secret{} + + secretKey := client.ObjectKey{ + Name: secretRef.Name, + Namespace: ns, + } + + err = r.cl.Get(ctx, secretKey, secret) + if err != nil { + return nil, fmt.Errorf("getting service account secret: %w", err) + } + + if secret.Type == corev1.SecretTypeServiceAccountToken { + return secret, nil + } + } + + return nil, fmt.Errorf("service account '%s' does not have any token secrets", serviceAccountName) +} + func (r *repository) GetDelivery(ctx context.Context, name string) (*v1alpha1.ClusterDelivery, error) { log := logr.FromContextOrDiscard(ctx) log.V(logger.DEBUG).Info("GetDelivery") diff --git a/pkg/repository/repository_test.go b/pkg/repository/repository_test.go index 30af19d1f..fcd57c238 100644 --- a/pkg/repository/repository_test.go +++ b/pkg/repository/repository_test.go @@ -16,7 +16,9 @@ package repository_test import ( "context" + "encoding/json" "errors" + "fmt" "reflect" . "github.com/onsi/ginkgo" @@ -537,6 +539,212 @@ spec: }) + Describe("GetServiceAccountSecret", func() { + Context("when the service account and secret exist", func() { + var ( + serviceAccount *v1.ServiceAccount + serviceAccountSecret *v1.Secret + serviceAccountName string + ) + + BeforeEach(func() { + serviceAccountName = "my-service-account" + serviceAccountSecretName := "my-service-account-secret" + + serviceAccountSecret = &v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceAccountSecretName, + Annotations: map[string]string{ + "kubernetes.io/service-account.name": serviceAccountName, + }, + }, + Data: map[string][]byte{ + "token": []byte("ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU="), + }, + Type: v1.SecretTypeServiceAccountToken, + } + + serviceAccount = &v1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceAccountName, + }, + Secrets: []v1.ObjectReference{ + { + Name: serviceAccountSecretName, + }, + }, + } + + cl.GetStub = func(_ context.Context, key client.ObjectKey, obj client.Object) error { + if key.Name == serviceAccountName { + bytes, _ := json.Marshal(serviceAccount) + _ = json.Unmarshal(bytes, obj) + } else if key.Name == serviceAccountSecretName { + bytes, _ := json.Marshal(serviceAccountSecret) + _ = json.Unmarshal(bytes, obj) + } else { + Fail(fmt.Sprintf("unexpected get call for name %s", key.Name)) + } + return nil + } + }) + + It("returns the secret associated with the specified service account", func() { + secret, err := repo.GetServiceAccountSecret(context.TODO(), serviceAccountName, "") + Expect(err).NotTo(HaveOccurred()) + + Expect(secret).To(Equal(serviceAccountSecret)) + }) + }) + + Context("when there is an error getting the service account", func() { + BeforeEach(func() { + cl.GetReturnsOnCall(0, fmt.Errorf("some error")) + }) + + It("returns a helpful error message", func() { + _, err := repo.GetServiceAccountSecret(context.TODO(), "some-service-account", "") + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring("getting service account")) + }) + }) + + Context("when there is an error getting the service account secret", func() { + var ( + serviceAccount *v1.ServiceAccount + serviceAccountName string + ) + + BeforeEach(func() { + serviceAccountName = "my-service-account" + serviceAccountSecretName := "my-service-account-secret" + + serviceAccount = &v1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceAccountName, + }, + Secrets: []v1.ObjectReference{ + { + Name: serviceAccountSecretName, + }, + }, + } + + cl.GetStub = func(_ context.Context, key client.ObjectKey, obj client.Object) error { + if key.Name == serviceAccountName { + bytes, _ := json.Marshal(serviceAccount) + _ = json.Unmarshal(bytes, obj) + } else if key.Name == serviceAccountSecretName { + return fmt.Errorf("some error") + } else { + Fail(fmt.Sprintf("unexpected get call for name %s", key.Name)) + } + return nil + } + }) + + It("returns a helpful error message", func() { + _, err := repo.GetServiceAccountSecret(context.TODO(), serviceAccountName, "") + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring("getting service account secret")) + }) + }) + + Context("when the service account does not have any secrets", func() { + var ( + serviceAccount *v1.ServiceAccount + serviceAccountName string + ) + + BeforeEach(func() { + serviceAccountName = "my-service-account" + + serviceAccount = &v1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceAccountName, + }, + Secrets: []v1.ObjectReference{}, + } + + cl.GetStub = func(_ context.Context, key client.ObjectKey, obj client.Object) error { + if key.Name == serviceAccountName { + bytes, _ := json.Marshal(serviceAccount) + _ = json.Unmarshal(bytes, obj) + } else { + Fail(fmt.Sprintf("unexpected get call for name %s", key.Name)) + } + return nil + } + }) + + It("returns a helpful error message", func() { + _, err := repo.GetServiceAccountSecret(context.TODO(), serviceAccountName, "") + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("service account '%s' does not have any secrets", serviceAccountName))) + }) + }) + + Context("when the service account does not have any token secrets", func() { + var ( + serviceAccount *v1.ServiceAccount + secret *v1.Secret + serviceAccountName string + ) + + BeforeEach(func() { + serviceAccountName = "my-service-account" + secretName := "my-secret" + + secret = &v1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + Type: v1.SecretTypeBasicAuth, + } + + serviceAccount = &v1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: serviceAccountName, + }, + Secrets: []v1.ObjectReference{ + { + Name: secretName, + }, + }, + } + + cl.GetStub = func(_ context.Context, key client.ObjectKey, obj client.Object) error { + if key.Name == serviceAccountName { + bytes, _ := json.Marshal(serviceAccount) + _ = json.Unmarshal(bytes, obj) + } else if key.Name == secretName { + bytes, _ := json.Marshal(secret) + _ = json.Unmarshal(bytes, obj) + } else { + Fail(fmt.Sprintf("unexpected get call for name %s", key.Name)) + } + return nil + } + }) + + It("returns a helpful error message", func() { + _, err := repo.GetServiceAccountSecret(context.TODO(), serviceAccountName, "") + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("service account '%s' does not have any token secrets", serviceAccountName))) + }) + }) + }) + }) Describe("tests using apiMachinery fake client", func() { diff --git a/pkg/repository/repositoryfakes/fake_repository.go b/pkg/repository/repositoryfakes/fake_repository.go index 6eb8ce1cd..71cc566e8 100644 --- a/pkg/repository/repositoryfakes/fake_repository.go +++ b/pkg/repository/repositoryfakes/fake_repository.go @@ -7,6 +7,7 @@ import ( "github.com/vmware-tanzu/cartographer/pkg/apis/v1alpha1" "github.com/vmware-tanzu/cartographer/pkg/repository" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -136,6 +137,21 @@ type FakeRepository struct { getSchemeReturnsOnCall map[int]struct { result1 *runtime.Scheme } + GetServiceAccountSecretStub func(context.Context, string, string) (*v1.Secret, error) + getServiceAccountSecretMutex sync.RWMutex + getServiceAccountSecretArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + } + getServiceAccountSecretReturns struct { + result1 *v1.Secret + result2 error + } + getServiceAccountSecretReturnsOnCall map[int]struct { + result1 *v1.Secret + result2 error + } GetSupplyChainStub func(context.Context, string) (*v1alpha1.ClusterSupplyChain, error) getSupplyChainMutex sync.RWMutex getSupplyChainArgsForCall []struct { @@ -782,6 +798,72 @@ func (fake *FakeRepository) GetSchemeReturnsOnCall(i int, result1 *runtime.Schem }{result1} } +func (fake *FakeRepository) GetServiceAccountSecret(arg1 context.Context, arg2 string, arg3 string) (*v1.Secret, error) { + fake.getServiceAccountSecretMutex.Lock() + ret, specificReturn := fake.getServiceAccountSecretReturnsOnCall[len(fake.getServiceAccountSecretArgsForCall)] + fake.getServiceAccountSecretArgsForCall = append(fake.getServiceAccountSecretArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + }{arg1, arg2, arg3}) + stub := fake.GetServiceAccountSecretStub + fakeReturns := fake.getServiceAccountSecretReturns + fake.recordInvocation("GetServiceAccountSecret", []interface{}{arg1, arg2, arg3}) + fake.getServiceAccountSecretMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeRepository) GetServiceAccountSecretCallCount() int { + fake.getServiceAccountSecretMutex.RLock() + defer fake.getServiceAccountSecretMutex.RUnlock() + return len(fake.getServiceAccountSecretArgsForCall) +} + +func (fake *FakeRepository) GetServiceAccountSecretCalls(stub func(context.Context, string, string) (*v1.Secret, error)) { + fake.getServiceAccountSecretMutex.Lock() + defer fake.getServiceAccountSecretMutex.Unlock() + fake.GetServiceAccountSecretStub = stub +} + +func (fake *FakeRepository) GetServiceAccountSecretArgsForCall(i int) (context.Context, string, string) { + fake.getServiceAccountSecretMutex.RLock() + defer fake.getServiceAccountSecretMutex.RUnlock() + argsForCall := fake.getServiceAccountSecretArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeRepository) GetServiceAccountSecretReturns(result1 *v1.Secret, result2 error) { + fake.getServiceAccountSecretMutex.Lock() + defer fake.getServiceAccountSecretMutex.Unlock() + fake.GetServiceAccountSecretStub = nil + fake.getServiceAccountSecretReturns = struct { + result1 *v1.Secret + result2 error + }{result1, result2} +} + +func (fake *FakeRepository) GetServiceAccountSecretReturnsOnCall(i int, result1 *v1.Secret, result2 error) { + fake.getServiceAccountSecretMutex.Lock() + defer fake.getServiceAccountSecretMutex.Unlock() + fake.GetServiceAccountSecretStub = nil + if fake.getServiceAccountSecretReturnsOnCall == nil { + fake.getServiceAccountSecretReturnsOnCall = make(map[int]struct { + result1 *v1.Secret + result2 error + }) + } + fake.getServiceAccountSecretReturnsOnCall[i] = struct { + result1 *v1.Secret + result2 error + }{result1, result2} +} + func (fake *FakeRepository) GetSupplyChain(arg1 context.Context, arg2 string) (*v1alpha1.ClusterSupplyChain, error) { fake.getSupplyChainMutex.Lock() ret, specificReturn := fake.getSupplyChainReturnsOnCall[len(fake.getSupplyChainArgsForCall)] @@ -1126,6 +1208,8 @@ func (fake *FakeRepository) Invocations() map[string][][]interface{} { defer fake.getRunnableMutex.RUnlock() fake.getSchemeMutex.RLock() defer fake.getSchemeMutex.RUnlock() + fake.getServiceAccountSecretMutex.RLock() + defer fake.getServiceAccountSecretMutex.RUnlock() fake.getSupplyChainMutex.RLock() defer fake.getSupplyChainMutex.RUnlock() fake.getSupplyChainsForWorkloadMutex.RLock() diff --git a/pkg/utils/kubeconfigrestricted.go b/pkg/utils/kubeconfigrestricted.go new file mode 100644 index 000000000..031bd7281 --- /dev/null +++ b/pkg/utils/kubeconfigrestricted.go @@ -0,0 +1,86 @@ +// Copyright 2020 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "fmt" + + "github.com/go-yaml/yaml" + clientcmd "k8s.io/client-go/tools/clientcmd/api/v1" +) + +type KubeconfigRestricted struct { + result string +} + +// NewKubeconfigRestricted takes kubeconfig yaml as input and returns kubeconfig yaml with certain fields restricted (removed). +// Developers may find it informative to view their own config at ~/.kube/config +func NewKubeconfigRestricted(input string) (*KubeconfigRestricted, error) { + var inputConfig clientcmd.Config + + err := yaml.Unmarshal([]byte(input), &inputConfig) + if err != nil { + return nil, fmt.Errorf("Parsing kubeconfig: %s", err) + } + + if len(inputConfig.Clusters) == 0 { + return nil, fmt.Errorf("Expected to find at least one cluster in kubeconfig") + } + + resultConfig := clientcmd.Config{ + Kind: inputConfig.Kind, + APIVersion: inputConfig.APIVersion, + CurrentContext: inputConfig.CurrentContext, + } + + for _, inputCluster := range inputConfig.Clusters { + resultConfig.Clusters = append(resultConfig.Clusters, clientcmd.NamedCluster{ + Name: inputCluster.Name, + Cluster: clientcmd.Cluster{ + Server: inputCluster.Cluster.Server, + TLSServerName: inputCluster.Cluster.TLSServerName, + InsecureSkipTLSVerify: inputCluster.Cluster.InsecureSkipTLSVerify, + CertificateAuthorityData: inputCluster.Cluster.CertificateAuthorityData, + ProxyURL: inputCluster.Cluster.ProxyURL, + }, + }) + } + + for _, inputAI := range inputConfig.AuthInfos { + resultConfig.AuthInfos = append(resultConfig.AuthInfos, clientcmd.NamedAuthInfo{ + Name: inputAI.Name, + AuthInfo: clientcmd.AuthInfo{ + ClientCertificateData: inputAI.AuthInfo.ClientCertificateData, + ClientKeyData: inputAI.AuthInfo.ClientKeyData, + Token: inputAI.AuthInfo.Token, + Impersonate: inputAI.AuthInfo.Impersonate, + ImpersonateGroups: inputAI.AuthInfo.ImpersonateGroups, + ImpersonateUserExtra: inputAI.AuthInfo.ImpersonateUserExtra, + Username: inputAI.AuthInfo.Username, + Password: inputAI.AuthInfo.Password, + AuthProvider: inputAI.AuthInfo.AuthProvider, + }, + }) + } + + for _, inputCtx := range inputConfig.Contexts { + resultConfig.Contexts = append(resultConfig.Contexts, clientcmd.NamedContext{ + Name: inputCtx.Name, + Context: clientcmd.Context{ + Cluster: inputCtx.Context.Cluster, + AuthInfo: inputCtx.Context.AuthInfo, + Namespace: inputCtx.Context.Namespace, + }, + }) + } + + bs, err := yaml.Marshal(resultConfig) + if err != nil { + return nil, fmt.Errorf("Marshaling kubeconfig: %s", err) + } + + return &KubeconfigRestricted{string(bs)}, nil +} + +func (r *KubeconfigRestricted) AsYAML() string { return r.result } diff --git a/pkg/utils/service_account.go b/pkg/utils/service_account.go new file mode 100644 index 000000000..a67ff65ad --- /dev/null +++ b/pkg/utils/service_account.go @@ -0,0 +1,132 @@ +// Copyright 2020 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + "encoding/base64" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +type ServiceAccounts struct { + coreClient kubernetes.Interface +} + +func (s *ServiceAccounts) Find(nsName string, saName string) (*KubeconfigRestricted, error) { + kubeconfigYAML, err := s.fetchServiceAccount(nsName, saName) + if err != nil { + return nil, err + } + + kubeconfigRestricted, err := NewKubeconfigRestricted(kubeconfigYAML) + if err != nil { + return nil, err + } + + return kubeconfigRestricted, nil +} + +func (s *ServiceAccounts) fetchServiceAccount(nsName string, saName string) (string, error) { + if len(nsName) == 0 { + return "", fmt.Errorf("Internal inconsistency: Expected namespace name to not be empty") + } + if len(saName) == 0 { + return "", fmt.Errorf("Internal inconsistency: Expected service account name to not be empty") + } + + sa, err := s.coreClient.CoreV1().ServiceAccounts(nsName).Get(context.Background(), saName, metav1.GetOptions{}) + if err != nil { + return "", fmt.Errorf("Getting service account: %s", err) + } + + for _, secretRef := range sa.Secrets { + secret, err := s.coreClient.CoreV1().Secrets(nsName).Get(context.Background(), secretRef.Name, metav1.GetOptions{}) + if err != nil { + return "", fmt.Errorf("Getting service account secret: %s", err) + } + + if secret.Type != corev1.SecretTypeServiceAccountToken { + continue + } + + return s.MakeKubeConfig(secret) + } + + return "", fmt.Errorf("Expected to find one service account token secret, but found none") +} + +func (s *ServiceAccounts) MakeKubeConfig(secret *corev1.Secret) (string, error) { + caBytes, found := secret.Data[corev1.ServiceAccountRootCAKey] + if !found { + return "", fmt.Errorf("Expected to find service account token ca") + } + + tokenBytes, found := secret.Data[corev1.ServiceAccountTokenKey] + if !found { + return "", fmt.Errorf("Expected to find service account token value") + } + + nsBytes, found := secret.Data[corev1.ServiceAccountNamespaceKey] + if !found { + return "", fmt.Errorf("Expected to find service account token namespace") + } + + const kubeconfigYAMLTpl = ` +apiVersion: v1 +kind: Config +clusters: +- name: dst-cluster + cluster: + certificate-authority-data: "%s" + server: https://${KAPP_KUBERNETES_SERVICE_HOST_PORT} +users: +- name: dst-user + user: + token: "%s" +contexts: +- name: dst-ctx + context: + cluster: dst-cluster + namespace: "%s" + user: dst-user +current-context: dst-ctx +` + + caB64Encoded := base64.StdEncoding.EncodeToString(caBytes) + + return fmt.Sprintf(kubeconfigYAMLTpl, caB64Encoded, tokenBytes, nsBytes), nil +} + +/* + +Example SA + secret: + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: app1-sa + namespace: app1 +secrets: +- name: app1-sa-token-grr7z +--- +apiVersion: v1 +kind: Secret +metadata: + name: app1-sa-token-grr7z + namespace: app1 + annotations: + kubernetes.io/service-account.name: app1-sa + kubernetes.io/service-account.uid: 26675b19-769a-4145-a386-7ca2b3ab3435 +type: kubernetes.io/service-account-token +data: + ca.crt: LS0tLS... + namespace: a2FwcC1jb250cm9sbGVy + token: ZXlKaGJ... + +*/ \ No newline at end of file diff --git a/tests/integration/delivery/deliverable_reconciler_test.go b/tests/integration/delivery/deliverable_reconciler_test.go index 2142712d0..dbaa248f1 100644 --- a/tests/integration/delivery/deliverable_reconciler_test.go +++ b/tests/integration/delivery/deliverable_reconciler_test.go @@ -162,6 +162,42 @@ var _ = Describe("DeliverableReconciler", func() { BeforeEach(func() { ctx = context.Background() + + myServiceAccountSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service-account-secret", + Namespace: testNS, + Annotations: map[string]string{ + "kubernetes.io/service-account.name": "my-service-account", + }, + }, + Data: map[string][]byte{ + "token": []byte("ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU="), + }, + Type: corev1.SecretTypeServiceAccountToken, + } + + myServiceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service-account", + Namespace: testNS, + }, + Secrets: []corev1.ObjectReference{ + { + Name: "my-service-account-secret", + }, + }, + } + + cleanups = append(cleanups, myServiceAccountSecret) + err := c.Create(ctx, myServiceAccountSecret, &client.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cleanups = append(cleanups, myServiceAccount) + err = c.Create(ctx, myServiceAccount, &client.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { @@ -170,29 +206,6 @@ var _ = Describe("DeliverableReconciler", func() { } }) - Context("when deliverable enters exponential backoff from lack of matching delivery", func() { - BeforeEach(func() { - deliverableYaml := utils.HereYaml(` - --- - apiVersion: carto.run/v1alpha1 - kind: Deliverable - metadata: - name: deliverable-bob - labels: - name: webapp - spec: - source: - git: - url: https://github.com/ekcasey/hello-world-ops - ref: - branch: prod - `) - - deliverable := createObject(ctx, deliverableYaml, testNS) - cleanups = append(cleanups, deliverable) - }) - }) - Context("when the deliverable is installed", func() { BeforeEach(func() { deliverableYaml := utils.HereYaml(` @@ -204,6 +217,7 @@ var _ = Describe("DeliverableReconciler", func() { labels: name: webapp spec: + serviceAccountName: my-service-account source: git: url: https://github.com/ekcasey/hello-world-ops diff --git a/tests/integration/delivery/delivery_test.go b/tests/integration/delivery/delivery_test.go index deaa9b819..59e00bf53 100644 --- a/tests/integration/delivery/delivery_test.go +++ b/tests/integration/delivery/delivery_test.go @@ -18,6 +18,8 @@ import ( "context" "time" + corev1 "k8s.io/api/core/v1" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" @@ -450,6 +452,41 @@ var _ = Describe("Deliveries", func() { err = c.Create(ctx, delivery, &client.CreateOptions{}) cleanups = append(cleanups, delivery) Expect(err).NotTo(HaveOccurred()) + myServiceAccountSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service-account-secret", + Namespace: testNS, + Annotations: map[string]string{ + "kubernetes.io/service-account.name": "my-service-account", + }, + }, + Data: map[string][]byte{ + "token": []byte("ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU="), + }, + Type: corev1.SecretTypeServiceAccountToken, + } + + myServiceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service-account", + Namespace: testNS, + }, + Secrets: []corev1.ObjectReference{ + { + Name: "my-service-account-secret", + }, + }, + } + + cleanups = append(cleanups, myServiceAccountSecret) + err = c.Create(ctx, myServiceAccountSecret, &client.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cleanups = append(cleanups, myServiceAccount) + err = c.Create(ctx, myServiceAccount, &client.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) deliverable := &v1alpha1.Deliverable{ TypeMeta: metav1.TypeMeta{}, @@ -460,6 +497,9 @@ var _ = Describe("Deliveries", func() { "some-key": "some-value", }, }, + Spec: v1alpha1.DeliverableSpec{ + ServiceAccountName: "my-service-account", + }, } cleanups = append(cleanups, deliverable) diff --git a/tests/integration/runnable/runnable_test.go b/tests/integration/runnable/runnable_test.go index 03065c49a..452b7826a 100644 --- a/tests/integration/runnable/runnable_test.go +++ b/tests/integration/runnable/runnable_test.go @@ -40,6 +40,7 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { ctx context.Context runnableDefinition *unstructured.Unstructured runTemplateDefinition *unstructured.Unstructured + serviceAccountName string ) var createNamespacedObject = func(ctx context.Context, objYaml, namespace string) *unstructured.Unstructured { @@ -58,6 +59,37 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { BeforeEach(func() { ctx = context.Background() + + serviceAccountName = "my-service-account" + + serviceAccountSecretYaml := HereYamlF(`--- + apiVersion: v1 + kind: Secret + metadata: + namespace: %s + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account + data: + token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU= + type: kubernetes.io/service-account-token + `, + testNS) + + _ = createNamespacedObject(ctx, serviceAccountSecretYaml, testNS) + + serviceAccountYaml := HereYamlF(`--- + apiVersion: v1 + kind: ServiceAccount + metadata: + namespace: %s + name: %s + secrets: + - name: my-service-account-secret + `, + testNS, serviceAccountName) + + _ = createNamespacedObject(ctx, serviceAccountYaml, testNS) }) getRunnableTestStatus := func() (metav1.Condition, error) { @@ -121,6 +153,7 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { namespace: %s name: my-runnable spec: + serviceAccountName: %s runTemplateRef: name: my-run-template namespace: %s @@ -128,7 +161,7 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { inputs: key: val `, - testNS, testNS) + testNS, serviceAccountName, testNS) runnableDefinition = createNamespacedObject(ctx, runnableYaml, testNS) }) @@ -239,12 +272,13 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { namespace: %s name: my-runnable spec: + serviceAccountName: %s runTemplateRef: name: my-run-template-does-not-match namespace: %s kind: ClusterRunTemplate `, - testNS, testNS) + testNS, serviceAccountName, testNS) runnableDefinition = &unstructured.Unstructured{} err := yaml.Unmarshal([]byte(runnableYaml), runnableDefinition) @@ -318,6 +352,8 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { labels: my-label: this-is-it spec: + serviceAccountName: %s + runTemplateRef: name: run-template---multi-label-selector namespace: %s @@ -329,7 +365,7 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { matchingLabels: runnables.carto.run/group: dev---multi-label-selector runnables.carto.run/stage: production - `, testNS) + `, serviceAccountName, testNS) runnableDefinition = createNamespacedObject(ctx, runnableYaml, testNS) }) @@ -454,6 +490,8 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { labels: my-label: this-is-it spec: + serviceAccountName: %s + runTemplateRef: name: run-template---multi-label-selector namespace: %s @@ -465,7 +503,7 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { matchingLabels: runnables.carto.run/group: dev---multi-label-selector runnables.carto.run/stage: production - `, testNS) + `, serviceAccountName, testNS) runnableDefinition = createNamespacedObject(ctx, runnableYaml, testNS) }) @@ -560,12 +598,13 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { labels: some-val: first spec: + serviceAccountName: %s runTemplateRef: name: my-run-template namespace: %s kind: ClusterRunTemplate `, - testNS, testNS) + testNS, serviceAccountName, testNS) runnableDefinition = &unstructured.Unstructured{} err = yaml.Unmarshal([]byte(runnableYaml), runnableDefinition) @@ -726,12 +765,13 @@ var _ = Describe("Stamping a resource on Runnable Creation", func() { labels: some-val: first spec: + serviceAccountName: %s runTemplateRef: name: my-run-template namespace: %s kind: ClusterRunTemplate `, - testNS, testNS) + testNS, serviceAccountName, testNS) runnableDefinition = &unstructured.Unstructured{} err := yaml.Unmarshal([]byte(runnableYaml), runnableDefinition) diff --git a/tests/integration/supplychain/workload_reconciler_test.go b/tests/integration/supplychain/workload_reconciler_test.go index a20a0b84f..ce660552d 100644 --- a/tests/integration/supplychain/workload_reconciler_test.go +++ b/tests/integration/supplychain/workload_reconciler_test.go @@ -74,6 +74,7 @@ var _ = Describe("WorkloadReconciler", func() { err := c.Get(context.Background(), client.ObjectKey{Name: "workload-bob", Namespace: testNS}, workload) Expect(err).NotTo(HaveOccurred()) + workload.Spec.ServiceAccountName = "my-service-account" workload.Spec.Params = []v1alpha1.Param{{Name: "foo", Value: apiextensionsv1.JSON{ Raw: []byte(`"definitelybar"`), }}} @@ -95,6 +96,42 @@ var _ = Describe("WorkloadReconciler", func() { BeforeEach(func() { ctx = context.Background() + + myServiceAccountSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service-account-secret", + Namespace: testNS, + Annotations: map[string]string{ + "kubernetes.io/service-account.name": "my-service-account", + }, + }, + Data: map[string][]byte{ + "token": []byte("ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU="), + }, + Type: corev1.SecretTypeServiceAccountToken, + } + + myServiceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-service-account", + Namespace: testNS, + }, + Secrets: []corev1.ObjectReference{ + { + Name: "my-service-account-secret", + }, + }, + } + + cleanups = append(cleanups, myServiceAccountSecret) + err := c.Create(ctx, myServiceAccountSecret, &client.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cleanups = append(cleanups, myServiceAccount) + err = c.Create(ctx, myServiceAccount, &client.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { @@ -114,7 +151,7 @@ var _ = Describe("WorkloadReconciler", func() { "name": "webapp", }, }, - Spec: v1alpha1.WorkloadSpec{}, + Spec: v1alpha1.WorkloadSpec{ServiceAccountName: "my-service-account"}, } cleanups = append(cleanups, workload) @@ -265,7 +302,7 @@ var _ = Describe("WorkloadReconciler", func() { "some-key": "some-value", }, }, - Spec: v1alpha1.WorkloadSpec{}, + Spec: v1alpha1.WorkloadSpec{ServiceAccountName: "my-service-account"}, } cleanups = append(cleanups, workload) diff --git a/tests/kuttl/delivery/deliverable/00-service-account.yaml b/tests/kuttl/delivery/deliverable/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/delivery/deliverable/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/delivery/deliverable/01-deliverable.yaml b/tests/kuttl/delivery/deliverable/01-deliverable.yaml index 0e37c8ed4..b9c39bed5 100644 --- a/tests/kuttl/delivery/deliverable/01-deliverable.yaml +++ b/tests/kuttl/delivery/deliverable/01-deliverable.yaml @@ -20,6 +20,7 @@ metadata: labels: app.tanzu.vmware.com/deliverable-type: web---deliverable spec: + serviceAccountName: my-service-account source: git: url: https://github.com/ekcasey/hello-world-ops diff --git a/tests/kuttl/delivery/deploy-w-bad-match/00-service-account.yaml b/tests/kuttl/delivery/deploy-w-bad-match/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/delivery/deploy-w-bad-match/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/delivery/deploy-w-bad-match/01-deliverable.yaml b/tests/kuttl/delivery/deploy-w-bad-match/01-deliverable.yaml index a5f681d73..118f7030b 100644 --- a/tests/kuttl/delivery/deploy-w-bad-match/01-deliverable.yaml +++ b/tests/kuttl/delivery/deploy-w-bad-match/01-deliverable.yaml @@ -20,6 +20,7 @@ metadata: labels: app.tanzu.vmware.com/deliverable-type: web---deploy-w-bad-match spec: + serviceAccountName: my-service-account source: git: url: https://github.com/ekcasey/hello-world-ops diff --git a/tests/kuttl/delivery/deploy-w-missing-match/00-service-account.yaml b/tests/kuttl/delivery/deploy-w-missing-match/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/delivery/deploy-w-missing-match/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/delivery/deploy-w-missing-match/01-deliverable.yaml b/tests/kuttl/delivery/deploy-w-missing-match/01-deliverable.yaml index 7ed058076..97589aa32 100644 --- a/tests/kuttl/delivery/deploy-w-missing-match/01-deliverable.yaml +++ b/tests/kuttl/delivery/deploy-w-missing-match/01-deliverable.yaml @@ -20,6 +20,7 @@ metadata: labels: app.tanzu.vmware.com/deliverable-type: web---deploy-w-missing-match spec: + serviceAccountName: my-service-account source: git: url: https://github.com/ekcasey/hello-world-ops diff --git a/tests/kuttl/delivery/params-delivery/00-service-account.yaml b/tests/kuttl/delivery/params-delivery/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/delivery/params-delivery/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/delivery/params-delivery/01-deliverable.yaml b/tests/kuttl/delivery/params-delivery/01-deliverable.yaml index f8c01ac91..bc9d98f3e 100644 --- a/tests/kuttl/delivery/params-delivery/01-deliverable.yaml +++ b/tests/kuttl/delivery/params-delivery/01-deliverable.yaml @@ -20,6 +20,7 @@ metadata: labels: app.tanzu.vmware.com/deliverable-type: web---params-delivery spec: + serviceAccountName: my-service-account source: git: url: https://github.com/ekcasey/hello-world-ops diff --git a/tests/kuttl/runnable/default/00-service-account.yaml b/tests/kuttl/runnable/default/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/runnable/default/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/runnable/default/01-runnable.yaml b/tests/kuttl/runnable/default/01-runnable.yaml index e25646117..1ca809719 100644 --- a/tests/kuttl/runnable/default/01-runnable.yaml +++ b/tests/kuttl/runnable/default/01-runnable.yaml @@ -20,5 +20,6 @@ metadata: labels: my-label: this-is-it spec: + serviceAccountName: my-service-account runTemplateRef: name: my-run-template diff --git a/tests/kuttl/runnable/multi-label-selector/00-service-account.yaml b/tests/kuttl/runnable/multi-label-selector/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/runnable/multi-label-selector/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/runnable/multi-label-selector/01-runnable.yaml b/tests/kuttl/runnable/multi-label-selector/01-runnable.yaml index 44594821f..b26e236cf 100644 --- a/tests/kuttl/runnable/multi-label-selector/01-runnable.yaml +++ b/tests/kuttl/runnable/multi-label-selector/01-runnable.yaml @@ -20,6 +20,8 @@ metadata: labels: my-label: this-is-it spec: + serviceAccountName: my-service-account + runTemplateRef: name: run-template---multi-label-selector diff --git a/tests/kuttl/runnable/runnable-with-inputs/00-service-account.yaml b/tests/kuttl/runnable/runnable-with-inputs/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/runnable/runnable-with-inputs/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/runnable/runnable-with-inputs/00-assert.yaml b/tests/kuttl/runnable/runnable-with-inputs/01-assert.yaml similarity index 100% rename from tests/kuttl/runnable/runnable-with-inputs/00-assert.yaml rename to tests/kuttl/runnable/runnable-with-inputs/01-assert.yaml diff --git a/tests/kuttl/runnable/runnable-with-inputs/00-runnable.yaml b/tests/kuttl/runnable/runnable-with-inputs/01-runnable.yaml similarity index 93% rename from tests/kuttl/runnable/runnable-with-inputs/00-runnable.yaml rename to tests/kuttl/runnable/runnable-with-inputs/01-runnable.yaml index 37fa5e05e..acc15a822 100644 --- a/tests/kuttl/runnable/runnable-with-inputs/00-runnable.yaml +++ b/tests/kuttl/runnable/runnable-with-inputs/01-runnable.yaml @@ -15,11 +15,13 @@ apiVersion: carto.run/v1alpha1 kind: Runnable metadata: - name: my-runnable + name: my-runnable-inputs labels: my-label: this-is-it my-other-label: bye spec: + serviceAccountName: my-service-account + runTemplateRef: name: my-run-template-inputs diff --git a/tests/kuttl/runnable/runnables-by-label-selector/00-service-account.yaml b/tests/kuttl/runnable/runnables-by-label-selector/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/runnable/runnables-by-label-selector/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/runnable/runnables-by-label-selector/01-runnable.yaml b/tests/kuttl/runnable/runnables-by-label-selector/01-runnable.yaml index 8de844aba..51feac6df 100644 --- a/tests/kuttl/runnable/runnables-by-label-selector/01-runnable.yaml +++ b/tests/kuttl/runnable/runnables-by-label-selector/01-runnable.yaml @@ -20,6 +20,8 @@ metadata: labels: my-label: this-is-it spec: + serviceAccountName: my-service-account + runTemplateRef: name: run-template---runnables-by-label-selector diff --git a/tests/kuttl/runnable/runnables-driven-by-supply-chain/00-service-account.yaml b/tests/kuttl/runnable/runnables-driven-by-supply-chain/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/runnable/runnables-driven-by-supply-chain/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/runnable/runnables-driven-by-supply-chain/00-templates.yaml b/tests/kuttl/runnable/runnables-driven-by-supply-chain/00-templates.yaml index 5ec6f6846..22f4c114b 100644 --- a/tests/kuttl/runnable/runnables-driven-by-supply-chain/00-templates.yaml +++ b/tests/kuttl/runnable/runnables-driven-by-supply-chain/00-templates.yaml @@ -43,6 +43,8 @@ spec: - name: a-config-map default: key: value + - name: service-account-name + default: default template: apiVersion: carto.run/v1alpha1 @@ -52,6 +54,8 @@ spec: labels: my-label: this-is-it spec: + serviceAccountName: $(params.service-account-name)$ + runTemplateRef: name: my-run-template-driven-by-supply-chain diff --git a/tests/kuttl/runnable/runnables-driven-by-supply-chain/01-supply-chain.yaml b/tests/kuttl/runnable/runnables-driven-by-supply-chain/01-supply-chain.yaml index dd7c5811b..30b78a5ba 100644 --- a/tests/kuttl/runnable/runnables-driven-by-supply-chain/01-supply-chain.yaml +++ b/tests/kuttl/runnable/runnables-driven-by-supply-chain/01-supply-chain.yaml @@ -34,5 +34,7 @@ spec: - resource: source-provider name: source-provider params: + - name: service-account-name + value: $(workload.spec.serviceAccountName)$ - name: a-config-map default: "" diff --git a/tests/kuttl/runnable/runnables-driven-by-supply-chain/02-workload.yaml b/tests/kuttl/runnable/runnables-driven-by-supply-chain/02-workload.yaml index e0ef0ce2d..b4abdfe10 100644 --- a/tests/kuttl/runnable/runnables-driven-by-supply-chain/02-workload.yaml +++ b/tests/kuttl/runnable/runnables-driven-by-supply-chain/02-workload.yaml @@ -20,6 +20,7 @@ metadata: labels: integration-test: "runnables-driven-by-supply-chain" spec: + serviceAccountName: my-service-account params: - name: a-config-map value: diff --git a/tests/kuttl/supplychain/default-params/00-service-account.yaml b/tests/kuttl/supplychain/default-params/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/default-params/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/params-supply-chain/00-service-account.yaml b/tests/kuttl/supplychain/params-supply-chain/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/params-supply-chain/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/params-supply-chain/02-workload.yaml b/tests/kuttl/supplychain/params-supply-chain/02-workload.yaml index f59eace45..936a0b824 100644 --- a/tests/kuttl/supplychain/params-supply-chain/02-workload.yaml +++ b/tests/kuttl/supplychain/params-supply-chain/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "params-supply-chain" spec: + serviceAccountName: my-service-account params: - name: template-and-workload-but-not-supply-chain-nor-resource value: not-me-workload diff --git a/tests/kuttl/supplychain/templates-consume-output-of-components/00-service-account.yaml b/tests/kuttl/supplychain/templates-consume-output-of-components/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/templates-consume-output-of-components/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/templates-consume-output-of-components/02-workload.yaml b/tests/kuttl/supplychain/templates-consume-output-of-components/02-workload.yaml index 4b94e20b5..f28678a7e 100644 --- a/tests/kuttl/supplychain/templates-consume-output-of-components/02-workload.yaml +++ b/tests/kuttl/supplychain/templates-consume-output-of-components/02-workload.yaml @@ -20,6 +20,7 @@ metadata: labels: integration-test: "templates-consume-output-of-resources" spec: + serviceAccountName: my-service-account source: git: url: https://github.com/spring-projects/spring-petclinic.git diff --git a/tests/kuttl/supplychain/templates-refer-to-workload/00-service-account.yaml b/tests/kuttl/supplychain/templates-refer-to-workload/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/templates-refer-to-workload/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/templates-refer-to-workload/02-workload.yaml b/tests/kuttl/supplychain/templates-refer-to-workload/02-workload.yaml index 78c09c2a6..e9388cbb8 100644 --- a/tests/kuttl/supplychain/templates-refer-to-workload/02-workload.yaml +++ b/tests/kuttl/supplychain/templates-refer-to-workload/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "templates-refer-to-workload" spec: + serviceAccountName: my-service-account params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/templates-ytt/00-service-account.yaml b/tests/kuttl/supplychain/templates-ytt/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/templates-ytt/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/templates-ytt/02-workload.yaml b/tests/kuttl/supplychain/templates-ytt/02-workload.yaml index 38d725896..a8546b86c 100644 --- a/tests/kuttl/supplychain/templates-ytt/02-workload.yaml +++ b/tests/kuttl/supplychain/templates-ytt/02-workload.yaml @@ -20,6 +20,7 @@ metadata: labels: integration-test: "templates-ytt" spec: + serviceAccountName: my-service-account source: git: url: https://github.com/spring-projects/spring-petclinic.git diff --git a/tests/kuttl/supplychain/unknown/00-service-account.yaml b/tests/kuttl/supplychain/unknown/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/unknown/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/unknown/02-workload.yaml b/tests/kuttl/supplychain/unknown/02-workload.yaml index 58145a155..9db46008f 100644 --- a/tests/kuttl/supplychain/unknown/02-workload.yaml +++ b/tests/kuttl/supplychain/unknown/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: type: "webapp-unknown" spec: + serviceAccountName: my-service-account params: - name: some-param value: some-value diff --git a/tests/kuttl/supplychain/values-cannot-be-interpolated/00-service-account.yaml b/tests/kuttl/supplychain/values-cannot-be-interpolated/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/values-cannot-be-interpolated/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/values-cannot-be-interpolated/02-workload.yaml b/tests/kuttl/supplychain/values-cannot-be-interpolated/02-workload.yaml index 61b43e778..230266334 100644 --- a/tests/kuttl/supplychain/values-cannot-be-interpolated/02-workload.yaml +++ b/tests/kuttl/supplychain/values-cannot-be-interpolated/02-workload.yaml @@ -19,7 +19,8 @@ metadata: name: petclinic-broken-path labels: integration-test: "broken-path" -spec: {} +spec: + serviceAccountName: my-service-account --- apiVersion: carto.run/v1alpha1 @@ -28,4 +29,5 @@ metadata: name: petclinic-broken-tag labels: integration-test: "broken-tag" -spec: {} +spec: + serviceAccountName: my-service-account diff --git a/tests/kuttl/supplychain/workload-generation/00-service-account.yaml b/tests/kuttl/supplychain/workload-generation/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-generation/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-generation/00-workload.yaml b/tests/kuttl/supplychain/workload-generation/00-workload.yaml index f30522598..6a55ff2e1 100644 --- a/tests/kuttl/supplychain/workload-generation/00-workload.yaml +++ b/tests/kuttl/supplychain/workload-generation/00-workload.yaml @@ -17,6 +17,7 @@ kind: Workload metadata: name: petclinic spec: + serviceAccountName: my-service-account params: - name: waciuma-com/number value: 5 \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-generation/01-workload.yaml b/tests/kuttl/supplychain/workload-generation/01-workload.yaml index db290b7b8..c6e1cdc59 100644 --- a/tests/kuttl/supplychain/workload-generation/01-workload.yaml +++ b/tests/kuttl/supplychain/workload-generation/01-workload.yaml @@ -17,6 +17,7 @@ kind: Workload metadata: name: petclinic spec: + serviceAccountName: my-service-account params: - name: waciuma-com/number value: 20 \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-multiple-labels-multiple-supply-chains/02-workload.yaml b/tests/kuttl/supplychain/workload-multiple-labels-multiple-supply-chains/02-workload.yaml index 2ad358985..554975c8c 100644 --- a/tests/kuttl/supplychain/workload-multiple-labels-multiple-supply-chains/02-workload.yaml +++ b/tests/kuttl/supplychain/workload-multiple-labels-multiple-supply-chains/02-workload.yaml @@ -20,6 +20,7 @@ metadata: integration-test: "workload-multiple-labels-multiple-supply-chains" integration-test2: "workload-multiple-labels-multiple-supply-chains" spec: + serviceAccountName: default params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-multiple-labels-supply-chain-single-selector/00-service-account.yaml b/tests/kuttl/supplychain/workload-multiple-labels-supply-chain-single-selector/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-multiple-labels-supply-chain-single-selector/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-multiple-labels-supply-chain-single-selector/02-workload.yaml b/tests/kuttl/supplychain/workload-multiple-labels-supply-chain-single-selector/02-workload.yaml index a8da12fe9..e5475e61d 100644 --- a/tests/kuttl/supplychain/workload-multiple-labels-supply-chain-single-selector/02-workload.yaml +++ b/tests/kuttl/supplychain/workload-multiple-labels-supply-chain-single-selector/02-workload.yaml @@ -20,6 +20,7 @@ metadata: integration-test: "wl-many-labels-sc-1-selector" integration-test2: "wl-many-labels-sc-1-selector" spec: + serviceAccountName: my-service-account params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-no-supply-chain/00-workload.yaml b/tests/kuttl/supplychain/workload-no-supply-chain/00-workload.yaml index e99e1ece2..5eb7a5963 100644 --- a/tests/kuttl/supplychain/workload-no-supply-chain/00-workload.yaml +++ b/tests/kuttl/supplychain/workload-no-supply-chain/00-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "workload-no-supply-chain" spec: + serviceAccountName: default params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-provided-params/00-service-account.yaml b/tests/kuttl/supplychain/workload-provided-params/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-provided-params/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-single-label-multiple-supply-chains/02-workload.yaml b/tests/kuttl/supplychain/workload-single-label-multiple-supply-chains/02-workload.yaml index 05cf3d39c..d1fd579a6 100644 --- a/tests/kuttl/supplychain/workload-single-label-multiple-supply-chains/02-workload.yaml +++ b/tests/kuttl/supplychain/workload-single-label-multiple-supply-chains/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "workload-multiple-supply-chains-single-label" spec: + serviceAccountName: default params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-subpath-on-sources/00-workload.yaml b/tests/kuttl/supplychain/workload-subpath-on-sources/00-workload.yaml index 6ac81cdbc..77c96890b 100644 --- a/tests/kuttl/supplychain/workload-subpath-on-sources/00-workload.yaml +++ b/tests/kuttl/supplychain/workload-subpath-on-sources/00-workload.yaml @@ -17,6 +17,7 @@ kind: Workload metadata: name: petclinic spec: + serviceAccountName: default params: - name: waciuma-com/number value: 5 diff --git a/tests/kuttl/supplychain/workload-supply-chain-hardcoded-templates/00-service-account.yaml b/tests/kuttl/supplychain/workload-supply-chain-hardcoded-templates/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-supply-chain-hardcoded-templates/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-supply-chain-hardcoded-templates/02-workload.yaml b/tests/kuttl/supplychain/workload-supply-chain-hardcoded-templates/02-workload.yaml index e6f398ffc..9f8482f80 100644 --- a/tests/kuttl/supplychain/workload-supply-chain-hardcoded-templates/02-workload.yaml +++ b/tests/kuttl/supplychain/workload-supply-chain-hardcoded-templates/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "workload-supply-chain-hardcoded-templates" spec: + serviceAccountName: my-service-account params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-supply-chain-malformed-templates/00-service-account.yaml b/tests/kuttl/supplychain/workload-supply-chain-malformed-templates/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-supply-chain-malformed-templates/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-supply-chain-malformed-templates/02-workload.yaml b/tests/kuttl/supplychain/workload-supply-chain-malformed-templates/02-workload.yaml index 9a7209e5d..dfac999fd 100644 --- a/tests/kuttl/supplychain/workload-supply-chain-malformed-templates/02-workload.yaml +++ b/tests/kuttl/supplychain/workload-supply-chain-malformed-templates/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "workload-supply-chain-malformed-templates" spec: + serviceAccountName: my-service-account source: git: url: https://github.com/spring-projects/spring-petclinic.git diff --git a/tests/kuttl/supplychain/workload-supply-chain-multiple-selectors/02-workload.yaml b/tests/kuttl/supplychain/workload-supply-chain-multiple-selectors/02-workload.yaml index c32563083..2bcbb0d6a 100644 --- a/tests/kuttl/supplychain/workload-supply-chain-multiple-selectors/02-workload.yaml +++ b/tests/kuttl/supplychain/workload-supply-chain-multiple-selectors/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "workload-supply-chain-multiple-selectors" spec: + serviceAccountName: default params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-supply-chain-no-templates/00-service-account.yaml b/tests/kuttl/supplychain/workload-supply-chain-no-templates/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-supply-chain-no-templates/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-supply-chain-no-templates/01-workload.yaml b/tests/kuttl/supplychain/workload-supply-chain-no-templates/01-workload.yaml index d73f80953..94478effc 100644 --- a/tests/kuttl/supplychain/workload-supply-chain-no-templates/01-workload.yaml +++ b/tests/kuttl/supplychain/workload-supply-chain-no-templates/01-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "workload-supply-chain-no-templates" spec: + serviceAccountName: my-service-account params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-supply-chain-template-cannot-be-applied/00-service-account.yaml b/tests/kuttl/supplychain/workload-supply-chain-template-cannot-be-applied/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-supply-chain-template-cannot-be-applied/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-supply-chain-template-cannot-be-applied/02-workload.yaml b/tests/kuttl/supplychain/workload-supply-chain-template-cannot-be-applied/02-workload.yaml index 0aa8a3807..39e6f1721 100644 --- a/tests/kuttl/supplychain/workload-supply-chain-template-cannot-be-applied/02-workload.yaml +++ b/tests/kuttl/supplychain/workload-supply-chain-template-cannot-be-applied/02-workload.yaml @@ -19,6 +19,7 @@ metadata: labels: integration-test: "workload-supply-chain-template-cannot-be-applied" spec: + serviceAccountName: my-service-account params: - name: waciuma-com/quality value: beta diff --git a/tests/kuttl/supplychain/workload-without-label/00-service-account.yaml b/tests/kuttl/supplychain/workload-without-label/00-service-account.yaml new file mode 100644 index 000000000..f426e55df --- /dev/null +++ b/tests/kuttl/supplychain/workload-without-label/00-service-account.yaml @@ -0,0 +1,34 @@ +# Copyright 2021 VMware +# +# 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. + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-service-account +secrets: + - name: my-service-account-secret + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: my-service-account-secret + annotations: + kubernetes.io/service-account.name: my-service-account +data: + token: "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklubFNWM1YxVDNSRldESnZVRE4wTUd0R1EzQmlVVlJOVWtkMFNGb3RYMGh2VUhKYU1FRnVOR0Y0WlRBaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTE1TFhOaExYUnZhMlZ1TFd4dVkzRndJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpYSjJhV05sTFdGalkyOTFiblF1Ym1GdFpTSTZJbTE1TFhOaElpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVkV2xrSWpvaU9HSXhNV1V3WldNdFlURTVOeTAwWVdNeUxXRmpORFF0T0RjelpHSmpOVE13TkdKbElpd2ljM1ZpSWpvaWMzbHpkR1Z0T25ObGNuWnBZMlZoWTJOdmRXNTBPbVJsWm1GMWJIUTZiWGt0YzJFaWZRLmplMzRsZ3hpTUtnd0QxUGFhY19UMUZNWHdXWENCZmhjcVhQMEE2VUV2T0F6ek9xWGhpUUdGN2poY3RSeFhmUVFJVEs0Q2tkVmZ0YW5SUjNPRUROTUxVMVBXNXVsV3htVTZTYkMzdmZKT3ozLVJPX3BOVkNmVW8tZURpblN1Wm53bjNzMjNjZU9KM3IzYk04cnBrMHZZZFgyRVlQRGItMnd4cjIzZ1RxUjVxZU5ULW11cS1qYktXVE8wYnRYVl9wVHNjTnFXUkZIVzJBVTVHYVBpbmNWVXg1bXExLXN0SFdOOGtjTG96OF96S2RnUnJGYV92clFjb3NWZzZCRW5MSEt2NW1fVEhaR3AybU8wYmtIV3J1Q2xEUDdLc0tMOFVaZWxvTDN4Y3dQa000VlBBb2V0bDl5MzlvUi1KbWh3RUlIcS1hX3BzaVh5WE9EQU44STcybEZpUSU=" +type: kubernetes.io/service-account-token \ No newline at end of file diff --git a/tests/kuttl/supplychain/workload-without-label/00-workload.yaml b/tests/kuttl/supplychain/workload-without-label/00-workload.yaml index ff06e4816..d2ab516e0 100644 --- a/tests/kuttl/supplychain/workload-without-label/00-workload.yaml +++ b/tests/kuttl/supplychain/workload-without-label/00-workload.yaml @@ -17,6 +17,7 @@ kind: Workload metadata: name: petclinic spec: + serviceAccountName: my-service-account params: - name: waciuma-com/quality value: beta