Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
33 changes: 33 additions & 0 deletions .github/actions/github-actions.actionlint/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ inputs:
required: false
default: "true"
description: "(Optional) Whether to check `run` scripts with `shellcheck`. Requires the `shellcheck` CLI on the `PATH`. Defaults to `true`."
annotations_enabled:
required: false
default: "true"
description: "(Optional) Whether to annotate the reported lines in the pull request when `actionlint` fails, by re-running it with a JSON format and turning each finding into a workflow error. Defaults to `true`."

outputs:
skipped:
Expand Down Expand Up @@ -88,6 +92,35 @@ runs:
readarray -t files <<< "$FILES"
actionlint "${args[@]}" "${files[@]}"

# GitHub only renders annotations from `::error` lines, so the findings are re-read as JSON. The files are passed
# repository-relative, and `actionlint` reports them as given.
- name: Annotate Findings
id: actionlint-annotate
if: always() && inputs.annotations_enabled == 'true' && steps.actionlint.outcome == 'failure'
shell: bash
env:
FILES: ${{ steps.target.outputs.files }}
CONFIG: ${{ steps.target.outputs.config }}
SHELLCHECK_ENABLED: ${{ inputs.shellcheck_enabled }}
run: |
args=(-no-color -format '{{json .}}')
if [ -n "$CONFIG" ]; then
args+=(-config-file "$CONFIG")
fi
if [ "$SHELLCHECK_ENABLED" != "true" ]; then
args+=(-shellcheck=)
fi

# A workflow command takes one line, so newlines and the property separators are percent-encoded the way
# the runner decodes them.
readarray -t files <<< "$FILES"
actionlint "${args[@]}" "${files[@]}" 2>/dev/null | jq -r '
def esc: gsub("%"; "%25") | gsub("\r"; "%0D") | gsub("\n"; "%0A");
def prop: esc | gsub(":"; "%3A") | gsub(","; "%2C");
.[]
| "::error file=\(.filepath),line=\(.line),col=\(.column)\(if (.end_column // 0) > 0 then ",endColumn=\(.end_column)" else "" end),title=\("actionlint · " + .kind | prop)::\(.message | esc)"
' || true

- name: Add Failure Details to Job Summary
id: actionlint-summary
if: always() && steps.actionlint.outcome == 'failure'
Expand Down
38 changes: 38 additions & 0 deletions .github/actions/terraform.tflint/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ inputs:
required: false
default: ${{ github.token }}
description: "(Optional) The GitHub token used by `tflint --init` to download plugins from GitHub without hitting the anonymous rate limit. Defaults to the automatically generated `github.token`."
annotations_enabled:
required: false
default: "true"
description: "(Optional) Whether to annotate the reported lines in the pull request when `tflint` fails, by re-running it with `--format json` and turning each issue into a workflow error, warning, or notice. Defaults to `true`."

outputs:
skipped:
Expand Down Expand Up @@ -146,6 +150,40 @@ runs:
--format "$FORMAT" \
--minimum-failure-severity "$MINIMUM_FAILURE_SEVERITY"

# GitHub only renders annotations from `::error` lines, so the issues are re-read as JSON. The paths in that
# output are relative to the working directory, so they already point into the repository.
- name: Annotate Findings
id: tflint-annotate
if: always() && inputs.annotations_enabled == 'true' && steps.tflint.outcome == 'failure'
shell: bash
env:
TARGET_DIR: ${{ steps.target.outputs.target_dir }}
CONFIG: ${{ steps.target.outputs.config }}
RECURSIVE: ${{ inputs.recursive }}
run: |
args=(--chdir "$TARGET_DIR")
if [ -n "$CONFIG" ]; then
args+=(--config "$CONFIG")
fi
if [ "$RECURSIVE" = "true" ]; then
args+=(--recursive)
fi

# A workflow command takes one line, so newlines and the property separators are percent-encoded the way
# the runner decodes them.
tflint "${args[@]}" --format json 2>/dev/null | jq -r '
def esc: gsub("%"; "%25") | gsub("\r"; "%0D") | gsub("\n"; "%0A");
def prop: esc | gsub(":"; "%3A") | gsub(","; "%2C");
(.issues[] | {severity: .rule.severity, title: ("tflint · " + .rule.name), message, range}),
(.errors[] | {severity: (.severity // "error"), title: "tflint", message, range})
| (if .severity == "error" then "error" elif .severity == "warning" then "warning" else "notice" end) as $severity
| if .range then
"::\($severity) file=\(.range.filename),line=\(.range.start.line),col=\(.range.start.column),endLine=\(.range.end.line),endColumn=\(.range.end.column),title=\(.title | prop)::\(.message | esc)"
else
"::\($severity) title=\(.title | prop)::\(.message | esc)"
end
' || true

- name: Add Failure Details to Job Summary
id: tflint-summary
if: always() && steps.tflint.outcome == 'failure'
Expand Down
59 changes: 59 additions & 0 deletions .github/actions/terraform.validate/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ inputs:
required: false
default: "false"
description: "(Optional) Whether to also validate the query files (`*.tfquery.hcl`) by passing `-query`, which needs Terraform 1.14 or later. Defaults to `false`."
annotations_enabled:
required: false
default: "true"
description: "(Optional) Whether to annotate the failing lines in the pull request when `terraform init` or `terraform validate` fails, by re-running the failed command with `-json` and turning each diagnostic into a workflow error or warning. Defaults to `true`."

outputs:
skipped:
Expand Down Expand Up @@ -114,6 +118,61 @@ runs:

terraform -chdir="$TARGET_DIR" validate "${args[@]}"

# GitHub only renders annotations from `::error` lines, so the diagnostics are re-read as JSON rather than parsed
# from the text. A syntax error fails `init`, which loads the configuration, so both commands are covered.
- name: Annotate Findings
id: validate-annotate
if: always() && inputs.annotations_enabled == 'true' && (steps.init.outcome == 'failure' || steps.validate.outcome == 'failure')
shell: bash
env:
INIT_OUTCOME: ${{ steps.init.outcome }}
TARGET_DIR: ${{ steps.target.outputs.target_dir }}
WORKSPACE: ${{ inputs.workspace }}
VALIDATE_TESTS: ${{ inputs.validate_tests }}
TEST_DIRECTORY: ${{ inputs.test_directory }}
VALIDATE_QUERY: ${{ inputs.validate_query }}
run: |
if [ -n "$WORKSPACE" ]; then
export TF_WORKSPACE="$WORKSPACE"
fi

if [ "$INIT_OUTCOME" = "failure" ]; then
tool="terraform init"
args=(init -backend=false -input=false -json)
filter='select(.type == "diagnostic") | .diagnostic'
else
tool="terraform validate"
args=(validate -json)
if [ "$VALIDATE_TESTS" != "true" ]; then
args+=(-no-tests)
elif [ -n "$TEST_DIRECTORY" ]; then
args+=(-test-directory="$TEST_DIRECTORY")
fi
if [ "$VALIDATE_QUERY" = "true" ]; then
args+=(-query)
fi
filter='.diagnostics[]?'
fi

prefix=""
if [ "$TARGET_DIR" != "." ]; then
prefix="$TARGET_DIR/"
fi

# Paths are relative to the module, and a workflow command takes one line, so newlines and the
# property separators are percent-encoded the way the runner decodes them.
terraform -chdir="$TARGET_DIR" "${args[@]}" 2>/dev/null | jq -c "$filter" | jq -r --arg prefix "$prefix" --arg tool "$tool" '
def esc: gsub("%"; "%25") | gsub("\r"; "%0D") | gsub("\n"; "%0A");
def prop: esc | gsub(":"; "%3A") | gsub(","; "%2C");
(if .severity == "error" then "error" else "warning" end) as $severity
| (.summary + (if (.detail // "") != "" then "\n\n" + .detail else "" end) | esc) as $message
| if .range then
"::\($severity) file=\($prefix + .range.filename),line=\(.range.start.line),col=\(.range.start.column),endLine=\(.range.end.line),endColumn=\(.range.end.column),title=\($tool + " · " + .summary | prop)::\($message)"
else
"::\($severity) title=\($tool + " · " + .summary | prop)::\($message)"
end
' || true

- name: Add Failure Details to Job Summary
id: validate-summary
if: always() && steps.validate.outcome == 'failure'
Expand Down
Loading