Skip to content
Open
8 changes: 8 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,11 @@
- --args=terraform
files: \.(tf|tofu)$
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\/.*$
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,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 trivy checkov terrascan infracost tfupdate minamijoyo/hcledit/hcledit jq
brew install pre-commit terraform-docs tflint tfsec trivy checkov terrascan infracost tfupdate minamijoyo/hcledit/hcledit jq graphviz
```

</details>
Expand All @@ -165,6 +165,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 auth login
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 @@ -187,6 +188,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 auth login
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 @@ -236,6 +238,7 @@ Full list of dependencies and where they are used:
* [`infracost`][infracost repo] required for `infracost_breakdown` hook
* [`jq`][jq repo] required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook
* [`tfupdate`][tfupdate repo] required for `tfupdate` hook
* [`graphviz`](https://www.graphviz.org/download) required for `terraform_graph` hook.
* [`hcledit`][hcledit repo] required for `terraform_wrapper_module_for_each` hook


Expand Down Expand Up @@ -337,6 +340,8 @@ 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][terrascan repo] Detect compliance and security violations. [Hook notes](#terrascan) | `terrascan` |
| `tfupdate` | [tfupdate][tfupdate repo] 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) |


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 @@ -1154,6 +1159,8 @@ 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.



### terragrunt_providers_lock

> [!TIP]
Expand Down Expand Up @@ -1198,6 +1205,16 @@ Example:
>
> See docs for the [iam_role](https://terragrunt.gruntwork.io/docs/reference/config-blocks-and-attributes/#iam_role) attribute and [--terragrunt-iam-role](https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-iam-role) flag for more.

### 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

@MaxymVlasov MaxymVlasov Dec 11, 2023

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

### About Docker image security
Expand Down
91 changes: 91 additions & 0 deletions hooks/terraform_graph.sh
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +41 to +44
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use a robust check for GraphViz's dot executable

The current test [[ ! $(command -v dot) ]] can misfire. Prefer:

if ! command -v dot >/dev/null 2>&1; then
  common::colorify "red" "ERROR: \`dot\` is required but not found in PATH."
  exit 1
fi

This reliably detects dot and applies consistent error styling.


# 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
Comment on lines +61 to +63
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unintended debug output

Left-over debug lines append args to files (>> per_dir_hook_unique_part), polluting the working directory. These should be removed.

terraform graph "${args[@]}" | dot -Tsvg > "$temp_file"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Respect the detected Terraform binary path

Rather than hardcoding terraform, invoke the binary discovered by common::get_tf_binary_path. For example:

- terraform graph "${args[@]}" | dot -Tsvg > "$temp_file"
+ "$tf_path" graph "${args[@]}" | dot -Tsvg > "$temp_file"

This ensures compatibility with custom Terraform/OpenTofu installations.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
terraform graph "${args[@]}" | dot -Tsvg > "$temp_file"
- terraform graph "${args[@]}" | dot -Tsvg > "$temp_file"
+ "$tf_path" 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
Comment on lines +67 to +72
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Only update SVG on content changes

The current logic always moves the temp file, updating the SVG even when identical. This can trigger false diffs. Instead:

- cmp -s "$temp_file" "$text_file"
- local exit_code=$?
- mv "$temp_file" "$text_file"
- return $exit_code
+ if cmp -s "$temp_file" "$text_file"; then
+   rm "$temp_file"
+   return 0
+ else
+   mv "$temp_file" "$text_file"
+   return 1
+ fi

This preserves modification timestamps when no content change has occurred.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
# return exit code to common::per_dir_hook
if cmp -s "$temp_file" "$text_file"; then
rm "$temp_file"
return 0
else
mv "$temp_file" "$text_file"
return 1
fi

}

# 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"
Comment on lines +83 to +84
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Clean up debug echo and use custom Terraform path

In run_hook_on_whole_repo, remove the debug echo and switch to the detected binary:

- echo "${args[@]}" >> run_hook_on_whole_repo
- terraform graph "$(pwd)" "${args[@]}" | dot -Tsvg > "$text_file"
+ "$tf_path" graph "$(pwd)" "${args[@]}" | dot -Tsvg > "$text_file"

This prevents stray files and honors custom binaries.


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

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