Repository navigation
Expand file tree
/
Copy pathaction.yml
More file actions
224 lines (210 loc) · 8.98 KB
/
Copy pathaction.yml
File metadata and controls
224 lines (210 loc) · 8.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# straitjacket-allow-file:no-comments — an action definition is interface, read
# by people wiring it into a workflow, and YAML has no documentation-comment
# syntax to hoist the reasoning into.
name: Powderworks Straitjacket
description: Scan a repository with Straitjacket and fail the build on findings.
author: Powderworks
branding:
icon: alert-octagon
color: red
inputs:
version:
description: >-
Release tag to install, such as v0.1.0, or `latest` to track releases.
Left unset, this is the release the pinned Action ref ships, so the tag
on the `uses:` line selects the scanner as well as the wrapper.
required: false
default: ""
paths:
description: >-
Files or directories to scan. Accepts one per line or several on one line.
required: false
default: "."
only:
description: >-
Run only these rules. Accepts one per line or a comma-separated list.
required: false
default: ""
skip:
description: >-
Disable these rules. Accepts one per line or a comma-separated list.
required: false
default: ""
format:
description: Output format written to the log — `text`, `json`, or `sarif`.
required: false
default: text
max-lines:
description: Maximum lines per file. `0` disables the `file-size` rule.
required: false
default: ""
max-nesting:
description: Maximum indentation depth. `0` disables the `deep-nesting` rule.
required: false
default: ""
no-comments:
description: Enable the opt-in `no-comments` rule.
required: false
default: "false"
include-json:
description: Scan JSON files, which are skipped by default.
required: false
default: "false"
no-ignore:
description: Scan files that .gitignore, .ignore, and the hidden-file convention exclude.
required: false
default: "false"
config:
description: Use this configuration file instead of discovering one.
required: false
default: ""
no-config:
description: Ignore checked-in configuration.
required: false
default: "false"
sarif-file:
description: Write a SARIF report to this path. Empty writes none.
required: false
default: ""
fail-on-findings:
description: Fail the step when there are error-level findings.
required: false
default: "true"
fail-on-unused-markers:
description: Report suppression markers that suppress nothing.
required: false
default: "true"
token:
description: >-
Token used to read the release. Worth setting even for a public release:
GitHub allows 60 unauthenticated API requests an hour per address and CI
runners share addresses, so without one this eventually fails to install
at all. The workflow's own `github.token` is enough; it needs no access
to the Straitjacket repository while that repository is public. (Written
here without the expression braces on purpose: GitHub evaluates every
expression it finds in this file, including the ones inside a
description, and refuses to load an action that names a context it does
not provide there.)
required: false
default: ""
outputs:
exit-code:
description: The straitjacket exit code — 0 clean, 1 findings, 2 operational failure.
value: ${{ steps.scan.outputs.exit-code }}
runs:
using: composite
steps:
- name: Install straitjacket
id: install
shell: bash
env:
VERSION: ${{ inputs.version }}
STRAITJACKET_INSTALL_DIR: ${{ runner.temp }}/straitjacket-bin
GITHUB_TOKEN: ${{ inputs.token }}
ACTION_PATH: ${{ github.action_path }}
run: |
set -eu
# An unset `version` means the release this Action ref *is*, read from
# the Cargo.toml sitting beside this file in the checked-out ref. That
# makes the tag on the `uses:` line pin the scanner as well as the
# wrapper: a workflow that changed nothing cannot be handed new rules
# by a release it never asked for, which for a gate is the difference
# between a red build somebody caused and one nobody did. `latest` is
# still available, spelled out.
from_ref=false
if [ -z "$VERSION" ]; then
VERSION="v$(sed -n 's/^version = "\(.*\)"$/\1/p' "${ACTION_PATH}/Cargo.toml" | head -n 1)"
from_ref=true
fi
install() {
if [ "$1" = latest ]; then
unset STRAITJACKET_VERSION
else
export STRAITJACKET_VERSION="$1"
fi
sh "${ACTION_PATH}/scripts/install.sh"
}
# A ref that is not a release names a version nothing published: `@main`,
# a fork, or the window between the commit that bumps Cargo.toml and the
# tag that releases it. Those refs keep working, on the latest release,
# and the warning says which scanner actually ran. A version the
# workflow asked for by name never falls back -- it was chosen on
# purpose, and silently running a different scanner is worse than
# failing to run one.
#
# Failing to install is not the same as finding something, and at a
# glance in the checks list the two look identical: both are a red job
# whose log ends in `exit code 1`. Say which one this is.
if install "$VERSION"; then
:
elif [ "$from_ref" = true ] && install latest; then
echo "::warning title=Straitjacket ${VERSION} is not a published release::This Action ref does not correspond to a release, so the latest one was installed instead. Pin the \`uses:\` line to a release tag, or set the \`version\` input, for a scan that does not move." >&2
else
echo "::error title=Straitjacket could not be installed::The scan did not run. This is an installation failure, not a finding -- see the log above, and set the action's \`token\` input if it mentions rate limiting." >&2
exit 1
fi
echo "$STRAITJACKET_INSTALL_DIR" >> "$GITHUB_PATH"
# Inputs reach the script through the environment rather than through
# expression interpolation, so a value containing shell syntax is an
# argument and never a command.
- name: Run straitjacket
id: scan
shell: bash
env:
PATHS: ${{ inputs.paths }}
ONLY: ${{ inputs.only }}
SKIP: ${{ inputs.skip }}
FORMAT: ${{ inputs.format }}
MAX_LINES: ${{ inputs.max-lines }}
MAX_NESTING: ${{ inputs.max-nesting }}
NO_COMMENTS: ${{ inputs.no-comments }}
INCLUDE_JSON: ${{ inputs.include-json }}
NO_IGNORE: ${{ inputs.no-ignore }}
CONFIG: ${{ inputs.config }}
NO_CONFIG: ${{ inputs.no-config }}
SARIF_FILE: ${{ inputs.sarif-file }}
FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings }}
FAIL_ON_UNUSED_MARKERS: ${{ inputs.fail-on-unused-markers }}
run: |
set -u
# A boolean input is a string, and `True` or `yes` would otherwise be
# silently read as false. A scanner that quietly stops enforcing is
# worse than one that fails, so an unrecognized value is an error.
boolean() {
case "$2" in
true | false) ;;
*)
echo "straitjacket action: ${1} must be true or false, got '${2}'" >&2
exit 2
;;
esac
[ "$2" = "true" ]
}
# A YAML list arrives as a newline-separated string; `--only` and
# `--skip` want it comma-separated. Both spellings are accepted so the
# workflow can read the way its author finds clearest.
commas() {
printf '%s' "$1" | tr '\n ' ',,' | sed 's/,\{1,\}/,/g; s/^,//; s/,$//'
}
# Word splitting is the point: paths are an argument list, and IFS
# splits on newlines as well as spaces, so a YAML block works too.
# shellcheck disable=SC2086
set -- $PATHS
if [ -n "$ONLY" ]; then set -- "$@" --only "$(commas "$ONLY")"; fi
if [ -n "$SKIP" ]; then set -- "$@" --skip "$(commas "$SKIP")"; fi
if [ -n "$FORMAT" ]; then set -- "$@" --format "$FORMAT"; fi
if [ -n "$MAX_LINES" ]; then set -- "$@" --max-lines "$MAX_LINES"; fi
if [ -n "$MAX_NESTING" ]; then set -- "$@" --max-nesting "$MAX_NESTING"; fi
if [ -n "$CONFIG" ]; then set -- "$@" --config "$CONFIG"; fi
if [ -n "$SARIF_FILE" ]; then set -- "$@" --sarif "$SARIF_FILE"; fi
if boolean no-comments "$NO_COMMENTS"; then set -- "$@" --no-comments; fi
if boolean include-json "$INCLUDE_JSON"; then set -- "$@" --include-json; fi
if boolean no-ignore "$NO_IGNORE"; then set -- "$@" --no-ignore; fi
if boolean no-config "$NO_CONFIG"; then set -- "$@" --no-config; fi
if ! boolean fail-on-findings "$FAIL_ON_FINDINGS"; then set -- "$@" --no-fail; fi
if ! boolean fail-on-unused-markers "$FAIL_ON_UNUSED_MARKERS"; then
set -- "$@" --no-fail-on-unused-markers
fi
straitjacket "$@" && code=0 || code=$?
echo "exit-code=${code}" >> "$GITHUB_OUTPUT"
exit "$code"