Skip to content

Instantly share code, notes, and snippets.

@rubysolo
Forked from dmattia/terragrunt_light.js
Created December 31, 2020 20:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rubysolo/1daad938148719ca91ca5f66a21cf4b9 to your computer and use it in GitHub Desktop.
Save rubysolo/1daad938148719ca91ca5f66a21cf4b9 to your computer and use it in GitHub Desktop.
A less verbose terragrunt
/**
* 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