From 5ce96a09b77394480d04e4f0ff7198b636225a15 Mon Sep 17 00:00:00 2001 From: James Armes Date: Fri, 10 May 2024 15:48:36 -0400 Subject: [PATCH] Added GitHub actions and pull request template. --- .github/pull_request_template.md | 21 +++++ .github/workflows/branch.yml | 117 ++++++++++++++++++++++++++++ .github/workflows/deploy.yaml | 45 +++++++++++ .github/workflows/main.yaml | 117 ++++++++++++++++++++++++++++ .github/workflows/plan.yaml | 69 ++++++++++++++++ .github/workflows/pull-request.yaml | 53 +++++++++++++ README.md | 30 +++++++ 7 files changed, 452 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/branch.yml create mode 100644 .github/workflows/deploy.yaml create mode 100644 .github/workflows/main.yaml create mode 100644 .github/workflows/plan.yaml create mode 100644 .github/workflows/pull-request.yaml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..5bef95b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +#### ๐Ÿ”— Jira ticket + +CCAP-XXX + +#### โœ๏ธ Description + + + +#### ๐Ÿ“ท Design reference + + + +#### ๐Ÿงช Testing instructions + +- [ ] Step 1... +- [ ] Step 2... + +#### โœ… Completion tasks + +- [ ] Added relevant tests +- [ ] Meets acceptance criteria diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml new file mode 100644 index 0000000..0a40df0 --- /dev/null +++ b/.github/workflows/branch.yml @@ -0,0 +1,117 @@ +name: Branch Checks + +on: + push: + branches-ignore: + - main + +jobs: + find-modules: + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Find all terraform modules + id: find + uses: bendrucker/find-terraform-modules@v1 + with: + working-directory: tofu + + - name: Show all matching modules + shell: bash + run: | + mods=(${{ join(fromJSON(steps.find.outputs.modules), ' ') }}) + printf "%s\n" "${mods[@]}" + + - name: Find all changed files + id: diff + uses: technote-space/get-diff-action@v6 + with: + FORMAT: json + + - name: Show changed files + run: | + echo "${{ steps.diff.outputs.diff }}" + + - name: Get the modified modules + id: modified + uses: actions/github-script@v7 + with: + script: | + const modules = ${{ steps.find.outputs.modules }} + const diff = ${{ steps.diff.outputs.diff }} + const modifiedModules = modules.filter( + (module) => { + return !!diff.find(file => new RegExp(`^${module}/.+`).test(file)) + } + ) + + core.setOutput('modules', modifiedModules) + + - name: Show modified modules + run: | + echo "${{ steps.modified.outputs.modules }}" + outputs: + modules: ${{ steps.modified.outputs.modules }} + + lint: + runs-on: ubuntu-latest + needs: find-modules + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - uses: actions/cache@v4 + name: Cache plugin directory + with: + path: ~/.tflint.d/plugins + key: tflint-${{ hashFiles('.tflint.hcl') }} + + - uses: terraform-linters/setup-tflint@v4 + name: Setup TFLint + + - name: Show version + run: tflint --version + + - name: Init TFLint + run: tflint --init + + # Use a bash script to run tflint on each modified module. + - name: Run TFLint + shell: bash + run: | + set +e + + exit_code=0 + modules=(${{ join(fromJSON(needs.find-modules.outputs.modules), ' ') }}) + for module in ${modules[@]} + do + echo "Linting module $module" + tflint --format compact --chdir $module + exit_code=$(( $? > exit_code ? $? : exit_code )) + done + + exit $exit_code + + trivy: + name: trivy + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Run Trivy vulnarability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: config + ignore-unfixed: true + skip-dirs: '"**/*/.terraform"' + exit-code: 1 + format: sarif + output: 'trivy-results.sarif' + + - name: Parse SARIF file + if: always() + uses: Ayrx/sarif_to_github_annotations@v0.2.2 + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..e17d18d --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,45 @@ +name: Deploy pipeline + +on: + workflow_dispatch: + inputs: + environment: + description: 'Environment to deploy to' + default: 'staging' + required: true + type: environment + config: + description: 'The OpenTofu configuration to plan' + default: 'staging' + required: true + type: choice + options: + - staging + +jobs: + deploy: + runs-on: ubuntu-latest + environment: ${{ github.event.inputs.environment }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@v1 + + - name: Initialize OpenTofu + working-directory: ./tofu/config/${{ inputs.config }} + run: tofu init + + # TODO: Add a manual approval step here. For now, we'll use GitHub + # Actions' environment protection feature for sensitive environments. + - name: Apply changes + working-directory: ./tofu/config/${{ inputs.config }} + run: tofu apply --auto-approve diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..0e2c086 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,117 @@ +name: Main Checks + +on: + push: + branches: + - main + +jobs: + find-modules: + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Find all terraform modules + id: find + uses: bendrucker/find-terraform-modules@v1 + with: + working-directory: tofu + + - name: Show all matching modules + shell: bash + run: | + mods=(${{ join(fromJSON(steps.find.outputs.modules), ' ') }}) + printf "%s\n" "${mods[@]}" + + - name: Find all changed files + id: diff + uses: technote-space/get-diff-action@v6 + with: + FORMAT: json + + - name: Show changed files + run: | + echo "${{ steps.diff.outputs.diff }}" + + - name: Get the modified modules + id: modified + uses: actions/github-script@v7 + with: + script: | + const modules = ${{ steps.find.outputs.modules }} + const diff = ${{ steps.diff.outputs.diff }} + const modifiedModules = modules.filter( + (module) => { + return !!diff.find(file => new RegExp(`^${module}/.+`).test(file)) + } + ) + + core.setOutput('modules', modifiedModules) + + - name: Show modified modules + run: | + echo "${{ steps.modified.outputs.modules }}" + outputs: + modules: ${{ steps.modified.outputs.modules }} + + lint: + runs-on: ubuntu-latest + needs: find-modules + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - uses: actions/cache@v4 + name: Cache plugin directory + with: + path: ~/.tflint.d/plugins + key: tflint-${{ hashFiles('.tflint.hcl') }} + + - uses: terraform-linters/setup-tflint@v4 + name: Setup TFLint + + - name: Show version + run: tflint --version + + - name: Init TFLint + run: tflint --init + + # Use a bash script to run tflint on each modified module. + - name: Run TFLint + shell: bash + run: | + set +e + + exit_code=0 + modules=(${{ join(fromJSON(needs.find-modules.outputs.modules), ' ') }}) + for module in ${modules[@]} + do + echo "Linting module $module" + tflint --format compact --chdir $module + exit_code=$(( $? > exit_code ? $? : exit_code )) + done + + exit $exit_code + + trivy: + name: trivy + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + - name: Run Trivy vulnarability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: config + ignore-unfixed: true + skip-dirs: '**/*/.terraform' + exit-code: 1 + format: sarif + output: 'trivy-results.sarif' + + - name: Parse SARIF file + if: always() + uses: Ayrx/sarif_to_github_annotations@v0.2.2 + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/plan.yaml b/.github/workflows/plan.yaml new file mode 100644 index 0000000..0302a1e --- /dev/null +++ b/.github/workflows/plan.yaml @@ -0,0 +1,69 @@ +name: Plan the deployment pipeline + +on: + workflow_call: + inputs: + environment: + description: 'Environment to plan on' + default: 'staging' + required: true + type: string + config: + description: 'The OpenTofu configuration to plan' + default: 'staging' + required: true + type: string + outputs: + plan: + description: "The plan output from the tofu plan command" + value: ${{ jobs.plan.outputs.plan }} + secrets: + AWS_ACCESS_KEY_ID: + required: true + AWS_SECRET_ACCESS_KEY: + required: true + workflow_dispatch: + inputs: + environment: + description: 'Environment to plan on' + default: 'staging' + required: true + type: environment + config: + description: 'The OpenTofu configuration to plan' + required: true + type: choice + options: + - staging + +jobs: + plan: + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + outputs: + plan: ${{ steps.plan.outputs.stdout }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Setup OpenTofu + uses: opentofu/setup-opentofu@v1 + + - name: Initialize OpenTofu + working-directory: ./tofu/config/${{ inputs.config }} + run: tofu init + + - name: Plan changes + id: plan + working-directory: ./tofu/config/${{ inputs.config }} + run: tofu plan -no-color + + - name: Display plan + run: echo "${{ steps.plan.outputs.stdout }}" diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml new file mode 100644 index 0000000..391040b --- /dev/null +++ b/.github/workflows/pull-request.yaml @@ -0,0 +1,53 @@ +name: Pull request checks + +on: + pull_request: + +jobs: + plan: + uses: ./.github/workflows/plan.yaml + with: + # TODO: Get the environments to plan on from the diff. + environment: staging + config: staging + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + comment: + runs-on: ubuntu-latest + needs: plan + steps: + - uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // Retrieve existing bot comments for the pull request. + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }) + const botComment = comments.find(comment => { + return comment.user.type === 'Bot' && comment.body.includes('## Plan output') + }) + + // Prepare the format of the comment. + const output = `## Plan output\n\n\`\`\`\n${{ needs.plan.outputs.plan }}\n\`\`\`` + + // If we have a comment, update it. Otherwise, create a new one. + if (botComment) { + github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: output + }) + } else { + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + } diff --git a/README.md b/README.md index 2269731..2ec9f7b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,37 @@ Review the plan output. If the changes are acceptable, apply the changes: tofu apply tfplan.out ``` +### GitHub Actions + +You can also run the configurations using GitHub Actions. There are two +workflows that can be called manually: [`plan.yaml`][plan] and +[`deploy.yaml`][deploy]. These workflows can be run directly from GitHub by +following their links. + +Additionally, these workflows can be triggered using the +[GitHub CLI][github-cli] (where `config` is the name of a directory under +`tofu/config/`): + +```bash +gh workflow run .yaml -f environment=staging -f config=staging +``` + +You can then run `gh workflow list --workflow plan.yaml` to see the status of +the execution and get its id. With this id, you can watch the run and get the +logs: + +```bash +gh run watch +gh run view --log +``` + +To run the workflow for a branch other than `main`, you can pass the +`--ref ` flag. + +[deploy]: https://github.com/codeforamerica/il-gcc-infra/actions/workflows/deploy.yaml +[github-cli]: https://cli.github.com/ [hcl]: https://github.com/hashicorp/hcl [identity-center]: https://www.notion.so/cfa/AWS-Identity-Center-e8a28122b2f44595a2ef56b46788ce2c +[plan]: https://github.com/codeforamerica/il-gcc-infra/actions/workflows/plan.yaml [terraform]: https://www.terraform.io/ [tofu]: https://opentofu.org/