-
-
Save dmattia/0d17696bad1dffd90ec7c899e0343955 to your computer and use it in GitHub Desktop.
| /** | |
| * Wrapper around terragrunt to display output succinctly on Atlantis. | |
| * | |
| * Terragrunt is notoriously verbose, which can cause Atlantis to output | |
| * hundreds of comments on single PRs, which can be annoying. | |
| * | |
| * This script will output just the final plan for resources to update on | |
| * successful terragrunt runs, but will output all terragrunt output on | |
| * errors. | |
| */ | |
| const shell = require('shelljs'); | |
| const path = require('path'); | |
| const { PLANFILE } = process.env; | |
| const logger = console; | |
| /** | |
| * A map of terraform field names to mask the output of to a Github | |
| * Issue explaining why that field is masked | |
| */ | |
| const maskMap = { | |
| // Sensitive values from aws_cognito_identity_provider | |
| client_id: | |
| 'https://github.com/terraform-providers/terraform-provider-aws/issues/9934', | |
| client_secret: | |
| 'https://github.com/terraform-providers/terraform-provider-aws/issues/9934', | |
| }; | |
| /** | |
| * A map of patterns for terragrunt modules that we would expect to fail to a Github | |
| * Issue explaining why those modules are expected to fail. | |
| * | |
| * When these modules fail, we will display a message clearly stating that | |
| * this is an expected behavior | |
| */ | |
| const expectedFailingModules = { | |
| 'some/terragrunt/module': | |
| 'https://github.com/some-org/some-repo/issues/1234', | |
| }; | |
| /** | |
| * Masks any blocklisted field names in the terraform output. | |
| * | |
| * Ideally, PRs would be sent to mark these fields as sensitive in | |
| * the terraform provider itself, but this works as a temporary measure | |
| * while fields those PRs are in review | |
| * | |
| * @param output - The original plan output | |
| * @returns the plan output with sensitive values removed | |
| */ | |
| function maskSensitiveValues(output) { | |
| return Object.keys(maskMap).reduce( | |
| (out, fieldName) => | |
| out.replace( | |
| new RegExp(`("${fieldName}" *=) ".*"`, 'g'), | |
| (_, match) => | |
| `${match} This field is sensitive and cannot be shown in PRs`, | |
| ), | |
| output, | |
| ); | |
| } | |
| /** | |
| * Promisifies shelljs.exec | |
| * | |
| * @param {string} command - Command to execute in the local shell | |
| * @returns The resolved command | |
| */ | |
| async function run(command) { | |
| return new Promise((resolve) => { | |
| shell.exec(command, { silent: true }, (code, stdout, stderr) => { | |
| resolve({ code, stdout, stderr }); | |
| }); | |
| }); | |
| } | |
| /** | |
| * Runs a plan via terragrunt. Output is only shown on error | |
| */ | |
| async function runPlan() { | |
| const wasExpectedToFail = Object.keys( | |
| expectedFailingModules, | |
| ).some((pattern) => new RegExp(pattern).test(shell.pwd())); | |
| if (wasExpectedToFail) { | |
| logger.log( | |
| `Atlantis does not currently support the module in ${shell.pwd()}. Please run this module locally`, | |
| ); | |
| shell.touch(PLANFILE); | |
| return; | |
| } | |
| const { code, stderr } = await run( | |
| `terragrunt plan -no-color -out=${PLANFILE}`, | |
| ); | |
| if (code !== 0) { | |
| logger.log(stderr); | |
| throw Error(`Failed to run plan in ${shell.pwd()}`); | |
| } | |
| } | |
| /** | |
| * Prints a representation of the terraform plan output to the console | |
| */ | |
| async function printPlanFile() { | |
| const { dir, base } = path.parse(PLANFILE); | |
| shell.cd(dir); | |
| const { stdout } = await run(`terragrunt show -no-color ${base}`); | |
| logger.log(maskSensitiveValues(stdout)); | |
| } | |
| /** | |
| * Runs an apply via terragrunt. Output is only shown on error | |
| */ | |
| async function runAndPrintApply() { | |
| const { code, stdout, stderr } = await run( | |
| `terragrunt apply -no-color ${PLANFILE}`, | |
| ); | |
| if (code !== 0) { | |
| logger.log(stderr); | |
| throw Error(`Failed to run apply in ${shell.pwd()}`); | |
| } else { | |
| logger.log(stdout); | |
| } | |
| } | |
| /** | |
| * Main function | |
| */ | |
| async function main() { | |
| const args = process.argv.slice(2); | |
| const command = args[0]; | |
| if (command.toString().trim() === 'apply') { | |
| await runAndPrintApply(); | |
| } else { | |
| await runPlan(); | |
| await printPlanFile(); | |
| } | |
| } | |
| /** | |
| * Run the program, exiting with a status code of 1 on any error | |
| */ | |
| main().catch((err) => { | |
| logger.error(err); | |
| process.exit(1); | |
| }); |
It was a little tricky at first to get this up and running, especially since the comments above with suggestions are a bit old and the script seem to have changed since then (most notably for example switched name from terragroan.js to terragrunt_light.js) and I started off trying without thinking too much.. =)
As a help for people trying to use this, we had to do the following to get this to work:
(Setup: Having terragrunt-atlantis-config running in a docker container and used by github actions)
In the repos.yaml (github action for atlantis)
Under plan:
run: terragrunt plan -no-color -out=$PLANFILE -> run: /atlantis/config/terragrunt_light.js plan
Under apply:
run: terragrunt apply -no-color $PLANFILE -> run: /atlantis/config/terragrunt_light.js apply
InDockfile:
Added:
RUN apk add --no-cache nodejs
RUN apk add --no-cache npm
RUN npm install shelljs --global
ENV NODE_PATH=/usr/lib/node_modules
COPY terragrunt_light.js /atlantis/config/
Now it runs beautifully and we are happy =)
It is a great script/help for when using terragrunt in atlantis!
@dmattia I feel this should be part of the big (and also great) https://github.com/transcend-io/terragrunt-atlantis-config or at least referenced from there.. =)
@dmattia thanks for sharing this small wrapper around Terragunt + Atlantis. I use it in combination with your terragrunt-atlantis-config to generate and validate atlantis.yaml files :) - I was wondering whether you're familiar with some tool to help visualise better the actual colours in the Atlantis output when using Terragrunt plan? I know the reasons why we use the no-colour CLI flag but not sure if another parsing to the Atlantis output could be performed?
Yes, that's true.
I ended up with my own implementation of such wrapper, but the whole idea kept the same. For
tg plan-allI use grep to remove from its output excessive information because I couldn't find a way how to use PLANFILE withtg showfor several resources.