@codacy/tools-hadolint-2 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1155 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codacy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @codacy/tools-hadolint-2
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Overview](#overview)
|
|
5
|
+
- [Updating patterns](#updating-patterns)
|
|
6
|
+
- [Updating the Hadolint version](#updating-the-hadolint-version)
|
|
7
|
+
- [Development](#development)
|
|
8
|
+
- [Notes for maintainers](#notes-for-maintainers)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Dockerfile linter using the [Hadolint](https://github.com/hadolint/hadolint) binary. Uses the CLI execution strategy — spawns `hadolint` via `spawnTool()` and parses its JSON output.
|
|
15
|
+
|
|
16
|
+
| Property | Value |
|
|
17
|
+
|----------|-------|
|
|
18
|
+
| Tool ID | `Hadolint` |
|
|
19
|
+
| Codacy UUID | `d3ec07a8-71b3-4b89-a25e-9c32ef827668` |
|
|
20
|
+
| Strategy | CLI |
|
|
21
|
+
| Languages | Dockerfile |
|
|
22
|
+
| Binary | `hadolint` |
|
|
23
|
+
| File patterns | `**/Dockerfile`, `**/*.dockerfile` |
|
|
24
|
+
|
|
25
|
+
## Updating patterns
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Re-fetch pattern metadata from the Codacy API
|
|
29
|
+
pnpm prefetch
|
|
30
|
+
|
|
31
|
+
# Commit the result
|
|
32
|
+
git add src/patterns.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pattern IDs follow the format `Hadolint_DL3006`, `Hadolint_SC2086`, etc. Note the capital `H` — the tool ID matches the Codacy API prefix exactly.
|
|
36
|
+
|
|
37
|
+
Hadolint also includes ShellCheck rules for `RUN` instructions, so pattern IDs with `SC` prefixes are expected.
|
|
38
|
+
|
|
39
|
+
## Updating the Hadolint version
|
|
40
|
+
|
|
41
|
+
1. Update `preferredVersion` in `src/adapter.ts`
|
|
42
|
+
2. Update the download URL template if the release format changed
|
|
43
|
+
3. Run `pnpm prefetch` to check for new/removed rules
|
|
44
|
+
4. Run `pnpm test` to verify compatibility
|
|
45
|
+
5. If the major version changes, create a new adapter package (`hadolint-3/`)
|
|
46
|
+
|
|
47
|
+
## Development
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm build # Build with tsup
|
|
51
|
+
pnpm test # Run tests (requires hadolint in PATH or auto-install)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Notes for maintainers
|
|
55
|
+
|
|
56
|
+
- Hadolint is a single static binary (Haskell), downloaded from GitHub releases.
|
|
57
|
+
- The binary is downloaded via `downloadFile()` (not `downloadAndExtract()` — it's not an archive).
|
|
58
|
+
- JSON output via `--format json`.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,1155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => index_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_tooling2 = require("@codacy/tooling");
|
|
37
|
+
|
|
38
|
+
// src/adapter.ts
|
|
39
|
+
var import_tooling = require("@codacy/tooling");
|
|
40
|
+
var import_path = __toESM(require("path"));
|
|
41
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
42
|
+
|
|
43
|
+
// src/patterns.json
|
|
44
|
+
var patterns_default = [
|
|
45
|
+
{
|
|
46
|
+
id: "Hadolint_DL1001",
|
|
47
|
+
category: "BestPractice",
|
|
48
|
+
severityLevel: "Info",
|
|
49
|
+
title: "Avoid Using Inline Ignore Pragmas",
|
|
50
|
+
enabled: false,
|
|
51
|
+
parameters: []
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "Hadolint_DL3000",
|
|
55
|
+
category: "BestPractice",
|
|
56
|
+
severityLevel: "Warning",
|
|
57
|
+
title: "Enforce use of absolute WORKDIR",
|
|
58
|
+
enabled: true,
|
|
59
|
+
parameters: []
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "Hadolint_DL3001",
|
|
63
|
+
category: "BestPractice",
|
|
64
|
+
severityLevel: "Warning",
|
|
65
|
+
title: "Avoid Running Inappropriate Commands in Docker Containers",
|
|
66
|
+
enabled: true,
|
|
67
|
+
parameters: []
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "Hadolint_DL3002",
|
|
71
|
+
category: "Security",
|
|
72
|
+
severityLevel: "High",
|
|
73
|
+
title: "Avoid using root as the last USER in Dockerfile",
|
|
74
|
+
enabled: true,
|
|
75
|
+
parameters: []
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "Hadolint_DL3003",
|
|
79
|
+
category: "BestPractice",
|
|
80
|
+
severityLevel: "Warning",
|
|
81
|
+
title: "Use WORKDIR to Switch to a Directory",
|
|
82
|
+
enabled: true,
|
|
83
|
+
parameters: []
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "Hadolint_DL3004",
|
|
87
|
+
category: "BestPractice",
|
|
88
|
+
severityLevel: "Warning",
|
|
89
|
+
title: "Avoid Using sudo in Dockerfiles",
|
|
90
|
+
enabled: true,
|
|
91
|
+
parameters: []
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "Hadolint_DL3005",
|
|
95
|
+
category: "BestPractice",
|
|
96
|
+
severityLevel: "Warning",
|
|
97
|
+
title: "Avoid Using apt-get dist-upgrade",
|
|
98
|
+
enabled: true,
|
|
99
|
+
parameters: []
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "Hadolint_DL3006",
|
|
103
|
+
category: "BestPractice",
|
|
104
|
+
severityLevel: "Warning",
|
|
105
|
+
title: "Enforce Explicit Version Tag on Docker Image",
|
|
106
|
+
enabled: true,
|
|
107
|
+
parameters: []
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "Hadolint_DL3007",
|
|
111
|
+
category: "BestPractice",
|
|
112
|
+
severityLevel: "Warning",
|
|
113
|
+
title: "Avoid Using 'latest' Tag in Docker Images",
|
|
114
|
+
enabled: true,
|
|
115
|
+
parameters: []
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "Hadolint_DL3008",
|
|
119
|
+
category: "BestPractice",
|
|
120
|
+
severityLevel: "Warning",
|
|
121
|
+
title: "Enforce Pinning Versions in apt-get install",
|
|
122
|
+
enabled: true,
|
|
123
|
+
parameters: []
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "Hadolint_DL3009",
|
|
127
|
+
category: "Performance",
|
|
128
|
+
severityLevel: "Warning",
|
|
129
|
+
title: "Delete apt-get lists after installing packages",
|
|
130
|
+
enabled: true,
|
|
131
|
+
parameters: []
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "Hadolint_DL3010",
|
|
135
|
+
category: "BestPractice",
|
|
136
|
+
severityLevel: "Info",
|
|
137
|
+
title: "Use ADD for Extracting Archives into an Image",
|
|
138
|
+
enabled: true,
|
|
139
|
+
parameters: []
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "Hadolint_DL3011",
|
|
143
|
+
category: "ErrorProne",
|
|
144
|
+
severityLevel: "High",
|
|
145
|
+
title: "Enforce valid UNIX port range in Dockerfile EXPOSE instruction",
|
|
146
|
+
enabled: true,
|
|
147
|
+
parameters: []
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "Hadolint_DL3012",
|
|
151
|
+
category: "ErrorProne",
|
|
152
|
+
severityLevel: "Warning",
|
|
153
|
+
title: "Avoid Multiple HEALTHCHECK Instructions in Dockerfile",
|
|
154
|
+
enabled: true,
|
|
155
|
+
parameters: []
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "Hadolint_DL3013",
|
|
159
|
+
category: "BestPractice",
|
|
160
|
+
severityLevel: "Warning",
|
|
161
|
+
title: "Pin Versions in pip Installations",
|
|
162
|
+
enabled: true,
|
|
163
|
+
parameters: []
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: "Hadolint_DL3014",
|
|
167
|
+
category: "BestPractice",
|
|
168
|
+
severityLevel: "Warning",
|
|
169
|
+
title: "Enforce Use of -y Switch in apt-get Install",
|
|
170
|
+
enabled: true,
|
|
171
|
+
parameters: []
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "Hadolint_DL3015",
|
|
175
|
+
category: "BestPractice",
|
|
176
|
+
severityLevel: "Warning",
|
|
177
|
+
title: "Enforce Use of --no-install-recommends with apt-get install",
|
|
178
|
+
enabled: true,
|
|
179
|
+
parameters: []
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "Hadolint_DL3016",
|
|
183
|
+
category: "BestPractice",
|
|
184
|
+
severityLevel: "Warning",
|
|
185
|
+
title: "Pin Versions in npm Install",
|
|
186
|
+
enabled: false,
|
|
187
|
+
parameters: []
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "Hadolint_DL3018",
|
|
191
|
+
category: "BestPractice",
|
|
192
|
+
severityLevel: "Warning",
|
|
193
|
+
title: "Pin Versions in apk add",
|
|
194
|
+
enabled: false,
|
|
195
|
+
parameters: []
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "Hadolint_DL3019",
|
|
199
|
+
category: "Performance",
|
|
200
|
+
severityLevel: "Warning",
|
|
201
|
+
title: "Use --no-cache Switch for apk Package Installation",
|
|
202
|
+
enabled: false,
|
|
203
|
+
parameters: []
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "Hadolint_DL3020",
|
|
207
|
+
category: "BestPractice",
|
|
208
|
+
severityLevel: "Info",
|
|
209
|
+
title: "Use COPY Instead of ADD for Files and Folders",
|
|
210
|
+
enabled: true,
|
|
211
|
+
parameters: []
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "Hadolint_DL3021",
|
|
215
|
+
category: "ErrorProne",
|
|
216
|
+
severityLevel: "High",
|
|
217
|
+
title: "Enforce COPY Destination Ends with Slash for Multiple Sources",
|
|
218
|
+
enabled: true,
|
|
219
|
+
parameters: []
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "Hadolint_DL3022",
|
|
223
|
+
category: "ErrorProne",
|
|
224
|
+
severityLevel: "High",
|
|
225
|
+
title: "Enforce COPY --from References Defined FROM Alias",
|
|
226
|
+
enabled: true,
|
|
227
|
+
parameters: []
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "Hadolint_DL3023",
|
|
231
|
+
category: "ErrorProne",
|
|
232
|
+
severityLevel: "High",
|
|
233
|
+
title: "Avoid COPY --from referencing its own FROM alias",
|
|
234
|
+
enabled: true,
|
|
235
|
+
parameters: []
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "Hadolint_DL3024",
|
|
239
|
+
category: "ErrorProne",
|
|
240
|
+
severityLevel: "High",
|
|
241
|
+
title: "Enforce Unique FROM Aliases (Stage Names) in Dockerfiles",
|
|
242
|
+
enabled: true,
|
|
243
|
+
parameters: []
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "Hadolint_DL3025",
|
|
247
|
+
category: "BestPractice",
|
|
248
|
+
severityLevel: "Warning",
|
|
249
|
+
title: "Use JSON Array Notation for CMD and ENTRYPOINT Arguments",
|
|
250
|
+
enabled: false,
|
|
251
|
+
parameters: []
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "Hadolint_DL3026",
|
|
255
|
+
category: "Security",
|
|
256
|
+
severityLevel: "High",
|
|
257
|
+
title: "Enforce Use of Allowed Registry in FROM Image",
|
|
258
|
+
enabled: false,
|
|
259
|
+
parameters: []
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "Hadolint_DL3027",
|
|
263
|
+
category: "BestPractice",
|
|
264
|
+
severityLevel: "Warning",
|
|
265
|
+
title: "Avoid Using apt in Dockerfiles; Use apt-get or apt-cache Instead",
|
|
266
|
+
enabled: false,
|
|
267
|
+
parameters: []
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: "Hadolint_DL3028",
|
|
271
|
+
category: "BestPractice",
|
|
272
|
+
severityLevel: "Warning",
|
|
273
|
+
title: "Pin Versions in gem install",
|
|
274
|
+
enabled: false,
|
|
275
|
+
parameters: []
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: "Hadolint_DL3029",
|
|
279
|
+
category: "BestPractice",
|
|
280
|
+
severityLevel: "Warning",
|
|
281
|
+
title: "Avoid Using --platform Flag with FROM Instruction",
|
|
282
|
+
enabled: false,
|
|
283
|
+
parameters: []
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "Hadolint_DL3030",
|
|
287
|
+
category: "ErrorProne",
|
|
288
|
+
severityLevel: "High",
|
|
289
|
+
title: "Enforce using -y switch with yum install",
|
|
290
|
+
enabled: false,
|
|
291
|
+
parameters: []
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "Hadolint_DL3032",
|
|
295
|
+
category: "Performance",
|
|
296
|
+
severityLevel: "Warning",
|
|
297
|
+
title: "Enforce yum clean all after yum command",
|
|
298
|
+
enabled: false,
|
|
299
|
+
parameters: []
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "Hadolint_DL3033",
|
|
303
|
+
category: "BestPractice",
|
|
304
|
+
severityLevel: "Warning",
|
|
305
|
+
title: "Specify Version When Installing Packages with yum",
|
|
306
|
+
enabled: false,
|
|
307
|
+
parameters: []
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "Hadolint_DL3034",
|
|
311
|
+
category: "ErrorProne",
|
|
312
|
+
severityLevel: "High",
|
|
313
|
+
title: "Enforce Non-Interactive Switch in zypper Install Commands",
|
|
314
|
+
enabled: false,
|
|
315
|
+
parameters: []
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "Hadolint_DL3035",
|
|
319
|
+
category: "BestPractice",
|
|
320
|
+
severityLevel: "Warning",
|
|
321
|
+
title: "Avoid Using zypper dist-upgrade",
|
|
322
|
+
enabled: false,
|
|
323
|
+
parameters: []
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: "Hadolint_DL3036",
|
|
327
|
+
category: "Performance",
|
|
328
|
+
severityLevel: "Warning",
|
|
329
|
+
title: "Enforce zypper clean after zypper use",
|
|
330
|
+
enabled: false,
|
|
331
|
+
parameters: []
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: "Hadolint_DL3037",
|
|
335
|
+
category: "BestPractice",
|
|
336
|
+
severityLevel: "Warning",
|
|
337
|
+
title: "Enforce Version Pinning with zypper install",
|
|
338
|
+
enabled: false,
|
|
339
|
+
parameters: []
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: "Hadolint_DL3038",
|
|
343
|
+
category: "BestPractice",
|
|
344
|
+
severityLevel: "Warning",
|
|
345
|
+
title: "Enforce Use of -y Switch in dnf install",
|
|
346
|
+
enabled: false,
|
|
347
|
+
parameters: []
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: "Hadolint_DL3040",
|
|
351
|
+
category: "BestPractice",
|
|
352
|
+
severityLevel: "Warning",
|
|
353
|
+
title: "Enforce dnf clean all After dnf Command",
|
|
354
|
+
enabled: false,
|
|
355
|
+
parameters: []
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: "Hadolint_DL3041",
|
|
359
|
+
category: "BestPractice",
|
|
360
|
+
severityLevel: "Warning",
|
|
361
|
+
title: "Enforce Version Pinning with dnf install",
|
|
362
|
+
enabled: false,
|
|
363
|
+
parameters: []
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "Hadolint_DL3042",
|
|
367
|
+
category: "Performance",
|
|
368
|
+
severityLevel: "Warning",
|
|
369
|
+
title: "Avoid Using pip Cache Directory with pip install",
|
|
370
|
+
enabled: false,
|
|
371
|
+
parameters: []
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: "Hadolint_DL3043",
|
|
375
|
+
category: "ErrorProne",
|
|
376
|
+
severityLevel: "Error",
|
|
377
|
+
title: "Avoid ONBUILD Triggering ONBUILD, FROM, or MAINTAINER",
|
|
378
|
+
enabled: false,
|
|
379
|
+
parameters: []
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: "Hadolint_DL3044",
|
|
383
|
+
category: "ErrorProne",
|
|
384
|
+
severityLevel: "Warning",
|
|
385
|
+
title: "Avoid referring to environment variables within the same ENV statement where they are defined",
|
|
386
|
+
enabled: false,
|
|
387
|
+
parameters: []
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: "Hadolint_DL3045",
|
|
391
|
+
category: "ErrorProne",
|
|
392
|
+
severityLevel: "Warning",
|
|
393
|
+
title: "Avoid COPY to Relative Destination Without WORKDIR Set",
|
|
394
|
+
enabled: false,
|
|
395
|
+
parameters: []
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: "Hadolint_DL3046",
|
|
399
|
+
category: "Performance",
|
|
400
|
+
severityLevel: "Warning",
|
|
401
|
+
title: "Enforce useradd with -l flag for high UID",
|
|
402
|
+
enabled: false,
|
|
403
|
+
parameters: []
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "Hadolint_DL3047",
|
|
407
|
+
category: "Performance",
|
|
408
|
+
severityLevel: "Info",
|
|
409
|
+
title: "Enforce wget Progress Flag Usage",
|
|
410
|
+
enabled: false,
|
|
411
|
+
parameters: []
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: "Hadolint_DL3048",
|
|
415
|
+
category: "ErrorProne",
|
|
416
|
+
severityLevel: "High",
|
|
417
|
+
title: "Enforce Valid Label Key",
|
|
418
|
+
enabled: false,
|
|
419
|
+
parameters: []
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: "Hadolint_DL3049",
|
|
423
|
+
category: "BestPractice",
|
|
424
|
+
severityLevel: "Warning",
|
|
425
|
+
title: "Enforce Required Label Presence",
|
|
426
|
+
enabled: false,
|
|
427
|
+
parameters: []
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: "Hadolint_DL3050",
|
|
431
|
+
category: "BestPractice",
|
|
432
|
+
severityLevel: "Warning",
|
|
433
|
+
title: "Avoid Superfluous Labels in Dockerfile",
|
|
434
|
+
enabled: false,
|
|
435
|
+
parameters: []
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
id: "Hadolint_DL3051",
|
|
439
|
+
category: "ErrorProne",
|
|
440
|
+
severityLevel: "Warning",
|
|
441
|
+
title: "Avoid Empty Labels in Dockerfile",
|
|
442
|
+
enabled: false,
|
|
443
|
+
parameters: []
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: "Hadolint_DL3052",
|
|
447
|
+
category: "BestPractice",
|
|
448
|
+
severityLevel: "Warning",
|
|
449
|
+
title: "Enforce Valid URL in Label",
|
|
450
|
+
enabled: false,
|
|
451
|
+
parameters: []
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: "Hadolint_DL3053",
|
|
455
|
+
category: "ErrorProne",
|
|
456
|
+
severityLevel: "Warning",
|
|
457
|
+
title: "Enforce RFC3339 Time Format in Labels",
|
|
458
|
+
enabled: false,
|
|
459
|
+
parameters: []
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: "Hadolint_DL3054",
|
|
463
|
+
category: "BestPractice",
|
|
464
|
+
severityLevel: "Warning",
|
|
465
|
+
title: "Enforce Valid SPDX License Identifier in Label",
|
|
466
|
+
enabled: false,
|
|
467
|
+
parameters: []
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "Hadolint_DL3055",
|
|
471
|
+
category: "BestPractice",
|
|
472
|
+
severityLevel: "Warning",
|
|
473
|
+
title: "Enforce Valid Git Hash in Label",
|
|
474
|
+
enabled: false,
|
|
475
|
+
parameters: []
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "Hadolint_DL3056",
|
|
479
|
+
category: "BestPractice",
|
|
480
|
+
severityLevel: "Warning",
|
|
481
|
+
title: "Enforce Semantic Versioning in Label",
|
|
482
|
+
enabled: false,
|
|
483
|
+
parameters: []
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: "Hadolint_DL3057",
|
|
487
|
+
category: "BestPractice",
|
|
488
|
+
severityLevel: "Warning",
|
|
489
|
+
title: "Enforce HEALTHCHECK Instruction Presence",
|
|
490
|
+
enabled: false,
|
|
491
|
+
parameters: []
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: "Hadolint_DL3058",
|
|
495
|
+
category: "BestPractice",
|
|
496
|
+
severityLevel: "Warning",
|
|
497
|
+
title: "Enforce Valid Email Format in Label",
|
|
498
|
+
enabled: false,
|
|
499
|
+
parameters: []
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
id: "Hadolint_DL3059",
|
|
503
|
+
category: "Performance",
|
|
504
|
+
severityLevel: "Warning",
|
|
505
|
+
title: "Consolidate Multiple Consecutive RUN Instructions",
|
|
506
|
+
enabled: false,
|
|
507
|
+
parameters: []
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: "Hadolint_DL3060",
|
|
511
|
+
category: "Performance",
|
|
512
|
+
severityLevel: "Warning",
|
|
513
|
+
title: "Enforce yarn cache clean after yarn install",
|
|
514
|
+
enabled: false,
|
|
515
|
+
parameters: []
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: "Hadolint_DL3061",
|
|
519
|
+
category: "ErrorProne",
|
|
520
|
+
severityLevel: "High",
|
|
521
|
+
title: "Enforce Valid Instruction Order in Dockerfile",
|
|
522
|
+
enabled: false,
|
|
523
|
+
parameters: []
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
id: "Hadolint_DL4000",
|
|
527
|
+
category: "Compatibility",
|
|
528
|
+
severityLevel: "Warning",
|
|
529
|
+
title: "Avoid Deprecated MAINTAINER Instruction",
|
|
530
|
+
enabled: true,
|
|
531
|
+
parameters: []
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
id: "Hadolint_DL4001",
|
|
535
|
+
category: "BestPractice",
|
|
536
|
+
severityLevel: "Info",
|
|
537
|
+
title: "Enforce Consistent Use of Wget or Curl",
|
|
538
|
+
enabled: true,
|
|
539
|
+
parameters: []
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: "Hadolint_DL4003",
|
|
543
|
+
category: "ErrorProne",
|
|
544
|
+
severityLevel: "Warning",
|
|
545
|
+
title: "Avoid Multiple CMD Instructions in Dockerfile",
|
|
546
|
+
enabled: true,
|
|
547
|
+
parameters: []
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
id: "Hadolint_DL4004",
|
|
551
|
+
category: "ErrorProne",
|
|
552
|
+
severityLevel: "Warning",
|
|
553
|
+
title: "Avoid Multiple ENTRYPOINT Instructions",
|
|
554
|
+
enabled: true,
|
|
555
|
+
parameters: []
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
id: "Hadolint_DL4005",
|
|
559
|
+
category: "BestPractice",
|
|
560
|
+
severityLevel: "Info",
|
|
561
|
+
title: "Use SHELL Instruction to Change Default Shell",
|
|
562
|
+
enabled: true,
|
|
563
|
+
parameters: []
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: "Hadolint_DL4006",
|
|
567
|
+
category: "ErrorProne",
|
|
568
|
+
severityLevel: "High",
|
|
569
|
+
title: "Set SHELL option -o pipefail before RUN with pipe",
|
|
570
|
+
enabled: false,
|
|
571
|
+
parameters: []
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "Hadolint_SC1000",
|
|
575
|
+
category: "BestPractice",
|
|
576
|
+
severityLevel: "Info",
|
|
577
|
+
title: "Escape Literal Dollar Signs in Double Quotes",
|
|
578
|
+
enabled: true,
|
|
579
|
+
parameters: []
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
id: "Hadolint_SC1001",
|
|
583
|
+
category: "BestPractice",
|
|
584
|
+
severityLevel: "Info",
|
|
585
|
+
title: "Avoid Unnecessary Escape Sequences in Shell Scripts",
|
|
586
|
+
enabled: true,
|
|
587
|
+
parameters: []
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
id: "Hadolint_SC1007",
|
|
591
|
+
category: "ErrorProne",
|
|
592
|
+
severityLevel: "High",
|
|
593
|
+
title: "Remove Space After '=' in Variable Assignment",
|
|
594
|
+
enabled: true,
|
|
595
|
+
parameters: []
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
id: "Hadolint_SC1010",
|
|
599
|
+
category: "ErrorProne",
|
|
600
|
+
severityLevel: "High",
|
|
601
|
+
title: "Enforce Semicolon or Linefeed Before Shell Keywords like done",
|
|
602
|
+
enabled: true,
|
|
603
|
+
parameters: []
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
id: "Hadolint_SC1018",
|
|
607
|
+
category: "ErrorProne",
|
|
608
|
+
severityLevel: "High",
|
|
609
|
+
title: "Avoid Unicode Non-Breaking and Zero-Width Spaces",
|
|
610
|
+
enabled: true,
|
|
611
|
+
parameters: []
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
id: "Hadolint_SC1035",
|
|
615
|
+
category: "ErrorProne",
|
|
616
|
+
severityLevel: "High",
|
|
617
|
+
title: "Enforce Space After Negation in Shell Conditionals",
|
|
618
|
+
enabled: true,
|
|
619
|
+
parameters: []
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
id: "Hadolint_SC1045",
|
|
623
|
+
category: "ErrorProne",
|
|
624
|
+
severityLevel: "Warning",
|
|
625
|
+
title: "Avoid Using Both '&' and ';' to Terminate Commands",
|
|
626
|
+
enabled: true,
|
|
627
|
+
parameters: []
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
id: "Hadolint_SC1065",
|
|
631
|
+
category: "ErrorProne",
|
|
632
|
+
severityLevel: "High",
|
|
633
|
+
title: "Avoid Declaring Parameters in Shell Functions",
|
|
634
|
+
enabled: true,
|
|
635
|
+
parameters: []
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
id: "Hadolint_SC1066",
|
|
639
|
+
category: "ErrorProne",
|
|
640
|
+
severityLevel: "High",
|
|
641
|
+
title: "Avoid Using $ on Left Side of Assignments",
|
|
642
|
+
enabled: true,
|
|
643
|
+
parameters: []
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
id: "Hadolint_SC1068",
|
|
647
|
+
category: "ErrorProne",
|
|
648
|
+
severityLevel: "High",
|
|
649
|
+
title: "Avoid Spaces Around '=' in Shell Variable Assignments",
|
|
650
|
+
enabled: true,
|
|
651
|
+
parameters: []
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
id: "Hadolint_SC1077",
|
|
655
|
+
category: "ErrorProne",
|
|
656
|
+
severityLevel: "High",
|
|
657
|
+
title: "Enforce Correct Use of Backticks for Command Expansion",
|
|
658
|
+
enabled: true,
|
|
659
|
+
parameters: []
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
id: "Hadolint_SC1078",
|
|
663
|
+
category: "ErrorProne",
|
|
664
|
+
severityLevel: "High",
|
|
665
|
+
title: "Detect Unclosed Double-Quoted Strings",
|
|
666
|
+
enabled: true,
|
|
667
|
+
parameters: []
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
id: "Hadolint_SC1079",
|
|
671
|
+
category: "ErrorProne",
|
|
672
|
+
severityLevel: "Warning",
|
|
673
|
+
title: "Detect Suspected Misplaced End Quote in Dockerfile",
|
|
674
|
+
enabled: true,
|
|
675
|
+
parameters: []
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
id: "Hadolint_SC1081",
|
|
679
|
+
category: "ErrorProne",
|
|
680
|
+
severityLevel: "High",
|
|
681
|
+
title: "Enforce lowercase shell script keywords",
|
|
682
|
+
enabled: true,
|
|
683
|
+
parameters: []
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
id: "Hadolint_SC1083",
|
|
687
|
+
category: "ErrorProne",
|
|
688
|
+
severityLevel: "Warning",
|
|
689
|
+
title: "Avoid Literal Curly Braces Without Proper Syntax or Quoting",
|
|
690
|
+
enabled: true,
|
|
691
|
+
parameters: []
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
id: "Hadolint_SC1086",
|
|
695
|
+
category: "ErrorProne",
|
|
696
|
+
severityLevel: "High",
|
|
697
|
+
title: "Avoid Using Dollar Sign on Iterator Variable in For Loops",
|
|
698
|
+
enabled: true,
|
|
699
|
+
parameters: []
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
id: "Hadolint_SC1087",
|
|
703
|
+
category: "ErrorProne",
|
|
704
|
+
severityLevel: "Warning",
|
|
705
|
+
title: "Enforce Braces When Expanding Arrays",
|
|
706
|
+
enabled: true,
|
|
707
|
+
parameters: []
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
id: "Hadolint_SC1095",
|
|
711
|
+
category: "ErrorProne",
|
|
712
|
+
severityLevel: "High",
|
|
713
|
+
title: "Enforce Space Between Function Name and Body in Shell Scripts",
|
|
714
|
+
enabled: true,
|
|
715
|
+
parameters: []
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
id: "Hadolint_SC1097",
|
|
719
|
+
category: "ErrorProne",
|
|
720
|
+
severityLevel: "High",
|
|
721
|
+
title: "Avoid Using Unexpected '==' in Shell Scripts",
|
|
722
|
+
enabled: true,
|
|
723
|
+
parameters: []
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
id: "Hadolint_SC1098",
|
|
727
|
+
category: "Compatibility",
|
|
728
|
+
severityLevel: "Warning",
|
|
729
|
+
title: "Quote or Escape Special Characters in eval",
|
|
730
|
+
enabled: true,
|
|
731
|
+
parameters: []
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
id: "Hadolint_SC1099",
|
|
735
|
+
category: "CodeStyle",
|
|
736
|
+
severityLevel: "Info",
|
|
737
|
+
title: "Enforce Space Before Comment Hash",
|
|
738
|
+
enabled: true,
|
|
739
|
+
parameters: []
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
id: "Hadolint_SC2002",
|
|
743
|
+
category: "Performance",
|
|
744
|
+
severityLevel: "Info",
|
|
745
|
+
title: "Avoid Useless Use of cat",
|
|
746
|
+
enabled: true,
|
|
747
|
+
parameters: []
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
id: "Hadolint_SC2015",
|
|
751
|
+
category: "ErrorProne",
|
|
752
|
+
severityLevel: "High",
|
|
753
|
+
title: "Avoid Using 'A && B || C' Instead of If-Then-Else",
|
|
754
|
+
enabled: true,
|
|
755
|
+
parameters: []
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
id: "Hadolint_SC2026",
|
|
759
|
+
category: "ErrorProne",
|
|
760
|
+
severityLevel: "High",
|
|
761
|
+
title: "Avoid Unescaped Single Quotes Outside of Quotes in Shell Commands",
|
|
762
|
+
enabled: true,
|
|
763
|
+
parameters: []
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
id: "Hadolint_SC2028",
|
|
767
|
+
category: "BestPractice",
|
|
768
|
+
severityLevel: "Warning",
|
|
769
|
+
title: "Avoid Using echo for Escape Sequences; Use printf Instead",
|
|
770
|
+
enabled: true,
|
|
771
|
+
parameters: []
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: "Hadolint_SC2035",
|
|
775
|
+
category: "ErrorProne",
|
|
776
|
+
severityLevel: "High",
|
|
777
|
+
title: "Enforce Safe Glob Usage to Avoid Misinterpretation as Options",
|
|
778
|
+
enabled: true,
|
|
779
|
+
parameters: []
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
id: "Hadolint_SC2039",
|
|
783
|
+
category: "Compatibility",
|
|
784
|
+
severityLevel: "High",
|
|
785
|
+
title: "Avoid Using Undefined Features in POSIX sh",
|
|
786
|
+
enabled: false,
|
|
787
|
+
parameters: []
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
id: "Hadolint_SC2046",
|
|
791
|
+
category: "ErrorProne",
|
|
792
|
+
severityLevel: "Warning",
|
|
793
|
+
title: "Quote to Prevent Word Splitting",
|
|
794
|
+
enabled: true,
|
|
795
|
+
parameters: []
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
id: "Hadolint_SC2086",
|
|
799
|
+
category: "ErrorProne",
|
|
800
|
+
severityLevel: "High",
|
|
801
|
+
title: "Enforce double quoting to prevent globbing and word splitting",
|
|
802
|
+
enabled: true,
|
|
803
|
+
parameters: []
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
id: "Hadolint_SC2140",
|
|
807
|
+
category: "ErrorProne",
|
|
808
|
+
severityLevel: "Warning",
|
|
809
|
+
title: "Avoid Incorrectly Nested or Unnecessary Double Quotes in Shell Scripts",
|
|
810
|
+
enabled: true,
|
|
811
|
+
parameters: []
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
id: "Hadolint_SC2154",
|
|
815
|
+
category: "ErrorProne",
|
|
816
|
+
severityLevel: "High",
|
|
817
|
+
title: "Detect Unassigned Variable References in Shell Scripts",
|
|
818
|
+
enabled: true,
|
|
819
|
+
parameters: []
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
id: "Hadolint_SC2155",
|
|
823
|
+
category: "ErrorProne",
|
|
824
|
+
severityLevel: "High",
|
|
825
|
+
title: "Declare and Assign Separately to Avoid Masking Return Values",
|
|
826
|
+
enabled: false,
|
|
827
|
+
parameters: []
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
id: "Hadolint_SC2164",
|
|
831
|
+
category: "ErrorProne",
|
|
832
|
+
severityLevel: "High",
|
|
833
|
+
title: "Ensure cd Command Failure is Handled",
|
|
834
|
+
enabled: true,
|
|
835
|
+
parameters: []
|
|
836
|
+
}
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
// src/adapter.ts
|
|
840
|
+
var TOOL_ID = "Hadolint";
|
|
841
|
+
var PATTERN_META = new Map(
|
|
842
|
+
patterns_default.map((p) => [
|
|
843
|
+
p.id,
|
|
844
|
+
{
|
|
845
|
+
category: p.category,
|
|
846
|
+
severity: p.severityLevel ?? "Info"
|
|
847
|
+
}
|
|
848
|
+
])
|
|
849
|
+
);
|
|
850
|
+
function ruleMetadata(code) {
|
|
851
|
+
const patternId = `${TOOL_ID}_${code}`;
|
|
852
|
+
return PATTERN_META.get(patternId) ?? { category: "BestPractice", severity: "Info" };
|
|
853
|
+
}
|
|
854
|
+
function getLineContent(lines, lineNumber) {
|
|
855
|
+
return lines[lineNumber - 1] ?? "";
|
|
856
|
+
}
|
|
857
|
+
async function readFileLines(filePath) {
|
|
858
|
+
try {
|
|
859
|
+
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
860
|
+
return content.split("\n");
|
|
861
|
+
} catch {
|
|
862
|
+
return [];
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function buildResult(startedAt, completedAt, issues, errors, filesAnalyzed) {
|
|
866
|
+
const durationMs = completedAt.getTime() - startedAt.getTime();
|
|
867
|
+
return {
|
|
868
|
+
metadata: {
|
|
869
|
+
startedAt: startedAt.toISOString(),
|
|
870
|
+
completedAt: completedAt.toISOString(),
|
|
871
|
+
durationMs
|
|
872
|
+
},
|
|
873
|
+
issues,
|
|
874
|
+
errors,
|
|
875
|
+
summary: {
|
|
876
|
+
toolId: TOOL_ID,
|
|
877
|
+
status: errors.some((e) => e.level === "error") ? "partial" : "success",
|
|
878
|
+
issueCount: issues.length,
|
|
879
|
+
errorCount: errors.length,
|
|
880
|
+
durationMs,
|
|
881
|
+
filesAnalyzed
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
var BINARY = "hadolint";
|
|
886
|
+
var PLATFORM_MAP = {
|
|
887
|
+
"darwin-arm64": "macos-arm64",
|
|
888
|
+
"darwin-x64": "macos-x86_64",
|
|
889
|
+
"linux-x64": "linux-x86_64",
|
|
890
|
+
"linux-arm64": "linux-arm64"
|
|
891
|
+
};
|
|
892
|
+
async function getVersionFromBinary(binaryPath) {
|
|
893
|
+
try {
|
|
894
|
+
const { stdout, exitCode } = await (0, import_tooling.spawnTool)(binaryPath, ["--version"], {
|
|
895
|
+
cwd: process.cwd()
|
|
896
|
+
});
|
|
897
|
+
if (exitCode !== 0) return null;
|
|
898
|
+
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
899
|
+
return match ? match[1] : null;
|
|
900
|
+
} catch {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async function resolveBinaryPath(ctx) {
|
|
905
|
+
const codacyBin = import_path.default.join(ctx.toolInstallDir, BINARY);
|
|
906
|
+
const version = await getVersionFromBinary(codacyBin);
|
|
907
|
+
if (version) return codacyBin;
|
|
908
|
+
return BINARY;
|
|
909
|
+
}
|
|
910
|
+
var adapter = {
|
|
911
|
+
id: TOOL_ID,
|
|
912
|
+
displayName: "Hadolint",
|
|
913
|
+
preferredVersion: "2.14.0",
|
|
914
|
+
compatibleVersions: ">=2.0.0",
|
|
915
|
+
website: "https://github.com/hadolint/hadolint",
|
|
916
|
+
languages: ["Dockerfile"],
|
|
917
|
+
filePatterns: ["**/Dockerfile", "**/*.dockerfile", "**/Dockerfile.*"],
|
|
918
|
+
supportedOS: ["linux", "darwin", "win32"],
|
|
919
|
+
supportedArch: ["x64", "arm64"],
|
|
920
|
+
supportedRunners: ["local", "container"],
|
|
921
|
+
prerequisites: [],
|
|
922
|
+
/**
|
|
923
|
+
* Checks whether the `hadolint` binary is present and runnable.
|
|
924
|
+
*
|
|
925
|
+
* Checks in order:
|
|
926
|
+
* 1. Codacy-managed path (`ctx.toolInstallDir/hadolint`) → installation: "codacy"
|
|
927
|
+
* 2. Global PATH (`hadolint`) → installation: "global"
|
|
928
|
+
*
|
|
929
|
+
* Parses the version from output like `Haskell Dockerfile Linter 2.12.0`.
|
|
930
|
+
* Returns `available: false` (rather than throwing) if the binary is not
|
|
931
|
+
* found or returns a non-zero exit code.
|
|
932
|
+
*/
|
|
933
|
+
async checkAvailability(ctx) {
|
|
934
|
+
ctx.logger.debug("Checking availability", { toolId: TOOL_ID });
|
|
935
|
+
const codacyBin = import_path.default.join(ctx.toolInstallDir, BINARY);
|
|
936
|
+
const codacyVersion = await getVersionFromBinary(codacyBin);
|
|
937
|
+
if (codacyVersion) {
|
|
938
|
+
ctx.logger.debug("Found codacy-managed binary", { toolId: TOOL_ID, version: codacyVersion, path: codacyBin });
|
|
939
|
+
return { available: true, version: codacyVersion, path: codacyBin, installation: "codacy" };
|
|
940
|
+
}
|
|
941
|
+
const globalVersion = await getVersionFromBinary(BINARY);
|
|
942
|
+
if (globalVersion) {
|
|
943
|
+
ctx.logger.debug("Found global binary", { toolId: TOOL_ID, version: globalVersion });
|
|
944
|
+
return { available: true, version: globalVersion, installation: "global" };
|
|
945
|
+
}
|
|
946
|
+
ctx.logger.debug("Binary not found", { toolId: TOOL_ID, reason: "not in PATH or Codacy install directory" });
|
|
947
|
+
return {
|
|
948
|
+
available: false,
|
|
949
|
+
reason: "hadolint binary not found in PATH or Codacy install directory",
|
|
950
|
+
installHint: "brew install hadolint # macOS\ncodacy-analysis analyze . --install-dependencies # auto-install"
|
|
951
|
+
};
|
|
952
|
+
},
|
|
953
|
+
/**
|
|
954
|
+
* Downloads the hadolint binary from GitHub releases to ctx.toolInstallDir.
|
|
955
|
+
*
|
|
956
|
+
* Detects the current platform/arch and downloads the appropriate binary.
|
|
957
|
+
* Sets executable permission after download.
|
|
958
|
+
*/
|
|
959
|
+
async install(ctx) {
|
|
960
|
+
ctx.logger.info("Starting install", { toolId: TOOL_ID });
|
|
961
|
+
const platformKey = `${ctx.platform.os}-${ctx.platform.arch}`;
|
|
962
|
+
const platformSuffix = PLATFORM_MAP[platformKey];
|
|
963
|
+
if (!platformSuffix) {
|
|
964
|
+
return {
|
|
965
|
+
success: false,
|
|
966
|
+
error: {
|
|
967
|
+
toolId: TOOL_ID,
|
|
968
|
+
phase: "toolInstall",
|
|
969
|
+
kind: "UnsupportedPlatform",
|
|
970
|
+
message: `Unsupported platform: ${platformKey}. Supported: ${Object.keys(PLATFORM_MAP).join(", ")}`,
|
|
971
|
+
level: "error"
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
const version = adapter.preferredVersion;
|
|
976
|
+
const url = `https://github.com/hadolint/hadolint/releases/download/v${version}/hadolint-${platformSuffix}`;
|
|
977
|
+
const destPath = import_path.default.join(ctx.toolInstallDir, BINARY);
|
|
978
|
+
try {
|
|
979
|
+
ctx.logger.info(`Downloading hadolint v${version} for ${platformKey}...`);
|
|
980
|
+
await (0, import_tooling.downloadFile)(url, destPath, ctx.logger);
|
|
981
|
+
await import_promises.default.chmod(destPath, 493);
|
|
982
|
+
ctx.logger.info(`Installed hadolint v${version} to ${destPath}`);
|
|
983
|
+
return { success: true, installedPath: destPath, installedVersion: version };
|
|
984
|
+
} catch (err) {
|
|
985
|
+
return {
|
|
986
|
+
success: false,
|
|
987
|
+
error: {
|
|
988
|
+
toolId: TOOL_ID,
|
|
989
|
+
phase: "toolInstall",
|
|
990
|
+
kind: "DownloadError",
|
|
991
|
+
message: `Failed to download hadolint: ${err instanceof Error ? err.message : String(err)}`,
|
|
992
|
+
level: "error"
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
/**
|
|
998
|
+
* Checks for `.hadolint.yaml` or `.hadolint.yml` in the repository root.
|
|
999
|
+
*
|
|
1000
|
+
* Hadolint automatically discovers these files when present. This method
|
|
1001
|
+
* reports whether one exists so the runner can surface it to the user;
|
|
1002
|
+
* the adapter passes `--config <path>` only when `useLocalConfigurationFile`
|
|
1003
|
+
* is explicitly set in the Codacy config.
|
|
1004
|
+
*
|
|
1005
|
+
* Prefers `.hadolint.yaml` over `.hadolint.yml` when both exist.
|
|
1006
|
+
*/
|
|
1007
|
+
async checkLocalConfigurationFile(ctx) {
|
|
1008
|
+
ctx.logger.debug("Checking for local config", { toolId: TOOL_ID });
|
|
1009
|
+
const candidates = [
|
|
1010
|
+
import_path.default.join(ctx.repositoryRoot, ".hadolint.yaml"),
|
|
1011
|
+
import_path.default.join(ctx.repositoryRoot, ".hadolint.yml")
|
|
1012
|
+
];
|
|
1013
|
+
for (const candidate of candidates) {
|
|
1014
|
+
try {
|
|
1015
|
+
await import_promises.default.access(candidate);
|
|
1016
|
+
ctx.logger.debug("Local config found", { toolId: TOOL_ID, path: candidate });
|
|
1017
|
+
return { found: true, path: candidate };
|
|
1018
|
+
} catch {
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return { found: false };
|
|
1022
|
+
},
|
|
1023
|
+
/**
|
|
1024
|
+
* Runs `hadolint --format json` against the target Dockerfile files and
|
|
1025
|
+
* converts the JSON output into Codacy Issue objects.
|
|
1026
|
+
*
|
|
1027
|
+
* Config resolution:
|
|
1028
|
+
* - If `useLocalConfigurationFile` is true and `localConfigurationFile` is
|
|
1029
|
+
* set, passes `--config <path>` to hadolint so it uses that specific file.
|
|
1030
|
+
* - Otherwise, hadolint auto-discovers `.hadolint.yaml` / `.hadolint.yml`
|
|
1031
|
+
* in the repository root (or its built-in defaults if neither is found).
|
|
1032
|
+
*
|
|
1033
|
+
* Note: Hadolint supports `--ignore <RULE>` flags to disable individual
|
|
1034
|
+
* rules. Full pattern-based filtering (only run the rules in `patterns`)
|
|
1035
|
+
* is not implemented here because it would require computing the complement
|
|
1036
|
+
* of the enabled set, which is impractical without a complete rule registry.
|
|
1037
|
+
* When Codacy patterns are provided without a local config, all rules run
|
|
1038
|
+
* and the issue set may be broader than the configured patterns.
|
|
1039
|
+
*
|
|
1040
|
+
* Exit codes:
|
|
1041
|
+
* - 0: success, no issues found
|
|
1042
|
+
* - 1: success, issues found (normal — not treated as an error)
|
|
1043
|
+
* - 2+: genuine error (binary not found, invalid arguments, etc.)
|
|
1044
|
+
*
|
|
1045
|
+
* Line content is resolved by reading each unique file once and caching the
|
|
1046
|
+
* lines, so files with multiple issues are not read repeatedly.
|
|
1047
|
+
*
|
|
1048
|
+
* Never throws — all errors are captured in the returned `errors` array.
|
|
1049
|
+
*/
|
|
1050
|
+
async analyze(ctx, codacyConfig) {
|
|
1051
|
+
const startMs = Date.now();
|
|
1052
|
+
ctx.logger.info("Starting analysis", { toolId: TOOL_ID, fileCount: ctx.targetFiles.length });
|
|
1053
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
1054
|
+
const issues = [];
|
|
1055
|
+
const errors = [];
|
|
1056
|
+
if (ctx.targetFiles.length === 0) {
|
|
1057
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, 0);
|
|
1058
|
+
}
|
|
1059
|
+
const binary = await resolveBinaryPath(ctx);
|
|
1060
|
+
const absoluteFiles = ctx.targetFiles.map((f) => import_path.default.resolve(ctx.repositoryRoot, f));
|
|
1061
|
+
const args = ["--format", "json"];
|
|
1062
|
+
if (codacyConfig.useLocalConfigurationFile && codacyConfig.localConfigurationFile) {
|
|
1063
|
+
args.push("--config", codacyConfig.localConfigurationFile);
|
|
1064
|
+
}
|
|
1065
|
+
args.push(...absoluteFiles);
|
|
1066
|
+
try {
|
|
1067
|
+
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(binary, args, {
|
|
1068
|
+
cwd: ctx.repositoryRoot,
|
|
1069
|
+
logger: ctx.logger
|
|
1070
|
+
});
|
|
1071
|
+
if (exitCode >= 2) {
|
|
1072
|
+
errors.push({
|
|
1073
|
+
toolId: TOOL_ID,
|
|
1074
|
+
phase: "toolInvoke",
|
|
1075
|
+
kind: "NonZeroExit",
|
|
1076
|
+
message: `hadolint exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`,
|
|
1077
|
+
level: "error"
|
|
1078
|
+
});
|
|
1079
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
1080
|
+
}
|
|
1081
|
+
if (!stdout.trim()) {
|
|
1082
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
1083
|
+
}
|
|
1084
|
+
let findings;
|
|
1085
|
+
try {
|
|
1086
|
+
findings = JSON.parse(stdout);
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
errors.push({
|
|
1089
|
+
toolId: TOOL_ID,
|
|
1090
|
+
phase: "outputParse",
|
|
1091
|
+
kind: "MalformedOutput",
|
|
1092
|
+
message: `Failed to parse hadolint JSON output: ${err instanceof Error ? err.message : String(err)}`,
|
|
1093
|
+
level: "error"
|
|
1094
|
+
});
|
|
1095
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
1096
|
+
}
|
|
1097
|
+
const enabledPatterns = new Set(codacyConfig.patterns.map((p) => p.patternId));
|
|
1098
|
+
const findingsByFile = /* @__PURE__ */ new Map();
|
|
1099
|
+
for (const finding of findings) {
|
|
1100
|
+
const list = findingsByFile.get(finding.file) ?? [];
|
|
1101
|
+
list.push(finding);
|
|
1102
|
+
findingsByFile.set(finding.file, list);
|
|
1103
|
+
}
|
|
1104
|
+
for (const [filePath, fileFindings] of findingsByFile) {
|
|
1105
|
+
const absoluteFilePath = import_path.default.isAbsolute(filePath) ? filePath : import_path.default.resolve(ctx.repositoryRoot, filePath);
|
|
1106
|
+
const lines = await readFileLines(absoluteFilePath);
|
|
1107
|
+
const relPath = import_path.default.isAbsolute(filePath) ? import_path.default.relative(ctx.repositoryRoot, filePath) : filePath;
|
|
1108
|
+
for (const finding of fileFindings) {
|
|
1109
|
+
const patternId = `${TOOL_ID}_${finding.code}`;
|
|
1110
|
+
if (enabledPatterns.size > 0 && !enabledPatterns.has(patternId)) {
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
const meta = ruleMetadata(finding.code);
|
|
1114
|
+
issues.push({
|
|
1115
|
+
filePath: relPath,
|
|
1116
|
+
patternId,
|
|
1117
|
+
toolId: TOOL_ID,
|
|
1118
|
+
line: finding.line,
|
|
1119
|
+
column: finding.column,
|
|
1120
|
+
endLine: finding.endLine,
|
|
1121
|
+
endColumn: finding.endColumn,
|
|
1122
|
+
message: finding.message,
|
|
1123
|
+
lineContent: getLineContent(lines, finding.line),
|
|
1124
|
+
multilineContent: (0, import_tooling.getMultilineContent)(lines, finding.line, finding.endLine),
|
|
1125
|
+
category: meta.category,
|
|
1126
|
+
severity: meta.severity
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
if (stderr.trim()) {
|
|
1131
|
+
errors.push({
|
|
1132
|
+
toolId: TOOL_ID,
|
|
1133
|
+
phase: "outputParse",
|
|
1134
|
+
kind: "ParseWarning",
|
|
1135
|
+
message: `hadolint stderr: ${stderr.trim()}`,
|
|
1136
|
+
level: "warning"
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
errors.push({
|
|
1141
|
+
toolId: TOOL_ID,
|
|
1142
|
+
phase: "toolInvoke",
|
|
1143
|
+
kind: "InvocationError",
|
|
1144
|
+
message: `Failed to run hadolint: ${err instanceof Error ? err.message : String(err)}`,
|
|
1145
|
+
level: "error"
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
const durationMs = Date.now() - startMs;
|
|
1149
|
+
ctx.logger.info("Analysis complete", { toolId: TOOL_ID, issueCount: issues.length, errorCount: errors.length, durationMs });
|
|
1150
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// src/index.ts
|
|
1155
|
+
var index_default = (0, import_tooling2.defineToolAdapter)(adapter);
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codacy/tools-hadolint-2",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Hadolint adapter — CLI-mode Dockerfile linter",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@codacy/tooling": "0.1.0"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/codacy/analysis-cli.git",
|
|
18
|
+
"directory": "packages/tools/hadolint-2"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/codacy/analysis-cli",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20.0.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format cjs --dts --clean",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"lint": "eslint src/",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"prefetch": "node ../scripts/fetch-patterns.mjs d3ec07a8-71b3-4b89-a25e-9c32ef827668 src/patterns.json"
|
|
30
|
+
}
|
|
31
|
+
}
|