Skip to content

Instantly share code, notes, and snippets.

@angeloskaltsikis
Forked from dmattia/terragrunt_light.js
Last active April 26, 2023 10:14
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save angeloskaltsikis/f5ded5e94495e6254a9171365bcfaf7a to your computer and use it in GitHub Desktop.
Save angeloskaltsikis/f5ded5e94495e6254a9171365bcfaf7a to your computer and use it in GitHub Desktop.
A wrapper to make Terragrunt less verbose (both plan & apply supported). Also includes all the files required to efficiently run Terragrunt with Atlantis.
ARG atlantis_version=v0.15.0
FROM runatlantis/atlantis:${atlantis_version}
LABEL maintainer="Beat DevOps Team"
LABEL description="thebeat.co atlantis image used in IaC CI/CD!"
LABEL version="0.2"
# https://github.com/gruntwork-io/terragrunt/releases
ARG terragrunt_version=v0.25.1
ARG terragrunt_sha256sum=533f43ece374476efc8bbe4eea9395892dfcd9e4f1ae033cdd6855c227632f31
ARG terragrunt_uri=https://github.com/gruntwork-io/terragrunt/releases/download/${terragrunt_version}/terragrunt_linux_amd64
# Custom fork of https://github.com/transcend-io/terragrunt-atlantis-config for linux_amd64 binary
ARG terragrunt_atlantis_config_version=0.8.0
ARG terragrunt_atlantis_config_sha256sum=3b146cdba021410a5f6de836c29da9546c3424cd55c060722fed5a724a01236b
# This is a fork of the original project because it doesn't support Linux AMD64 Binary yet. It will be changed as soon as the repo supports it.
ARG terragrunt_atlantis_config_uri=https://github.com/angeloskaltsikis/terragrunt-atlantis-config/releases/download/v${terragrunt_atlantis_config_version}/terragrunt-atlantis-config_${terragrunt_atlantis_config_version}_linux_amd64.tar.gz
# https://releases.hashicorp.com/terraform-provider-aws
ARG terraform_provider_aws_version=3.8.0
ARG terraform_provider_aws_sha256sum=d931d23ad961616f1ad437b48cb4ad147b3b68fedf8d1b541ab6c5e49eacb32c
ARG terraform_provider_aws_uri=https://releases.hashicorp.com/terraform-provider-aws/${terraform_provider_aws_version}/terraform-provider-aws_${terraform_provider_aws_version}_linux_amd64.zip
# https://releases.hashicorp.com/terraform-provider-github
ARG terraform_provider_github_version=2.9.1
ARG terraform_provider_github_sha256sum=f9f4ee5444d58ed94f608ed580feccded225a6e26899f016b1013a388dc3cce5
ARG terraform_provider_github_uri=https://releases.hashicorp.com/terraform-provider-github/${terraform_provider_github_version}/terraform-provider-github_${terraform_provider_github_version}_linux_amd64.zip
# https://github.com/Mongey/terraform-provider-kafka
ARG terraform_provider_kafka_version=0.2.10
ARG terraform_provider_kafka_sha256sum=8faa5fca58cc8c73a604e097dc8d205d521141db0db25747b874e4d65a1680db
ARG terraform_provider_kafka_uri=https://github.com/Mongey/terraform-provider-kafka/releases/download/v${terraform_provider_kafka_version}/terraform-provider-kafka_${terraform_provider_kafka_version}_linux_amd64.zip
# https://releases.hashicorp.com/terraform-provider-random
ARG terraform_provider_random_version=2.2.1
ARG terraform_provider_random_sha256sum=7fa7737661380d18cba3cdc71c4ec6f2fd281b9d61112f6b48d06ca8bbf97771
ARG terraform_provider_random_uri=https://releases.hashicorp.com/terraform-provider-random/${terraform_provider_random_version}/terraform-provider-random_${terraform_provider_random_version}_linux_amd64.zip
# Downloads multiple custom dependencies we have for our Atlantis Installation
RUN AVAILABLE_TERRAFORM_VERSIONS="0.12.26 0.12.28 0.13.2" && \
# Fetch all Terraform Versions described in the AVAILABLE_TERRAFORM_VERSIONS
for VERSION in ${AVAILABLE_TERRAFORM_VERSIONS}; do \
curl -LOs https://releases.hashicorp.com/terraform/${VERSION}/terraform_${VERSION}_linux_amd64.zip && \
curl -LOs https://releases.hashicorp.com/terraform/${VERSION}/terraform_${VERSION}_SHA256SUMS && \
sed -n "/terraform_${VERSION}_linux_amd64.zip/p" terraform_${VERSION}_SHA256SUMS | sha256sum -c && \
mkdir -p /usr/local/bin/tf/versions/${VERSION} && \
unzip terraform_${VERSION}_linux_amd64.zip -d /usr/local/bin/tf/versions/${VERSION} && \
ln -s /usr/local/bin/tf/versions/${VERSION}/terraform /usr/local/bin/terraform${VERSION} && \
rm terraform_${VERSION}_linux_amd64.zip && \
rm terraform_${VERSION}_SHA256SUMS; \
done && \
# Fetch Terragrunt
wget --directory-prefix=/tmp ${terragrunt_uri} && \
echo "${terragrunt_sha256sum} /tmp/terragrunt_linux_amd64" | sha256sum -c - && \
cp /tmp/terragrunt_linux_amd64 /usr/local/bin/terragrunt && \
chmod +x /usr/local/bin/terragrunt && \
rm /tmp/terragrunt_linux_amd64 && \
# Fetch Terragrunt NodeJS Wrapper Dependencies
apk add --update nodejs npm && \
npm install shelljs path && \
# Fetch Terragrunt Atlantis Config Generator
wget --directory-prefix=/tmp ${terragrunt_atlantis_config_uri} && \
echo "${terragrunt_atlantis_config_sha256sum} /tmp/terragrunt-atlantis-config_${terragrunt_atlantis_config_version}_linux_amd64.tar.gz" | sha256sum -c - && \
cd /tmp && tar -zxvf terragrunt-atlantis-config_${terragrunt_atlantis_config_version}_linux_amd64.tar.gz && \
cp /tmp/terragrunt-atlantis-config_${terragrunt_atlantis_config_version}_linux_amd64/terragrunt-atlantis-config_${terragrunt_atlantis_config_version}_linux_amd64 /usr/local/bin/terragrunt-atlantis-config && \
chmod +x /usr/local/bin/terragrunt-atlantis-config && \
# Terraform Providers
mkdir -p /home/atlantis/terraform-providers && \
mkdir -p /home/atlantis/.terraform.d/plugins/ && \
# Fetch Terraform AWS Provider (Both for TF 0.12 & TF 0.13)
wget --directory-prefix=/tmp ${terraform_provider_aws_uri} && \
echo "$terraform_provider_aws_sha256sum terraform-provider-aws_${terraform_provider_aws_version}_linux_amd64.zip" | sha256sum -c - && \
cd /tmp && unzip terraform-provider-aws_${terraform_provider_aws_version}_linux_amd64.zip && \
chmod +x terraform-provider-aws_v${terraform_provider_aws_version}_x5 && \
cp terraform-provider-aws_v${terraform_provider_aws_version}_x5 /home/atlantis/terraform-providers && \
ln -s /home/atlantis/terraform-providers/terraform-provider-aws_v${terraform_provider_aws_version}_x5 /home/atlantis/.terraform.d/plugins/terraform-provider-aws && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/hashicorp/aws/${terraform_provider_aws_version}/linux_amd64/ && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/aws/${terraform_provider_aws_version}/linux_amd64/ && \
ln -s /home/atlantis/terraform-providers/terraform-provider-aws_v${terraform_provider_aws_version}_x5 /home/atlantis/.terraform.d/plugins/registry.terraform.io/hashicorp/aws/${terraform_provider_aws_version}/linux_amd64/terraform-provider-aws_v${terraform_provider_aws_version} && \
ln -s /home/atlantis/terraform-providers/terraform-provider-aws_v${terraform_provider_aws_version}_x5 /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/aws/${terraform_provider_aws_version}/linux_amd64/terraform-provider-aws_v${terraform_provider_aws_version} && \
# Fetch Terraform Github Provider (Both for TF 0.12 & TF 0.13)
wget --directory-prefix=/tmp ${terraform_provider_github_uri} && \
echo "$terraform_provider_github_sha256sum terraform-provider-github_${terraform_provider_github_version}_linux_amd64.zip" | sha256sum -c - && \
cd /tmp && unzip terraform-provider-github_${terraform_provider_github_version}_linux_amd64.zip && \
chmod +x terraform-provider-github_v${terraform_provider_github_version}_x4 && \
cp terraform-provider-github_v${terraform_provider_github_version}_x4 /home/atlantis/terraform-providers && \
ln -s /home/atlantis/terraform-providers/terraform-provider-github_v${terraform_provider_github_version}_x4 /home/atlantis/.terraform.d/plugins/terraform-provider-github && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/hashicorp/google/${terraform_provider_github_version}/linux_amd64/ && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/google/${terraform_provider_github_version}/linux_amd64/ && \
ln -s /home/atlantis/terraform-providers/terraform-provider-github_v${terraform_provider_github_version}_x4 /home/atlantis/.terraform.d/plugins/registry.terraform.io/hashicorp/google/${terraform_provider_github_version}/linux_amd64/terraform-provider-github_v${terraform_provider_github_version} && \
ln -s /home/atlantis/terraform-providers/terraform-provider-github_v${terraform_provider_github_version}_x4 /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/google/${terraform_provider_github_version}/linux_amd64/terraform-provider-github_v${terraform_provider_github_version} && \
# Fetch Terraform Kafka Provider (Both for TF 0.12 & TF 0.13)
wget --directory-prefix=/tmp ${terraform_provider_kafka_uri} && \
echo "$terraform_provider_kafka_sha256sum terraform-provider-kafka_${terraform_provider_kafka_version}_linux_amd64.zip" | sha256sum -c - && \
cd /tmp && unzip terraform-provider-kafka_${terraform_provider_kafka_version}_linux_amd64.zip && \
chmod +x terraform-provider-kafka_v${terraform_provider_kafka_version} && \
cp terraform-provider-kafka_v${terraform_provider_kafka_version} /home/atlantis/terraform-providers && \
ln -s /home/atlantis/terraform-providers/terraform-provider-kafka_v${terraform_provider_kafka_version} /home/atlantis/.terraform.d/plugins/terraform-provider-kafka && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/Mongey/kafka/${terraform_provider_kafka_version}/linux_amd64/ && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/kafka/${terraform_provider_kafka_version}/linux_amd64/ && \
ln -s /home/atlantis/terraform-providers/terraform-provider-kafka_v${terraform_provider_kafka_version} /home/atlantis/.terraform.d/plugins/registry.terraform.io/Mongey/kafka/${terraform_provider_kafka_version}/linux_amd64/terraform-provider-kafka_v${terraform_provider_kafka_version} && \
ln -s /home/atlantis/terraform-providers/terraform-provider-kafka_v${terraform_provider_kafka_version} /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/kafka/${terraform_provider_kafka_version}/linux_amd64/terraform-provider-kafka_v${terraform_provider_kafka_version} && \
# Fetch Terraform Random Provider (Both for TF 0.12 & TF 0.13)
wget --directory-prefix=/tmp ${terraform_provider_random_uri} && \
echo "$terraform_provider_random_sha256sum terraform-provider-random_${terraform_provider_random_version}_linux_amd64.zip" | sha256sum -c - && \
cd /tmp && unzip terraform-provider-random_${terraform_provider_random_version}_linux_amd64.zip && \
chmod +x terraform-provider-random_v${terraform_provider_random_version}_x4 && \
cp terraform-provider-random_v${terraform_provider_random_version}_x4 /home/atlantis/terraform-providers && \
ln -s /home/atlantis/terraform-providers/terraform-provider-random_v${terraform_provider_random_version}_x4 /home/atlantis/.terraform.d/plugins/terraform-provider-random && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/hashicorp/random/${terraform_provider_random_version}/linux_amd64/ && \
mkdir -p /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/random/${terraform_provider_random_version}/linux_amd64/ && \
ln -s /home/atlantis/terraform-providers/terraform-provider-random_v${terraform_provider_random_version}_x4 /home/atlantis/.terraform.d/plugins/registry.terraform.io/hashicorp/random/${terraform_provider_random_version}/linux_amd64/terraform-provider-random_v${terraform_provider_random_version} && \
ln -s /home/atlantis/terraform-providers/terraform-provider-random_v${terraform_provider_random_version}_x4 /home/atlantis/.terraform.d/plugins/registry.terraform.io/-/random/${terraform_provider_random_version}/linux_amd64/terraform-provider-random_v${terraform_provider_random_version} && \
# Delete all the /tmp folder to make sure it deletes all downloads
rm -rf /tmp/*
# Based on https://gist.github.com/dmattia/0d17696bad1dffd90ec7c899e0343955 for less verbose terragrunt output from . Our version is a more extended one.
COPY terragrunt_light.js /home/atlantis/terragrunt_light.js
# Wrapper Script which tries to run git hooks if the repo cloned to atlantis include some in a certain directory.
COPY post-checkout.sh /home/atlantis/hooks/post-checkout
#!/bin/bash
## Make sure to give execution permissions.
# Checks if a post-checkout.sh script exists in `atlantis-hooks` folder in order to auto-run that.
PWD=`pwd`
FILE=$PWD/atlantis-git-hooks/repo-post-checkout.sh
if [ -f "$FILE" ]; then
echo "$FILE exists in the repo $PWD."
exec $FILE
else
echo "$FILE does not exist in the repo $PWD."
fi
#!/bin/bash
## This should be placed in folder `atlantis-git-hooks/repo-post-checkout.sh` in your Environments repo (where Terragrunt code lives).
## Make sure to give execution permissions.
result=${PWD##*/}
if [ $result = "default" ]; then
echo "Default Workspace. Atlantis Config will be generated."
exec terragrunt-atlantis-config generate --ignore-parent-terragrunt --autoplan --workflow terragrunt --parallel --create-workspace --create-project-name --output ./atlantis.yaml
else
echo "Not the Default Workspace. Will clone atlantis.yaml from default workspace and will skip re-generating it."
ln -s ../default/atlantis.yaml atlantis.yaml
fi
terragrunt:
plan:
steps:
- env:
name: TERRAGRUNT_TFPATH
command: 'echo "terraform${ATLANTIS_TERRAFORM_VERSION}"'
- run: node ~/terragrunt_light.js plan $PLANFILE
apply:
steps:
- env:
name: TERRAGRUNT_TFPATH
command: 'echo "terraform${ATLANTIS_TERRAFORM_VERSION}"'
- run: node ~/terragrunt_light.js apply $PLANFILE
/**
* 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');
/**
* Promisifies shelljs.exec
*
* @param {string} command - Command to execute in the local shell
*/
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
*
* @param {string} file - name of the plan file to show the output of
*/
async function runPlan(file) {
const { code, stderr } = await run(`terragrunt plan -no-color -out=${file}`);
if (code != 0) {
console.log(stderr);
throw Error(`Failed to run plan in ${shell.pwd()}`);
}
}
/**
* Prints a representation of the terraform plan output to the console
*
* @param {string} file - name of the plan file to show the output of
*/
async function printPlanFile(file) {
const { dir, base } = path.parse(file);
shell.cd(dir);
const { stdout } = await run(`terragrunt show -no-color ${base}`);
console.log(stdout);
}
/**
* Runs an apply via terragrunt. Output is only shown on error
*
* @param {string} file - name of the plan file to be used for apply
*/
async function runAndPrintApply(file) {
const { code, stdout, stderr } = await run(`terragrunt apply -no-color ${file}`);
if (code != 0) {
console.log(stderr);
throw Error(`Failed to run apply in ${shell.pwd()}`);
} else {
console.log(stdout);
shell.rm(file)
}
}
/**
* Main function
*/
async function main() {
var args = process.argv.slice(2);
var command = args[0];
var planFilePath = args[1];
if (command.toString().trim() === 'apply') {
await runAndPrintApply(planFilePath);
} else {
await runPlan(planFilePath);
await printPlanFile(planFilePath);
}
}
/**
* Run the program, exiting with a status code of 1 on any error
*/
main().catch((err) => {
console.error(err);
process.exit(1);
});
@angeloskaltsikis
Copy link
Author

