-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpython-package-lint-and-scan.yml
More file actions
243 lines (243 loc) · 9.12 KB
/
Copy pathpython-package-lint-and-scan.yml
File metadata and controls
243 lines (243 loc) · 9.12 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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
---
name: Lint and security scan for Python
on:
workflow_call:
inputs:
package-path:
required: false
type: string
description: Path to a Python package or project
default: .
python-version:
required: false
type: string
description: Python version to use (not applicable if uv.lock is present)
default: 3.x
uv-version:
required: false
type: string
description: Version of uv to use (applicable only if uv.lock is present)
default: latest
enable-cache:
required: false
type: boolean
description: Cache Python dependency downloads
default: true
cache-dependency-path:
required: false
type: string
description: Dependency file used to scope the cache
default: null
cache-salt:
required: false
type: string
description: Optional value used to bust the dependency cache
default: null
use-pyright:
required: false
type: boolean
description: Use pyright to check types
default: true
use-ruff-format:
required: false
type: boolean
description: Use ruff format to check code
default: true
use-mypy:
required: false
type: boolean
description: Use mypy to check types
default: false
use-flake8:
required: false
type: boolean
description: Use flake8 to lint the code
default: false
use-bandit:
required: false
type: boolean
description: Use bandit to find security issues
default: false
audit:
required: false
type: boolean
description: Run security audit on locked dependencies (applicable only if uv.lock is present)
default: true
additional-python-packages:
required: false
type: string
description: Additional Python packages to install
default: null
requirements-txt:
required: false
type: string
description: Path to the requirements.txt file (not applicable if uv.lock is present)
default: null
runs-on:
required: false
type: string
description: GitHub Actions runner to use
default: ubuntu-slim
permissions:
contents: read
defaults:
run:
shell: bash -euo pipefail {0}
working-directory: .
jobs:
lint-and-scan:
runs-on: ${{ inputs.runs-on }}
steps:
- name: Disable pip cache
if: '! inputs.enable-cache'
run: echo 'PIP_NO_CACHE_DIR=true' >> "${GITHUB_ENV}"
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 1
persist-credentials: false
- name: Detect uv.lock or poetry.lock
working-directory: ${{ inputs.package-path }}
run: |
if [[ -f uv.lock ]]; then
echo "LOCK_FILE=uv.lock" >> "${GITHUB_ENV}"
elif [[ -f poetry.lock ]]; then
echo "LOCK_FILE=poetry.lock" >> "${GITHUB_ENV}"
else
echo "LOCK_FILE=" >> "${GITHUB_ENV}"
fi
- name: Set up uv
if: env.LOCK_FILE == 'uv.lock'
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
version: ${{ inputs.uv-version }}
enable-cache: ${{ inputs.enable-cache }}
cache-dependency-glob: ${{ inputs.cache-dependency-path || format('{0}/uv.lock', inputs.package-path) }}
cache-suffix: ${{ inputs.cache-salt }}
- name: Install packages using uv
if: env.LOCK_FILE == 'uv.lock'
working-directory: ${{ inputs.package-path }}
env:
INPUTS_ADDITIONAL_PYTHON_PACKAGES: ${{ inputs.additional-python-packages }}
INPUTS_AUDIT: ${{ inputs.audit }}
USE_FLAKE8: ${{ inputs.use-flake8 }}
USE_BANDIT: ${{ inputs.use-bandit }}
USE_MYPY: ${{ inputs.use-mypy }}
USE_PYRIGHT: ${{ inputs.use-pyright }}
run: |
uv sync --all-groups
if [[ "${INPUTS_AUDIT}" == "true" ]]; then
uv audit --locked
fi
packages=(ruff)
[[ "${USE_FLAKE8}" == "true" ]] && packages+=(flake8)
[[ "${USE_BANDIT}" == "true" ]] && packages+=(bandit)
[[ "${USE_MYPY}" == "true" ]] && packages+=(mypy)
[[ "${USE_PYRIGHT}" == "true" ]] && packages+=(pyright)
uv add --dev "${packages[@]}"
for p in $(tr ' ' '\n' <<< "${INPUTS_ADDITIONAL_PYTHON_PACKAGES}"); do
if [[ -n "${p}" ]]; then
uv add --dev "${p}"
fi
done
echo "EXECUTOR_KIND=uv" >> "${GITHUB_ENV}"
- name: Set up Python
if: env.LOCK_FILE != 'uv.lock'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ inputs.python-version }}
cache: ${{ inputs.enable-cache && (inputs.cache-dependency-path != '' || env.LOCK_FILE == 'poetry.lock' || inputs.requirements-txt != '') && 'pip' || '' }}
cache-dependency-path: ${{ inputs.cache-dependency-path || (env.LOCK_FILE == 'poetry.lock' && format('{0}/poetry.lock', inputs.package-path)) || (inputs.requirements-txt != '' && format('{0}/{1}', inputs.package-path, inputs.requirements-txt)) || '' }}
- name: Install packages
if: env.LOCK_FILE != 'uv.lock'
env:
ADDITIONAL_PYTHON_PACKAGES: ${{ inputs.additional-python-packages }}
REQUIREMENTS_TXT_PATH: ${{ inputs.requirements-txt }}
USE_FLAKE8: ${{ inputs.use-flake8 }}
USE_BANDIT: ${{ inputs.use-bandit }}
USE_MYPY: ${{ inputs.use-mypy }}
USE_PYRIGHT: ${{ inputs.use-pyright }}
working-directory: ${{ inputs.package-path }}
run: |
pip install -U pip
if [[ -n "${REQUIREMENTS_TXT_PATH}" ]]; then
pip install -U -r "${REQUIREMENTS_TXT_PATH}"
fi
packages=(ruff)
[[ "${USE_FLAKE8}" == "true" ]] && packages+=(flake8)
[[ "${USE_BANDIT}" == "true" ]] && packages+=(bandit)
[[ "${USE_MYPY}" == "true" ]] && packages+=(mypy)
[[ "${USE_PYRIGHT}" == "true" ]] && packages+=(pyright)
if [[ "${LOCK_FILE}" == "poetry.lock" ]]; then
pip install poetry
poetry lock --no-interaction
poetry add --group=dev --no-interaction "${packages[@]}"
for p in $(tr ' ' '\n' <<< "${ADDITIONAL_PYTHON_PACKAGES}"); do
if [[ -n "${p}" ]]; then
poetry add --group=dev --no-interaction "${p}"
fi
done
poetry install --no-interaction --no-root
echo "EXECUTOR_KIND=poetry" >> "${GITHUB_ENV}"
else
pip install "${packages[@]}"
for p in $(tr ' ' '\n' <<< "${ADDITIONAL_PYTHON_PACKAGES}"); do
if [[ -n "${p}" ]]; then
pip install "${p}"
fi
done
echo "EXECUTOR_KIND=none" >> "${GITHUB_ENV}"
fi
- name: Lint the code using ruff check
working-directory: ${{ inputs.package-path }}
run: |
case "${EXECUTOR_KIND}" in
uv) uv run ruff check --output-format=github . ;;
poetry) poetry run ruff check --output-format=github . ;;
*) ruff check --output-format=github . ;;
esac
- name: Check the code using ruff format
if: inputs.use-ruff-format
working-directory: ${{ inputs.package-path }}
run: |
case "${EXECUTOR_KIND}" in
uv) uv run ruff format --check . ;;
poetry) poetry run ruff format --check . ;;
*) ruff format --check . ;;
esac
- name: Lint the code using flake8
if: inputs.use-flake8
working-directory: ${{ inputs.package-path }}
run: |
case "${EXECUTOR_KIND}" in
uv) uv run flake8 . ;;
poetry) poetry run flake8 . ;;
*) flake8 . ;;
esac
- name: Find security issues using bandit
if: inputs.use-bandit
working-directory: ${{ inputs.package-path }}
run: |
case "${EXECUTOR_KIND}" in
uv) uv run bandit --recursive . ;;
poetry) poetry run bandit --recursive . ;;
*) bandit --recursive . ;;
esac
- name: Check types using mypy
if: inputs.use-mypy
working-directory: ${{ inputs.package-path }}
run: |
case "${EXECUTOR_KIND}" in
uv) uv run mypy --install-types --non-interactive . ;;
poetry) poetry run mypy --install-types --non-interactive . ;;
*) mypy --install-types --non-interactive . ;;
esac
- name: Check types using pyright
if: inputs.use-pyright
working-directory: ${{ inputs.package-path }}
run: |
case "${EXECUTOR_KIND}" in
uv) uv run pyright --threads=0 . ;;
poetry) poetry run pyright --threads=0 . ;;
*) pyright --threads=0 . ;;
esac