Skip to content

Commit 9a136cb

Browse files
authored
Merge pull request hashicorp#288 from hashicorp/add-outputs-to-automation-script
print outputs
2 parents 5892a27 + a0fcaed commit 9a136cb

File tree

2 files changed

+69
-12
lines changed

2 files changed

+69
-12
lines changed

operations/automation-script/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# TFE Automation Script
2-
Script to automate interactions with Terraform Enterprise, including the cloning of a repository containing Terraform configuration code, creation of a workspace, tarring and uploading of the Terraform code, setting of variables, triggering a run, checking Sentinel policies, and finally doing an apply if permitted. If an apply is done, the script waits for it to finish and then downloads the apply log and the before and after state files. If an apply cannot be done, it downloads the plan log instead.
2+
Script to automate interactions with Terraform Enterprise, including the cloning of a repository containing Terraform configuration code, creation of a workspace, tarring and uploading of the Terraform code, setting of variables, triggering a run, checking Sentinel policies, and finally doing an apply if permitted. If an apply is done, the script waits for it to finish and then downloads and prints the apply log and the state file. It also prints the outputs separately even though they are also in the state file. If an apply cannot be done, it downloads the plan log instead.
33

44
Note that this script is only meant as an example that shows how to use the various Terraform Cloud APIs. It is not suitable for production usage since it does not support modifying workspace variables after they have already been created in a workspace.
55

@@ -42,7 +42,7 @@ The script does the following steps:
4242
- Other values of $run_status cause the loop to repeat after a brief sleep.
4343
1. If $save_plan was set to "true" in the above loop, the script outputs and saves the plan log.
4444
1. If any apply was done, the script goes into a second loop to wait for the apply to finish, error, or be canceled.
45-
1. If and when the apply finishes, the script downloads the apply log and the new state file from before and after the apply.
45+
1. If and when the apply finishes, the script downloads the apply log, determines the state version ID, retrieves the outputs from the state version with that ID, and then downloads and prints the new state file.
4646

4747
In addition to the loadAndRunWorkspace.sh script, this example includes the following files:
4848

operations/automation-script/loadAndRunWorkspace.sh

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# checked against the workspace, and if $override=true and there were
77
# no hard-mandatory violations of Sentinel policies, does an apply.
88
# If an apply is done, the script waits for it to finish and then
9-
# downloads the apply log and the before and after state files.
9+
# downloads the apply log and the state file.
1010

1111
# Make sure TFE_TOKEN and TFE_ORG environment variables are set
1212
# to owners team token and organization name for the respective
@@ -188,47 +188,57 @@ EOF
188188
sed "s/placeholder/${workspace}/" < workspace.template.json > workspace.json
189189

190190
# Check to see if the workspace already exists
191+
echo ""
191192
echo "Checking to see if workspace exists"
192193
check_workspace_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" "https://${address}/api/v2/organizations/${organization}/workspaces/${workspace}")
193194

194195
# Parse workspace_id from check_workspace_result
195196
workspace_id=$(echo $check_workspace_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['id'])")
197+
echo ""
196198
echo "Workspace ID: " $workspace_id
197199

198200
# Create workspace if it does not already exist
199201
if [ -z "$workspace_id" ]; then
202+
echo ""
200203
echo "Workspace did not already exist; will create it."
201204
workspace_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" --request POST --data @workspace.json "https://${address}/api/v2/organizations/${organization}/workspaces")
202205

203206
# Parse workspace_id from workspace_result
204207
workspace_id=$(echo $workspace_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['id'])")
208+
echo ""
205209
echo "Workspace ID: " $workspace_id
206210
else
211+
echo ""
207212
echo "Workspace already existed."
208213
fi
209214

210215
# Create configuration version
216+
echo ""
211217
echo "Creating configuration version."
212218
configuration_version_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" --data @configversion.json "https://${address}/api/v2/workspaces/${workspace_id}/configuration-versions")
213219

