-
-
Save rubysolo/1daad938148719ca91ca5f66a21cf4b9 to your computer and use it in GitHub Desktop.
A less verbose terragrunt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment