Skip to content

Instantly share code, notes, and snippets.

@eddie-knight
Created March 1, 2021 16:23
Show Gist options
  • Save eddie-knight/0872c56b3d0f388b936dd6bd33448f37 to your computer and use it in GitHub Desktop.
Save eddie-knight/0872c56b3d0f388b936dd6bd33448f37 to your computer and use it in GitHub Desktop.
anchors:
tf_init: &tf_init |
terraform init -lock=false \
-backend-config="resource_group_name=terraform" \
-backend-config="key=${{ env.TF_BACKEND_KEY }}" \
-backend-config="access_key=${{ secrets.TF_BACKEND_ACCESS_KEY }}" \
-backend-config="storage_account_name=${{ secrets.TF_BACKEND_SA }}"
setup_backend_key: &setup_backend_key "githubdeployment.${{ github.event.inputs.environment }}.aks_setup.terraform.tfstate"
configure_backend_key: &configure_backend_key "githubdeployment.${{ github.event.inputs.environment }}.aks_configure.terraform.tfstate"
validation_steps: &validation_steps
- name: Checkout
uses: actions/checkout@v2
- name: Setup Terraform Environment
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 0.14.5
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
- name: Terraform Format
id: fmt
run: terraform fmt -check
continue-on-error: true
- name: Terraform Init
run: *tf_init
id: init
continue-on-error: true
- name: Terraform Validate
id: validate
if: github.event_name == 'pull_request'
run: terraform validate -no-color
continue-on-error: true
- name: Create PR comment with check results
uses: actions/[email protected]
if: github.event_name == 'pull_request' && (steps.fmt.outcome == 'failure' || steps.init.outcome == 'failure' || steps.validate.outcome == 'failure')
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `### Result from failed workflow: ${{ github.workflow }}
#### Terraform Format and Style :pencil2: \`${{ steps.fmt.outcome }}\`
#### Terraform Initialization :gear:\`${{ steps.init.outcome }}\`
#### Terraform Validate :interrobang:\`${{ steps.validate.outcome }}\`
*Pusher: @${{ github.actor }}, Action: ${{ github.event_name }}*`;
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
- name: Exit based on status of fmt, init, and validate
if: steps.fmt.outcome == 'failure' || steps.init.outcome == 'failure' || steps.validate.outcome == 'failure'
run: |
echo Init: ${{ steps.init.outcome }}
echo Format: ${{ steps.fmt.outcome }}
echo Validate: ${{ steps.validate.outcome }}
exit 1
jobs:
validate:
defaults:
run: {shell: bash, working-directory: terraform/aks/setup}
env: {TF_BACKEND_KEY: githubdeployment.dev.aks_setup.terraform.tfstate}
name: Validate Terraform
runs-on: ubuntu-latest
steps:
- {name: Checkout, uses: actions/checkout@v2}
- name: Setup Terraform Environment
uses: hashicorp/setup-terraform@v1
with: {cli_config_credentials_token: '${{ secrets.TF_API_TOKEN }}', terraform_version: 0.14.5}
- {continue-on-error: true, id: fmt, name: Terraform Format, run: terraform fmt
-check}
- {continue-on-error: true, id: init, name: Terraform Init, run: "terraform init\
\ -lock=false \\\n-backend-config=\"resource_group_name=terraform\" \\\n-backend-config=\"\
key=${{ env.TF_BACKEND_KEY }}\" \\\n-backend-config=\"access_key=${{ secrets.TF_BACKEND_ACCESS_KEY\
\ }}\" \\\n-backend-config=\"storage_account_name=${{ secrets.TF_BACKEND_SA\
\ }}\"\n"}
- {continue-on-error: true, id: validate, if: github.event_name == 'pull_request',
name: Terraform Validate, run: terraform validate -no-color}
- if: github.event_name == 'pull_request' && (steps.fmt.outcome == 'failure' ||
steps.init.outcome == 'failure' || steps.validate.outcome == 'failure')
name: Create PR comment with check results
uses: actions/[email protected]
with: {github-token: '${{ secrets.GITHUB_TOKEN }}', script: "const output =\
\ `### Result from failed workflow: ${{ github.workflow }}\n#### Terraform\
\ Format and Style :pencil2: \\`${{ steps.fmt.outcome }}\\`\n\n#### Terraform\
\ Initialization :gear:\\`${{ steps.init.outcome }}\\`\n\n#### Terraform\
\ Validate :interrobang:\\`${{ steps.validate.outcome }}\\`\n\n*Pusher:\
\ @${{ github.actor }}, Action: ${{ github.event_name }}*`;\n \ngithub.issues.createComment({\n\
\ issue_number: context.issue.number,\n owner: context.repo.owner,\n \
\ repo: context.repo.repo,\n body: output\n})\n"}
- {if: steps.fmt.outcome == 'failure' || steps.init.outcome == 'failure' || steps.validate.outcome
== 'failure', name: 'Exit based on status of fmt, init, and validate', run: "echo\
\ Init: ${{ steps.init.outcome }}\necho Format: ${{ steps.fmt.outcome }}\n\
echo Validate: ${{ steps.validate.outcome }}\nexit 1\n"}
name: Validate AKS Setup Terraform
on:
pull_request:
paths: [.github/anchors.yml, .github/workflows/aks-setup-validate.yml, terraform/aks/setup/*,
terraform/modules/aks/setup/*]
name: "Validate AKS Setup Terraform"
on:
pull_request:
paths:
- ".github/anchors.yml"
- ".github/workflows/aks-setup-validate.yml"
- "terraform/aks/setup/*"
- "terraform/modules/aks/setup/*"
jobs:
validate:
name: "Validate Terraform"
runs-on: ubuntu-latest
env:
TF_BACKEND_KEY: githubdeployment.dev.aks_setup.terraform.tfstate
defaults:
run:
shell: bash
working-directory: terraform/aks/setup
steps: *validation_steps
#!/c/Users/eknight/AppData/Local/Microsoft/WindowsApps/python
# Dev requirement for modifying "anchored" github workflows:
# Change the above line to YOUR `which python`
# then run `git config core.hooksPath .githooks`
import subprocess, sys
import ruamel.yaml
from pathlib import Path
import json
yaml = ruamel.yaml.YAML(typ='safe')
anchors = ''
def main():
''' Search for any .anchored workflows, parse them, then add any changed files to the current commit '''
cmd = subprocess.check_output(
["ls", ".github/anchored-workflows/*.anchored"],
stderr=subprocess.STDOUT).decode("utf-8") # Get all .anchored filenames
filepaths = cmd.splitlines()
with open(".github/anchors.yml", errors='ignore') as file:
global anchors
anchors = file.read()
for infile_path in filepaths:
parse(infile_path)
def parse(infile_path):
''' Parse anchored file, and if modified then stage the new file to be included in the current commit '''
outfile_path = infile_path.replace("anchored-workflows", "workflows").replace(".anchored", "")
parsed = parse_anchored_yaml(infile_path, outfile_path)
if parsed:
print(f"Parsed anchored workflow: {infile_path}->{outfile_path}")
subprocess.call(
["git", "add", outfile_path, infile_path],
stderr=subprocess.STDOUT) # Stage file into the current commit
def parse_anchored_yaml(infile_path, outfile_path):
''' Parses any .anchored file into a valid Github Actions workflow '''
yaml_data = parse_workflow(infile_path, outfile_path)
if not yaml_data:
return False
if write_parsed_workflow(infile_path, outfile_path, yaml_data):
return True
def write_parsed_workflow(infile_path, outfile_path, yaml_data):
''' If workflow would be modified by changes to the .anchored file, write those changes now '''
try:
with open(outfile_path, "r") as file:
old_yaml = yaml.load(file)
except:
old_yaml = None
if old_yaml != yaml_data:
with open(outfile_path, "w") as file:
yaml.dump(yaml_data, file)
return True
return False
def parse_workflow(infile, outfile):
''' Parse the .anchored workflow with anchors, then restore it to it's previous state '''
anchored = add_anchors_to_infile(infile)
yaml_data = get_parsed_workflow(infile)
restore_anchored_file(infile, anchored)
if yaml_data:
return yaml_data
return None
def add_anchors_to_infile(infile):
''' Rewrite the .anchored file with our anchors appended to the top '''
with open(infile, "r", errors='ignore') as file:
anchored = file.read()
with open(infile, "w") as file:
file.write(f"{anchors}\n{anchored}")
return anchored
def get_parsed_workflow(infile):
''' Load the .anchored workflow into memory, automatically parsing all anchors '''
with open(infile, "r", errors='ignore') as file:
try:
yaml_data = yaml.load(file)
del yaml_data["anchors"] # Github Actions will fail if this is present
return yaml_data
except Exception as e:
print(f"Invalid YAML in {infile}:\n {e}")
return None
def restore_anchored_file(infile, anchored):
''' Return the .anchored file to its previous state '''
with open(infile, "w", errors='ignore') as file:
file.seek(0)
file.write(anchored)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment