diff --git a/pkg/kapp/config/default.go b/pkg/kapp/config/default.go index 081fe06f5..fd1c4a466 100644 --- a/pkg/kapp/config/default.go +++ b/pkg/kapp/config/default.go @@ -569,6 +569,14 @@ changeGroupBindings: resourceMatchers: &serviceAccountMatchers - apiVersionKindMatcher: {kind: ServiceAccount, apiVersion: v1} +- name: change-groups.kapp.k14s.io/secret-associated-with-sa + resourceMatchers: + - andMatcher: + matchers: + - apiVersionKindMatcher: {kind: Secret, apiVersion: v1} + - hasAnnotationMatcher: + keys: [kubernetes.io/service-account.name] + - name: change-groups.kapp.k14s.io/kapp-controller-app resourceMatchers: *appMatchers @@ -588,6 +596,12 @@ changeRuleBindings: hasAnnotationMatcher: keys: [kapp.k14s.io/disable-default-change-group-and-rules] +# Create SA before creating secret associated with SA +- rules: + - "upsert before upserting change-groups.kapp.k14s.io/secret-associated-with-sa" + resourceMatchers: + - apiVersionKindMatcher: {kind: ServiceAccount, apiVersion: v1} + # Delete CRs before CRDs to retain detailed observability # instead of having CRD deletion trigger all CR deletion - rules: diff --git a/test/e2e/change_rule_test.go b/test/e2e/change_rule_test.go new file mode 100644 index 000000000..97c87bc37 --- /dev/null +++ b/test/e2e/change_rule_test.go @@ -0,0 +1,119 @@ +// Copyright 2020 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestServiceAccountAssociatedSecretDefaultChangeRule(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + config := ` +apiVersion: v1 +kind: Namespace +metadata: + name: kapp-token-test +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sa-0 + namespace: kapp-token-test +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-0 + namespace: kapp-token-test + annotations: + kubernetes.io/service-account.name: sa-0 +type: kubernetes.io/service-account-token +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sa-1 + namespace: kapp-token-test +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-1 + namespace: kapp-token-test + annotations: + kubernetes.io/service-account.name: sa-1 +type: kubernetes.io/service-account-token +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sa-2 + namespace: kapp-token-test +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-2 + namespace: kapp-token-test + annotations: + kubernetes.io/service-account.name: sa-2 +type: kubernetes.io/service-account-token +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sa-3 + namespace: kapp-token-test +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-3 + namespace: kapp-token-test + annotations: + kubernetes.io/service-account.name: sa-3 +type: kubernetes.io/service-account-token +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sa-4 + namespace: kapp-token-test +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-4 + namespace: kapp-token-test + annotations: + kubernetes.io/service-account.name: sa-4 +type: kubernetes.io/service-account-token +` + + name := "test-sa-created-before-secret-associated-with-sa" + cleanUp := func() { + kapp.Run([]string{"delete", "-a", name}) + } + + cleanUp() + + logger.Section("deploy resource with secret associated with service account", func() { + // deploying it multiple times to make sure it's able to find secret everytime + for i := 0; i < 5; i++ { + _, err := kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, RunOpts{ + StdinReader: strings.NewReader(config), + }) + if err != nil { + require.Errorf(t, err, "Expected resources to be created") + } + cleanUp() + } + }) +}