214220
# Parse configuration_version_id and upload_url
215221
config_version_id=$(echo $configuration_version_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['id'])")
216222
upload_url=$(echo $configuration_version_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['attributes']['upload-url'])")
223+
echo ""
217224
echo "Config Version ID: " $config_version_id
218225
echo "Upload URL: " $upload_url
219226

220227
# Upload configuration
228+
echo ""
221229
echo "Uploading configuration version using ${config_dir}.tar.gz"
222230
#curl -s --request PUT -F '[email protected]' "$upload_url"
223231
curl -s --header "Content-Type: application/octet-stream" --request PUT --data-binary @${config_dir}.tar.gz "$upload_url"
224232

225233
# Check if a variables.csv file is in the configuration directory
226234
# If so, use it. Otherwise, use the one in the current directory.
227235
if [ -f "${config_dir}/variables.csv" ]; then
236+
echo ""
228237
echo "Found variables.csv in ${config_dir}."
229238
echo "Will load variables from it."
230239
variables_file=${config_dir}/variables.csv
231240
else
241+
echo ""
232242
echo "Did not find variables.csv in configuration."
233243
echo "Will load variables from ./variables.csv"
234244
variables_file=variables.csv
@@ -243,6 +253,7 @@ escape_string()
243253
sedDelim=$(printf '\001')
244254

245255
# Add variables to workspace
256+
echo ""
246257
while IFS=',' read -r key value category hcl sensitive
247258
do
248259
fixedkey=$(escape_string "$key")
@@ -255,6 +266,7 @@ done < ${variables_file}
255266
# List Sentinel Policies
256267
sentinel_list_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" "https://${address}/api/v2/organizations/${organization}/policies")
257268
sentinel_policy_count=$(echo $sentinel_list_result | python -c "import sys, json; print(json.load(sys.stdin)['meta']['pagination']['total-count'])")
269+
echo ""
258270
echo "Number of Sentinel policies: " $sentinel_policy_count
259271

260272
# Do a run
@@ -263,13 +275,15 @@ run_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Conte
263275

264276
# Parse run_result
265277
run_id=$(echo $run_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['id'])")
278+
echo ""
266279
echo "Run ID: " $run_id
267280

268281
# Check run result in loop
269282
continue=1
270283
while [ $continue -ne 0 ]; do
271284
# Sleep
272285
sleep $sleep_duration
286+
echo ""
273287
echo "Checking run status"
274288

