Skip to content

Commit 2e904c2

Browse files
authored
BFD-4085: Greenfield GHA Release and Deployment Workflows (#2740)
1 parent ec54eca commit 2e904c2

75 files changed

Lines changed: 1322 additions & 578 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/await-cw-logging/action.yml

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: "Deploy Environment Terraservice"
2+
description: "Composite action to deploy an environment-specific BFD Terraservice"
3+
inputs:
4+
bfd-env:
5+
description: "The BFD environment to deploy the given service to"
6+
required: true
7+
service-path:
8+
description: "The path to the Terraservice relative to the root of the repository"
9+
required: true
10+
cw-log-group:
11+
description: >-
12+
Name of CloudWatch Log Group to submit OpenTofu logs to; will be created if necessary
13+
required: true
14+
cw-log-stream:
15+
description: >-
16+
Name of CloudWatch Log Stream to submit OpenTofu logs to; will be created if necessary
17+
required: true
18+
skip-apply:
19+
description: >-
20+
Skip the apply of the given Terraservice. Useful for logging the plan without making any
21+
modifications
22+
default: "false" # Composite Action inputs must be strings
23+
required: false
24+
tofu-vars-json:
25+
description: "JSON object map of variables to their values"
26+
required: false
27+
default: "{}"
28+
runs:
29+
using: "composite"
30+
steps:
31+
- name: Setup OpenTofu and shell environment
32+
id: setup-tofu
33+
uses: ./.github/actions/bfd-setup-tofu
34+
with:
35+
service-path: ${{ inputs.service-path }}
36+
cw-log-group: ${{ inputs.cw-log-group }}
37+
cw-log-stream: ${{ inputs.cw-log-stream }}
38+
tofu-vars-json: ${{ inputs.tofu-vars-json }}
39+
40+
- name: Get parent environment
41+
id: get-parent-env
42+
run: |
43+
parent_env="$(echo "${{ inputs.bfd-env }}" | grep -Po '(prod|sandbox|test)$')"
44+
45+
echo "parent-env=$parent_env" >> $GITHUB_OUTPUT
46+
shell: bash
47+
48+
- name: OpenTofu init
49+
run: |
50+
cd "${{ github.workspace }}/${{ inputs.service-path }}"
51+
52+
tofu --version
53+
54+
# Select the default workspace unconditionally in the (unlikely) case that the workspace is
55+
# set to something at all. This ensures we can init any backend
56+
tofu workspace select default -no-color &>/dev/null || true
57+
58+
# Often OpenTofu stdout/stderr logs contain sensitive/private information. GHA logs are
59+
# available for anyone logged in with a GitHub account to view, and so this information must
60+
# be protected. Instead of logging to stdout, all potentially sensitive OpenTofu log output
61+
# is instead logged to CloudWatch
62+
tofu init \
63+
-var parent_env="${{ steps.get-parent-env.outputs.parent-env }}" \
64+
-reconfigure \
65+
-no-color 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
66+
echo "tofu init for \"${{ inputs.bfd-env }}\" completed"
67+
tofu workspace select \
68+
-no-color \
69+
-var parent_env="${{ steps.get-parent-env.outputs.parent-env }}" \
70+
-or-create "${{ inputs.bfd-env }}" 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
71+
echo "Selected workspace \"${{ inputs.bfd-env }}\""
72+
shell: bash
73+
74+
- name: Generate OpenTofu plan
75+
run: |
76+
cd "${{ github.workspace }}/${{ inputs.service-path }}"
77+
78+
echo "Generating OpenTofu plan for ${{ inputs.service-path }}..."
79+
tofu plan ${{ steps.setup-tofu.outputs.tf-vars-args }} -no-color \
80+
-out=tfplan 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
81+
echo "Plan generated for ${{ inputs.service-path }} successfully"
82+
shell: bash
83+
84+
- name: Apply env service
85+
if: ${{ inputs.skip-apply != 'true' }}
86+
run: |
87+
cd "${{ github.workspace }}/${{ inputs.service-path }}"
88+
89+
echo "Applying OpenTofu plan for ${{ inputs.service-path }}..."
90+
tofu apply -no-color -input=false tfplan 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
91+
echo "OpenTofu plan for ${{ inputs.service-path }} applied"
92+
shell: bash
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: "Deploy Platform Terraservice"
2+
description: "Composite action to deploy an account-specific Terraservice"
3+
inputs:
4+
account-type:
5+
description: "The account type to deploy the given service to"
6+
required: false
7+
service-path:
8+
description: "The path to the Terraservice relative to the root of the repository"
9+
required: true
10+
cw-log-group:
11+
description: >-
12+
Name of CloudWatch Log Group to submit OpenTofu logs to; will be created if necessary
13+
required: true
14+
cw-log-stream:
15+
description: >-
16+
Name of CloudWatch Log Stream to submit OpenTofu logs to; will be created if necessary
17+
required: true
18+
skip-apply:
19+
description: >-
20+
Skip the apply of the given Terraservice. Useful for logging the plan without making any
21+
modifications
22+
default: "false" # Composite Action inputs must be strings
23+
required: false
24+
tofu-vars-json:
25+
description: "JSON object map of variables to their values"
26+
required: false
27+
default: "{}"
28+
runs:
29+
using: "composite"
30+
steps:
31+
- name: Setup OpenTofu and shell environment
32+
id: setup-tofu
33+
uses: ./.github/actions/bfd-setup-tofu
34+
with:
35+
service-path: ${{ inputs.service-path }}
36+
cw-log-group: ${{ inputs.cw-log-group }}
37+
cw-log-stream: ${{ inputs.cw-log-stream }}
38+
tofu-vars-json: ${{ inputs.tofu-vars-json }}
39+
40+
- name: OpenTofu init
41+
run: |
42+
cd "${{ github.workspace }}/${{ inputs.service-path }}"
43+
44+
tofu --version
45+
46+
# Select the default workspace unconditionally in the (unlikely) case that the workspace is
47+
# set to something at all. This ensures we can init any backend
48+
tofu workspace select default -no-color &>/dev/null || true
49+
50+
# Often OpenTofu stdout/stderr logs contain sensitive/private information. GHA logs are
51+
# available for anyone logged in with a GitHub account to view, and so this information must
52+
# be protected. Instead of logging to stdout, all potentially sensitive OpenTofu log output
53+
# is instead logged to CloudWatch
54+
tofu init \
55+
-var account_type="${{ inputs.account-type }}" \
56+
-reconfigure \
57+
-no-color 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
58+
echo "tofu init for \"${{ inputs.account-type }}\" completed"
59+
tofu workspace select \
60+
-no-color \
61+
-var account_type="${{ inputs.account-type }}" \
62+
-or-create "${{ inputs.account-type }}" 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
63+
echo "Selected workspace \"${{ inputs.account-type }}\""
64+
shell: bash
65+
66+
- name: Generate OpenTofu plan
67+
run: |
68+
cd "${{ github.workspace }}/${{ inputs.service-path }}"
69+
70+
echo "Generating OpenTofu plan for ${{ inputs.service-path }}..."
71+
tofu plan ${{ steps.gen-tf-vars-args.outputs.tf-vars-args }} -no-color \
72+
-out=tfplan 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
73+
echo "Plan generated for ${{ inputs.service-path }} successfully"
74+
shell: bash
75+
76+
- name: Apply Terraservice
77+
if: ${{ inputs.skip-apply != 'true' }}
78+
run: |
79+
cd "${{ github.workspace }}/${{ inputs.service-path }}"
80+
81+
echo "Applying OpenTofu plan for ${{ inputs.service-path }}..."
82+
tofu apply -no-color -input=false tfplan 2>&1 | "$STDOUT_TO_CWLOGS_SCRIPT"
83+
echo "OpenTofu plan for ${{ inputs.service-path }} applied"
84+
shell: bash
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
name: "Setup OpenTofu"
2+
description: >-
3+
Composite action to setup OpenTofu such that it can be used on a runner and logging can be sent to
4+
CloudWatch
5+
inputs:
6+
service-path:
7+
description: "The path to the Terraservice relative to the root of the repository"
8+
required: true
9+
cw-log-group:
10+
description: >-
11+
Name of CloudWatch Log Group to submit OpenTofu logs to; will be created if necessary
12+
required: true
13+
cw-log-stream:
14+
description: >-
15+
Name of CloudWatch Log Stream to submit OpenTofu logs to; will be created if necessary
16+
required: true
17+
tofu-vars-json:
18+
description: "JSON object map of variables to their values"
19+
required: false
20+
default: "{}"
21+
runs:
22+
using: "composite"
23+
steps:
24+
# This step is necessary as it seems that some objects (like "github" or "inputs") are
25+
# unavailable when the top-level "env" is evaluated for Composite Actions
26+
- name: Setup environment
27+
run: |
28+
echo "STDOUT_TO_CWLOGS_SCRIPT=${{ github.workspace }}/.github/scripts/stdout-to-cwlogs.sh" \
29+
>> $GITHUB_ENV
30+
# Necessary for the "stdout-to-cwlogs.sh" script
31+
echo "CLOUDWATCH_LOG_GROUP=${{ inputs.cw-log-group }}" >> $GITHUB_ENV
32+
echo "CLOUDWATCH_LOG_STREAM=${{ inputs.cw-log-stream }}" >> $GITHUB_ENV
33+
shell: bash
34+
35+
- name: Validate inputs
36+
run: |
37+
if [[ ! -d "${{ github.workspace }}/${{ inputs.service-path }}" ]]; then
38+
echo "Directory '${{ inputs.service-path }}' does not exist; has the BFD repo been" \
39+
"checked-out?"
40+
exit 1
41+
fi
42+
shell: bash
43+
44+
- name: Create Log Group if needed
45+
run: |
46+
# Attempt to create the Log Group, swallowing any error code that is returned and also
47+
# capture the stderr output so that it can be checked
48+
create_log_group_stderr="$(
49+
aws logs create-log-group \
50+
--log-group-name "${{ inputs.cw-log-group }}" 2>&1 >/dev/null || true
51+
)"
52+
53+
# If there was an error message logged by create-log-group and that error was not
54+
# indicating that the Log Group already exists (which is fine), then log that there was an
55+
# unrecoverable error and exit
56+
if [[
57+
-n $create_log_group_stderr &&
58+
$create_log_group_stderr != *"ResourceAlreadyExistsException"* ]] \
59+
; then
60+
echo "Unrecoverable error occurred when trying to create Log Group" \
61+
"'${{inputs.cw-log-stream }}' in Log Group '${{ inputs.cw-log-group }}'"
62+
echo "$create_log_group_stderr"
63+
exit 1
64+
fi
65+
shell: bash
66+
67+
- name: Create Log Stream if needed
68+
run: |
69+
create_log_stream_stderr="$(
70+
aws logs create-log-stream --log-group-name "${{ inputs.cw-log-group }}" \
71+
--log-stream-name "${{ inputs.cw-log-stream }}" 2>&1 >/dev/null || true
72+
)"
73+
74+
if [[
75+
-n $create_log_stream_stderr &&
76+
$create_log_stream_stderr != *"ResourceAlreadyExistsException"* ]] \
77+
; then
78+
echo "Unrecoverable error occurred when trying to create Log Stream" \
79+
"'${{inputs.cw-log-stream }}' in Log Group '${{ inputs.cw-log-group }}'"
80+
echo "$create_log_stream_stderr"
81+
exit 1
82+
fi
83+
84+
echo "'${{inputs.cw-log-stream }}' in Log Group '${{ inputs.cw-log-group }}'" \
85+
"created or exists already"
86+
echo "Tail the Log Stream to view OpenTofu output in realtime:"
87+
echo "aws logs tail --since 1h --follow '${{ inputs.cw-log-group }}'"
88+
shell: bash
89+
90+
- name: Check if tofu is installed
91+
id: check-tofu-installed
92+
run: |
93+
is_tofu_installed="$(
94+
if [[ -x "$(command -v tofu)" ]]; then
95+
echo "true"
96+
else
97+
echo "false"
98+
fi
99+
)"
100+
echo "is-tofu-installed=$is_tofu_installed" >> $GITHUB_OUTPUT
101+
shell: bash
102+
103+
# Maps a given JSON object string of variable names to values, i.e.:
104+
# {
105+
# "var1": "val1",
106+
# "var2": 123,
107+
# "var3": true
108+
# }
109+
# into a space-delimited argument list that the OpenTofu CLI understands:
110+
# -var=var1=val1 -var=var2=123 -var=var3=true
111+
- name: Generate OpenTofu vars args
112+
id: gen-tf-vars-args
113+
run: |
114+
tf_vars_args="$(
115+
echo "${{ inputs.tofu-vars-json }}" | jq -r 'to_entries |
116+
map(select(.value != null and .value != "")) |
117+
map("\"-var=" + .key + "=" + (.value | tostring)+ "\"") |
118+
join(" ")'
119+
)"
120+
echo "tf-vars-args=$tf_vars_args" >> $GITHUB_OUTPUT
121+
shell: bash
122+
123+
- name: Get OpenTofu Version
124+
if: steps.check-tfvm-installed.outputs.is-tofu-installed == 'false'
125+
id: get-opentofu-version
126+
run: |
127+
cd "${{ github.workspace }}/${{ inputs.service-path }}"
128+
129+
found_file=""
130+
current_dir="$PWD"
131+
132+
# Walk up the directory tree from current working directory
133+
while [[ "$current_dir" != "/" && -z "$found_file" ]]; do
134+
if [[ -f "$current_dir/.opentofu-version" ]]; then
135+
tofu_version="$(head -n 1 "$current_dir/.opentofu-version")"
136+
found_file="$current_dir/.opentofu-version"
137+
fi
138+
139+
# Move to parent directory
140+
current_dir="$(dirname "$current_dir")"
141+
done
142+
143+
if [[ -n "$found_file" ]]; then
144+
echo "Found .opentofu-version file at $found_file specifying version: $tofu_version"
145+
else
146+
echo ".opentofu-version file not found."
147+
exit 1
148+
fi
149+
150+
echo "tofu-version=$tofu_version" >> $GITHUB_OUTPUT
151+
shell: bash
152+
153+
- name: Setup OpenTofu
154+
if: steps.check-tfvm-installed.outputs.is-tofu-installed == 'false'
155+
uses: opentofu/setup-opentofu@592200bd4b9bbf4772ace78f887668b1aee8f716 # v1.0.5
156+
with:
157+
tofu_version: ${{ steps.get-opentofu-version.outputs.tofu-version }}

0 commit comments

Comments
 (0)