The above gist is based on https://gist.github.com/dmattia/0d17696bad1dffd90ec7c899e0343955 but also includes the following changes:

  • Support multiple planfiles supplied by Atlantis (Makes atlantis plan/apply work for all cases (general command or with directory)
  • Fixes bug with some planfile with blank Atlantis output (fixed by using terragrunt show instead of terraform show)
  • Support terragrunt apply less verbose output 🎉

@dmattia
Copy link

dmattia commented Jun 26, 2020

Very nice!

@mqmr
Copy link

mqmr commented Jun 29, 2020

https://gist.github.com/angeloskaltsikis/f5ded5e94495e6254a9171365bcfaf7a#file-terragrunt_light-js-L46-L51

With terragrunt show is it still necessary to change a directory to that where a planfile resides?

@angeloskaltsikis
Copy link
Author

@mqmr I have tested that and it seems that, yes we need to change directory in order to run terragrunt show using the correct terragrunt leaf file as it seems that it uses the current's directory terragrunt file (which in our case cannot run from the root Terragrunt file). I guess that this happens due to having to use the correct state which in our case can be found on the leaf terragrunt file which calls the module. You can check the help of the command for that.

bash-5.0$ terragrunt output -h
[terragrunt] 2020/06/29 21:16:41 Running command: terraform output -h
Usage: terraform output [options] [NAME]

  Reads an output variable from a Terraform state file and prints
  the value. With no additional arguments, output will display all
  the outputs for the root module.  If NAME is not specified, all
  outputs are printed.

Options:

  -state=path      Path to the state file to read. Defaults to
                   "terraform.tfstate".

  -no-color        If specified, output won't contain any color.

  -json            If specified, machine readable output will be
                   printed in JSON format

Is there a certain reason you are looking whether this is necessary?

@ryan-dyer-sp
Copy link

ryan-dyer-sp commented Oct 14, 2020

TY for this. I have modified this for us to use detailed-exitcode for plan in order to reduce the output further and allow us to search for 'to change' in the PR in order to find just the changed things.

const shell = require('shelljs');

/**
 * Promisifies shelljs.exec
 *
 * @param {string} command - Command to execute in the local shell
 */
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
 *
 * @param {string} file - name of the plan file to show the output of
 */
async function runPlan(file) {
  const { code, stderr } = await run(`terragrunt plan -detailed-exitcode -no-color -out=${file}`);
  if (code == 1) {
    console.log(stderr);
    throw Error(`Failed to run plan in ${shell.pwd()}`);
  } else if (code == 0) {
        console.log("=== No Changes Detected ===");
  }
  return code;
}

async function printPlan(file) {
  const { stdout } = await run(`terragrunt show -no-color ${file}`);
  console.log(stdout);
}

/**
 * Runs an apply via terragrunt. Output is only shown on error
 *
 * @param {string} file - name of the plan file to be used for apply
 */
async function runAndPrintApply(file) {
  const { code, stdout, stderr } = await run(`terragrunt apply -no-color ${file}`);
  if (code != 0) {
    console.log(stderr);
    throw Error(`Failed to run apply in ${shell.pwd()}`);
  } else {
    console.log(stdout);
  }
}

/**
 * Main function
 */
async function main() {
  var args = process.argv.slice(2);
  var command = args[0];
  var planFilePath = args[1];
  if (command.toString().trim() === 'apply') {
    await runAndPrintApply(planFilePath);
  } else {
    const rc = await runPlan(planFilePath);
    if (rc == 2) {
      await printPlan(planFilePath);
    }
  }
}

/**
 * Run the program, exiting with a status code of 1 on any error
 */
main().catch((err) => {
  console.error(err);
  process.exit(1);
});

@angeloskaltsikis
Copy link
Author

angeloskaltsikis commented Oct 15, 2020

Nice. Thanks for your contribution @ryan-dyer-sp!
We use another way to easily find which plans have changes. (Actually this expands all comments from Atlantis)

javascript:document.querySelectorAll("details").forEach((detail) => { if (detail.className == '') { detail.setAttribute("open", ""); } });

I have also pushed an update to the wrapper today which fixes a small race condition with already applied plan files (revision 5 of the gist).

@mijdavis2
Copy link

For some reason this didn't work for me. Kept getting errors. I ended up making this, which is more succinct but less featurefull:

#!/bin/bash

#
# terrabetter.sh 
#
# Better (cleaner) 'plan' stage output for terragrunt
#
# Combine with tfmask for secure output
#

# Store stdout and stderr
PLAN_OUTPUT=$(terragrunt plan -out=$1 2>&1 >/dev/null)

# Show clean plan if successful via `show`,
# show full plan command output if errors
status=$?
[ $status -eq 0 ] && echo "Plan success" && terragrunt show $1 2>/dev/null || echo "$PLAN_OUTPUT"

@dmattia
Copy link

dmattia commented Oct 16, 2020

ooh nice updates! I figured I should post my latest updates here, as it seems your fork of the gist is the one people check more 😄

Key updates:

  • Masking sensitive values
  • Adding output for specific modules that you expect to fail letting the dev know that CI can't handle the module yet
/**
 * 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 = {
  'backend/env-.*/database/users/.*':
    'https://github.com/transcend-io/main/issues/4284',
};