275289
# Check the status of run
@@ -295,18 +309,21 @@ while [ $continue -ne 0 ]; do
295309
# exist or are applicable to the workspace
296310
if [[ "$run_status" == "planned" ]] && [[ "$is_confirmable" == "True" ]] && [[ "$override" == "no" ]]; then
297311
continue=0
312+
echo ""
298313
echo "There are " $sentinel_policy_count "policies, but none of them are applicable to this workspace."
299314
echo "Check the run in Terraform Enterprise UI and apply there if desired."
300315
save_plan="true"
301316
# cost_estimated means plan finished and costs were estimated
302317
# exist or are applicable to the workspace
303318
elif [[ "$run_status" == "cost_estimated" ]] && [[ "$is_confirmable" == "True" ]] && [[ "$override" == "no" ]]; then
304319
continue=0
320+
echo ""
305321
echo "There are " $sentinel_policy_count "policies, but none of them are applicable to this workspace."
306322
echo "Check the run in Terraform Enterprise UI and apply there if desired."
307323
save_plan="true"
308324
elif [[ "$run_status" == "planned" ]] && [[ "$is_confirmable" == "True" ]] && [[ "$override" == "yes" ]]; then
309325
continue=0
326+
echo ""
310327
echo "There are " $sentinel_policy_count "policies, but none of them are applicable to this workspace."
311328
echo "Since override was set to \"yes\", we are applying."
312329
# Do the apply
@@ -315,6 +332,7 @@ while [ $continue -ne 0 ]; do
315332
applied="true"
316333
elif [[ "$run_status" == "cost_estimated" ]] && [[ "$is_confirmable" == "True" ]] && [[ "$override" == "yes" ]]; then
317334
continue=0
335+
echo ""
318336
echo "There are " $sentinel_policy_count "policies, but none of them are applicable to this workspace."
319337
echo "Since override was set to \"yes\", we are applying."
320338
# Do the apply
@@ -325,51 +343,63 @@ while [ $continue -ne 0 ]; do
325343
elif [[ "$run_status" == "policy_checked" ]]; then
326344
continue=0
327345
# Do the apply
346+
echo ""
328347
echo "Policies passed. Doing Apply"
329348
apply_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" --data @apply.json https://${address}/api/v2/runs/${run_id}/actions/apply)
330349
applied="true"
331350
# policy_override means at least 1 Sentinel policy failed
332351
# but since $override is "yes", we will override and then apply
333352
elif [[ "$run_status" == "policy_override" ]] && [[ "$override" == "yes" ]]; then
334353
continue=0
354+
echo ""
335355
echo "Some policies failed, but overriding"
336356
# Get the policy check ID
357+
echo ""
337358
echo "Getting policy check ID"
338359
policy_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" https://${address}/api/v2/runs/${run_id}/policy-checks)
339360
# Parse out the policy check ID
340361
policy_check_id=$(echo $policy_result | python -c "import sys, json; print(json.load(sys.stdin)['data'][0]['id'])")
362+
echo ""
341363
echo "Policy Check ID: " $policy_check_id
342364
# Override policy
365+
echo ""
343366
echo "Overriding policy check"
344367
override_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" --request POST https://${address}/api/v2/policy-checks/${policy_check_id}/actions/override)
345368
# Do the apply
369+
echo ""
346370
echo "Doing Apply"
347371
apply_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" --data @apply.json https://${address}/api/v2/runs/${run_id}/actions/apply)
348372
applied="true"
349373
# policy_override means at least 1 Sentinel policy failed
350374
# but since $override is "no", we will not override
351375
# and will not apply
352376
elif [[ "$run_status" == "policy_override" ]] && [[ "$override" == "no" ]]; then
377+
echo ""
353378
echo "Some policies failed, but will not override. Check run in Terraform Enterprise UI."
354379
save_plan="true"
355380
continue=0
356381
# errored means that plan had an error or that a hard-mandatory
357382
# policy failed
358383
elif [[ "$run_status" == "errored" ]]; then
384+
echo ""
359385
echo "Plan errored or hard-mandatory policy failed"
360386
save_plan="true"
361387
continue=0
362388
elif [[ "$run_status" == "planned_and_finished" ]]; then
389+
echo ""
363390
echo "Plan indicates no changes to apply."
364391
save_plan="true"
365392
continue=0
366393
elif [[ "run_status" == "canceled" ]]; then
394+
echo ""
367395
echo "The run was canceled."
368396
continue=0
369397
elif [[ "run_status" == "force_canceled" ]]; then
398+
echo ""
370399
echo "The run was canceled forcefully."
371400
continue=0
372401
elif [[ "run_status" == "discarded" ]]; then
402+
echo ""
373403
echo "The run was discarded."
374404
continue=0
375405
else
@@ -380,18 +410,21 @@ done
380410

381411
# Get the plan log if $save_plan is true
382412
if [[ "$save_plan" == "true" ]]; then
413+
echo ""
383414
echo "Getting the result of the Terraform Plan."
384415
plan_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" --header "Content-Type: application/vnd.api+json" https://${address}/api/v2/runs/${run_id}?include=plan)
385416
plan_log_url=$(echo $plan_result | python -c "import sys, json; print(json.load(sys.stdin)['included'][0]['attributes']['log-read-url'])")
417+
echo ""
386418
echo "Plan Log:"
387419
# Retrieve Plan Log from the URL
388420
# and output to shell and file
389421
curl -s $plan_log_url | tee ${run_id}.log
390422
fi
391423

