@nitra/cursor 1.8.142 → 1.8.143
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/mdc/docker.mdc +3 -3
- package/package.json +1 -1
- package/scripts/check-docker.mjs +42 -46
package/mdc/docker.mdc
CHANGED
|
@@ -9,7 +9,7 @@ alwaysApply: false
|
|
|
9
9
|
|
|
10
10
|
[hadolint](https://github.com/hadolint/hadolint) перевіряє Dockerfile на типові помилки та рекомендації (`FROM`, `RUN`, `COPY`, shell form тощо).
|
|
11
11
|
|
|
12
|
-
Для образів з Docker Hub — **`oven/bun`**, **`alpine`**, **`nginx`**, **`node`** — у **`FROM`** треба вказувати дзеркало GCR, а не pull напряму з Hub: **`mirror.gcr.io/oven/bun`**, **`mirror.gcr.io/library/alpine`**, **`mirror.gcr.io/
|
|
12
|
+
Для образів з Docker Hub — **`oven/bun`**, **`alpine`**, **`nginxinc/nginx-unprivileged`**, **`node`** — у **`FROM`** треба вказувати дзеркало GCR, а не pull напряму з Hub: **`mirror.gcr.io/oven/bun`**, **`mirror.gcr.io/library/alpine`**, **`mirror.gcr.io/nginxinc/nginx-unprivileged`**, **`mirror.gcr.io/library/node`**. Перевіряє **`check-docker.mjs`**, деталі в **`npm/scripts/utils/docker-mirror.mjs`**.
|
|
13
13
|
|
|
14
14
|
Також Dockerfile/Containerfile **має бути multistage build**: окремий build stage (залежності/компіляція) і окремий runtime stage. У фінальному stage дозволені лише мінімальні базові образи:
|
|
15
15
|
|
|
@@ -17,13 +17,13 @@ alwaysApply: false
|
|
|
17
17
|
- **ультра-легкі (glibc / одна статична збірка)**: `scratch` — тільки як `FROM scratch` (офіційний порожній базовий шар), коли весь **runtime** уже в `COPY --from=…`
|
|
18
18
|
- **glibc, Debian (slim)**: `mirror.gcr.io/library/debian:*` **лише** з тегом, у якому є `slim` (наприклад `bookworm-slim`, `trixie-slim`), а не `bookworm` без `slim`
|
|
19
19
|
- **виняток (інтерпретовані стеки)**: `mirror.gcr.io/library/php:*` або `mirror.gcr.io/library/python:*` — якщо сервіс має крутитися в офіційному runtime PHP чи Python, а не як один бінарник на Alpine; інакше лишай **alpine** у фінальному stage
|
|
20
|
-
- **frontend**: `mirror.gcr.io/
|
|
20
|
+
- **frontend**: `mirror.gcr.io/nginxinc/nginx-unprivileged:*` або `mirror.gcr.io/openresty/openresty:*`
|
|
21
21
|
|
|
22
22
|
Це стримує зайвий build tooling (Bun, **node_modules** зі збірки) у фінальному образі; для **alpine** / **nginx** / **openresty** у **runtime** лишаються лише відповідні вимоги, для **php** / **python** (виняток) — цільовий інтерпретований **stack**; **scratch** і **debian** з тегом **`*slim*`** — коли glibc і мінімальне оточення Debian важливіші за musl в **alpine**.
|
|
23
23
|
|
|
24
24
|
## Мінімальні образи
|
|
25
25
|
|
|
26
|
-
Якщо використовується nginx, то
|
|
26
|
+
Якщо використовується nginx, то **використовуй** `mirror.gcr.io/nginxinc/nginx-unprivileged:alpine-slim` (і не `latest` / `alpine`).
|
|
27
27
|
|
|
28
28
|
## компіляція
|
|
29
29
|
|
package/package.json
CHANGED
package/scripts/check-docker.mjs
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* - backend: `mirror.gcr.io/library/alpine:*`, `scratch`, `mirror.gcr.io/library/debian:` з тегом, що
|
|
10
10
|
* містить `slim` (не повний `debian:bookworm`), за винятком PHP/Python — `mirror.gcr.io/library/php:*` або
|
|
11
11
|
* `mirror.gcr.io/library/python:*`
|
|
12
|
-
* - frontend: `mirror.gcr.io/nginxinc/nginx-unprivileged
|
|
13
|
-
* `mirror.gcr.io/openresty/openresty:*`
|
|
12
|
+
* - frontend: `mirror.gcr.io/nginxinc/nginx-unprivileged:*`, `mirror.gcr.io/openresty/openresty:*`
|
|
14
13
|
*
|
|
15
14
|
* Якщо в Dockerfile є крок `bun install` і це не frontend-образ (фінальний stage — alpine),
|
|
16
15
|
* то очікується компіляція в один бінарник через `bun build --compile` у build stage, а у
|
|
@@ -19,8 +18,8 @@
|
|
|
19
18
|
* Мета — щоб у фінальному образі не було build tooling (Bun/Node та залежностей), а лише
|
|
20
19
|
* дозволений runtime (alpine, scratch, debian slim, за потреби php/python, nginx або openresty).
|
|
21
20
|
*
|
|
22
|
-
* Для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged
|
|
23
|
-
*
|
|
21
|
+
* Для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged`) у будь-якому `FROM` очікується
|
|
22
|
+
* тег `alpine-slim` (docker.mdc: мінімальні образи), не `latest` /
|
|
24
23
|
* `alpine` / інші. `nginx-unprivileged` запускається від не-root користувача (uid=101) без явного
|
|
25
24
|
* `USER` у Dockerfile — перевірка non-root для нього пропускається.
|
|
26
25
|
*
|
|
@@ -42,7 +41,6 @@ const BUN_BUILD_COMPILE_RE = /\bbun\s+build\b[^\n]*\s--compile\b/iu
|
|
|
42
41
|
const BUN_WORD_RE = /\bbun\b/iu
|
|
43
42
|
const USER_LINE_RE = /^\s*USER\s+([^\s#]+)/iu
|
|
44
43
|
|
|
45
|
-
const NGINX_MIRROR_PREFIX = 'mirror.gcr.io/library/nginx'
|
|
46
44
|
const NGINX_UNPRIVILEGED_MIRROR_PREFIX = 'mirror.gcr.io/nginxinc/nginx-unprivileged'
|
|
47
45
|
|
|
48
46
|
/**
|
|
@@ -97,7 +95,6 @@ const RUNTIME_IMAGES = /** @type {const} */ ([
|
|
|
97
95
|
'mirror.gcr.io/library/alpine',
|
|
98
96
|
'mirror.gcr.io/library/php',
|
|
99
97
|
'mirror.gcr.io/library/python',
|
|
100
|
-
'mirror.gcr.io/library/nginx',
|
|
101
98
|
'mirror.gcr.io/nginxinc/nginx-unprivileged',
|
|
102
99
|
'mirror.gcr.io/openresty/openresty'
|
|
103
100
|
])
|
|
@@ -179,7 +176,6 @@ export function getMultistageAndRuntimeHint(fileContent) {
|
|
|
179
176
|
* Очікування:
|
|
180
177
|
* - у build stage є `bun build --compile`;
|
|
181
178
|
* - у фінальному stage немає викликів `bun` (залишків build tooling).
|
|
182
|
-
*
|
|
183
179
|
* @param {string} fileContent вміст Dockerfile/Containerfile
|
|
184
180
|
* @returns {string | null} повідомлення помилки або null
|
|
185
181
|
*/
|
|
@@ -194,7 +190,6 @@ export function getBunCompileHint(fileContent) {
|
|
|
194
190
|
const hasBunInstall = BUN_INSTALL_RE.test(fileContent)
|
|
195
191
|
const isFinalAlpine = lastLower.startsWith('mirror.gcr.io/library/alpine:')
|
|
196
192
|
const isFinalFrontend =
|
|
197
|
-
lastLower.startsWith('mirror.gcr.io/library/nginx:') ||
|
|
198
193
|
lastLower.startsWith(`${NGINX_UNPRIVILEGED_MIRROR_PREFIX}:`) ||
|
|
199
194
|
lastLower.startsWith('mirror.gcr.io/openresty/openresty:')
|
|
200
195
|
|
|
@@ -216,14 +211,13 @@ export function getBunCompileHint(fileContent) {
|
|
|
216
211
|
}
|
|
217
212
|
|
|
218
213
|
/**
|
|
219
|
-
* Перевіряє, що для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged`
|
|
220
|
-
*
|
|
221
|
-
*
|
|
214
|
+
* Перевіряє, що для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged`) у `FROM` вказано
|
|
215
|
+
* тег `alpine-slim` (docker.mdc).
|
|
222
216
|
* @param {string} fileContent вміст Dockerfile/Containerfile
|
|
223
217
|
* @returns {string | null} повідомлення помилки або null
|
|
224
218
|
*/
|
|
225
219
|
export function getNginxAlpineSlimTagHint(fileContent) {
|
|
226
|
-
const prefixes = [NGINX_UNPRIVILEGED_MIRROR_PREFIX
|
|
220
|
+
const prefixes = [NGINX_UNPRIVILEGED_MIRROR_PREFIX]
|
|
227
221
|
for (const { line, image } of parseFromStages(fileContent)) {
|
|
228
222
|
const noDigest = (image.split('@')[0] || '').trim()
|
|
229
223
|
const d = noDigest.toLowerCase()
|
|
@@ -247,7 +241,6 @@ export function getNginxAlpineSlimTagHint(fileContent) {
|
|
|
247
241
|
* Очікування:
|
|
248
242
|
* - у фінальному stage має бути інструкція `USER <name|uid>`;
|
|
249
243
|
* - користувач не має бути `root` і не має бути `0`.
|
|
250
|
-
*
|
|
251
244
|
* @param {string} fileContent вміст Dockerfile/Containerfile
|
|
252
245
|
* @returns {string | null} повідомлення помилки або null
|
|
253
246
|
*/
|
|
@@ -289,7 +282,7 @@ export function getNonRootRuntimeHint(fileContent) {
|
|
|
289
282
|
*/
|
|
290
283
|
export async function check() {
|
|
291
284
|
const reporter = createCheckReporter()
|
|
292
|
-
const { pass
|
|
285
|
+
const { pass } = reporter
|
|
293
286
|
|
|
294
287
|
const root = process.cwd()
|
|
295
288
|
const files = await findDockerfilePaths(root)
|
|
@@ -302,42 +295,45 @@ export async function check() {
|
|
|
302
295
|
pass(`Знайдено файлів для hadolint: ${files.length}`)
|
|
303
296
|
|
|
304
297
|
for (const abs of files) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const hint = getMirrorGcrHint(content)
|
|
308
|
-
if (hint) {
|
|
309
|
-
fail(`${rel} (mirror.gcr.io): ${hint}`)
|
|
310
|
-
}
|
|
298
|
+
await checkDockerfile(reporter, root, abs)
|
|
299
|
+
}
|
|
311
300
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
fail(`${rel} (multistage): ${multistageHint}`)
|
|
315
|
-
}
|
|
301
|
+
return reporter.getExitCode()
|
|
302
|
+
}
|
|
316
303
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Перевіряє один Dockerfile/Containerfile: mirror.gcr.io, multistage/runtime, compile/non-root/nginx tag і hadolint.
|
|
306
|
+
* @param {ReturnType<typeof createCheckReporter>} reporter репортер перевірок
|
|
307
|
+
* @param {string} root корінь репозиторію
|
|
308
|
+
* @param {string} abs абсолютний шлях до Dockerfile/Containerfile
|
|
309
|
+
* @returns {Promise<void>}
|
|
310
|
+
*/
|
|
311
|
+
async function checkDockerfile(reporter, root, abs) {
|
|
312
|
+
const { pass, fail } = reporter
|
|
313
|
+
const rel = posixRel(root, abs) || basename(abs)
|
|
314
|
+
const content = await readFile(abs, 'utf8')
|
|
321
315
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
fail(`${rel} (non-root): ${nonRootHint}`)
|
|
325
|
-
}
|
|
316
|
+
const hint = getMirrorGcrHint(content)
|
|
317
|
+
if (hint) fail(`${rel} (mirror.gcr.io): ${hint}`)
|
|
326
318
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
fail(`${rel} (nginx tag): ${nginxSlimHint}`)
|
|
330
|
-
}
|
|
319
|
+
const multistageHint = getMultistageAndRuntimeHint(content)
|
|
320
|
+
if (multistageHint) fail(`${rel} (multistage): ${multistageHint}`)
|
|
331
321
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (ok) {
|
|
335
|
-
pass(`${rel} (${via})`)
|
|
336
|
-
} else {
|
|
337
|
-
const detail = tail ? `:\n${tail}` : ''
|
|
338
|
-
fail(`${rel} (${via})${detail}`)
|
|
339
|
-
}
|
|
340
|
-
}
|
|
322
|
+
const compileHint = getBunCompileHint(content)
|
|
323
|
+
if (compileHint) fail(`${rel} (compile): ${compileHint}`)
|
|
341
324
|
|
|
342
|
-
|
|
325
|
+
const nonRootHint = getNonRootRuntimeHint(content)
|
|
326
|
+
if (nonRootHint) fail(`${rel} (non-root): ${nonRootHint}`)
|
|
327
|
+
|
|
328
|
+
const nginxSlimHint = getNginxAlpineSlimTagHint(content)
|
|
329
|
+
if (nginxSlimHint) fail(`${rel} (nginx tag): ${nginxSlimHint}`)
|
|
330
|
+
|
|
331
|
+
const { ok, stdout, stderr, via } = lintDockerfileWithHadolint(root, abs)
|
|
332
|
+
const tail = (stdout + stderr).trim()
|
|
333
|
+
if (ok) {
|
|
334
|
+
pass(`${rel} (${via})`)
|
|
335
|
+
} else {
|
|
336
|
+
const detail = tail ? `:\n${tail}` : ''
|
|
337
|
+
fail(`${rel} (${via})${detail}`)
|
|
338
|
+
}
|
|
343
339
|
}
|