Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add terraform_graph hook #589

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions .pre-commit-hooks.yaml
Expand Up @@ -143,3 +143,11 @@
- --args=terraform
files: \.tf$
require_serial: true

- id: terraform_graph
name: Terraform graph
description: Outputs Terraform dependency graphs in dot format.
entry: hooks/terraform_graph.sh
language: script
files: \.tf$
exclude: \.terraform\/.*$
16 changes: 15 additions & 1 deletion README.md
Expand Up @@ -85,6 +85,7 @@ If you are using `pre-commit-terraform` already or want to support its developme
* [`infracost`](https://github.com/infracost/infracost) required for `infracost_breakdown` hook.
* [`jq`](https://github.com/stedolan/jq) required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook.
* [`tfupdate`](https://github.com/minamijoyo/tfupdate) required for `tfupdate` hook.
* [`graphviz`](https://www.graphviz.org/download) required for `terraform_graph` hook.
* [`hcledit`](https://github.com/minamijoyo/hcledit) required for `terraform_wrapper_module_for_each` hook.

<details><summary><b>Docker</b></summary><br>
Expand Down Expand Up @@ -138,7 +139,7 @@ Set `-e PRE_COMMIT_COLOR=never` to disable the color output in `pre-commit`.
<details><summary><b>MacOS</b></summary><br>

```bash
brew install pre-commit terraform-docs tflint tfsec checkov terrascan infracost tfupdate minamijoyo/hcledit/hcledit jq
brew install pre-commit terraform-docs tflint tfsec checkov terrascan infracost tfupdate minamijoyo/hcledit/hcledit jq graphviz
```

</details>
Expand All @@ -161,6 +162,7 @@ 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
curl -L "$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > tfupdate.tar.gz && tar -xzf tfupdate.tar.gz tfupdate && rm tfupdate.tar.gz && sudo mv tfupdate /usr/bin/
curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > hcledit.tar.gz && tar -xzf hcledit.tar.gz hcledit && rm hcledit.tar.gz && sudo mv hcledit /usr/bin/
sudo apt install -y graphviz
```

</details>
Expand All @@ -182,6 +184,7 @@ 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
curl -L "$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > tfupdate.tar.gz && tar -xzf tfupdate.tar.gz tfupdate && rm tfupdate.tar.gz && sudo mv tfupdate /usr/bin/
curl -L "$(curl -s https://api.github.com/repos/minamijoyo/hcledit/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > hcledit.tar.gz && tar -xzf hcledit.tar.gz hcledit && rm hcledit.tar.gz && sudo mv hcledit /usr/bin/
sudo apt install -y graphviz
```

</details>
Expand Down Expand Up @@ -280,6 +283,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform
| `terraform_wrapper_module_for_each` | Generates Terraform wrappers with `for_each` in module. [Hook notes](#terraform_wrapper_module_for_each) | `hcledit` |
| `terrascan` | [terrascan](https://github.com/tenable/terrascan) Detect compliance and security violations. [Hook notes](#terrascan) | `terrascan` |
| `tfupdate` | [tfupdate](https://github.com/minamijoyo/tfupdate) Update version constraints of Terraform core, providers, and modules. [Hook notes](#tfupdate) | `tfupdate` |
| `terraform_graph` | Generates charts using [terraform graph](https://developer.hashicorp.com/terraform/cli/commands/graph) and [GraphViz](https://www.graphviz.org/). [Hook notes](#terraform_graph) | `dot` from [GraphViz](https://www.graphviz.org/download) |
<!-- markdownlint-enable no-inline-html -->

Check the [source file](https://github.com/antonbabenko/pre-commit-terraform/blob/master/.pre-commit-hooks.yaml) to know arguments used for each hook.
Expand Down Expand Up @@ -926,6 +930,16 @@ If the generated name is incorrect, set them by providing the `module-repo-short
Check [`tfupdate` usage instructions](https://github.com/minamijoyo/tfupdate#usage) for other available options and usage examples.
No need to pass `--recursive .` as it is added automatically.

### terraform_graph
`terraform_graph` utilizes Terraform's built-in [`graph` command](https://developer.hashicorp.com/terraform/cli/commands/graph) and `dot` from [GraphViz](https://www.graphviz.org/) to generate charts of your configuration.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have an example image of resulting graph here

You can populate it inside assets/ dir

```yaml
- id: terraform_graph
args:
- --args=-type=apply # Default is `plan`
- --hook-config=--path-to-file=test-config.svg # Default is tf-graph.svg
```

## Docker Usage

### File Permissions
Expand Down
91 changes: 91 additions & 0 deletions hooks/terraform_graph.sh
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -eo pipefail

# globals variables
# shellcheck disable=SC2155 # No way to assign to readonly variable in separate lines
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=_common.sh
. "$SCRIPT_DIR/_common.sh"

function main {
common::initialize "$SCRIPT_DIR"
common::parse_cmdline "$@"
common::export_provided_env_vars "${ENV_VARS[@]}"
common::parse_and_export_env_vars

# shellcheck disable=SC2153 # False positive
common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}"
}

#######################################################################
# Unique part of `common::per_dir_hook`. The function is executed in loop
# on each provided dir path. Run wrapped tool with specified arguments
# Arguments:
# dir_path (string) PATH to dir relative to git repo root.
# Can be used in error logging
# change_dir_in_unique_part (string/false) Modifier which creates
# possibilities to use non-common chdir strategies.
# Availability depends on hook.
# args (array) arguments that configure wrapped tool behavior
# Outputs:
# If failed - print out hook checks status
#######################################################################
function per_dir_hook_unique_part {
# shellcheck disable=SC2034 # Unused var.
local -r dir_path="$1"
# shellcheck disable=SC2034 # Unused var.
local -r change_dir_in_unique_part="$2"
shift 2
local -a -r args=("$@")

if [[ ! $(command -v dot) ]]; then
echo "ERROR: dot is required by terraform_graph pre-commit hook but is not installed or in the system's PATH."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add some colors

Suggested change
echo "ERROR: dot is required by terraform_graph pre-commit hook but is not installed or in the system's PATH."
common::colorify "red" "ERROR: dot is required by terraform_graph pre-commit hook but is not installed or in the system's PATH."

exit 1
fi

# set file name passed from --hook-config
local text_file="tf-graph.svg"
IFS=";" read -r -a configs <<< "${HOOK_CONFIG[*]}"
for c in "${configs[@]}"; do
IFS="=" read -r -a config <<< "$c"
key=${config[0]}
value=${config[1]}

case $key in
--path-to-file)
text_file=$value
;;
esac
done

temp_file=$(mktemp)
# pass the arguments to hook
echo "${args[@]}" >> per_dir_hook_unique_part
terraform graph "${args[@]}" | dot -Tsvg > "$temp_file"

# check if files are the same
cmp -s "$temp_file" "$text_file"

# return exit code to common::per_dir_hook
local exit_code=$?
mv "$temp_file" "$text_file"
return $exit_code
}

# Arguments:
# args (array) arguments that configure wrapped tool behavior
#######################################################################
function run_hook_on_whole_repo {
local -a -r args=("$@")
local text_file="graph.svg"

# pass the arguments to hook
echo "${args[@]}" >> run_hook_on_whole_repo
terraform graph "$(pwd)" "${args[@]}" | dot -Tsvg > "$text_file"

# return exit code to common::per_dir_hook
local exit_code=$?
return $exit_code
}

[ "${BASH_SOURCE[0]}" != "$0" ] || main "$@"