@nitra/cursor 1.8.138 → 1.8.140
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/abie.mdc +1 -1
- package/mdc/docker.mdc +4 -0
- package/package.json +1 -1
- package/scripts/check-docker.mjs +50 -3
- package/scripts/utils/docker-mirror.mjs +3 -2
package/mdc/abie.mdc
CHANGED
package/mdc/docker.mdc
CHANGED
|
@@ -21,6 +21,10 @@ alwaysApply: false
|
|
|
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
|
+
## Мінімальні образи
|
|
25
|
+
|
|
26
|
+
Якщо використовується nginx, то намагайся щоб тег був `alpine-slim`
|
|
27
|
+
|
|
24
28
|
## компіляція
|
|
25
29
|
|
|
26
30
|
Якщо проект має bun install крок, та не є фронтенд проектом (тобто не має bun build крок), то потрібно щоб була компіляція коду, і далі у фінальному образі був тільки бінарник і Цей образ не містив компілятора, npm, Bun — тільки runtime libs. Наприклад:
|
package/package.json
CHANGED
package/scripts/check-docker.mjs
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
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/
|
|
12
|
+
* - frontend: `mirror.gcr.io/nginxinc/nginx-unprivileged:*` (preferred) або `mirror.gcr.io/library/nginx:*`,
|
|
13
|
+
* `mirror.gcr.io/openresty/openresty:*`
|
|
13
14
|
*
|
|
14
15
|
* Якщо в Dockerfile є крок `bun install` і це не frontend-образ (фінальний stage — alpine),
|
|
15
16
|
* то очікується компіляція в один бінарник через `bun build --compile` у build stage, а у
|
|
@@ -18,6 +19,11 @@
|
|
|
18
19
|
* Мета — щоб у фінальному образі не було build tooling (Bun/Node та залежностей), а лише
|
|
19
20
|
* дозволений runtime (alpine, scratch, debian slim, за потреби php/python, nginx або openresty).
|
|
20
21
|
*
|
|
22
|
+
* Для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged`, `mirror.gcr.io/library/nginx`) у
|
|
23
|
+
* будь-якому `FROM` очікується тег `alpine-slim` (docker.mdc: мінімальні образи), не `latest` /
|
|
24
|
+
* `alpine` / інші. `nginx-unprivileged` запускається від не-root користувача (uid=101) без явного
|
|
25
|
+
* `USER` у Dockerfile — перевірка non-root для нього пропускається.
|
|
26
|
+
*
|
|
21
27
|
* Знаходить Dockerfile, Dockerfile.*, Containerfile, Containerfile.*; пропускає node_modules, .git
|
|
22
28
|
* тощо. Спочатку hadolint з PATH, інакше docker run з образом hadolint/hadolint.
|
|
23
29
|
* Кореневий .hadolint.yaml підхоплюється hadolint автоматично.
|
|
@@ -36,6 +42,9 @@ const BUN_BUILD_COMPILE_RE = /\bbun\s+build\b[^\n]*\s--compile\b/iu
|
|
|
36
42
|
const BUN_WORD_RE = /\bbun\b/iu
|
|
37
43
|
const USER_LINE_RE = /^\s*USER\s+([^\s#]+)/iu
|
|
38
44
|
|
|
45
|
+
const NGINX_MIRROR_PREFIX = 'mirror.gcr.io/library/nginx'
|
|
46
|
+
const NGINX_UNPRIVILEGED_MIRROR_PREFIX = 'mirror.gcr.io/nginxinc/nginx-unprivileged'
|
|
47
|
+
|
|
39
48
|
/**
|
|
40
49
|
* @typedef {{
|
|
41
50
|
* line: number
|
|
@@ -89,6 +98,7 @@ const RUNTIME_IMAGES = /** @type {const} */ ([
|
|
|
89
98
|
'mirror.gcr.io/library/php',
|
|
90
99
|
'mirror.gcr.io/library/python',
|
|
91
100
|
'mirror.gcr.io/library/nginx',
|
|
101
|
+
'mirror.gcr.io/nginxinc/nginx-unprivileged',
|
|
92
102
|
'mirror.gcr.io/openresty/openresty'
|
|
93
103
|
])
|
|
94
104
|
|
|
@@ -98,7 +108,7 @@ const DEBIAN_VIA_MIRROR_RE = /^mirror\.gcr\.io\/library\/debian:(.+)$/i
|
|
|
98
108
|
/**
|
|
99
109
|
* Чи ref фінального `FROM` відповідає дозволеним у docker.mdc (multistage / runtime).
|
|
100
110
|
* @param {string} lastLower ref без digest, lower case
|
|
101
|
-
* @returns {boolean}
|
|
111
|
+
* @returns {boolean} true, якщо образ дозволений як фінальний runtime
|
|
102
112
|
*/
|
|
103
113
|
function isAllowedFinalRuntimeImage(lastLower) {
|
|
104
114
|
if (lastLower === 'scratch' || lastLower.startsWith('scratch:')) {
|
|
@@ -184,7 +194,9 @@ export function getBunCompileHint(fileContent) {
|
|
|
184
194
|
const hasBunInstall = BUN_INSTALL_RE.test(fileContent)
|
|
185
195
|
const isFinalAlpine = lastLower.startsWith('mirror.gcr.io/library/alpine:')
|
|
186
196
|
const isFinalFrontend =
|
|
187
|
-
lastLower.startsWith('mirror.gcr.io/library/nginx:') ||
|
|
197
|
+
lastLower.startsWith('mirror.gcr.io/library/nginx:') ||
|
|
198
|
+
lastLower.startsWith(`${NGINX_UNPRIVILEGED_MIRROR_PREFIX}:`) ||
|
|
199
|
+
lastLower.startsWith('mirror.gcr.io/openresty/openresty:')
|
|
188
200
|
|
|
189
201
|
if (!hasBunInstall) return null
|
|
190
202
|
if (!isFinalAlpine) return null
|
|
@@ -203,6 +215,32 @@ export function getBunCompileHint(fileContent) {
|
|
|
203
215
|
return null
|
|
204
216
|
}
|
|
205
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Перевіряє, що для nginx-образів (`mirror.gcr.io/nginxinc/nginx-unprivileged` або
|
|
220
|
+
* `mirror.gcr.io/library/nginx`) у `FROM` вказано тег `alpine-slim` (docker.mdc).
|
|
221
|
+
*
|
|
222
|
+
* @param {string} fileContent вміст Dockerfile/Containerfile
|
|
223
|
+
* @returns {string | null} повідомлення помилки або null
|
|
224
|
+
*/
|
|
225
|
+
export function getNginxAlpineSlimTagHint(fileContent) {
|
|
226
|
+
const prefixes = [NGINX_UNPRIVILEGED_MIRROR_PREFIX, NGINX_MIRROR_PREFIX]
|
|
227
|
+
for (const { line, image } of parseFromStages(fileContent)) {
|
|
228
|
+
const noDigest = (image.split('@')[0] || '').trim()
|
|
229
|
+
const d = noDigest.toLowerCase()
|
|
230
|
+
const prefix = prefixes.find(p => d.startsWith(`${p}:`) || d === p)
|
|
231
|
+
if (prefix) {
|
|
232
|
+
if (d === prefix) {
|
|
233
|
+
return `рядок ${line}: \`FROM ${prefix}\` має явний тег \`alpine-slim\` (docker.mdc: мінімальні образи), зараз без тега (типово latest)`
|
|
234
|
+
}
|
|
235
|
+
const tag = noDigest.slice(prefix.length + 1)
|
|
236
|
+
if (tag.toLowerCase() !== 'alpine-slim') {
|
|
237
|
+
return `рядок ${line}: для nginx потрібен тег \`alpine-slim\` (docker.mdc: мінімальні образи), зараз: \`${tag}\``
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
|
|
206
244
|
/**
|
|
207
245
|
* Перевіряє вимогу "non-root" у фінальному runtime stage (docker.mdc).
|
|
208
246
|
*
|
|
@@ -218,6 +256,8 @@ export function getNonRootRuntimeHint(fileContent) {
|
|
|
218
256
|
if (stages.length === 0) return null
|
|
219
257
|
|
|
220
258
|
const last = stages.at(-1)
|
|
259
|
+
const lastImage = (last?.from.image || '').split('@')[0] || ''
|
|
260
|
+
const lastLower = lastImage.toLowerCase()
|
|
221
261
|
const lastStageContent = last?.stageContent || ''
|
|
222
262
|
|
|
223
263
|
/** @type {string | null} */
|
|
@@ -230,6 +270,8 @@ export function getNonRootRuntimeHint(fileContent) {
|
|
|
230
270
|
}
|
|
231
271
|
|
|
232
272
|
if (!lastUserToken) {
|
|
273
|
+
// nginx-unprivileged вже запускається від uid=101 (nginx) без явного USER у Dockerfile
|
|
274
|
+
if (lastLower.startsWith(`${NGINX_UNPRIVILEGED_MIRROR_PREFIX}:`)) return null
|
|
233
275
|
return 'у фінальному stage має бути `USER <non-root>` (наприклад `USER app`) — принцип non-root (docker.mdc: не превілейований образ)'
|
|
234
276
|
}
|
|
235
277
|
|
|
@@ -282,6 +324,11 @@ export async function check() {
|
|
|
282
324
|
fail(`${rel} (non-root): ${nonRootHint}`)
|
|
283
325
|
}
|
|
284
326
|
|
|
327
|
+
const nginxSlimHint = getNginxAlpineSlimTagHint(content)
|
|
328
|
+
if (nginxSlimHint) {
|
|
329
|
+
fail(`${rel} (nginx tag): ${nginxSlimHint}`)
|
|
330
|
+
}
|
|
331
|
+
|
|
285
332
|
const { ok, stdout, stderr, via } = lintDockerfileWithHadolint(root, abs)
|
|
286
333
|
const tail = (stdout + stderr).trim()
|
|
287
334
|
if (ok) {
|
|
@@ -110,14 +110,15 @@ export function normalizeHubRepoPath(imageToken) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
const HUB_REPOS_REQUIRING_MIRROR = /** @type {const} */ (
|
|
113
|
-
new Set(['oven/bun', 'library/alpine', 'library/nginx', 'library/node'])
|
|
113
|
+
new Set(['oven/bun', 'library/alpine', 'library/nginx', 'library/node', 'nginxinc/nginx-unprivileged'])
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
const EXPECTED_MIRROR = /** @type {const} */ ({
|
|
117
117
|
'oven/bun': 'mirror.gcr.io/oven/bun',
|
|
118
118
|
'library/alpine': 'mirror.gcr.io/library/alpine',
|
|
119
119
|
'library/nginx': 'mirror.gcr.io/library/nginx',
|
|
120
|
-
'library/node': 'mirror.gcr.io/library/node'
|
|
120
|
+
'library/node': 'mirror.gcr.io/library/node',
|
|
121
|
+
'nginxinc/nginx-unprivileged': 'mirror.gcr.io/nginxinc/nginx-unprivileged'
|
|
121
122
|
})
|
|
122
123
|
|
|
123
124
|
/**
|