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:
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:[email protected]
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:[email protected]
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:[email protected]
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
).
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 <<EOF >$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:[email protected]
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:[email protected]
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:[email protected]
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