/**
 * 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);
});

@tsunamishaun
Copy link

I've extended the script to source in terragrunt/terraform binaries from .terragrunt-version and .terraform-version like tfenv/tgenv do. I would be glad to add it but do either of you know if concurrent terraform/terragrunt runs are possible with tgenv and tfenv? I know @angeloskaltsikis 's Dockerfile holds the binaries (which I am using) but dependencies with different versions break in terragrunt when the binary name is terragrunt12.29 and the version required in the dependency is terragrunt13.5 (for example). Using tfenv/tgenv would keep the terragrunt/terraform binary names consistent but I've heard concurrent runs isn't possible (but never tried myself).

@angeloskaltsikis
Copy link
Author

Hey @tsunamishaun thanks for your comment.
If i understood it correctly your use case is that you just want to add a .terragrunt-version or .terraform-version in a few folders and you need this to run with those versions, right?
To be honest we didn't have this need as we try to keep the Terraform/Terragrunt Versions uniform per repository and as a result we never tried to do something like that.
Having used tfenv only locally in my laptop i have no experience to say if concurrent runs are possible.
In case you try that though please let us know, as this may be useful in the future.
Thanks in advance.

@evanstoddard23
Copy link

Hey @angeloskaltsikis, thanks for all your work on this. I'm debugging an issue where my atlantis.yaml is still not getting generated and think I may have narrowed it down. I see you're installing the hook to /home/atlantis/hooks/post-checkout but is that your .git directory or are you telling git about it somewhere else?

@angeloskaltsikis
Copy link
Author

Hey @evanstoddard23,
You are welcome.
Damn, i missed that part.
So in the gitconfig key in values.yaml of Atlantis helm chart you will have to add the following before the url at the same level with them.

  # Indicates the hooks path that should run on all Git Repos in Atlantis
  [core]
    hooksPath = /home/atlantis/hooks

For example

# gitconfig: |
  [core]
    hooksPath = /home/atlantis/hooks
  [url "https://YOUR_GH_TOKEN@github.com"]
    insteadOf = https://github.com

I will add that here as well.
Thanks for finding this omission. 😄

Btw it seems that since pre_workflow_hooks have been added to Atlantis more people run the generator using those.
You can read more here but unfortunately, I cannot help with those.

@angeloskaltsikis
Copy link
Author

Any luck @evanstoddard23 ?
Please let me know so I can update both the Gist & the blogpost to include that.
TIA

@evanstoddard23
Copy link

Hey @angeloskaltsikis, thanks for that!

As I'm deploying on ECS fargate, I ended up adding this to my Dockerfile: RUN git config --system core.hooksPath /home/atlantis/hooks and it's working great now!

I actually started with the pre_worflow_hooks and found it to be lacking in some regards. Since I'm using multiple workspaces (via the transcendio generator) to enable parallelism, I found it would generate the atlantis.yaml file in my default workspace but then as it created other directories for the other workspaces the atlantis.yaml would no longer be in the directory path and the apply steps would fail. Not completely sure if I was using it all correctly, but your solution is working great.

Also, the node script to cleanup the output is very nice! Thanks so much for sharing all this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment