Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
Executable
+588
@@ -0,0 +1,588 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#########################################################################################
|
||||
#### terraform-lint-reporter.sh — Lint and plan Terraform configs with formatted ####
|
||||
#### reports. Wraps tflint and terraform plan with severity levels and CI exit codes ####
|
||||
#### Requires: bash 4+, terraform, tflint ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.01 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### ./terraform-lint-reporter.sh --full --dir ./infra ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
#########################################################################################
|
||||
# v1.01 changes:
|
||||
# - Fixed: ((0++)) returns 1 under set -e; added || true guards
|
||||
# - Fixed: grep in pipeline crashes under set -euo pipefail when no matches found. Added || true guard
|
||||
#########################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
RUN_MODE=""
|
||||
TF_DIR="${TF_DIR:-.}"
|
||||
TFLINT_PATH="${TFLINT_PATH:-tflint}"
|
||||
TF_PATH="${TF_PATH:-terraform}"
|
||||
OUTPUT_FORMAT="${TF_REPORT_FORMAT:-text}"
|
||||
OUTPUT_FILE=""
|
||||
FAIL_ON="${TF_FAIL_ON:-error}"
|
||||
AUTO_INIT="${TF_AUTO_INIT:-true}"
|
||||
PLAN_FILE=""
|
||||
VAR_FILE=""
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
COLOR="${COLOR:-auto}"
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
START_TIME=""
|
||||
LINT_WARNINGS=0
|
||||
LINT_ERRORS=0
|
||||
PLAN_ADDS=0
|
||||
PLAN_CHANGES=0
|
||||
PLAN_DESTROYS=0
|
||||
TMPDIR_WORK=""
|
||||
RESULTS_JSON="[]"
|
||||
|
||||
# ── Colors ────────────────────────────────────────────────────────────
|
||||
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
|
||||
setup_colors() {
|
||||
if [[ "$COLOR" == "never" ]]; then
|
||||
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
return
|
||||
fi
|
||||
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────
|
||||
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
||||
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
||||
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
|
||||
die() { err "$*"; exit 1; }
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
section_header() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${CYAN}── $1 ──${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
field() {
|
||||
printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
field_color() {
|
||||
printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
|
||||
}
|
||||
|
||||
elapsed() {
|
||||
local end_time
|
||||
end_time=$(date +%s)
|
||||
echo "$(( end_time - START_TIME ))s"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$TMPDIR_WORK" && -d "$TMPDIR_WORK" ]]; then
|
||||
rm -rf "$TMPDIR_WORK"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
make_tmpdir() {
|
||||
if [[ -z "$TMPDIR_WORK" ]]; then
|
||||
TMPDIR_WORK=$(mktemp -d "${TMPDIR:-/tmp}/tf-lint-reporter.XXXXXX")
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Dependency checks ────────────────────────────────────────────────
|
||||
require_terraform() {
|
||||
if ! command -v "$TF_PATH" &>/dev/null; then
|
||||
die "Terraform binary not found: ${TF_PATH}"
|
||||
fi
|
||||
verbose "Terraform found: $(command -v "$TF_PATH") ($("$TF_PATH" version -json 2>/dev/null | jq -r '.terraform_version' 2>/dev/null || "$TF_PATH" version | head -1))"
|
||||
}
|
||||
|
||||
require_tflint() {
|
||||
if ! command -v "$TFLINT_PATH" &>/dev/null; then
|
||||
die "tflint binary not found: ${TFLINT_PATH} — install from https://github.com/terraform-linters/tflint"
|
||||
fi
|
||||
verbose "tflint found: $(command -v "$TFLINT_PATH") ($("$TFLINT_PATH" --version 2>/dev/null || echo 'unknown'))"
|
||||
}
|
||||
|
||||
# ── Lint ─────────────────────────────────────────────────────────────
|
||||
do_lint() {
|
||||
require_tflint
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
section_header "Terraform Lint (tflint)"
|
||||
field "Directory:" "$TF_DIR"
|
||||
echo ""
|
||||
|
||||
local lint_output exit_code=0
|
||||
verbose "Running: ${TFLINT_PATH} --format json --chdir ${TF_DIR}"
|
||||
lint_output=$("$TFLINT_PATH" --format json --chdir "$TF_DIR" 2>&1) || exit_code=$?
|
||||
|
||||
local issues_count=0
|
||||
|
||||
if command -v jq &>/dev/null && echo "$lint_output" | jq empty 2>/dev/null; then
|
||||
issues_count=$(echo "$lint_output" | jq '[.issues // [] | .[] ] | length' 2>/dev/null || echo 0)
|
||||
|
||||
if [[ "$issues_count" -gt 0 ]]; then
|
||||
printf " ${BOLD}%-10s %-30s %-8s %s${RESET}\n" "SEVERITY" "RULE" "LINE" "MESSAGE"
|
||||
printf " %-10s %-30s %-8s %s\n" "----------" "------------------------------" "--------" "$(printf '%0.s-' {1..30})"
|
||||
|
||||
echo "$lint_output" | jq -r '.issues[] | [.rule.severity, .rule.name, (.range.filename + ":" + (.range.start.line | tostring)), .message] | @tsv' 2>/dev/null | \
|
||||
while IFS=$'\t' read -r sev rule loc msg; do
|
||||
local color="$RESET"
|
||||
case "$sev" in
|
||||
error) color="$RED"; ((LINT_ERRORS++)) || true ;;
|
||||
warning) color="$YELLOW"; ((LINT_WARNINGS++)) || true ;;
|
||||
notice) color="$DIM" ;;
|
||||
esac
|
||||
printf " ${color}%-10s${RESET} %-30s %-8s %s\n" "${sev^^}" "$rule" "$loc" "$msg"
|
||||
|
||||
RESULTS_JSON=$(echo "$RESULTS_JSON" | jq --arg s "$sev" --arg r "$rule" \
|
||||
--arg l "$loc" --arg m "$msg" \
|
||||
'. + [{"type":"lint","severity":$s,"rule":$r,"location":$l,"message":$m}]')
|
||||
done
|
||||
fi
|
||||
else
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ (Error|error) ]]; then
|
||||
((LINT_ERRORS++)) || true
|
||||
echo -e " ${RED}${line}${RESET}"
|
||||
elif [[ "$line" =~ (Warning|warning) ]]; then
|
||||
((LINT_WARNINGS++)) || true
|
||||
echo -e " ${YELLOW}${line}${RESET}"
|
||||
elif [[ -n "$line" ]]; then
|
||||
echo " $line"
|
||||
fi
|
||||
done <<< "$lint_output"
|
||||
fi
|
||||
|
||||
local tf_files=0
|
||||
if [[ -d "$TF_DIR" ]]; then
|
||||
tf_files=$(find "$TF_DIR" -maxdepth 1 -name '*.tf' | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
section_header "Lint Summary"
|
||||
field "Files checked:" "$tf_files"
|
||||
field_color "Errors:" "${RED}${LINT_ERRORS}${RESET}"
|
||||
field_color "Warnings:" "${YELLOW}${LINT_WARNINGS}${RESET}"
|
||||
field "Duration:" "$(elapsed)"
|
||||
}
|
||||
|
||||
# ── Plan ─────────────────────────────────────────────────────────────
|
||||
do_plan() {
|
||||
require_terraform
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
section_header "Terraform Plan"
|
||||
field "Directory:" "$TF_DIR"
|
||||
field "Auto-init:" "$AUTO_INIT"
|
||||
[[ -n "$VAR_FILE" ]] && field "Var file:" "$VAR_FILE"
|
||||
echo ""
|
||||
|
||||
if [[ "$AUTO_INIT" == "true" ]]; then
|
||||
log "Running terraform init..."
|
||||
verbose "Running: ${TF_PATH} -chdir=${TF_DIR} init -input=false -no-color"
|
||||
if ! "$TF_PATH" -chdir="$TF_DIR" init -input=false -no-color >/dev/null 2>&1; then
|
||||
die "terraform init failed in ${TF_DIR}"
|
||||
fi
|
||||
log "Init complete"
|
||||
fi
|
||||
|
||||
make_tmpdir
|
||||
local plan_out="${TMPDIR_WORK}/tfplan"
|
||||
local plan_args=(-detailed-exitcode -no-color -out="$plan_out")
|
||||
[[ -n "$VAR_FILE" ]] && plan_args+=(-var-file="$VAR_FILE")
|
||||
|
||||
local plan_output plan_exit=0
|
||||
verbose "Running: ${TF_PATH} -chdir=${TF_DIR} plan ${plan_args[*]}"
|
||||
plan_output=$("$TF_PATH" -chdir="$TF_DIR" plan "${plan_args[@]}" 2>&1) || plan_exit=$?
|
||||
|
||||
verbose "terraform plan exit code: ${plan_exit}"
|
||||
|
||||
if [[ $plan_exit -eq 1 ]]; then
|
||||
err "terraform plan failed:"
|
||||
echo "$plan_output" | tail -20
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if echo "$plan_output" | grep -qE 'Plan: [0-9]+ to add'; then
|
||||
local plan_line
|
||||
plan_line=$(echo "$plan_output" | { grep -oE 'Plan: [0-9]+ to add, [0-9]+ to change, [0-9]+ to destroy' || true; })
|
||||
if [[ -n "$plan_line" ]]; then
|
||||
PLAN_ADDS=$(echo "$plan_line" | { grep -oE '[0-9]+ to add' || true; } | { grep -oE '[0-9]+' || true; })
|
||||
PLAN_CHANGES=$(echo "$plan_line" | { grep -oE '[0-9]+ to change' || true; } | { grep -oE '[0-9]+' || true; })
|
||||
PLAN_DESTROYS=$(echo "$plan_line" | { grep -oE '[0-9]+ to destroy' || true; } | { grep -oE '[0-9]+' || true; })
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$PLAN_FILE" ]]; then
|
||||
cp "$plan_out" "$PLAN_FILE"
|
||||
log "Plan file saved to ${PLAN_FILE}"
|
||||
fi
|
||||
|
||||
RESULTS_JSON=$(echo "$RESULTS_JSON" | jq \
|
||||
--argjson a "$PLAN_ADDS" --argjson c "$PLAN_CHANGES" --argjson d "$PLAN_DESTROYS" \
|
||||
'. + [{"type":"plan","adds":$a,"changes":$c,"destroys":$d}]')
|
||||
|
||||
section_header "Plan Summary"
|
||||
if [[ $plan_exit -eq 0 ]]; then
|
||||
echo -e " ${GREEN}No changes.${RESET} Infrastructure is up-to-date."
|
||||
else
|
||||
field_color "Resources to add:" "${GREEN}${PLAN_ADDS}${RESET}"
|
||||
field_color "Resources to change:" "${YELLOW}${PLAN_CHANGES}${RESET}"
|
||||
field_color "Resources to destroy:" "${RED}${PLAN_DESTROYS}${RESET}"
|
||||
fi
|
||||
field "Duration:" "$(elapsed)"
|
||||
}
|
||||
|
||||
# ── Full ─────────────────────────────────────────────────────────────
|
||||
do_full() {
|
||||
START_TIME=$(date +%s)
|
||||
local full_start
|
||||
full_start=$(date +%s)
|
||||
|
||||
do_lint
|
||||
echo ""
|
||||
do_plan
|
||||
|
||||
section_header "Combined Summary"
|
||||
field_color "Lint errors:" "${RED}${LINT_ERRORS}${RESET}"
|
||||
field_color "Lint warnings:" "${YELLOW}${LINT_WARNINGS}${RESET}"
|
||||
field_color "Plan adds:" "${GREEN}${PLAN_ADDS}${RESET}"
|
||||
field_color "Plan changes:" "${YELLOW}${PLAN_CHANGES}${RESET}"
|
||||
field_color "Plan destroys:" "${RED}${PLAN_DESTROYS}${RESET}"
|
||||
local total_elapsed=$(( $(date +%s) - full_start ))
|
||||
field "Total duration:" "${total_elapsed}s"
|
||||
}
|
||||
|
||||
# ── Fmt ──────────────────────────────────────────────────────────────
|
||||
do_fmt() {
|
||||
require_terraform
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
section_header "Terraform Format Check"
|
||||
field "Directory:" "$TF_DIR"
|
||||
echo ""
|
||||
|
||||
local fmt_output exit_code=0
|
||||
verbose "Running: ${TF_PATH} fmt -check -diff -recursive ${TF_DIR}"
|
||||
fmt_output=$("$TF_PATH" fmt -check -diff -recursive "$TF_DIR" 2>&1) || exit_code=$?
|
||||
|
||||
local unformatted=0
|
||||
local unformatted_files=()
|
||||
|
||||
if [[ $exit_code -ne 0 && -n "$fmt_output" ]]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ \.tf$ ]]; then
|
||||
((unformatted++)) || true
|
||||
unformatted_files+=("$line")
|
||||
echo -e " ${YELLOW}needs formatting:${RESET} ${line}"
|
||||
fi
|
||||
done <<< "$fmt_output"
|
||||
fi
|
||||
|
||||
section_header "Format Summary"
|
||||
if [[ $unformatted -eq 0 ]]; then
|
||||
echo -e " ${GREEN}✓${RESET} All files are properly formatted"
|
||||
else
|
||||
field_color "Files needing format:" "${YELLOW}${unformatted}${RESET}"
|
||||
echo ""
|
||||
echo -e " Run ${BOLD}${TF_PATH} fmt -recursive ${TF_DIR}${RESET} to fix"
|
||||
fi
|
||||
field "Duration:" "$(elapsed)"
|
||||
|
||||
for f in "${unformatted_files[@]+"${unformatted_files[@]}"}"; do
|
||||
RESULTS_JSON=$(echo "$RESULTS_JSON" | jq --arg f "$f" \
|
||||
'. + [{"type":"fmt","file":$f,"message":"needs formatting"}]')
|
||||
done
|
||||
}
|
||||
|
||||
# ── Validate ─────────────────────────────────────────────────────────
|
||||
do_validate() {
|
||||
require_terraform
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
section_header "Terraform Validate"
|
||||
field "Directory:" "$TF_DIR"
|
||||
echo ""
|
||||
|
||||
if [[ "$AUTO_INIT" == "true" ]]; then
|
||||
log "Running terraform init -backend=false..."
|
||||
if ! "$TF_PATH" -chdir="$TF_DIR" init -backend=false -input=false -no-color >/dev/null 2>&1; then
|
||||
die "terraform init failed in ${TF_DIR}"
|
||||
fi
|
||||
fi
|
||||
|
||||
local validate_output exit_code=0
|
||||
verbose "Running: ${TF_PATH} -chdir=${TF_DIR} validate -json"
|
||||
validate_output=$("$TF_PATH" -chdir="$TF_DIR" validate -json 2>&1) || exit_code=$?
|
||||
|
||||
local valid diag_count
|
||||
valid=$(echo "$validate_output" | jq -r '.valid' 2>/dev/null || echo "false")
|
||||
diag_count=$(echo "$validate_output" | jq '[.diagnostics // [] | .[]] | length' 2>/dev/null || echo 0)
|
||||
|
||||
if [[ "$valid" == "true" ]]; then
|
||||
echo -e " ${GREEN}✓${RESET} Configuration is valid"
|
||||
else
|
||||
echo -e " ${RED}✗${RESET} Configuration has errors"
|
||||
echo ""
|
||||
|
||||
if [[ "$diag_count" -gt 0 ]]; then
|
||||
echo "$validate_output" | jq -r '.diagnostics[] | [.severity, .summary, .detail] | @tsv' 2>/dev/null | \
|
||||
while IFS=$'\t' read -r sev summary detail; do
|
||||
local color="$RESET"
|
||||
case "$sev" in
|
||||
error) color="$RED" ;;
|
||||
warning) color="$YELLOW" ;;
|
||||
esac
|
||||
echo -e " ${color}[${sev^^}]${RESET} ${summary}"
|
||||
[[ -n "$detail" ]] && echo " $detail"
|
||||
|
||||
RESULTS_JSON=$(echo "$RESULTS_JSON" | jq --arg s "$sev" \
|
||||
--arg m "$summary" --arg d "$detail" \
|
||||
'. + [{"type":"validate","severity":$s,"summary":$m,"detail":$d}]')
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
section_header "Validation Summary"
|
||||
field "Valid:" "$valid"
|
||||
field "Diagnostics:" "$diag_count"
|
||||
field "Duration:" "$(elapsed)"
|
||||
}
|
||||
|
||||
# ── Output formats ───────────────────────────────────────────────────
|
||||
write_output() {
|
||||
[[ -z "$OUTPUT_FILE" ]] && return
|
||||
case "$OUTPUT_FORMAT" in
|
||||
json) write_json ;;
|
||||
junit) write_junit ;;
|
||||
text) write_text ;;
|
||||
*) die "Unknown output format: ${OUTPUT_FORMAT}" ;;
|
||||
esac
|
||||
log "Results written to ${OUTPUT_FILE}"
|
||||
}
|
||||
|
||||
write_json() {
|
||||
jq -n --argjson results "$RESULTS_JSON" \
|
||||
--argjson lint_errors "$LINT_ERRORS" \
|
||||
--argjson lint_warnings "$LINT_WARNINGS" \
|
||||
--argjson plan_adds "$PLAN_ADDS" \
|
||||
--argjson plan_changes "$PLAN_CHANGES" \
|
||||
--argjson plan_destroys "$PLAN_DESTROYS" \
|
||||
--arg dir "$TF_DIR" \
|
||||
--arg mode "$RUN_MODE" \
|
||||
'{
|
||||
mode: $mode,
|
||||
directory: $dir,
|
||||
summary: {
|
||||
lint_errors: $lint_errors,
|
||||
lint_warnings: $lint_warnings,
|
||||
plan_adds: $plan_adds,
|
||||
plan_changes: $plan_changes,
|
||||
plan_destroys: $plan_destroys
|
||||
},
|
||||
results: $results
|
||||
}' > "$OUTPUT_FILE"
|
||||
}
|
||||
|
||||
write_junit() {
|
||||
local total_tests failures
|
||||
total_tests=$(echo "$RESULTS_JSON" | jq '[.[] | select(.type == "lint")] | length' 2>/dev/null || echo 0)
|
||||
failures=$LINT_ERRORS
|
||||
|
||||
{
|
||||
echo '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
echo "<testsuites tests=\"${total_tests}\" failures=\"${failures}\">"
|
||||
echo " <testsuite name=\"terraform-lint-reporter\" tests=\"${total_tests}\" failures=\"${failures}\">"
|
||||
|
||||
echo "$RESULTS_JSON" | jq -r '.[] | select(.type == "lint") | @base64' 2>/dev/null | while read -r entry; do
|
||||
local sev rule loc msg
|
||||
sev=$(echo "$entry" | base64 -d | jq -r '.severity')
|
||||
rule=$(echo "$entry" | base64 -d | jq -r '.rule')
|
||||
loc=$(echo "$entry" | base64 -d | jq -r '.location')
|
||||
msg=$(echo "$entry" | base64 -d | jq -r '.message')
|
||||
if [[ "$sev" == "error" ]]; then
|
||||
echo " <testcase name=\"${rule}\" classname=\"${loc}\">"
|
||||
echo " <failure message=\"${msg}\" type=\"${sev}\"/>"
|
||||
echo " </testcase>"
|
||||
else
|
||||
echo " <testcase name=\"${rule}\" classname=\"${loc}\"/>"
|
||||
fi
|
||||
done
|
||||
|
||||
echo " </testsuite>"
|
||||
echo "</testsuites>"
|
||||
} > "$OUTPUT_FILE"
|
||||
}
|
||||
|
||||
write_text() {
|
||||
{
|
||||
echo "Terraform Lint Reporter Results"
|
||||
echo "==============================="
|
||||
echo ""
|
||||
echo "Mode: ${RUN_MODE}"
|
||||
echo "Directory: ${TF_DIR}"
|
||||
echo ""
|
||||
echo "$RESULTS_JSON" | jq -r '.[] | if .type == "lint" then
|
||||
"[\(.severity | ascii_upcase)] \(.rule) at \(.location): \(.message)"
|
||||
elif .type == "plan" then
|
||||
"Plan: \(.adds) to add, \(.changes) to change, \(.destroys) to destroy"
|
||||
elif .type == "fmt" then
|
||||
"[FMT] \(.file): \(.message)"
|
||||
elif .type == "validate" then
|
||||
"[\(.severity | ascii_upcase)] \(.summary)"
|
||||
else empty end' 2>/dev/null || true
|
||||
echo ""
|
||||
echo "Lint Errors: ${LINT_ERRORS} Warnings: ${LINT_WARNINGS}"
|
||||
echo "Plan: +${PLAN_ADDS} ~${PLAN_CHANGES} -${PLAN_DESTROYS}"
|
||||
} > "$OUTPUT_FILE"
|
||||
}
|
||||
|
||||
# ── Compute exit code ────────────────────────────────────────────────
|
||||
compute_exit() {
|
||||
case "$FAIL_ON" in
|
||||
warning)
|
||||
if [[ $LINT_ERRORS -gt 0 || $LINT_WARNINGS -gt 0 ]]; then
|
||||
exit 2
|
||||
fi
|
||||
if [[ $PLAN_DESTROYS -gt 0 || $PLAN_CHANGES -gt 0 ]]; then
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
error)
|
||||
if [[ $LINT_ERRORS -gt 0 ]]; then
|
||||
exit 2
|
||||
fi
|
||||
if [[ $PLAN_DESTROYS -gt 0 ]]; then
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Usage ─────────────────────────────────────────────────────────────
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
${BOLD}${SCRIPT_NAME}${RESET} — Terraform Lint & Plan Reporter
|
||||
|
||||
Lint and plan Terraform configurations with formatted reports,
|
||||
severity levels, and CI-friendly exit codes.
|
||||
|
||||
${BOLD}MODES${RESET}
|
||||
--lint Run tflint on Terraform directory
|
||||
--plan Run terraform init + plan, report resource changes
|
||||
--full Run lint then plan, combined report
|
||||
--fmt Check formatting with terraform fmt
|
||||
--validate Run terraform validate, report diagnostics
|
||||
|
||||
${BOLD}OPTIONS${RESET}
|
||||
--dir DIR Terraform directory (default: .)
|
||||
--var-file FILE Terraform var file for plan
|
||||
--plan-out FILE Save plan binary to file
|
||||
--format FORMAT Output format: text, json, junit (default: text)
|
||||
--output-file FILE Write results to file
|
||||
--fail-on LEVEL Fail threshold: warning or error (default: error)
|
||||
--no-init Skip automatic terraform init
|
||||
--verbose Show debug output
|
||||
--no-color Disable colored output
|
||||
--help Show this help message
|
||||
|
||||
${BOLD}ENVIRONMENT VARIABLES${RESET}
|
||||
TF_DIR Default Terraform directory
|
||||
TF_PATH Path to terraform binary (default: terraform)
|
||||
TFLINT_PATH Path to tflint binary (default: tflint)
|
||||
TF_REPORT_FORMAT Default output format
|
||||
TF_FAIL_ON Default fail threshold
|
||||
TF_AUTO_INIT Auto-run terraform init (default: true)
|
||||
VERBOSE Enable verbose output (true/false)
|
||||
COLOR Color mode: auto, always, never
|
||||
|
||||
${BOLD}EXAMPLES${RESET}
|
||||
# Lint only
|
||||
${SCRIPT_NAME} --lint --dir ./infra
|
||||
|
||||
# Plan with var file
|
||||
${SCRIPT_NAME} --plan --dir ./infra --var-file prod.tfvars
|
||||
|
||||
# Full lint + plan, save JSON report
|
||||
${SCRIPT_NAME} --full --dir ./infra --format json --output-file report.json
|
||||
|
||||
# Format check in CI
|
||||
${SCRIPT_NAME} --fmt --dir ./modules
|
||||
|
||||
# Validate configuration
|
||||
${SCRIPT_NAME} --validate --dir ./infra
|
||||
|
||||
# Strict CI pipeline (fail on warnings)
|
||||
${SCRIPT_NAME} --full --dir ./infra --fail-on warning --format junit --output-file results.xml
|
||||
|
||||
${BOLD}EXIT CODES${RESET}
|
||||
0 All checks passed
|
||||
1 Runtime error
|
||||
2 Lint errors or plan changes exceed threshold
|
||||
EOF
|
||||
}
|
||||
|
||||
# ── Parse arguments ──────────────────────────────────────────────────
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--lint) RUN_MODE="lint"; shift ;;
|
||||
--plan) RUN_MODE="plan"; shift ;;
|
||||
--full) RUN_MODE="full"; shift ;;
|
||||
--fmt) RUN_MODE="fmt"; shift ;;
|
||||
--validate) RUN_MODE="validate"; shift ;;
|
||||
--dir) TF_DIR="${2:?--dir requires a path}"; shift 2 ;;
|
||||
--var-file) VAR_FILE="${2:?--var-file requires a path}"; shift 2 ;;
|
||||
--plan-out) PLAN_FILE="${2:?--plan-out requires a path}"; shift 2 ;;
|
||||
--format) OUTPUT_FORMAT="${2:?--format requires a value}"; shift 2 ;;
|
||||
--output-file) OUTPUT_FILE="${2:?--output-file requires a path}"; shift 2 ;;
|
||||
--fail-on) FAIL_ON="${2:?--fail-on requires a value}"; shift 2 ;;
|
||||
--no-init) AUTO_INIT="false"; shift ;;
|
||||
--verbose) VERBOSE="true"; shift ;;
|
||||
--no-color) COLOR="never"; shift ;;
|
||||
--help|-h) setup_colors; show_help; exit 0 ;;
|
||||
*) die "Unknown option: $1 (see --help)" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
parse_args "$@"
|
||||
setup_colors
|
||||
|
||||
case "$RUN_MODE" in
|
||||
lint) do_lint; write_output; compute_exit ;;
|
||||
plan) do_plan; write_output; compute_exit ;;
|
||||
full) do_full; write_output; compute_exit ;;
|
||||
fmt) do_fmt; write_output; compute_exit ;;
|
||||
validate) do_validate; write_output; compute_exit ;;
|
||||
"") err "No mode specified"; echo ""; show_help; exit 1 ;;
|
||||
*) die "Unknown mode: ${RUN_MODE}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user