diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 28233fef5..dc3191a40 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -12,6 +12,11 @@ Enjoy the clean, valid, and documented code! * [Run via Docker](#run-via-docker) * [Check results](#check-results) * [Cleanup](#cleanup) +* [Add new hook](#add-new-hook) + * [Before write code](#before-write-code) + * [Prepare basic documentation](#prepare-basic-documentation) + * [Add code](#add-code) + * [Finish with the documentation](#finish-with-the-documentation) ## Run and debug hooks locally @@ -41,7 +46,7 @@ For example, to test that the [`terraform_fmt`](../README.md#terraform_fmt) hook To check is your improvement not violate performance, we have dummy execution time tests. Script accept next options: - + | # | Name | Example value | Description | | --- | ---------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------- | | 1 | `TEST_NUM` | `200` | How many times need repeat test | @@ -49,6 +54,7 @@ Script accept next options: | 3 | `TEST_DIR` | `'/tmp/infrastructure'` | Dir on what you run tests. | | 4 | `TEST_DESCRIPTION` | ```'`terraform_tfsec` PR #123:'``` | Text that you'd like to see in result | | 5 | `RAW_TEST_`
`RESULTS_FILE_NAME` | `terraform_tfsec_pr123` | (Temporary) File where all test data will be stored. | + ### Run via BASH @@ -87,3 +93,46 @@ Results will be located at `./test/results` dir. ```bash sudo rm -rf tests/results ``` + +## Add new hook + +You can use [this PR](https://github.com/antonbabenko/pre-commit-terraform/pull/252) as an example. + +### Before write code + +1. Try to figure out future hook usage. +2. Confirm the concept with [Anton Babenko](https://github.com/antonbabenko). + +### Prepare basic documentation + +1. Identify and describe dependencies in [Install dependencies](../README.md#1-install-dependencies) and [Available Hooks](../README.md#available-hooks) sections + +### Add code + +1. Based on prev. block, add hook dependencies installation to [Dockerfile](../Dockerfile). + Check that works: + * `docker build -t pre-commit --build-arg INSTALL_ALL=true .` + * `docker build -t pre-commit --build-arg _VERSION=latest .` + * `docker build -t pre-commit --build-arg _VERSION=<1.2.3> .` +2. Add new hook to [`.pre-commit-hooks.yaml`](../.pre-commit-hooks.yaml) +3. Create hook file. Don't forget to make it executable via `chmod +x /path/to/hook/file`. +4. Test hook. How to do it is described in [Run and debug hooks locally](#run-and-debug-hooks-locally) section. +5. Test hook one more time. + 1. Push commit with hook file to GitHub + 2. Grab SHA hash of the commit + 3. Test hook using `.pre-commit-config.yaml`: + + ```yaml + repos: + - repo: https://github.com/antonbabenko/pre-commit-terraform # Your repo + rev: 3d76da3885e6a33d59527eff3a57d246dfb66620 # Your commit SHA + hooks: + - id: terraform_docs # New hook name + args: + - --args=--config=.terraform-docs.yml # Some args that you'd like to test + ``` + +### Finish with the documentation + +1. Add hook description to [Available Hooks](../README.md#available-hooks). +2. Create and populate a new hook section in [Hooks usage notes and examples](../README.md#hooks-usage-notes-and-examples). diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 492b559a5..08e65ba27 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,3 +1,12 @@ +- id: infracost_breakdown + name: Infracost breakdown + description: Check terraform infrastructure cost + entry: infracost_breakdown.sh + language: script + require_serial: true + files: \.(tf(vars)?|hcl)$ + exclude: \.terraform\/.*$ + - id: terraform_fmt name: Terraform fmt description: Rewrites all Terraform configuration files to a canonical format. diff --git a/Dockerfile b/Dockerfile index 18e1c7c63..39a539cd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,9 @@ RUN apt update && \ software-properties-common \ curl \ python3 \ - python3-pip && \ + python3-pip \ + # infracost deps + jq && \ # Upgrade pip for be able get latest Checkov python3 -m pip install --upgrade pip && \ # Cleanup @@ -41,6 +43,7 @@ RUN curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - && \ WORKDIR /bin_dir ARG CHECKOV_VERSION=${CHECKOV_VERSION:-false} +ARG INFRACOST_VERSION=${INFRACOST_VERSION:-false} ARG TERRAFORM_DOCS_VERSION=${TERRAFORM_DOCS_VERSION:-false} ARG TERRAGRUNT_VERSION=${TERRAGRUNT_VERSION:-false} ARG TERRASCAN_VERSION=${TERRASCAN_VERSION:-false} @@ -54,6 +57,7 @@ ARG TFSEC_VERSION=${TFSEC_VERSION:-false} ARG INSTALL_ALL=${INSTALL_ALL:-false} RUN if [ "$INSTALL_ALL" != "false" ]; then \ echo "export CHECKOV_VERSION=latest" >> /.env && \ + echo "export INFRACOST_VERSION=latest" >> /.env && \ echo "export TERRAFORM_DOCS_VERSION=latest" >> /.env && \ echo "export TERRAGRUNT_VERSION=latest" >> /.env && \ echo "export TERRASCAN_VERSION=latest" >> /.env && \ @@ -73,6 +77,16 @@ RUN . /.env && \ ) \ ; fi +# infracost +RUN . /.env && \ + if [ "$INFRACOST_VERSION" != "false" ]; then \ + ( \ + INFRACOST_RELEASES="https://api.github.com/repos/infracost/infracost/releases" && \ + [ "$INFRACOST_VERSION" = "latest" ] && curl -L "$(curl -s ${INFRACOST_RELEASES}/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz \ + || curl -L "$(curl -s ${INFRACOST_RELEASES} | grep -o -E "https://.+?v${INFRACOST_VERSION}/infracost-linux-amd64.tar.gz")" > infracost.tgz \ + ) && tar -xzf infracost.tgz && rm infracost.tgz && mv infracost-linux-amd64 infracost \ + ; fi + # Terraform docs RUN . /.env && \ if [ "$TERRAFORM_DOCS_VERSION" != "false" ]; then \ @@ -131,6 +145,7 @@ RUN . /.env && \ pre-commit --version >> $F && \ terraform --version | head -n 1 >> $F && \ (if [ "$CHECKOV_VERSION" != "false" ]; then echo "checkov $(checkov --version)" >> $F; else echo "checkov SKIPPED" >> $F ; fi) && \ + (if [ "$INFRACOST_VERSION" != "false" ]; then echo "$(./infracost --version)" >> $F; else echo "infracost SKIPPED" >> $F ; fi) && \ (if [ "$TERRAFORM_DOCS_VERSION" != "false" ]; then ./terraform-docs --version >> $F; else echo "terraform-docs SKIPPED" >> $F; fi) && \ (if [ "$TERRAGRUNT_VERSION" != "false" ]; then ./terragrunt --version >> $F; else echo "terragrunt SKIPPED" >> $F ; fi) && \ (if [ "$TERRASCAN_VERSION" != "false" ]; then echo "terrascan $(./terrascan version)" >> $F; else echo "terrascan SKIPPED" >> $F ; fi) && \ @@ -159,10 +174,14 @@ COPY --from=builder \ /usr/local/bin/pre-commit \ /usr/bin/git \ /usr/bin/git-shell \ + /usr/bin/jq \ /usr/bin/ # Copy terrascan policies COPY --from=builder /root/ /root/ ENV PRE_COMMIT_COLOR=${PRE_COMMIT_COLOR:-always} +ENV INFRACOST_API_KEY=${INFRACOST_API_KEY:-} +ENV INFRACOST_SKIP_UPDATE_CHECK=${INFRACOST_SKIP_UPDATE_CHECK:-false} + ENTRYPOINT [ "pre-commit" ] diff --git a/README.md b/README.md index bead61f68..265817e9c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Want to Contribute? Check [open issues](https://github.com/antonbabenko/pre-comm * [Available Hooks](#available-hooks) * [Hooks usage notes and examples](#hooks-usage-notes-and-examples) * [checkov](#checkov) + * [infracost_breakdown](#infracost_breakdown) * [terraform_docs](#terraform_docs) * [terraform_docs_replace](#terraform_docs_replace) * [terraform_fmt](#terraform_fmt) @@ -45,6 +46,8 @@ Want to Contribute? Check [open issues](https://github.com/antonbabenko/pre-comm * [`terrascan`](https://github.com/accurics/terrascan) required for `terrascan` hook. * [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook. * [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook. +* [`infracost`](https://github.com/infracost/infracost) required for `infracost_breakdown` hook. +* [`jq`](https://github.com/stedolan/jq) required for `infracost_breakdown` hook.
Docker
@@ -65,6 +68,7 @@ docker build -t pre-commit \ --build-arg PRE_COMMIT_VERSION=latest \ --build-arg TERRAFORM_VERSION=latest \ --build-arg CHECKOV_VERSION=2.0.405 \ + --build-arg INFRACOST_VERSION=latest \ --build-arg TERRAFORM_DOCS_VERSION=0.15.0 \ --build-arg TERRAGRUNT_VERSION=latest \ --build-arg TERRASCAN_VERSION=1.10.0 \ @@ -83,8 +87,9 @@ To disable the pre-commit color output, set `-e PRE_COMMIT_COLOR=never`. [`coreutils`](https://formulae.brew.sh/formula/coreutils) required for `terraform_validate` hook on macOS (due to use of `realpath`). ```bash -brew install pre-commit terraform-docs tflint tfsec coreutils checkov terrascan +brew install pre-commit terraform-docs tflint tfsec coreutils checkov terrascan infracost jq terrascan init +infracost register ```
@@ -103,6 +108,8 @@ curl -L "$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/re curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/latest | grep -o -E -m 1 "https://.+?tfsec-linux-amd64")" > tfsec && chmod +x tfsec && sudo mv tfsec /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/accurics/terrascan/releases/latest | grep -o -E -m 1 "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && sudo mv terrascan /usr/bin/ && terrascan init +sudo apt install -y jq && \ +curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost register ``` @@ -120,6 +127,8 @@ curl -L "$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/re curl -L "$(curl -s https://api.github.com/repos/accurics/terrascan/releases/latest | grep -o -E -m 1 "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && sudo mv terrascan /usr/bin/ && terrascan init curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/latest | grep -o -E -m 1 "https://.+?tfsec-linux-amd64")" > tfsec && chmod +x tfsec && sudo mv tfsec /usr/bin/ +sudo apt install -y jq && \ +curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost register ``` @@ -182,6 +191,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | Hook name | Description | Dependencies
[Install instructions here](#1-install-dependencies) | | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | `checkov` | [checkov](https://github.com/bridgecrewio/checkov) static analysis of terraform templates to spot potential security issues. [Hook notes](#checkov) | `checkov`
Ubuntu deps: `python3`, `python3-pip` | +| `infracost_breakdown` | Check how much your infra costs with [infracost](https://github.com/infracost/infracost). [Hook notes](#infracost_breakdown) | `infracost`, `jq`, [Infracost API key](https://www.infracost.io/docs/#2-get-api-key), Internet connection | `terraform_docs_replace` | Runs `terraform-docs` and pipes the output directly to README.md | `python3`, `terraform-docs` | | `terraform_docs_without_`
`aggregate_type_defaults` | Inserts input and output documentation into `README.md` without aggregate type defaults. Hook notes same as for [terraform_docs](#terraform_docs) | `terraform-docs` | | `terraform_docs` | Inserts input and output documentation into `README.md`. Recommended. [Hook notes](#terraform_docs) | `terraform-docs` | @@ -211,6 +221,94 @@ For [checkov](https://github.com/bridgecrewio/checkov) you need to specify each ] ``` +### infracost_breakdown + +`infracost_breakdown` build on top of the `infracost breakdown` command. It, if needed, runs `terraform init`, `terraform plan` and calls `infracost` API - so this hook can run up to several minutes. + +Unlike most other hooks, this one triggers all changes to the files but checks predefined paths each time. + +For example, the hook tracks `--path=./env/dev` and `./env/dev` depend on `./main.tf`. So when you will make changes to `./main.tf` - the hook will run and show the cost changes for `./env/dev`. + + +1. `infracost_breakdown` supports custom arguments so you can pass [supported flags](https://www.infracost.io/docs/#useful-options). + The following example only shows costs: + + ```yaml + - id: infracost_breakdown + args: + - --args=--path=./env/dev + verbose: true # Always show costs + ``` + +
Output + + ```bash + Running in "env/dev" + + Summary: { + "unsupportedResourceCounts": { + "aws_sns_topic_subscription": 1 + } + } + + Total Monthly Cost: 86.83 USD + Total Monthly Cost (diff): 86.83 USD + ``` + +
+ +2. You can provide limitations when the hook should fail: + + ```yaml + - id: infracost_breakdown + args: + - --args=--path=./env/dev + - --hook-config=.totalHourlyCost|tonumber > 0.1 + - --hook-config=.totalHourlyCost|tonumber > 1 + - --hook-config=.projects[].diff.totalMonthlyCost|tonumber != 10000 + - --hook-config=.currency == "USD" + ``` + +
Output + + ```bash + Running in "env/dev" + Passed: .totalHourlyCost|tonumber > 0.1 0.11894520547945205 > 0.1 + Failed: .totalHourlyCost|tonumber > 1 0.11894520547945205 > 1 + Passed: .projects[].diff.totalMonthlyCost|tonumber !=10000 86.83 != 10000 + Passed: .currency == "USD" "USD" == "USD" + + Summary: { + "unsupportedResourceCounts": { + "aws_sns_topic_subscription": 1 + } + } + + Total Monthly Cost: 86.83 USD + Total Monthly Cost (diff): 86.83 USD + ``` + +
+ + * Hook uses `jq` to parse `infracost` output, so paths to values like `.totalHourlyCost` and `.totalMonthlyCost` should be in jq-compatible format. + To check available structure use `infracost breakdown -p PATH_TO_TF_DIR --format json | jq -r . > infracost.json`. And play with it on [jqplay.org](https://jqplay.org/) + * Supported comparison operators: `<`, `<=`, `==`, `!=`, `>=`, `>`. + * Most useful paths and checks: + * `.totalHourlyCost` (same to `.projects[].breakdown.totalHourlyCost`) - show total hourly infra cost + * `.totalMonthlyCost` (same to `.projects[].breakdown.totalMonthlyCost`) - show total monthly infra cost + * `.projects[].diff.totalHourlyCost` - show hourly cost diff between existing infra and tf plan + * `.projects[].diff.totalMonthlyCost` - show monthly cost diff between existing infra and tf plan + * `.diffTotalHourlyCost` (for Infracost version 0.9.12 or newer) or `[.projects[].diff.totalMonthlyCost | select (.!=null) | tonumber] | add > 1000` (for Infracost older than 0.9.12): + * fail if changes push the total monthly cost estimate above $1K + * fail if changes increase the cost by $1K. + * You can set up only one path per one hook (`- id: infracost_breakdown`) - this is an `infracost` limitation. + * Set `verbose: true` to see cost even when the checks are passed. + * To disable hook color output, set `PRE_COMMIT_COLOR=never` env var + +3. **Docker usage**. In `docker build` or `docker run` command: + * You need to provide [Infracost API key](https://www.infracost.io/docs/integrations/environment_variables/#infracost_api_key) via `-e INFRACOST_API_KEY=`. By default it is saved in `~/.config/infracost/credentials.yml` + * Set `-e INFRACOST_SKIP_UPDATE_CHECK=true` to skip the Infracost update check; can be useful in CI/CD systems. [Doc](https://www.infracost.io/docs/integrations/environment_variables/#infracost_skip_update_check) + ### terraform_docs 1. `terraform_docs` and `terraform_docs_without_aggregate_type_defaults` will insert/update documentation generated by [terraform-docs](https://github.com/terraform-docs/terraform-docs) framed by markers: @@ -227,7 +325,7 @@ For [checkov](https://github.com/bridgecrewio/checkov) you need to specify each 3. It is possible to automatically: * create docfile (and PATH to it) - * extend exiting docs files, by appending markers to the end of file (see p.1) + * extend existing doc files by appending markers to the end of the file (see item 1) * use different than `README.md` docfile name. ```yaml @@ -245,7 +343,7 @@ For [checkov](https://github.com/bridgecrewio/checkov) you need to specify each args: - --args=--config=.terraform-docs.yml -5. If you need some exotic settings, it can be be done too. I.e. this one generates HCL files: +5. If you need some exotic settings, it can be done too. I.e. this one generates HCL files: ```yaml - id: terraform_docs @@ -323,7 +421,7 @@ Example: - --args=--enable-rule=terraform_documented_variables ``` -2. When you have multiple directories and want to run `tflint` in all of them and share a single config file, it is impractical to hard-code the path to `.tflint.hcl` file. The solution is to use the `__GIT_WORKING_DIR__` placeholder which will be replaced by `terraform_tflint` hooks with Git working directory (repo root) at run time. For example: +2. When you have multiple directories and want to run `tflint` in all of them and share a single config file, it is impractical to hard-code the path to the `.tflint.hcl` file. The solution is to use the `__GIT_WORKING_DIR__` placeholder which will be replaced by `terraform_tflint` hooks with Git working directory (repo root) at run time. For example: ```yaml - id: terraform_tflint diff --git a/infracost_breakdown.sh b/infracost_breakdown.sh new file mode 100755 index 000000000..c088b7733 --- /dev/null +++ b/infracost_breakdown.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +set -eo pipefail + +function main { + common::initialize + common::parse_cmdline "$@" + infracost_breakdown_ "${HOOK_CONFIG[*]}" "${ARGS[*]}" +} + +function common::colorify { + # Colors. Provided as first string to first arg of function. + # shellcheck disable=SC2034 + local -r red="$(tput setaf 1)" + # shellcheck disable=SC2034 + local -r green="$(tput setaf 2)" + # shellcheck disable=SC2034 + local -r yellow="$(tput setaf 3)" + # Color reset + local -r RESET="$(tput sgr0)" + + # Params start # + local COLOR="${!1}" + local -r TEXT=$2 + # Params end # + + if [ "$PRE_COMMIT_COLOR" = "never" ]; then + COLOR=$RESET + fi + + echo -e "${COLOR}${TEXT}${RESET}" +} + +function common::initialize { + local SCRIPT_DIR + # get directory containing this script + SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" + + # source getopt function + # shellcheck source=lib_getopt + . "$SCRIPT_DIR/lib_getopt" +} + +# common global arrays. +# Populated in `parse_cmdline` and can used in hooks functions +declare -a ARGS=() +declare -a HOOK_CONFIG=() +declare -a FILES=() +function common::parse_cmdline { + local argv + argv=$(getopt -o a:,h: --long args:,hook-config: -- "$@") || return + eval "set -- $argv" + + for argv; do + case $argv in + -a | --args) + shift + ARGS+=("$1") + shift + ;; + -h | --hook-config) + shift + HOOK_CONFIG+=("$1;") + shift + ;; + --) + shift + FILES=("$@") + break + ;; + esac + done +} + +function infracost_breakdown_ { + local -r hook_config="$1" + local args + read -r -a args <<< "$2" + + # Get hook settings + IFS=";" read -r -a checks <<< "$hook_config" + + if [ "$PRE_COMMIT_COLOR" = "never" ]; then + args+=("--no-color") + fi + + local RESULTS + RESULTS="$(infracost breakdown "${args[@]}" --format json)" + local API_VERSION + API_VERSION="$(jq -r .version <<< "$RESULTS")" + + if [ "$API_VERSION" != "0.2" ]; then + common::colorify "yellow" "WARNING: Hook supports Infracost API version \"0.2\", got \"$API_VERSION\"" + common::colorify "yellow" " Some things may not work as expected" + fi + + local dir + dir="$(jq '.projects[].metadata.vcsSubPath' <<< "$RESULTS")" + echo -e "\nRunning in $dir" + + local have_failed_checks=false + + for check in "${checks[@]}"; do + # $hook_config receives string like '1 > 2; 3 == 4;' etc. + # It gets split by `;` into array, which we're parsing here ('1 > 2' ' 3 == 4') + # Next line removes leading spaces, just for fancy output reason. + check=$(echo "$check" | sed 's/^[[:space:]]*//') + + operation="$(echo "$check" | grep -oE '[!<>=]+')" + IFS="$operation" read -r -a jq_check <<< "$check" + real_value="$(jq "${jq_check[0]}" <<< "$RESULTS")" + compare_value="${jq_check[1]}${jq_check[2]}" + # Check types + jq_check_type="$(jq -r "${jq_check[0]} | type" <<< "$RESULTS")" + compare_value_type="$(jq -r "$compare_value | type" <<< "$RESULTS")" + # Fail if comparing different types + if [ "$jq_check_type" != "$compare_value_type" ]; then + common::colorify "yellow" "Warning: Comparing values with different types may give incorrect result" + common::colorify "yellow" " Expression: $check" + common::colorify "yellow" " Types in the expression: [$jq_check_type] $operation [$compare_value_type]" + common::colorify "yellow" " Use 'tonumber' filter when comparing costs (e.g. '.totalMonthlyCost|tonumber')" + have_failed_checks=true + continue + fi + # Fail if string is compared not with `==` or `!=` + if [ "$jq_check_type" == "string" ] && { + [ "$operation" != '==' ] && [ "$operation" != '!=' ] + }; then + common::colorify "yellow" "Warning: Wrong comparison operator is used in expression: $check" + common::colorify "yellow" " Use 'tonumber' filter when comparing costs (e.g. '.totalMonthlyCost|tonumber')" + common::colorify "yellow" " Use '==' or '!=' when comparing strings (e.g. '.currency == \"USD\"')." + have_failed_checks=true + continue + fi + + # Compare values + check_passed="$(echo "$RESULTS" | jq "$check")" + + status="Passed" + color="green" + if ! $check_passed; then + status="Failed" + color="red" + have_failed_checks=true + fi + + # Print check result + common::colorify $color "$status: $check\t\t$real_value $operation $compare_value" + done + + # Fancy informational output + currency="$(jq -r '.currency' <<< "$RESULTS")" + + echo -e "\nSummary: $(jq -r '.summary' <<< "$RESULTS")" + + echo -e "\nTotal Monthly Cost: $(jq -r .totalMonthlyCost <<< "$RESULTS") $currency" + echo "Total Monthly Cost (diff): $(jq -r .projects[].diff.totalMonthlyCost <<< "$RESULTS") $currency" + + if $have_failed_checks; then + exit 1 + fi +} + +[[ ${BASH_SOURCE[0]} != "$0" ]] || main "$@"