# Simple addition of one string value [DRY]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself Suppose you have several distinct cloud _projects_ (on GCP or AWS or whatever) named: * cat-111 * dog-222 * fox-333 These might be project names within the company, cloud billing identifiers, or both. Further suppose * You want to deploy these projects to different k8s namespaces, named after the projects. * You need to specify the project name in various resource subfields. * You want to name the configuration directories using the project name. Additionally you might want to deploy the projects one at a time, or all at once. Ideally, you'll want to avoid specifying the project name in more than one place (i.e. you want to stay [DRY]). Here's a possible layout: > ``` > ├── all > │ └── kustomization.yaml > │ > ├── bases > │ └── iam-iap-tunnel > │ ├── kustomization.yaml > │ └── policymembers.yaml > │ > └── projects > ├── cat-111 > │ └── kustomization.yaml > ├── dog-222 > │ └── kustomization.yaml > └── fox-333 > └── kustomization.yaml > ``` This layout allows each project to be individually buildable: > ``` > kustomize build projects/cat-111 > kustomize build projects/dog-222 > kustomize build projects/fox-333 > ``` or collectively buildable: > ``` > kustomize build all > ``` ----- Make a place to work: ``` DEMO_HOME=$(mktemp -d) ``` ``` mkdir -p $DEMO_HOME/bases/iam-iap-tunnel mkdir -p $DEMO_HOME/projects/cat-111 mkdir -p $DEMO_HOME/projects/dog-222 mkdir -p $DEMO_HOME/projects/fox-333 ``` To ground this example with a common problem, assume a set of engineers: * red@example.com * blue@example.com * yellow@example.com who need particular access to one or more projects. Define an instance of `IAMCustomRole`: ``` cat <<'EOF' >$DEMO_HOME/bases/iam-iap-tunnel/customroles.yaml apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMCustomRole metadata: name: engineer spec: title: Colorful Engineer permissions: - iap.tunnelInstances.accessViaIAP stage: GA EOF ``` Define corresponding instances of `IAMPolicyMember`. The `resourceRef/external` fields in these instances are intentionally incomplete, and will be completed by kustomize below. The boilerplate in these instances could be removed by making a custom generator, but that's for different tutorial. ``` cat <<'EOF' >$DEMO_HOME/bases/iam-iap-tunnel/policymembers.yaml apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMPolicyMember metadata: name: iap-tunnel-red spec: member: user:red@example.com role: roles/engineer resourceRef: apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1 kind: Project external: projects --- apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMPolicyMember metadata: name: iap-tunnel-blue spec: member: user:blue@example.com role: roles/engineer resourceRef: apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1 kind: Project external: projects --- apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMPolicyMember metadata: name: iap-tunnel-yellow spec: member: user:yellow@example.com role: roles/engineer resourceRef: apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1 kind: Project external: projects EOF ``` Make a base that combines these: ``` cat <<'EOF' >$DEMO_HOME/bases/iam-iap-tunnel/kustomization.yaml resources: - customroles.yaml - policymembers.yaml EOF ``` Make a transformer, which at the moment has no equivalent directive in the kustomization file. It's purpose will be more evident momentarily. ``` mkdir -p $DEMO_HOME/transformers/setProject cat <<'EOF' >$DEMO_HOME/transformers/setProject/kustomization.yaml resources: - setProject.yaml EOF ``` ``` cat <<'EOF' >$DEMO_HOME/transformers/setProject/setProject.yaml apiVersion: builtin kind: ValueAddTransformer metadata: name: dirNameAdd # Omitting the 'value:' field means that the current # kustomization root directory name will be used as # the value. # value: not specified! targets: - fieldPath: metadata/namespace - selector: kind: IAMPolicyMember fieldPath: spec/resourceRef/external filePathPosition: 2 EOF ``` Now make the _cat_, _dog_ and _fox_ _variants_. These are the targets that one could independently apply to a cluster. ``` cat <<'EOF' >$DEMO_HOME/projects/cat-111/kustomization.yaml resources: - ../../bases/iam-iap-tunnel transformers: - ../../transformers/setProject EOF ``` ``` cat <<'EOF' >$DEMO_HOME/projects/dog-222/kustomization.yaml resources: - ../../bases/iam-iap-tunnel transformers: - ../../transformers/setProject EOF ``` ``` cat <<'EOF' >$DEMO_HOME/projects/fox-333/kustomization.yaml resources: - ../../bases/iam-iap-tunnel transformers: - ../../transformers/setProject EOF ``` Then, optionally, a target to deploy all the projects at once: ``` mkdir -p $DEMO_HOME/all cat <<'EOF' >$DEMO_HOME/all/kustomization.yaml resources: - ../projects/cat-111 - ../projects/dog-222 - ../projects/fox-333 EOF ``` The layout is now: ``` tree $DEMO_HOME ``` It should look like: > ``` > /tmp/someTmpDir > ├── all > │   └── kustomization.yaml > ├── bases > │   └── iam-iap-tunnel > │   ├── customroles.yaml > │   ├── kustomization.yaml > │   └── policymembers.yaml > ├── projects > │   ├── cat-111 > │   │   └── kustomization.yaml > │   ├── dog-222 > │   │   └── kustomization.yaml > │   └── fox-333 > │   └── kustomization.yaml > └── transformers > └── setProject > ├── kustomization.yaml > └── setProject.yaml > ``` The expected output from building the dog project is as follows: ``` cat <$DEMO_HOME/out_expected.yaml apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMCustomRole metadata: name: engineer namespace: dog-222 spec: permissions: - iap.tunnelInstances.accessViaIAP stage: GA title: Colorful Engineer --- apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMPolicyMember metadata: name: iap-tunnel-blue namespace: dog-222 spec: member: user:blue@example.com resourceRef: apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1 external: projects/dog-222 kind: Project role: roles/engineer --- apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMPolicyMember metadata: name: iap-tunnel-red namespace: dog-222 spec: member: user:red@example.com resourceRef: apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1 external: projects/dog-222 kind: Project role: roles/engineer --- apiVersion: iam.cnrm.cloud.google.com/v1beta1 kind: IAMPolicyMember metadata: name: iap-tunnel-yellow namespace: dog-222 spec: member: user:yellow@example.com resourceRef: apiVersion: resourcemanager.cnrm.cloud.google.com/v1beta1 external: projects/dog-222 kind: Project role: roles/engineer EOF ``` In this output, the namespace of all instances is the project name, and the project name also appears in the resourceRef field of the `IAMPolicyMember` instances. Confirm this is happens: ``` kustomize build $DEMO_HOME/projects/dog-222 >$DEMO_HOME/out_actual.yaml ``` Confirm expectations: ``` diff $DEMO_HOME/out_actual.yaml $DEMO_HOME/out_expected.yaml ``` Build all the projects at once like this: ``` kustomize build $DEMO_HOME/all ```