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: terrascan - Improve performance during pre-commit --all (-a) run #327

Merged
merged 3 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion hooks/_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,43 @@ function common::parse_cmdline {
done
}

#######################################################################
# This is a workaround to improve performance when all files are passed
# See: https://github.com/antonbabenko/pre-commit-terraform/issues/309
# Arguments:
# hook_id (string) hook ID, see `- id` for details in .pre-commit-hooks.yaml file
# files (array) filenames to check
# Outputs:
# Return 0 if `-a|--all` arg was passed to `pre-commit`
#######################################################################
function common::is_hook_run_on_whole_repo {
local -r hook_id="$1"
shift 1
local -a -r files=("$@")
# get directory containing `.pre-commit-hooks.yaml` file
local -r script_parent_dir="$(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]}")")")"
carlosbustillordguez marked this conversation as resolved.
Show resolved Hide resolved
# get included and excluded files from .pre-commit-hooks.yaml file
local -r hook_config_block=$(sed -n "/^- id: $hook_id$/,/^$/p" "$script_parent_dir/.pre-commit-hooks.yaml")
local -r included_files=$(awk '$1 == "files:" {print $2; exit}' <<< "$hook_config_block")
local -r excluded_files=$(awk '$1 == "exclude:" {print $2; exit}' <<< "$hook_config_block")
# sorted string with the files passed to the hook by pre-commit
local -r files_to_check=$(printf '%s\n' "${files[@]}" | sort | tr '\n' ' ')
# git ls-files sorted string
local all_files_that_can_be_checked

if [ -z "$excluded_files" ]; then
all_files_that_can_be_checked=$(git ls-files | sort | grep -e "$included_files" | tr '\n' ' ')
else
all_files_that_can_be_checked=$(git ls-files | sort | grep -e "$included_files" | grep -v -e "$excluded_files" | tr '\n' ' ')
fi

if [ "$files_to_check" == "$all_files_that_can_be_checked" ]; then
return 0
else
return 1
fi
}

#######################################################################
# Hook execution boilerplate logic which is common to hooks, that run
# on per dir basis.
Expand All @@ -64,13 +101,23 @@ function common::parse_cmdline {
# 3. Complete hook execution and return exit code
# Arguments:
# args (string with array) arguments that configure wrapped tool behavior
# hook_id (string) hook ID, see `- id` for details in .pre-commit-hooks.yaml file
# files (array) filenames to check
#######################################################################
function common::per_dir_hook {
local -r args="$1"
shift 1
local -r hook_id="$2"
shift 2
local -a -r files=("$@")

# check is (optional) function defined
if [ "$(type -t run_hook_on_whole_repo)" == function ] &&
# check is hook run via `pre-commit run --all`
common::is_hook_run_on_whole_repo "$hook_id" "${files[@]}"; then
run_hook_on_whole_repo "$args"
exit 0
carlosbustillordguez marked this conversation as resolved.
Show resolved Hide resolved
fi

# consume modified files passed from pre-commit so that
# hook runs against only those relevant directories
local index=0
Expand Down
24 changes: 23 additions & 1 deletion hooks/terrascan.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
set -eo pipefail

# globals variables
# hook ID, see `- id` for details in .pre-commit-hooks.yaml file
declare -r HOOK_ID='terrascan'
carlosbustillordguez marked this conversation as resolved.
Show resolved Hide resolved

# shellcheck disable=SC2155 # No way to assign to readonly variable in separate lines
readonly SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
# shellcheck source=_common.sh
Expand All @@ -10,7 +14,7 @@ function main {
common::initialize "$SCRIPT_DIR"
common::parse_cmdline "$@"
# shellcheck disable=SC2153 # False positive
common::per_dir_hook "${ARGS[*]}" "${FILES[@]}"
common::per_dir_hook "${ARGS[*]}" "$HOOK_ID" "${FILES[@]}"
}

#######################################################################
Expand All @@ -37,4 +41,22 @@ function per_dir_hook_unique_part {
return $exit_code
}

#######################################################################
# Unique part of `common::per_dir_hook`. The function is executed one time
# in the root git repo
# Arguments:
# args (string with array) arguments that configure wrapped tool behavior
#######################################################################
function run_hook_on_whole_repo {
local -r args="$1"

# pass the arguments to hook
# shellcheck disable=SC2068 # hook fails when quoting is used ("$arg[@]")
terrascan scan -i terraform ${args[@]}

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

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