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.

Revisions

  1. Eddie Knight created this gist Mar 1, 2021.
    66 changes: 66 additions & 0 deletions example-anchors.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,66 @@
    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
    42 changes: 42 additions & 0 deletions example-workflow.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    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/*]
    22 changes: 22 additions & 0 deletions example-workflow.yml.anchored
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,22 @@
    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
    103 changes: 103 additions & 0 deletions precommit
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,103 @@
    #!/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()