# 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/transformers/setProject 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 values in the `resourceRef/external` fields in these instances are only partially complete. kustomize will add projectIds to these below. The boilerplate in these instances could be eliminated by making a _custom generator_, but that's for a different tutorial, and with only three instances here it's not worth the effort. ``` 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 configuration file. The transformer used is called `ValueAddTransformer`. It's intended to implement the 'add' operation of [IETF RFC 6902 JSON]. The add operation is simple declaration of what value to add, and a powerful syntax for specifying where to add the value. The value can, for example, be inserted into an existing field holding a file path as either a prefix, a postfix, or some change in the middle (e.g. `/volume/data` becomes `/volume/projectId/data`). [IETF RFC 6902 JSON]: https://tools.ietf.org/html/rfc6902 At the time of writing, this transformer has no dedicated keyword in the kustomization file to hold its config. This means the config must live in its own file: ``` 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 ``` This file defined both the value to insert and a list of places to insert it. It's saying 1) _take the name of the directory I am in_ and 2) use the name as a namespace on all objects in scope, and 3) add that name to the 2nd position in the file path found in the `spec/resourceRef/external` field of all `IAMPolicyMember` instances. To be used, this transformer config file must be referenced from some kustomization file's `transformers:` field. This field can contain a path directly to the transformer config file, or a path to an encapsulating kustomization root. The latter approach allows any number of transformers to be loaded as a group from a local or remote location. Here's an example of the latter case that uses a kustomization file to list pointers to transformer configs, although in this case it references only one transformer config. ``` cat <<'EOF' >$DEMO_HOME/transformers/setProject/kustomization.yaml resources: - setProject.yaml 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 `dog-222`, and the project name also appears in the resourceRef field of the `IAMPolicyMember` instances. This project name only appears in the project _directory name_, achieving our [DRY] goal. Run the build: ``` kustomize build $DEMO_HOME/projects/dog-222 >$DEMO_HOME/out_actual.yaml ``` and confirm that the actual output matches the expected output: ``` diff $DEMO_HOME/out_actual.yaml $DEMO_HOME/out_expected.yaml ``` Build all the projects at once like this: ``` kustomize build $DEMO_HOME/all ```