392-
# Get the apply log and state files (before and after) if an apply was done
424+
# Get the apply log and state file if an apply was done
393425
if [[ "$applied" == "true" ]]; then
394426

427+
echo ""
395428
echo "An apply was done."
396429
echo "Will download apply log and state file."
397430

@@ -400,13 +433,15 @@ if [[ "$applied" == "true" ]]; then
400433

401434
# Get apply ID
402435
apply_id=$(echo $check_result | python -c "import sys, json; print(json.load(sys.stdin)['included'][0]['id'])")
436+
echo ""
403437
echo "Apply ID:" $apply_id
404438

405439
# Check apply status periodically in loop
406440
continue=1
407441
while [ $continue -ne 0 ]; do
408442

409443
sleep $sleep_duration
444+
echo ""
410445
echo "Checking apply status"
411446

412447
# Check the apply status
@@ -434,29 +469,51 @@ if [[ "$applied" == "true" ]]; then
434469

435470
# Get apply log URL
436471
apply_log_url=$(echo $check_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['attributes']['log-read-url'])")
472+
echo ""
437473
echo "Apply Log URL:"
438474
echo "${apply_log_url}"
439475

440476
# Retrieve Apply Log from the URL
441477
# and output to shell and file
478+
echo ""
442479
curl -s $apply_log_url | tee ${apply_id}.log
443480

444481
# Get state version ID from after the apply
445-
state_id_after=$(echo $check_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['relationships']['state-versions']['data'][0]['id'])")
446-
echo "State ID:" ${state_id_after}
447-
448-
# Call API to get information about the state version including its URL
449-
state_file_after_url_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" https://${address}/api/v2/state-versions/${state_id_after})
482+
state_id=$(echo $check_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['relationships']['state-versions']['data'][0]['id'])")
483+
echo ""
484+
echo "State ID:" ${state_id}
485+
486+
# Call API to get information about the state version including its URL and outputs
487+
state_file_url_result=$(curl -s --header "Authorization: Bearer $TFE_TOKEN" "https://${address}/api/v2/state-versions/${state_id}?include=outputs")
488+
489+
# Retrieve and echo outputs from state
490+
# Note that we retrieved outputs in the last API call by
491+
# adding `?include=outputs`
492+
# Instead of doing that, we could have retrieved the state version output
493+
# IDs from the relationships of the above API call and could have then
494+
# called the State Version Output API to retrieve details for each output.
495+
# That would have involved URLs like
496+
# "https://${address}/api/v2/state-version-outputs/${output_id}"
497+
# See `https://www.terraform.io/docs/cloud/api/state-version-outputs.html#show-a-state-version-output`
498+
num_outputs=$(echo $state_file_url_result | python -c "import sys, json; print(len(json.load(sys.stdin)['included']))")
499+
echo ""
500+
echo "Outputs from State:"
501+
for ((output=0;output<$num_outputs;output++))
502+
do
503+
echo $state_file_url_result | python -c "import sys, json; print(json.dumps(json.load(sys.stdin)['included'][$output]['attributes'], sort_keys=True))"
504+
done
450505

451506
# Get state file URL from the result
452-
state_file_after_url=$(echo $state_file_after_url_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['attributes']['hosted-state-download-url'])")
507+
state_file_url=$(echo $state_file_url_result | python -c "import sys, json; print(json.load(sys.stdin)['data']['attributes']['hosted-state-download-url'])")
508+
echo ""
453509
echo "URL for state file after apply:"
454-
echo ${state_file_after_url}
510+
echo ${state_file_url}
455511

456512
# Retrieve state file from the URL
457513
# and output to shell and file
514+
echo ""
458515
echo "State file after the apply:"
459-
curl -s $state_file_after_url | tee ${apply_id}-after.tfstate
516+
curl -s $state_file_url | tee ${apply_id}-after.tfstate
460517

461518
fi
462519

0 commit comments

Comments
 (0)