-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTaskfile.yml
377 lines (339 loc) · 18.4 KB
/
Taskfile.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
version: '3'
dotenv: ['setup.config']
env:
# Get the Project name from the current directory
PROJECT:
sh: "basename {{.PWD}}"
# Was planning to support docker and rancher desktop.
CMD:
sh: (which nerdctl > /dev/null 2>&1 && echo "nerdctl" || echo "docker")
# OpenTofu image version
IMAGE: "ghcr.io/opentofu/opentofu:${TF_VERSION}"
# Thanks to hairyhenderson for gomplate, we are using it as our templater tool
TEMPLATER: hairyhenderson/gomplate
# Determine the platform based on the architecture
PLATFORM:
sh: (case $(uname -m) in x86_64) echo "linux/amd64" ;; arm64 | aarch64) echo "linux/arm64" ;; arm*) echo "linux/arm/v7" ;; *) echo "Unsupported platform" && exit 1 ;; esac)
# OPA does not work on snapdragon x elite with arm64. Use amd64 instead to suppress error.
OPA: "docker run --platform linux/amd64 --rm -v $(pwd):/workspace -w /workspace openpolicyagent/opa:latest"
# Infracost requires a key to be set in the config file.
# You may need to adjust infracost command to suit your situation,
# by editing the where the config file is mounted
# Take note that 42dev.co has public infracost image support multiple platforms including arm64
# If you do not trust the image, you can build your own image or use the official infracost image
INFRACOST: "docker run --rm -v $(pwd)/tmp:/workspace -v ${HOME}/.config/infracost:/root/.config/infracost -w /workspace registry.gitlab.com/42dev_pub/apps-pub/infracost"
# Mikefarah's yq is to consolidate data in the config yamls for rendering the templates
YQ: "docker run --rm -v $(pwd):/workspace -w /workspace mikefarah/yq:latest"
tasks:
default:
desc: "List all tasks"
silent: true
cmds:
- task --list
debug:
desc: "Print out viarables set before exexuting tasks"
silent: true
cmds:
- |
echo "TIER:{{.TIER}}"
echo "TF_VERSION:{{.TF_VERSION}}"
echo "DOMAIN:{{.DOMAIN}}"
echo "CMD:{{.CMD}}"
echo "IMAGE:{{.IMAGE}}"
echo "PLATFORM:{{.PLATFORM}}"
shell:
desc: "Run a shell in the container: Requires: account. Optional region, group"
silent: true
vars:
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
WS_PATH: '$(case ${TIER} in 1) echo "{{.account}}" ;; 2) echo "{{.account}}/{{.region}}" ;; 3) echo "{{.account}}/{{.region}}/{{.group}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
COMMAND: '{{.CMD}} run --platform {{.PLATFORM}} --rm -it -e USER=$(id -u) -e GROUP=$(id -g) -e AWS_PROFILE={{.account}} -v {{.HOME}}/.aws:/root/.aws -v {{.HOME}}/.gitconfig:/root/.gitconfig -v {{.PWD}}:/tf -v {{.PWD}}/local_modules:/tf/workspaces/{{.WS_PATH}}/local_modules -w /tf/workspaces/{{.WS_PATH}} --entrypoint \"\" {{.IMAGE}}'
cmds:
- |
[ "{{.account}}" ] || { echo "account is required."; exit 1; }
[ "{{.TIER}}" -ge 2 ] && [ -z "{{.region}}" ] && echo "REGION is required." && exit 1 || true
[ "{{.TIER}}" -eq 3 ] && [ -z "{{.region}}" ] && echo "GROUP is required." && exit 1 || true
echo "{{.account}}"
[ "{{.TIER}}" -ge 2 ] && echo "{{.region}}"
[ "{{.TIER}}" -eq 3 ] && echo "{{.group}}"
echo "WORKSPACE_PATH: {{.WS_PATH}}"
echo "COMMAND: {{.COMMAND}}"
eval "{{.COMMAND}}" /bin/sh
eval "{{.COMMAND}}" chown -R $(id -u):$(id -g) .
init:
desc: "Run init. Requires: account. Optional region, group"
silent: true
cmds:
- task: tofu-cmd
vars:
cmd: "tofu init"
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
tofu-plan:
desc: "Run plan. Requires: account. Optional region, group"
silent: true
vars:
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
WS_PATH: '$(case ${TIER} in 1) echo "{{.account}}" ;; 2) echo "{{.account}}/{{.region}}" ;; 3) echo "{{.account}}/{{.region}}/{{.group}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
COMMAND: '{{.CMD}} run --platform {{.PLATFORM}} --rm -it -e USER=$(id -u) -e GROUP=$(id -g) -e AWS_PROFILE={{.account}} -v {{.HOME}}/.aws:/root/.aws -v {{.HOME}}/.gitconfig:/root/.gitconfig -v {{.PWD}}:/tf -v {{.PWD}}/local_modules:/tf/workspaces/{{.WS_PATH}}/local_modules -w /tf/workspaces/{{.WS_PATH}} --entrypoint \"\" {{.IMAGE}}'
cmds:
- |
[ "{{.account}}" ] || { echo "account is required."; exit 1; }
[ "{{.TIER}}" -ge 2 ] && [ -z "{{.region}}" ] && echo "REGION is required." && exit 1 || true
[ "{{.TIER}}" -eq 3 ] && [ -z "{{.region}}" ] && echo "GROUP is required." && exit 1 || true
echo "ACCOUNT: {{.account}}"
[ "{{.TIER}}" -ge 2 ] && echo "REGION: {{.region}}"
[ "{{.TIER}}" -eq 3 ] && echo "GROUP: {{.group}}"
echo "WORKSPACE_PATH: {{.WS_PATH}}"
# if plan.tfplan and plan.json exists, remove them
[ -f "tmp/plan.tfplan" ] && rm tmp/plan.tfplan || true
[ -f "tmp/plan.json" ] && rm tmp/plan.json || true
[ -f "tm/cost.json" ] && rm tmp/cost.json || true
eval "{{.COMMAND}}" tofu plan -out=/tf/tmp/plan.tfplan
eval "{{.COMMAND}}" tofu show -json /tf/tmp/plan.tfplan > tmp/plan.json
eval "{{.COMMAND}}" tofu show -json /tf/tmp/plan.tfplan > tmp/plan.json
apply:
desc: "Run apply. Requires: account. Optional region, group"
silent: true
vars:
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
WS_PATH: '$(case ${TIER} in 1) echo "{{.account}}" ;; 2) echo "{{.account}}/{{.region}}" ;; 3) echo "{{.account}}/{{.region}}/{{.group}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
COMMAND: '{{.CMD}} run --platform {{.PLATFORM}} --rm -it -e USER=$(id -u) -e GROUP=$(id -g) -e AWS_PROFILE={{.account}} -v {{.HOME}}/.aws:/root/.aws -v {{.HOME}}/.gitconfig:/root/.gitconfig -v {{.PWD}}:/tf -v {{.PWD}}/local_modules:/tf/workspaces/{{.WS_PATH}}/local_modules -w /tf/workspaces/{{.WS_PATH}} --entrypoint \"\" {{.IMAGE}}'
cmds:
- |
[ "{{.account}}" ] || { echo "account is required."; exit 1; }
[ "{{.TIER}}" -ge 2 ] && [ -z "{{.region}}" ] && echo "REGION is required." && exit 1 || true
[ "{{.TIER}}" -eq 3 ] && [ -z "{{.region}}" ] && echo "GROUP is required." && exit 1 || true
[ -f "tmp/plan.tfplan" ] || { echo "Plan file does not exist."; exit 1; }
echo "ACCOUNT: {{.account}}"
[ "{{.TIER}}" -ge 2 ] && echo "REGION: {{.region}}"
[ "{{.TIER}}" -eq 3 ] && echo "GROUP: {{.group}}"
echo "WORKSPACE_PATH: {{.WS_PATH}}"
eval "{{.COMMAND}}" tofu apply -auto-approve /tf/tmp/plan.tfplan
eval "{{.COMMAND}}" rm /tf/tmp/plan.tfplan /tf/tmp/plan.json /tf/tmp/cost.json
refresh:
desc: "Run refresh. Requires: account. Optional region, group"
silent: true
cmds:
- task: tofu-cmd
vars:
cmd: "tofu refresh"
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
state-list:
desc: "Run state list. Requires: account. Optional region, group"
silent: true
cmds:
- task: tofu-cmd
vars:
cmd: "tofu state list"
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
show:
desc: "Run tofu show. Requires: account. Optional region, group"
silent: true
cmds:
- task: tofu-cmd
vars:
cmd: "tofu show"
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
tofu-cmd:
internal: true
desc: "Generic tofu command wrapper"
vars:
cmd: '{{default "" .cmd}}'
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
WS_PATH: '$(case ${TIER} in 1) echo "{{.account}}" ;; 2) echo "{{.account}}/{{.region}}" ;; 3) echo "{{.account}}/{{.region}}/{{.group}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
COMMAND: '{{.CMD}} run --platform {{.PLATFORM}} --rm -it -e USER=$(id -u) -e GROUP=$(id -g) -e AWS_PROFILE={{.account}} -v {{.HOME}}/.aws:/root/.aws -v {{.HOME}}/.gitconfig:/root/.gitconfig -v {{.PWD}}:/tf -v {{.PWD}}/local_modules:/tf/workspaces/{{.WS_PATH}}/local_modules -w /tf/workspaces/{{.WS_PATH}} --entrypoint \"\" {{.IMAGE}}'
cmds:
- |
[ "{{.cmd}}" ] || { echo "cmd is required."; exit 1; }
[ "{{.account}}" ] || { echo "account is required."; exit 1; }
[ "{{.TIER}}" -ge 2 ] && [ -z "{{.region}}" ] && echo "REGION is required." && exit 1 || true
[ "{{.TIER}}" -eq 3 ] && [ -z "{{.region}}" ] && echo "GROUP is required." && exit 1 || true
echo "ACCOUNT: {{.account}}"
[ "{{.TIER}}" -ge 2 ] && echo "REGION: {{.region}}"
[ "{{.TIER}}" -eq 3 ] && echo "GROUP: {{.group}}"
echo "WORKSPACE_PATH: {{.WS_PATH}}"
eval "{{.COMMAND}}" {{.cmd}}
infracost:
desc: "Run infracost"
internal: true
silent: true
cmds:
- |
[ -f "tmp/plan.tfplan" ] || { echo "Plan file does not exist."; exit 1; }
# docker run --rm -it -v $(pwd)/tmp:/workspace -v ${HOME}/.config/infracost:/root/.config/infracost -w /workspace --entrypoint /bin/sh registry.gitlab.com/42dev_pub/apps-pub/infracost
{{.INFRACOST}} breakdown --project-name="{{.PROJECT}}" --path plan.json --format json --out-file cost.json
{{.INFRACOST}} breakdown --project-name="{{.PROJECT}}" --show-skipped --path plan.json
infracost-shell:
desc: "Run infracost"
internal: true
silent: true
cmds:
- |
[ -f "tmp/plan.tfplan" ] || { echo "Plan file does not exist."; exit 1; }
docker run --rm -it -v $(pwd)/tmp:/workspace -v ${HOME}/.config/infracost:/root/.config/infracost -w /workspace --entrypoint /bin/bash registry.gitlab.com/42dev_pub/apps-pub/infracost
plan:
desc: "Run plan. Requires: account. Optional region, group"
cmds:
- task: tofu-plan
- task: infracost
- task: check-violations
# Subtask for running compliance and budget checks
check-violations:
internal: false
desc: "Check for compliance and budget violations"
cmds:
- task: compliance-check
- task: budget-check
# Compliance check
compliance-check:
internal: true
silent: true
desc: "Check for compliance violations"
cmds:
- |
violations=$({{.OPA}} eval --format pretty --data policies/iac --input tmp/plan.json "data.iac.main.violations")
if [ "$violations" != "{}" ]; then
echo "Compliance violations detected:"
echo "$violations"
rm tmp/plan.tfplan tmp/plan.json tmp/cost.json
exit 1
else
echo "No compliance violations detected."
fi
# Budget check
budget-check:
desc: "Check for budget violations"
silent: true
cmds:
- |
violations=$({{.OPA}} eval --format pretty --data policies/budget --input tmp/cost.json "data.budget.deny")
if [ "$violations" != "[]" ]; then
echo "Budget violations detected:"
echo "$violations"
rm tmp/plan.tfplan tmp/plan.json tmp/cost.json
exit 1
else
echo "No budget violations detected."
fi
update-auto:
silent: true
desc: "Update *.auto.tf files from base to workspace. Requires: account, account_id. Optional region, group"
vars:
account: '{{default "" .account}}'
account_id: '{{default "" .account_id}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
WS_PATH: '$(case ${TIER} in 1) echo "{{.account}}" ;; 2) echo "{{.account}}/{{.region}}" ;; 3) echo "{{.account}}/{{.region}}/{{.group}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
KEY_PATH: '$(case ${TIER} in 1) echo "{{.account}}/{{.PROJECT}}" ;; 2) echo "{{.account}}/{{.PROJECT}}/{{.region}}" ;; 3) echo "{{.account}}/{{.PROJECT}}/{{.region}}/{{.region}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
RELATIVE_WS_PATH: '$(case "${TIER}" in 1) echo "../..";; 2) echo "../../..";; 3) echo "../../../../";; esac)'
RENDER: '{{.CMD}} run --platform {{ .PLATFORM }} --user $(id -u):$(id -g) --rm -it -v {{.PWD}}:/tf -w /tf {{.TEMPLATER}}'
cmds:
- |
# Check if required variables are set, some have default values
[ "{{.account}}" ] || { echo "account is required."; exit 1; }
[ "{{.TIER}}" -ge 2 ] && [ -z "{{.region}}" ] && echo "region is required." && exit 1 || true
[ "{{.TIER}}" -eq 3 ] && [ -z "{{.region}}" ] && [ -z "{{.group}}" ] && echo "group is required." && exit 1 || true
# Find and remove all auto.tf files in workspaces/{{.WS_PATH}}
find workspaces/{{.WS_PATH}} -type l -name "*.auto.tf" -exec rm {} \;
# if workspaces/{{.WS_PATH}}/*.auto.tf does not exist, link it from /base/auto/*.auto.tf files.
auto_files=$(find ./base/auto -type f -name "*.auto.tf")
for file in $auto_files; do
[ -f "workspaces/{{.WS_PATH}}/$(basename $file)" ] || ( cd workspaces/{{.WS_PATH}}; ln -s {{.RELATIVE_WS_PATH}}/$file . )
done
echo "Auto.tf files updated."
scaffold:
desc: "Scaffold a workspace. Requires: account, account_id. Optional region, group"
silent: true
vars:
account: '{{default "" .account}}'
account_id: '{{default "" .account_id}}'
region: '{{default "ap-southeast-1" .region}}'
group: '{{default "" .group}}'
domain: '{{default "example.com" .domain}}'
WS_PATH: '$(case ${TIER} in 1) echo "{{.account}}" ;; 2) echo "{{.account}}/{{.region}}" ;; 3) echo "{{.account}}/{{.region}}/{{.group}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
KEY_PATH: '$(case ${TIER} in 1) echo "{{.account}}/{{.PROJECT}}" ;; 2) echo "{{.account}}/{{.PROJECT}}/{{.region}}" ;; 3) echo "{{.account}}/{{.PROJECT}}/{{.region}}/{{.region}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
RELATIVE_WS_PATH: '$(case "${TIER}" in 1) echo "../..";; 2) echo "../../..";; 3) echo "../../../../";; esac)'
RENDER: '{{.CMD}} run --platform {{ .PLATFORM }} --user $(id -u):$(id -g) --rm -it -v {{.PWD}}:/tf -w /tf {{.TEMPLATER}}'
cmds:
- |
# Check if required variables are set, some have default values
[ "{{.account}}" ] || { echo "account is required."; exit 1; }
[ "{{.account_id}}" ] || { echo "account_id is required."; exit 1; }
[ "{{.TIER}}" -ge 2 ] && [ -z "{{.region}}" ] && echo "region is required." && exit 1 || true
[ "{{.TIER}}" -eq 3 ] && [ -z "{{.region}}" ] && [ -z "{{.group}}" ] && echo "group is required." && exit 1 || true
# Create workspace and setup essential files for it
[ -d "workspaces/{{.WS_PATH}}" ] && echo "Workspace already exists." && exit 1 || mkdir -p workspaces/{{.WS_PATH}}
[ -d "workspaces/{{.WS_PATH}}/local_modules" ] || ( cd workspaces/{{.WS_PATH}}; ln -s {{.RELATIVE_WS_PATH}}/local_modules . )
auto_files=$(find ./base/auto -type f -name "*.auto.tf")
for file in $auto_files; do
[ -f "workspaces/{{.WS_PATH}}/$(basename $file)" ] || ( cd workspaces/{{.WS_PATH}}; ln -s {{.RELATIVE_WS_PATH}}/$file . )
done
[ -f "workspaces/{{.WS_PATH}}/main.tf" ] || ( touch ./workspaces/{{.WS_PATH}}/main.tf )
[ -f "workspaces/{{.WS_PATH}}/vars.tf" ] || ( cp ./base/vars.tf ./workspaces/{{.WS_PATH}}/ )
[ -d "workspaces/{{.WS_PATH}}/resources" ] || ( mkdir -p ./workspaces/{{.WS_PATH}}/resources ; cp -r ./base/skeleton/* ./workspaces/{{.WS_PATH}}/resources )
# Save scaffold configuration to config.yaml
[ -f "workspaces/{{.WS_PATH}}/config.yaml" ] || touch ./workspaces/{{.WS_PATH}}/config.yaml
KEY_PATH="{{.KEY_PATH}}"
{{.YQ}} e '.ACCOUNT = "{{.account}}" | .ACCOUNT_ID = "{{.account_id}}" | .REGION = "{{.region}}" | .GROUP = "{{.group}}" | .PROJECT = "{{.PROJECT}}" | .DOMAIN = "{{.DOMAIN}}" | .TIER = "{{.TIER}}" | .DOMAIN = "{{.domain}}"' -i ./workspaces/{{.WS_PATH}}/config.yaml
{{.YQ}} e ".KEY_PATH = \"${KEY_PATH}\"" -i ./workspaces/{{.WS_PATH}}/config.yaml
# Render files
{{.RENDER}} -c .=/tf/workspaces/{{.WS_PATH}}/config.yaml -f /tf/base/provider.tf.tmpl -o /tf/workspaces/{{.WS_PATH}}/provider.tf
[ "{{.TIER}}" -ge 2 ] && {{.RENDER}} -c .=/tf/./workspaces/{{.WS_PATH}}/config.yaml -f /tf/base/terraform.auto.tfvars.tmpl -o /tf/workspaces/{{.WS_PATH}}/terraform.auto.tfvars || true
echo "Scaffold complete."
unscaffold:
desc: "Unscaffold a workspace. Requires: account. Optional region, group"
silent: true
vars:
account: '{{default "" .account}}'
region: '{{default "ap-southeast-1" .region}}'
GROUP: '{{default "" .region}}'
WS_PATH: '$(case ${TIER} in 1) echo "{{.account}}" ;; 2) echo "{{.account}}/{{.region}}" ;; 3) echo "{{.account}}/{{.region}}/{{.group}}" ;; *) echo "Unsupported tier" && exit 1 ;; esac)'
cmds:
- |
[ "{{.account}}" ] || ( echo "ACCOUNT is required."; exit 1 )
[ "{{.TIER}}" -ge 2 ] && [ -z "{{.region}}" ] && echo "REGION is required." && exit 1 || true
[ "{{.TIER}}" -eq 3 ] && [ -z "{{.region}}" ] && echo "GROUP is required." && exit 1 || true
[ -d "workspaces/{{.WS_PATH}}" ] || { echo "Workspace does not exist."; exit 1; }
rm -rf workspaces/{{.WS_PATH}}
echo "Workspace (workspaces/{{.WS_PATH}}) unscaffolded."
### OPA Tasks
opa-check:
desc: "Run rego check. Requires: rego_path:<relative path to rego file e.g. policies/iac/compliances/terraform.rego>"
silent: false
vars:
rego_file: '{{default "" .rego_file}}'
cmds:
- |
{{.OPA}} check {{.rego_file}}
opa-test:
desc: "Run rego test. Requires: rego_path:<relative path to rego file e.g. policies/iac/compliances/terraform.rego>"
silent: false
cmds:
- |
# If any violations are found, they will be printed out
{{.OPA}} eval --format pretty --data policies/iac --input tmp/plan.json "data.iac.main.violations"
- |
# Check for budget violations
{{.OPA}} eval --format pretty --data policies/budget --input tmp/cost.json "data.budget.deny"
### Others
cloc:
desc: "Run cloc"
cmds:
- |
cloc --exclude-dir=.terraform,tmp,workspaces .