fix: add dev base image provisioning path
This commit is contained in:
@@ -78,8 +78,11 @@ npm run --silent dev-base-image:preflight
|
||||
The preflight is local-only and checks for an approved builder base image through
|
||||
`HWLAB_DEV_BASE_IMAGE`, a local `node:20-*` Docker image, or an explicit local
|
||||
HWLAB dev base tag such as `hwlab-dev-base:<tag>`. It prints the image source,
|
||||
local tag, image ID, publish usability, blockers, and next steps. A blocked
|
||||
result means artifact publish must stop and must not be reported as a successful
|
||||
local tag, image ID, publish usability, blockers, next steps, and
|
||||
machine-readable preload/provision commands. The recommended source is
|
||||
`node:20-bookworm-slim`; the recommended HWLAB-local tag is
|
||||
`127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim`. A blocked result
|
||||
means artifact publish must stop and must not be reported as a successful
|
||||
publish.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -10,8 +10,11 @@ secrets, enable PROD, or push to GHCR/Docker Hub/other third-party registries.
|
||||
- Namespace: `hwlab-dev`.
|
||||
- Default registry prefix: `127.0.0.1:5000/hwlab`.
|
||||
- Base image: resolved by `scripts/preflight-dev-base-image.mjs` from
|
||||
`HWLAB_DEV_BASE_IMAGE` or a local `node:20-*` Docker image. There is no
|
||||
network pull or third-party fallback; the build runs with `--pull=false`.
|
||||
`HWLAB_DEV_BASE_IMAGE`, a local `node:20-*` Docker image, or a local HWLAB
|
||||
base tag such as
|
||||
`127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim`. There is no
|
||||
network pull or third-party fallback during publish; the build runs with
|
||||
`--pull=false`.
|
||||
- Image tag: first seven characters of the Git commit used as build source.
|
||||
- Required image labels:
|
||||
- `hwlab.pikastech.local/repo`
|
||||
@@ -81,8 +84,11 @@ publish when build, push, base image, registry, contract, or safety checks fail.
|
||||
|
||||
The report also includes `artifactPublish.baseImagePreflight` with the image
|
||||
source, local tag, local image ID, `publishUsable` gate, blockers, and next
|
||||
steps. When that preflight is blocked, artifact publish is blocked and no build
|
||||
or push is attempted.
|
||||
steps. It also carries `recommendation`, `provision`, and `blockedReport`
|
||||
objects so the blocked report includes the exact blocker, expected image tag,
|
||||
recommended `HWLAB_DEV_BASE_IMAGE` value, and preload commands. When that
|
||||
preflight is blocked, artifact publish is blocked and no build or push is
|
||||
attempted.
|
||||
|
||||
## Known Implementation States
|
||||
|
||||
|
||||
@@ -46,7 +46,9 @@ Selection order is intentionally narrow:
|
||||
exists in the local Docker image cache.
|
||||
2. If `HWLAB_DEV_BASE_IMAGE` is not set, the preflight looks for local
|
||||
`node:20-*` images and selects the first deterministic local tag.
|
||||
3. If neither source is available, the result is a blocker.
|
||||
3. If no `node:20-*` tag exists, the preflight also accepts a local HWLAB base
|
||||
tag such as `127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim`.
|
||||
4. If neither source is available, the result is a blocker.
|
||||
|
||||
Explicit base image values must match local `node:20-*`, `hwlab-dev-base:*`, or
|
||||
`hwlab-node20-base:*` references. They must not reference UniDesk, Code Queue,
|
||||
@@ -55,6 +57,13 @@ service image. A service image such as
|
||||
`ghcr.io/pikastech/hwlab-cloud-api:<sha>` is an artifact output, not a builder
|
||||
base image.
|
||||
|
||||
The recommended D601 preload source is `node:20-bookworm-slim`. The recommended
|
||||
HWLAB-specific local tag is:
|
||||
|
||||
```sh
|
||||
127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim
|
||||
```
|
||||
|
||||
## Output Contract
|
||||
|
||||
Important fields:
|
||||
@@ -66,9 +75,14 @@ Important fields:
|
||||
| `requestedImage` | The explicit environment value, when present. |
|
||||
| `localTag` | Local Docker tag selected for the builder base image. |
|
||||
| `imageId` | Local Docker image ID from `docker image inspect`. |
|
||||
| `candidates` | Approved local builder base images found in Docker. |
|
||||
| `rejectedCandidates` | Near misses such as local non-Node-20 Node images. |
|
||||
| `publishUsable` | Boolean gate for #35 artifact publish. |
|
||||
| `blockers` | Deterministic reasons artifact publish must stop. |
|
||||
| `nextSteps` | Operational fixes that do not require secret reads or runtime substitution. |
|
||||
| `recommendation` | Machine-readable recommended source, env var, and base image tags. |
|
||||
| `provision` | Copy-pasteable online, HWLAB-tag, offline import, and dry-run commands. |
|
||||
| `blockedReport` | Minimal machine-readable blocked report: exact blocker, expected tag, env value, and next preload command. |
|
||||
|
||||
## Blocker Handling
|
||||
|
||||
@@ -77,6 +91,30 @@ Docker cache, set `HWLAB_DEV_BASE_IMAGE` to the exact local tag if it is not a
|
||||
`node:20-*` tag, and rerun the preflight. Do not claim artifact publish success
|
||||
until the preflight returns `status: "ready"`.
|
||||
|
||||
Online D601 preload:
|
||||
|
||||
```sh
|
||||
docker pull node:20-bookworm-slim
|
||||
HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs
|
||||
```
|
||||
|
||||
Optional HWLAB-local tag provision:
|
||||
|
||||
```sh
|
||||
docker pull node:20-bookworm-slim
|
||||
docker tag node:20-bookworm-slim 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim
|
||||
HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim node scripts/preflight-dev-base-image.mjs
|
||||
```
|
||||
|
||||
Offline preload path when D601 cannot pull directly:
|
||||
|
||||
```sh
|
||||
docker pull node:20-bookworm-slim
|
||||
docker save node:20-bookworm-slim -o hwlab-node20-bookworm-slim.tar
|
||||
docker load -i hwlab-node20-bookworm-slim.tar
|
||||
HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs
|
||||
```
|
||||
|
||||
The preflight may use UniDesk and Code Queue as scheduling infrastructure only.
|
||||
It must not use their runtime images as HWLAB service images or builder base
|
||||
image substitutes.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"reportVersion": "v1",
|
||||
"issue": "pikasTech/HWLAB#31",
|
||||
"taskId": "dev-artifact-publish",
|
||||
"commitId": "ad903a7",
|
||||
"commitId": "70bb916",
|
||||
"acceptanceLevel": "dev_artifact_publish",
|
||||
"devOnly": true,
|
||||
"prodDisabled": true,
|
||||
@@ -64,7 +64,7 @@
|
||||
"type": "environment_blocker",
|
||||
"scope": "base-image",
|
||||
"status": "open",
|
||||
"summary": "HWLAB_DEV_BASE_IMAGE is not set and no local node:20-* image was found in the Docker image cache."
|
||||
"summary": "HWLAB_DEV_BASE_IMAGE is not set and no approved local DEV builder base image was found in the Docker image cache; expected node:20-bookworm-slim or 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim."
|
||||
},
|
||||
{
|
||||
"type": "runtime_blocker",
|
||||
@@ -90,7 +90,7 @@
|
||||
"status": "blocked",
|
||||
"mode": "preflight",
|
||||
"repo": "pikasTech/HWLAB",
|
||||
"sourceCommitId": "ad903a76e93bc8fab5c95138df15bfcb852bc339",
|
||||
"sourceCommitId": "70bb9168ead272ec1f77b752df8e178e70858df4",
|
||||
"registryPrefix": "127.0.0.1:5000/hwlab",
|
||||
"baseImage": null,
|
||||
"baseImagePreflight": {
|
||||
@@ -100,15 +100,91 @@
|
||||
"localTag": null,
|
||||
"imageId": null,
|
||||
"repoDigests": [],
|
||||
"candidates": [],
|
||||
"rejectedCandidates": [
|
||||
{
|
||||
"source": "local-docker-node",
|
||||
"localTag": "node:22-bookworm-slim",
|
||||
"imageId": "341b84210b33",
|
||||
"reason": "local Node image is not Node 20 and is not approved as the HWLAB DEV builder base image"
|
||||
}
|
||||
],
|
||||
"publishUsable": false,
|
||||
"blockers": [
|
||||
"HWLAB_DEV_BASE_IMAGE is not set and no local node:20-* image was found in the Docker image cache."
|
||||
"HWLAB_DEV_BASE_IMAGE is not set and no approved local DEV builder base image was found in the Docker image cache; expected node:20-bookworm-slim or 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim."
|
||||
],
|
||||
"nextSteps": [
|
||||
"Set HWLAB_DEV_BASE_IMAGE to an approved local DEV builder base image, or preload a local node:20-* image such as node:20-bookworm-slim.",
|
||||
"Preload node:20-bookworm-slim into the D601 Docker cache, or tag it as 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim.",
|
||||
"Set HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim or HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim when invoking artifact publish.",
|
||||
"Rerun this preflight and require status=ready before #35 artifact publish.",
|
||||
"Do not use UniDesk runtime, Code Queue runner, backend-core, provider-gateway, or microservice-proxy images as substitutes."
|
||||
],
|
||||
"recommendation": {
|
||||
"source": "Preloaded D601 local Docker cache or D601-local registry tag.",
|
||||
"envVar": "HWLAB_DEV_BASE_IMAGE",
|
||||
"preferredImageTag": "node:20-bookworm-slim",
|
||||
"preferredHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim",
|
||||
"recommendedEnvValue": "node:20-bookworm-slim"
|
||||
},
|
||||
"provision": {
|
||||
"required": true,
|
||||
"exactBlocker": "HWLAB_DEV_BASE_IMAGE is not set and no approved local DEV builder base image was found in the Docker image cache; expected node:20-bookworm-slim or 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim.",
|
||||
"expectedImageTag": "node:20-bookworm-slim",
|
||||
"recommendedEnv": {
|
||||
"name": "HWLAB_DEV_BASE_IMAGE",
|
||||
"value": "node:20-bookworm-slim",
|
||||
"assignment": "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim"
|
||||
},
|
||||
"recommendedHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim",
|
||||
"nextHumanCommand": "docker pull node:20-bookworm-slim",
|
||||
"commands": {
|
||||
"d601OnlinePreload": [
|
||||
"docker pull node:20-bookworm-slim",
|
||||
"HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs"
|
||||
],
|
||||
"d601HwlabTagProvision": [
|
||||
"docker pull node:20-bookworm-slim",
|
||||
"docker tag node:20-bookworm-slim 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim",
|
||||
"HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim node scripts/preflight-dev-base-image.mjs"
|
||||
],
|
||||
"offlineSourceExport": [
|
||||
"docker pull node:20-bookworm-slim",
|
||||
"docker save node:20-bookworm-slim -o hwlab-node20-bookworm-slim.tar"
|
||||
],
|
||||
"offlineD601Import": [
|
||||
"docker load -i hwlab-node20-bookworm-slim.tar",
|
||||
"HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs"
|
||||
],
|
||||
"artifactPublishDryRun": [
|
||||
"HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/dev-artifact-publish.mjs --preflight --no-report"
|
||||
]
|
||||
}
|
||||
},
|
||||
"blockedReport": {
|
||||
"exactBlocker": "HWLAB_DEV_BASE_IMAGE is not set and no approved local DEV builder base image was found in the Docker image cache; expected node:20-bookworm-slim or 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim.",
|
||||
"expectedImageTag": "node:20-bookworm-slim",
|
||||
"recommendedEnv": {
|
||||
"name": "HWLAB_DEV_BASE_IMAGE",
|
||||
"value": "node:20-bookworm-slim",
|
||||
"assignment": "HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim"
|
||||
},
|
||||
"recommendedHwlabImageTag": "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim",
|
||||
"nextHumanCommand": "docker pull node:20-bookworm-slim",
|
||||
"preloadCommands": [
|
||||
"docker pull node:20-bookworm-slim",
|
||||
"HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs"
|
||||
],
|
||||
"offlinePreloadCommands": {
|
||||
"sourceExport": [
|
||||
"docker pull node:20-bookworm-slim",
|
||||
"docker save node:20-bookworm-slim -o hwlab-node20-bookworm-slim.tar"
|
||||
],
|
||||
"d601Import": [
|
||||
"docker load -i hwlab-node20-bookworm-slim.tar",
|
||||
"HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim node scripts/preflight-dev-base-image.mjs"
|
||||
]
|
||||
}
|
||||
},
|
||||
"containerEngine": {
|
||||
"command": "docker",
|
||||
"available": true,
|
||||
@@ -121,13 +197,13 @@
|
||||
"serviceCount": 13,
|
||||
"builtCount": 0,
|
||||
"publishedCount": 0,
|
||||
"generatedAt": "2026-05-21T16:53:05.962Z",
|
||||
"generatedAt": "2026-05-21T17:31:56.761Z",
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
@@ -137,8 +213,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "cloud-web",
|
||||
@@ -148,8 +224,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
@@ -159,8 +235,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
@@ -170,8 +246,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
@@ -181,8 +257,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
@@ -192,8 +268,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
@@ -203,8 +279,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "node-command",
|
||||
@@ -214,8 +290,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "health-placeholder",
|
||||
@@ -225,8 +301,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "health-placeholder",
|
||||
@@ -236,8 +312,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "health-placeholder",
|
||||
@@ -247,8 +323,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "cli",
|
||||
@@ -258,8 +334,8 @@
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"status": "preflight_only",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:ad903a7",
|
||||
"imageTag": "ad903a7",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:70bb916",
|
||||
"imageTag": "70bb916",
|
||||
"digest": "not_published",
|
||||
"repositoryDigest": null,
|
||||
"runtimeKind": "skills-bundle",
|
||||
@@ -272,8 +348,8 @@
|
||||
"type": "environment_blocker",
|
||||
"scope": "base-image",
|
||||
"status": "open",
|
||||
"summary": "HWLAB_DEV_BASE_IMAGE is not set and no local node:20-* image was found in the Docker image cache.",
|
||||
"next": "Set HWLAB_DEV_BASE_IMAGE to an approved local DEV builder base image, or preload a local node:20-* image such as node:20-bookworm-slim. Rerun this preflight and require status=ready before #35 artifact publish. Do not use UniDesk runtime, Code Queue runner, backend-core, provider-gateway, or microservice-proxy images as substitutes."
|
||||
"summary": "HWLAB_DEV_BASE_IMAGE is not set and no approved local DEV builder base image was found in the Docker image cache; expected node:20-bookworm-slim or 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim.",
|
||||
"next": "Preload node:20-bookworm-slim into the D601 Docker cache, or tag it as 127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim. Set HWLAB_DEV_BASE_IMAGE=node:20-bookworm-slim or HWLAB_DEV_BASE_IMAGE=127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim when invoking artifact publish. Rerun this preflight and require status=ready before #35 artifact publish. Do not use UniDesk runtime, Code Queue runner, backend-core, provider-gateway, or microservice-proxy images as substitutes."
|
||||
},
|
||||
{
|
||||
"type": "runtime_blocker",
|
||||
|
||||
@@ -146,9 +146,14 @@ function summarizeBaseImagePreflight(result) {
|
||||
localTag: result.localTag,
|
||||
imageId: result.imageId,
|
||||
repoDigests: result.repoDigests,
|
||||
candidates: result.candidates,
|
||||
rejectedCandidates: result.rejectedCandidates,
|
||||
publishUsable: result.publishUsable,
|
||||
blockers: result.blockers,
|
||||
nextSteps: result.nextSteps,
|
||||
recommendation: result.recommendation,
|
||||
provision: result.provision,
|
||||
blockedReport: result.blockedReport,
|
||||
containerEngine: result.containerEngine
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ const envVarName = "HWLAB_DEV_BASE_IMAGE";
|
||||
const dockerCommand = "docker";
|
||||
const dockerTimeoutMs = 15_000;
|
||||
const node20TagPattern = /^20-/u;
|
||||
const recommendedNode20Image = "node:20-bookworm-slim";
|
||||
const recommendedHwlabBaseImage = "127.0.0.1:5000/hwlab/hwlab-dev-base:node20-bookworm-slim";
|
||||
const recommendedPreloadTar = "hwlab-node20-bookworm-slim.tar";
|
||||
const forbiddenImageFragments = Object.freeze([
|
||||
"unidesk",
|
||||
"backend-core",
|
||||
@@ -21,6 +24,69 @@ const allowedExplicitBaseImages = Object.freeze([
|
||||
"*/hwlab-node20-base:*"
|
||||
]);
|
||||
|
||||
function baseImageRecommendation(envValue = recommendedNode20Image) {
|
||||
return {
|
||||
source: "Preloaded D601 local Docker cache or D601-local registry tag.",
|
||||
envVar: envVarName,
|
||||
preferredImageTag: recommendedNode20Image,
|
||||
preferredHwlabImageTag: recommendedHwlabBaseImage,
|
||||
recommendedEnvValue: envValue
|
||||
};
|
||||
}
|
||||
|
||||
function provisionPlan({ exactBlocker = null, envValue = recommendedNode20Image, required = true } = {}) {
|
||||
return {
|
||||
required,
|
||||
exactBlocker,
|
||||
expectedImageTag: recommendedNode20Image,
|
||||
recommendedEnv: {
|
||||
name: envVarName,
|
||||
value: envValue,
|
||||
assignment: `${envVarName}=${envValue}`
|
||||
},
|
||||
recommendedHwlabImageTag: recommendedHwlabBaseImage,
|
||||
nextHumanCommand: required ? `docker pull ${recommendedNode20Image}` : null,
|
||||
commands: {
|
||||
d601OnlinePreload: [
|
||||
`docker pull ${recommendedNode20Image}`,
|
||||
`${envVarName}=${recommendedNode20Image} node scripts/preflight-dev-base-image.mjs`
|
||||
],
|
||||
d601HwlabTagProvision: [
|
||||
`docker pull ${recommendedNode20Image}`,
|
||||
`docker tag ${recommendedNode20Image} ${recommendedHwlabBaseImage}`,
|
||||
`${envVarName}=${recommendedHwlabBaseImage} node scripts/preflight-dev-base-image.mjs`
|
||||
],
|
||||
offlineSourceExport: [
|
||||
`docker pull ${recommendedNode20Image}`,
|
||||
`docker save ${recommendedNode20Image} -o ${recommendedPreloadTar}`
|
||||
],
|
||||
offlineD601Import: [
|
||||
`docker load -i ${recommendedPreloadTar}`,
|
||||
`${envVarName}=${recommendedNode20Image} node scripts/preflight-dev-base-image.mjs`
|
||||
],
|
||||
artifactPublishDryRun: [
|
||||
`${envVarName}=${envValue} node scripts/dev-artifact-publish.mjs --preflight --no-report`
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function blockedReportFor(exactBlocker) {
|
||||
const plan = provisionPlan({ exactBlocker });
|
||||
return {
|
||||
exactBlocker,
|
||||
expectedImageTag: plan.expectedImageTag,
|
||||
recommendedEnv: plan.recommendedEnv,
|
||||
recommendedHwlabImageTag: plan.recommendedHwlabImageTag,
|
||||
nextHumanCommand: plan.nextHumanCommand,
|
||||
preloadCommands: plan.commands.d601OnlinePreload,
|
||||
offlinePreloadCommands: {
|
||||
sourceExport: plan.commands.offlineSourceExport,
|
||||
d601Import: plan.commands.offlineD601Import
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function runDocker(args) {
|
||||
const result = spawnSync(dockerCommand, args, {
|
||||
encoding: "utf8",
|
||||
@@ -68,6 +134,10 @@ function isHwlabDevBaseRef(imageRef) {
|
||||
return /(^|\/)(hwlab-dev-base|hwlab-node20-base)(?=[:@]|$)/u.test(lowerRef);
|
||||
}
|
||||
|
||||
function isTaggedImage(image) {
|
||||
return image.Repository && image.Tag && image.Repository !== "<none>" && image.Tag !== "<none>";
|
||||
}
|
||||
|
||||
function approvalForBaseRef(imageRef) {
|
||||
if (isNode20BaseRef(imageRef)) {
|
||||
return {
|
||||
@@ -149,8 +219,12 @@ function baseResult(overrides) {
|
||||
imageId: null,
|
||||
repoDigests: [],
|
||||
candidates: [],
|
||||
rejectedCandidates: [],
|
||||
blockers: [],
|
||||
nextSteps: [],
|
||||
recommendation: baseImageRecommendation(),
|
||||
provision: provisionPlan(),
|
||||
blockedReport: null,
|
||||
constraints: {
|
||||
localOnly: true,
|
||||
doesNotPull: true,
|
||||
@@ -167,6 +241,10 @@ function baseResult(overrides) {
|
||||
}
|
||||
|
||||
function blocked(overrides) {
|
||||
const exactBlocker = overrides.blockers?.length
|
||||
? overrides.blockers.join("; ")
|
||||
: "DEV builder base image preflight did not return ready.";
|
||||
const envValue = overrides.recommendedEnvValue ?? overrides.requestedImage ?? recommendedNode20Image;
|
||||
return baseResult({
|
||||
status: "blocked",
|
||||
publishUsable: false,
|
||||
@@ -175,6 +253,9 @@ function blocked(overrides) {
|
||||
usable: false,
|
||||
reason: "DEV artifact publish must stop until the blocker is resolved."
|
||||
},
|
||||
recommendation: baseImageRecommendation(envValue),
|
||||
provision: provisionPlan({ exactBlocker, envValue }),
|
||||
blockedReport: blockedReportFor(exactBlocker),
|
||||
...overrides
|
||||
});
|
||||
}
|
||||
@@ -195,6 +276,12 @@ function ready({ imageSource, approval, requestedImage, localTag, inspectDoc, ca
|
||||
imageId: inspectDoc.Id ?? null,
|
||||
repoDigests: Array.isArray(inspectDoc.RepoDigests) ? inspectDoc.RepoDigests : [],
|
||||
candidates,
|
||||
recommendation: baseImageRecommendation(localTag),
|
||||
provision: provisionPlan({
|
||||
envValue: localTag,
|
||||
required: false
|
||||
}),
|
||||
blockedReport: null,
|
||||
blockers: [],
|
||||
nextSteps: [
|
||||
"Use the reported localTag as the DEV builder base image for artifact publish.",
|
||||
@@ -241,21 +328,70 @@ function dockerUnavailableResult(versionResult) {
|
||||
});
|
||||
}
|
||||
|
||||
function selectNode20Candidate(images) {
|
||||
const candidates = images
|
||||
.filter((image) => image.Repository === "node" && node20TagPattern.test(image.Tag))
|
||||
.map((image) => ({
|
||||
source: "local-docker-node:20-*",
|
||||
approval: approvalForBaseRef(candidateTag(image)),
|
||||
localTag: candidateTag(image),
|
||||
function candidateSource(image) {
|
||||
if (image.Repository === "node" && node20TagPattern.test(image.Tag)) {
|
||||
return "local-docker-node:20-*";
|
||||
}
|
||||
if (isHwlabDevBaseRef(candidateTag(image))) {
|
||||
return "local-docker-hwlab-dev-base";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function candidatePriority(candidate) {
|
||||
if (candidate.source === "local-docker-node:20-*") return 0;
|
||||
if (candidate.source === "local-docker-hwlab-dev-base") return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
function rejectedBaseCandidate(image) {
|
||||
if (!isTaggedImage(image)) return null;
|
||||
const localTag = candidateTag(image);
|
||||
if (image.Repository === "node" && !node20TagPattern.test(image.Tag)) {
|
||||
return {
|
||||
source: "local-docker-node",
|
||||
localTag,
|
||||
imageId: image.ID ?? null,
|
||||
size: image.Size ?? null,
|
||||
createdSince: image.CreatedSince ?? null
|
||||
reason: "local Node image is not Node 20 and is not approved as the HWLAB DEV builder base image"
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function selectLocalBaseCandidate(images) {
|
||||
const candidates = images
|
||||
.filter(isTaggedImage)
|
||||
.map((image) => {
|
||||
const source = candidateSource(image);
|
||||
if (!source) return null;
|
||||
const localTag = candidateTag(image);
|
||||
return {
|
||||
source,
|
||||
approval: approvalForBaseRef(localTag),
|
||||
localTag,
|
||||
imageId: image.ID ?? null,
|
||||
size: image.Size ?? null,
|
||||
createdSince: image.CreatedSince ?? null
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
.map((image) => ({
|
||||
...image,
|
||||
priority: candidatePriority(image)
|
||||
}))
|
||||
.sort((left, right) => (
|
||||
left.priority - right.priority || left.localTag.localeCompare(right.localTag)
|
||||
))
|
||||
.map(({ priority, ...candidate }) => candidate);
|
||||
|
||||
const rejectedCandidates = images
|
||||
.map(rejectedBaseCandidate)
|
||||
.filter(Boolean)
|
||||
.sort((left, right) => left.localTag.localeCompare(right.localTag));
|
||||
|
||||
return {
|
||||
candidates,
|
||||
rejectedCandidates,
|
||||
selected: candidates[0] ?? null
|
||||
};
|
||||
}
|
||||
@@ -289,9 +425,10 @@ function readyFromExplicitImage({ requestedImage, inspectDoc, containerEngine })
|
||||
localTag,
|
||||
imageId: inspectDoc.Id ?? null,
|
||||
blockers,
|
||||
recommendedEnvValue: recommendedNode20Image,
|
||||
nextSteps: [
|
||||
`Set ${envVarName} to an approved local builder base image, not a runtime service image.`,
|
||||
"Allowed references are local node:20-* tags or HWLAB dev base tags such as hwlab-dev-base:<tag>.",
|
||||
`Allowed references are local node:20-* tags or HWLAB dev base tags such as ${recommendedHwlabBaseImage}.`,
|
||||
"Do not continue #35 artifact publish with this image."
|
||||
],
|
||||
containerEngine
|
||||
@@ -344,9 +481,10 @@ export async function runDevBaseImagePreflight({ env = process.env } = {}) {
|
||||
...refBlockers,
|
||||
...(requestedApproval.approved ? [] : [requestedApproval.reason])
|
||||
],
|
||||
recommendedEnvValue: recommendedNode20Image,
|
||||
nextSteps: [
|
||||
`Set ${envVarName} to an approved local builder base image, not a runtime service image.`,
|
||||
"Allowed references are local node:20-* tags or HWLAB dev base tags such as hwlab-dev-base:<tag>.",
|
||||
`Allowed references are local node:20-* tags or HWLAB dev base tags such as ${recommendedHwlabBaseImage}.`,
|
||||
"Do not continue #35 artifact publish with this image."
|
||||
],
|
||||
containerEngine
|
||||
@@ -360,7 +498,7 @@ export async function runDevBaseImagePreflight({ env = process.env } = {}) {
|
||||
requestedImage,
|
||||
blockers: [`${envVarName} is set to ${requestedImage}, but that image is not present in the local Docker image cache.`],
|
||||
nextSteps: [
|
||||
"Preload or build the approved DEV builder base image locally before artifact publish.",
|
||||
`Preload ${recommendedNode20Image} or tag it as ${recommendedHwlabBaseImage} before artifact publish.`,
|
||||
`Keep ${envVarName} pointed at the exact local tag once the image is available.`,
|
||||
"Do not pull from third-party hosting or substitute a UniDesk runtime image during this preflight."
|
||||
],
|
||||
@@ -392,13 +530,15 @@ export async function runDevBaseImagePreflight({ env = process.env } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
const { candidates, selected } = selectNode20Candidate(splitJsonLines(imageList.stdout));
|
||||
const { candidates, rejectedCandidates, selected } = selectLocalBaseCandidate(splitJsonLines(imageList.stdout));
|
||||
if (!selected) {
|
||||
return blocked({
|
||||
blockers: [`${envVarName} is not set and no local node:20-* image was found in the Docker image cache.`],
|
||||
blockers: [`${envVarName} is not set and no approved local DEV builder base image was found in the Docker image cache; expected ${recommendedNode20Image} or ${recommendedHwlabBaseImage}.`],
|
||||
candidates,
|
||||
rejectedCandidates,
|
||||
nextSteps: [
|
||||
`Set ${envVarName} to an approved local DEV builder base image, or preload a local node:20-* image such as node:20-bookworm-slim.`,
|
||||
`Preload ${recommendedNode20Image} into the D601 Docker cache, or tag it as ${recommendedHwlabBaseImage}.`,
|
||||
`Set ${envVarName}=${recommendedNode20Image} or ${envVarName}=${recommendedHwlabBaseImage} when invoking artifact publish.`,
|
||||
"Rerun this preflight and require status=ready before #35 artifact publish.",
|
||||
"Do not use UniDesk runtime, Code Queue runner, backend-core, provider-gateway, or microservice-proxy images as substitutes."
|
||||
],
|
||||
@@ -413,6 +553,7 @@ export async function runDevBaseImagePreflight({ env = process.env } = {}) {
|
||||
requestedImage: null,
|
||||
localTag: selected.localTag,
|
||||
candidates,
|
||||
rejectedCandidates,
|
||||
blockers: [`Selected local candidate ${selected.localTag} could not be inspected.`],
|
||||
nextSteps: [
|
||||
`Set ${envVarName} to a known-good approved local base image and rerun this preflight.`,
|
||||
@@ -428,6 +569,7 @@ export async function runDevBaseImagePreflight({ env = process.env } = {}) {
|
||||
requestedImage: null,
|
||||
localTag: selected.localTag,
|
||||
candidates,
|
||||
rejectedCandidates,
|
||||
imageId: inspected.docs[0].Id ?? null,
|
||||
blockers: evidenceBlockers,
|
||||
nextSteps: [
|
||||
@@ -447,6 +589,7 @@ export async function runDevBaseImagePreflight({ env = process.env } = {}) {
|
||||
inspectDoc: inspected.docs[0],
|
||||
candidates
|
||||
}),
|
||||
rejectedCandidates,
|
||||
containerEngine
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user