diff --git a/tools/tailor/COMPATIBILITY.md b/tools/tailor/COMPATIBILITY.md index dc49ab9fe9..496aab376b 100644 --- a/tools/tailor/COMPATIBILITY.md +++ b/tools/tailor/COMPATIBILITY.md @@ -60,9 +60,6 @@ improving them: - **Human-readable stdout/stderr.** Log lines, progress output, and diagnostic wording are for people, not parsing. A stable machine-readable output mode is future work; until it ships, do not screen-scrape tailor's output. -- **Lockfile format.** `tailor.lock` is generated and consumed by tailor; its - on-disk layout may change. Regenerate it with `tailor lock` / `tailor update` - rather than editing or parsing it. - **Minimum Supported Rust Version (MSRV).** The Rust toolchain used to build tailor may move at any time. - **Deprecation policy.** There is not yet a formal warn-before-remove cadence. diff --git a/tools/tailor/README.md b/tools/tailor/README.md index c18bf555f1..06d24fb475 100644 --- a/tools/tailor/README.md +++ b/tools/tailor/README.md @@ -37,14 +37,14 @@ The nuance lives in a handful of small, reviewable files instead of N copy-paste You describe *what* you want; tailor handles the *how*. For each cell it assembles and runs the complete IC invocation through the Docker API (no shelling out): -- **Base resolution** — a local file, an `oci:` reference, or `azureLinux:` (MCR) shorthand, resolved to a digest and pinned in `tailor.lock`. +- **Base resolution** — a local file, an `oci:` reference, or `azureLinux:` (MCR) shorthand. Registry inputs resolve to digests for each run. - **The privileged run** — `--privileged`, a read-only workspace mount plus narrow writable carve-outs, the correct `--platform linux/`, and **every host path rewritten** into `/host` (an easy-to-get-wrong step you'd otherwise script by hand). - **Output & cleanup** — per-format `--output-image-format`, streamed logs, exit/output verification, and **sudo-free cleanup** of the root-owned files IC leaves behind. -- **Reproducibility** — `tailor build --locked` pins the IC container and base digests; `tailor build --dry-run` prints the exact `docker run …` first. +- **Iteration** — incremental build stamps avoid unchanged cells; `tailor build --dry-run` previews the container invocation offline. Registry tags can change between builds. ### Just an `image.yaml` -You don't need a workspace to start. This standalone definition — no `tailor.yaml`, no toolchain to pin — is a complete, buildable image: +You don't need a workspace to start. This standalone definition — no `tailor.yaml`, no configured toolchain — is a complete, buildable image: ```yaml # image.yaml @@ -82,27 +82,12 @@ Build it (add `--dry-run` to preview without launching the container): tailor build ``` -From that definition tailor resolves the base image, assembles the full privileged Image Customizer invocation — host paths rewritten into the `/host` mount, the platform set, the output format applied — and runs it: - -```text -docker run \ - --rm \ - --privileged \ - --platform linux/amd64 \ - -v /home/you/hello:/host/home/you/hello:ro \ - -v /home/you/hello/.tailor/cache:/host/home/you/hello/.tailor/cache:rw \ - -v /home/you/hello/artifacts:/host/home/you/hello/artifacts:rw \ - -v /dev:/dev \ - mcr.microsoft.com/azurelinux/imagecustomizer:latest \ - customize \ - --config-file /host/home/you/hello/.tailor-render.hello_amd64_cosi.ic.yaml \ - --build-dir /tmp \ - --image oci:mcr.microsoft.com/azurelinux/3.0/image/minimal-os \ - --output-image-format cosi \ - --output-image-file /host/home/you/hello/artifacts/hello_amd64_cosi.cosi -``` - -The result is `artifacts/hello_amd64_cosi.cosi`. You wrote no `outputs:` (so it defaulted to `cosi`) and pinned no toolchain (so it used the latest Image Customizer). Grow into a workspace — a shared toolchain, defaults, and many images — only when you need it. +From that definition tailor resolves the base, chooses `linux/amd64`, rewrites host paths under +`/host`, and runs IC with `--output-image-format cosi`. The result is +`artifacts/hello_amd64_cosi.cosi`. You wrote no `outputs:` (so it defaulted to `cosi`) and +configured no toolchain (so it used the `latest` Image Customizer tag). Run `tailor build --dry-run` to see the +actual invocation for your environment. Grow into a workspace — a shared toolchain, defaults, and +many images — only when you need it. ## Installation @@ -133,8 +118,7 @@ The `advanced` scaffold creates a workspace `tailor.yaml`, a `myimage/image.yaml ## Features - Workspace and standalone image definitions. -- Multiple Image Customizer toolchains, with a `tailor.lock` lockfile (`lock` to freeze, `update` to - refresh) and `--locked` reproducible builds. +- Multiple Image Customizer toolchains with configurable versions, tags, and pull policies. - Matrix expansion over user-defined axes, with `arch` as a typed, reserved axis. - Per-axis fragments in `by-/.yaml` and feature fragments in `by-feature/.yaml`. - Deterministic merge model: maps deep-merge, lists append, scalar conflicts require `$set`. @@ -143,13 +127,14 @@ The `advanced` scaffold creates a workspace `tailor.yaml`, a `myimage/image.yaml `inputs:`. - Managed tools-dir (`--tools-dir`) for sealed images, with a safety guard that keeps builds off the host root. -- Multiple output formats, `convert` for one-off format changes, and optional post-build `zstd` - compression. +- Multiple output formats, workspace-free `convert`, optional post-build `zstd` compression, and + extracted-artifact staging policies. - Preview-gated Secure Boot signing (`previewFeatures: [signing]`). - `export` rendered configs for a pipeline, third-party license notices via `tailor notice`, and a stable exit-code taxonomy (`0`/`1`/`2`/`130`). - Dry runs, validation, rendered config snapshots, selectors, exact cell slugs, and portable static builds. +- Order-aware inter-image builds, per-output-directory build locks, and guarded scratch/mount paths. Run `tailor --version` to see the version of your build; the current version line is `1.x` (see the [compatibility policy](COMPATIBILITY.md)). diff --git a/tools/tailor/docs/explanation/base-images.md b/tools/tailor/docs/explanation/base-images.md index 92e169af86..000d8c16a3 100644 --- a/tools/tailor/docs/explanation/base-images.md +++ b/tools/tailor/docs/explanation/base-images.md @@ -1,20 +1,20 @@ # Base images -A base image is the OS image Image Customizer modifies. tailor offers four ways to declare one, but -they split into two fundamentally different acquisition models — and the choice matters more for -reproducibility than for convenience. +A base image is the OS image Image Customizer modifies. tailor supports five sources. Registry +images resolve to digests for a run; file and workspace-image sources consume local artifacts. ## Registry bases vs. file bases -| Kind | Who fetches | Build input | Reproducible everywhere | +| Kind | Who supplies it | Build input | Portability | | --- | --- | --- | --- | -| `oci`, `azureLinux` | Image Customizer, at build time | a registry digest (`--image`) | needs the `input-image-oci` preview + a cache dir | -| `path` | you | a local file (`--image-file`) | yes, but the path is repeated per image | -| `ref` (catalogue) | `tailor bases download` **or** a CI feed | a local file (`--image-file`) | yes, file-based, CI-parity | - -The `oci`/`azureLinux` kinds are great for a quick dev build straight off a registry. But they pull -*at build time*, behind an IC preview feature, into a writable cache — exactly what a locked-down -pipeline cannot do, and not pinned to one verified artifact. A `path` base avoids that, but every +| `oci`, `azureLinux` | Image Customizer, at build time | resolved registry digest (`--image`) | requires IC's `input-image-oci` preview flag and a cache dir | +| `path` | you | local file (`--image-file`) | requires the same file at the declared path on each machine | +| `ref` (catalogue) | `tailor bases download` **or** a CI feed | local file (`--image-file`) | requires the same file in the named slot | +| `image` | another workspace image | paired producer artifact (`--image-file`) | requires the producer build; see [dependent images](../how-to/build-dependent-images.md) | + +The `oci`/`azureLinux` kinds are useful for dev builds straight from a registry, but may require +registry access during build and can resolve a newer digest on a later run. A `path` base avoids +that, but every image repeats the same `../../../artifacts/.vhdx`, and the `../` count breaks when the layout moves. @@ -22,7 +22,8 @@ moves. For local file bases (`path` and catalogue `ref` slots), tailor computes an **XXH3-128** content hash plus the file size before planning. This hash is only for rebuild/change detection; it is not a -security or provenance digest, and local files are not pinned in `tailor.lock`. +security or provenance digest. Supply identical bytes separately on each builder; a matching +path alone is no provenance guarantee. Large bases are cached on the build path. After the first hash, `tailor build` stores a versioned entry under `/.tailor/base-hashes/` keyed by the base path and guarded by the stored absolute @@ -30,8 +31,8 @@ path, size, and mtime. If the size and mtime still match, the cached XXH3-128 va the base file is not re-read (run with `-v` to see a `reusing cached hash, skipping read` line); if anything is missing, malformed, unwritable, or changed, tailor just hashes the file again. -`tailor build --force` deliberately ignores all incremental checks, so it re-hashes the base every -time — omit `--force` for fast back-to-back iterations on an unchanged base. +`tailor build --force` rebuilds even when a stamp says the artifact is current; it does **not** +disable the base hash cache. An unchanged base may still reuse its cached hash. ## The catalogue model diff --git a/tools/tailor/docs/explanation/crate-architecture.md b/tools/tailor/docs/explanation/crate-architecture.md index 578c8c38ee..4cfd3e963f 100644 --- a/tools/tailor/docs/explanation/crate-architecture.md +++ b/tools/tailor/docs/explanation/crate-architecture.md @@ -18,7 +18,7 @@ graph TD | Crate | Responsibility | | --- | --- | | `tailor-config` | Parse `tailor.yaml`/`image.yaml`, expand matrices, merge fragments, interpolate params, render cells. | -| `tailor-core` | Domain model, build plans, lockfile/stamp logic, orchestration, and port traits. | +| `tailor-core` | Domain model, build plans, incremental stamps, orchestration, and port traits. | | `tailor-resolve` | Resolve toolchain and base image digests/hashes. | | `tailor-exec` | Docker/Bollard execution adapter, IC arg construction, path translation, cleanup. | | `tailor-sign` | Host-side signing backends for the `Signer` port (openssl/`sbsign`); preview-gated. | diff --git a/tools/tailor/docs/explanation/design-rationale.md b/tools/tailor/docs/explanation/design-rationale.md index e0f3fa9a50..e3d1ef540b 100644 --- a/tools/tailor/docs/explanation/design-rationale.md +++ b/tools/tailor/docs/explanation/design-rationale.md @@ -11,7 +11,7 @@ Image Customizer already owns image semantics: storage, packages, services, scri - loading workspace and image definitions, - merging fragments, - expanding matrices, -- resolving/pinning toolchains and base images, +- resolving toolchains and base images for each build, - building the IC command line, - running the IC container per cell. diff --git a/tools/tailor/docs/explanation/merge-model.md b/tools/tailor/docs/explanation/merge-model.md index 4ffc224877..9e89b4540f 100644 --- a/tools/tailor/docs/explanation/merge-model.md +++ b/tools/tailor/docs/explanation/merge-model.md @@ -23,10 +23,12 @@ matrix: Here `by-edition/...` applies before `by-channel/...`. This is intentional: adding a directory or changing alphabetical order cannot silently change precedence. Authors control precedence where they declare the axes. A fragment can target a **combination** of axes (`by-edition+arch/pro+arm64.yaml`, a conjunction) or -**several values of one axis** (`by-channel/stable+edge.yaml`, a disjunction). Such fragments sort after the -single-axis ones they refine (more axes = more specific = later), and a narrower single-value fragment wins -over a broader disjunction on the same axis. `tailor explain --cell ` prints the exact order; -see [image.yaml](../reference/image-yaml.md) and [Merge directives](../reference/directives.md). +**several values of one axis** (`by-channel/stable+edge.yaml`, a disjunction). Its optional inline +`match:` further restricts the path match with nested `all`/`any`/`not` predicates; it does not +alter precedence. Composite fragments sort after single-axis ones, while a narrower single-value +fragment wins over a broader disjunction on the same axis. `tailor explain --cell ` +prints the applicable order; see [Fragments](../reference/image-yaml.md#fragments) and +[Field-specific merging](../reference/directives.md#field-specific-merging). ## Fragment sort order @@ -36,7 +38,7 @@ For a given cell, the fragments that apply are sorted by these keys, in order, a (`by-edition/pro.yaml`) applies before a multi-axis one (`by-edition+arch/pro+arm64.yaml`), so the more specific composite wins. 2. **Axis declaration order** — among fragments of equal arity, the axis declared earlier in `matrix:` - applies first (later-declared axes win later conflicts). + applies first. Feature fragments follow matrix-axis fragments of equal arity. 3. **Breadth** — a broader disjunction (`by-channel/stable+edge.yaml`) applies before a narrower single value (`by-channel/edge.yaml`) on the same axis, so the single value wins. @@ -64,8 +66,9 @@ previous): - Differing scalar assignments conflict unless the later assignment uses `$set`. - Set a key's value to the bare token `$unset` to remove the inherited key entirely. -This makes accidental double ownership loud while keeping additive IC lists easy. See -`docs/reference/directives.md` for every directive. +These rules apply to merged `config`, `base`, and `outputs`; `params` conflict on different +values, `rpmSources`/`extraParams` concatenate, and `skip` is last-wins. See +[Field-specific merging](../reference/directives.md#field-specific-merging) for the exact surface. ## Why no IC-aware merge? diff --git a/tools/tailor/docs/explanation/threat-model.md b/tools/tailor/docs/explanation/threat-model.md index 4a2bfe7fb0..323ae84d27 100644 --- a/tools/tailor/docs/explanation/threat-model.md +++ b/tools/tailor/docs/explanation/threat-model.md @@ -57,15 +57,12 @@ load-bearing — treat a regression in them as a serious bug: paths a build produced (the build directory's children, the image cache, the per-cell log), never arbitrary locations. -## Supply-chain pinning +## Registry input identity -Because a moving container tag is a reproducibility hazard (today's `:latest` is -not tomorrow's), tailor resolves and pins the digests of the images it runs — -the IC toolchain, tools-directory sources, base images, and the janitor image — -into `tailor.lock`. Pinning by digest is a **consistency and reproducibility** -measure under the trusted-host model, not a defense against a compromised -registry account; run `tailor lock` to freeze digests and `tailor update` to -refresh them deliberately. +Registry inputs resolve to digests for a build invocation, but a moving tag can +select different bytes next time. Digest resolution is not a defense against a +compromised registry account and does not make a build offline. Build only from +trusted registries and images within the trusted-host boundary above. ## Reporting diff --git a/tools/tailor/docs/how-to/README.md b/tools/tailor/docs/how-to/README.md index e581706c9b..9e12115538 100644 --- a/tools/tailor/docs/how-to/README.md +++ b/tools/tailor/docs/how-to/README.md @@ -1,9 +1,9 @@ # How-to guides How-to guides are task-oriented recipes. Most assume a **workspace** (`tailor.yaml` plus one or -more `image.yaml` files); the execution verbs (`build`, `render` with a real run) additionally need a -container **engine**, and some steps need a **registry** login or a base-image **catalogue**. Each -guide notes anything extra it needs. +more `image.yaml` files); actual `build` and `convert` runs need a container **engine**, while +`render`, `validate`, and `build --dry-run` do not. Some steps need a **registry** login or a +base-image **catalogue**. Each guide notes anything extra it needs. ## Author a workspace @@ -11,6 +11,8 @@ guide notes anything extra it needs. - [Add an axis](add-an-axis.md) - [Share config with `$include`](share-config-with-include.md) - [Override a base per axis](override-a-base-per-axis.md) +- [Build dependent images](build-dependent-images.md) +- [Track local build inputs](track-build-inputs.md) ## Choose a base image @@ -26,19 +28,24 @@ guide notes anything extra it needs. - [Change an output format](change-an-output-format.md) - [Convert an image format](convert-an-image-format.md) - [Set the build directory](set-the-build-directory.md) +- [Clean and rebuild a cell](clean-and-rebuild.md) +- [Manage extracted Image Customizer artifacts](manage-extracted-artifacts.md) +- [Skip experimental cells in bulk runs](skip-experimental-cells.md) -## Pin and reproduce +## Toolchains -- [Pin the Image Customizer version](pin-the-ic-version.md) +- [Select an Image Customizer tag](pin-the-ic-version.md) - [Use a locally-built Image Customizer image](use-a-local-ic-image.md) ## Environment and engine - [Select a container engine (Docker or Podman)](select-a-container-engine.md) +- [Capture Image Customizer logs](capture-build-logs.md) ## Pipelines and CI - [Export configs for a pipeline](export-configs-for-a-pipeline.md) +- [Generate a CI matrix](generate-a-ci-matrix.md) - [Handle exit codes in scripts](handle-exit-codes.md) ## Signing and preview features diff --git a/tools/tailor/docs/how-to/build-dependent-images.md b/tools/tailor/docs/how-to/build-dependent-images.md new file mode 100644 index 0000000000..8f5aef8a0d --- /dev/null +++ b/tools/tailor/docs/how-to/build-dependent-images.md @@ -0,0 +1,63 @@ +# Build dependent images + +Use a workspace when one image consumes another's artifact. Define both images as members; tailor +builds producer cells before their consumers, including when you name only the consumer. + +For a producer with a single `raw` output: + +```yaml +# base-os/image.yaml +name: base-os +base: { path: ../bases/input.img } +matrix: + arch: [amd64, arm64] +outputs: + - format: raw +config: + os: + hostname: base-os +``` + +Select its output as another image's base: + +```yaml +# appliance/image.yaml +name: appliance +base: { image: base-os, output: raw } +matrix: + arch: [amd64, arm64] +config: + os: + hostname: appliance +``` + +```bash +tailor validate appliance +tailor build appliance --dry-run +tailor build appliance --arch amd64 +``` + +The shared `arch` pairs each appliance cell with its producer cell. If the producer has multiple +outputs, specify `output` by **format** (not file extension). For producer-only axes, pin a +coordinate with `cell: { flavor: minimal }` on the `base:` reference. Missing or ambiguous pairs +fail validation; dependency cycles fail before building. + +To refer to an artifact *inside* the IC config instead of using it as the base, declare an input: + +```yaml +inputs: + - name: payload + image: base-os + output: raw +config: + os: + additionalFiles: + - source: "${inputs.payload}" + destination: /opt/payload.raw +``` + +Tailor substitutes the paired artifact path, hashes its contents for incremental rebuilds, and +builds its producer first. Use `dependsOn: [base-os]` only when you need build ordering **without** +consuming its bytes; order-only edges do not change the consumer's fingerprint. + +See [Inter-image dependencies](../reference/image-yaml.md#inter-image-dependencies). diff --git a/tools/tailor/docs/how-to/build-with-an-azure-linux-base.md b/tools/tailor/docs/how-to/build-with-an-azure-linux-base.md index a71f82403a..ffbc8cdf55 100644 --- a/tools/tailor/docs/how-to/build-with-an-azure-linux-base.md +++ b/tools/tailor/docs/how-to/build-with-an-azure-linux-base.md @@ -66,12 +66,12 @@ Pass `-vv` to also stream the Image Customizer logs. ## What tailor handles for you -- **Digest pinning.** tailor resolves the base to a digest and passes IC a reproducible - `--image oci:@sha256:…`. Run `tailor lock` to record it in `tailor.lock`. +- **Digest resolution.** tailor resolves the base to a digest and passes IC + `--image oci:@sha256:…` for that run. A tag may resolve to a different digest later. - **Image cache.** `oci`/`azureLinux` bases need a cache directory; tailor defaults `runtime.imageCacheDir` to `/.tailor/cache`. - **Ownership.** IC writes outputs as root; the sudo-free janitor normalizes them (and the cache) back to you, so artifacts are yours to read and delete. tailor defaults `runtime.janitorImage` to `mcr.microsoft.com/azurelinux/base/core:3.0`. -Set any of these explicitly under `runtime:` in `tailor.yaml` to override the defaults. +Set `imageCacheDir` or `janitorImage` under `runtime:` in `tailor.yaml` to override those defaults. diff --git a/tools/tailor/docs/how-to/capture-build-logs.md b/tools/tailor/docs/how-to/capture-build-logs.md new file mode 100644 index 0000000000..3a893b1dbc --- /dev/null +++ b/tools/tailor/docs/how-to/capture-build-logs.md @@ -0,0 +1,26 @@ +# Capture Image Customizer logs + +Live IC logs stream during a build. To retain a full per-cell debug log: + +```bash +tailor --log-dir ./build-logs build appliance +``` + +Logs are written as `/.log`; clones use `_clone.log`. The opt-in order +is `--log-dir` → `TAILOR_LOG_DIR` → `runtime.logDir` (off by default). Relative flag and +environment paths resolve from the current directory; a manifest path resolves from the workspace +root: + +```yaml +# tailor.yaml +runtime: + logDir: build-logs + logLevel: info +``` + +IC uses `debug` by default; `--ic-log-level warn` overrides `runtime.logLevel` for a run. +`-v`/`-q` affect tailor's own verbosity, **not** IC's log level. +`--timestamps elapsed|time|off` controls live status/log-line prefixes, not the IC log format. +To disable terminal colors set `NO_COLOR=1`; set `CLICOLOR_FORCE=1` to preserve colors in CI. + +See [CLI reference](../reference/cli.md). diff --git a/tools/tailor/docs/how-to/clean-and-rebuild.md b/tools/tailor/docs/how-to/clean-and-rebuild.md new file mode 100644 index 0000000000..b7eefc9e0d --- /dev/null +++ b/tools/tailor/docs/how-to/clean-and-rebuild.md @@ -0,0 +1,24 @@ +# Clean and rebuild a cell + +Find a cell, then remove its ordinary artifact using the janitor (requires a reachable container +engine): + +```bash +tailor slugs appliance +tailor clean appliance --cell appliance_amd64_cosi +tailor build appliance --cell appliance_amd64_cosi --force +``` + +`clean` targets only selected cells' default **uncompressed** artifact names in the workspace's +`artifacts/`, plus the default `.ca_cert.pem` for signed images using that path. If a +`local-test-ca` profile sets `publishCaCert`, remove that custom path yourself. It does not remove +build stamps, clone outputs, `.zst` compressed outputs, managed extracted `.artifacts` directories, +or a custom `build --output-dir` tree. Remove those separately if needed. `--force` reruns IC even +if a remaining stamp says the cell is current. + +A real `build` acquires an advisory lock at `/.tailor/build.lock`. A second build +targeting that output directory fails promptly; use a distinct `--output-dir` for parallel runs. +On interruption, tailor stops the running container and releases the lock. Builds process cells +sequentially; parallel `--jobs` scheduling is not shipped. + +See [CLI reference](../reference/cli.md#tailor-clean-images). diff --git a/tools/tailor/docs/how-to/convert-an-image-format.md b/tools/tailor/docs/how-to/convert-an-image-format.md index d1969f2727..9b06241b65 100644 --- a/tools/tailor/docs/how-to/convert-an-image-format.md +++ b/tools/tailor/docs/how-to/convert-an-image-format.md @@ -32,7 +32,7 @@ OS-build-only formats `iso`/`pxe-*` are not available): | `-o, --output ` | Output path (default: the input's name with the target extension, beside the input). | | `--container ` | The Image Customizer image to run (default: `mcr.microsoft.com/azurelinux/imagecustomizer:latest`). | | `--arch ` | `amd64` (default) or `arm64` — drives `--platform linux/`. | -| `--build-dir-base ` | Host base for IC's scratch (default: a unique dir under the system temp dir). Must not be `/`. | +| `--build-dir-base ` | Explicit host base for IC scratch; by default IC uses container-internal `/tmp`. The host path must pass the build-directory safety guard. | | `--dry-run` | Print the container invocation without running it (no engine needed). | The global engine flags apply too: `--engine docker|podman|auto` and `--host `. @@ -48,9 +48,7 @@ tailor convert ./disk.qcow2 --to raw --dry-run ## Notes - The input must be a **local file** — IC `convert` takes an `--image-file`, not a registry - download. + download. It must exist even for `--dry-run`. - `convert` only reformats the disk container; it does not customize the OS. To change the image's contents, use `tailor build` (or `imagecustomizer customize`). -- The output directory cannot be the exact directory you're running from *when you point `-o` at it* - — a subdirectory (the default "beside the input" when the input is in a subdirectory, or any - `-o subdir/…`) is always fine. +- Tailor stages conversion in a child of the output directory, then publishes the result there. diff --git a/tools/tailor/docs/how-to/cross-arch-building.md b/tools/tailor/docs/how-to/cross-arch-building.md index b6b15bbc0c..c82f689153 100644 --- a/tools/tailor/docs/how-to/cross-arch-building.md +++ b/tools/tailor/docs/how-to/cross-arch-building.md @@ -19,6 +19,7 @@ base: outputs: - format: cosi config: + previewFeatures: [input-image-oci] # IC must opt in to downloading an OCI base os: hostname: gizmo ``` diff --git a/tools/tailor/docs/how-to/export-configs-for-a-pipeline.md b/tools/tailor/docs/how-to/export-configs-for-a-pipeline.md index e98021f5ef..c5dd78569d 100644 --- a/tools/tailor/docs/how-to/export-configs-for-a-pipeline.md +++ b/tools/tailor/docs/how-to/export-configs-for-a-pipeline.md @@ -35,7 +35,7 @@ Add a check so a config change can never be committed without regenerating: tailor export --check ``` -`--check` renders to a temporary directory and compares against the committed files, exiting non-zero +`--check` compares freshly rendered configs against the committed files, exiting non-zero on any **changed**, **missing**, or **extra** (stale) file — without writing anything. Wire it into a pre-commit hook and a PR CI job: @@ -52,18 +52,20 @@ one-liner. The exported `.yaml` is only the Image Customizer **config**. Everything else in an IC invocation — the base image, `--rpm-source`, `--tools-dir`, output format, and any signing — is not in the config and is provided by the pipeline. This is deliberate: the config is portable and static, so -`tailor export` renders any cell whose config is complete, and the pipeline owns the invocation using +`tailor export` renders the IC config, and the pipeline owns the invocation using its own approved machinery. A minimal pipeline call per cell looks like: ```bash -imagecustomizer \ +imagecustomizer customize \ --config-file rendered/.yaml \ --image-file \ --output-image-file out/. \ --output-image-format ``` -Pin the Image Customizer image the pipeline runs to the digest in your committed `tailor.lock` so it -matches what tailor resolved. +`export` is offline: it does not resolve toolchain or base digests, nor substitute +`${inputs.}` producer paths. If your config uses producer inputs, supply the concrete +artifact paths in the pipeline rather than treating the exported tokens as self-contained YAML. +Choose the pipeline's IC image independently of tailor's toolchain settings. diff --git a/tools/tailor/docs/how-to/generate-a-ci-matrix.md b/tools/tailor/docs/how-to/generate-a-ci-matrix.md new file mode 100644 index 0000000000..7ee3289706 --- /dev/null +++ b/tools/tailor/docs/how-to/generate-a-ci-matrix.md @@ -0,0 +1,28 @@ +# Generate a CI matrix + +Use `matrix` to enumerate selected cells without running an engine: + +```bash +tailor matrix appliance --format json +tailor matrix appliance --format slugs -s arch=amd64 +``` + +JSON cells contain `image`, `slug`, `axes`, `format`, and (for a catalogue-backed cell) +`baseImage`. A slug can be fed to `tailor build ` or `tailor build --cell `. + +For Azure DevOps, generate the matrix object: + +```bash +tailor matrix appliance --format ado +tailor matrix appliance --ado BUILD_MATRIX +``` + +The first emits a bare JSON object. The second emits an ADO `##vso[task.setvariable]` line +for `BUILD_MATRIX` and fails on an empty selection. Each leg contains string variables +`image`, `slug`, `format`, optionally `baseImage`, and `axis_` for each coordinate. +ADO leg keys are sanitized (and disambiguated if needed); **use the leg's `slug` value** when +invoking `tailor build`, not its sanitized key. `--ado` and `--format` cannot be combined. + +Use `tailor export --check` separately if the pipeline consumes committed, rendered IC YAML; +the matrix command only enumerates cells. See [Export configs](export-configs-for-a-pipeline.md) +and the [CLI reference](../reference/cli.md). diff --git a/tools/tailor/docs/how-to/manage-extracted-artifacts.md b/tools/tailor/docs/how-to/manage-extracted-artifacts.md new file mode 100644 index 0000000000..9c4987c41c --- /dev/null +++ b/tools/tailor/docs/how-to/manage-extracted-artifacts.md @@ -0,0 +1,34 @@ +# Manage extracted Image Customizer artifacts + +When IC extracts `output.artifacts`, choose what tailor does with the extraction on **unsigned** +cells. Enable IC's `output-artifacts` feature in the image's `config:`: + +```yaml +# image.yaml +name: appliance +outputArtifacts: managed +base: { path: ./input.img } +config: + previewFeatures: [output-artifacts] + output: + artifacts: + items: [ukis] + path: ./extracted +``` + +The default `managed` policy replaces `path` in the execution copy of the config and publishes +extracted files at `artifacts/.artifacts`, owned by your user. The authored config and +`tailor render` output are unchanged. Choose `scratch` to reclaim the extraction after the run, +or `strip` to omit extraction entirely: + +```yaml +# tailor.yaml (optional default for unsigned images) +defaults: + outputArtifacts: scratch +``` + +An image's `outputArtifacts` overrides this default. Without `config.previewFeatures: +[output-artifacts]`, tailor does not rewrite the IC config. Signing manages extraction +independently; see [Sign an image](sign-an-image.md). + +See [Image definition](../reference/image-yaml.md#staging-extracted-artifacts). diff --git a/tools/tailor/docs/how-to/override-a-base-per-axis.md b/tools/tailor/docs/how-to/override-a-base-per-axis.md index badeaf7782..498aad879a 100644 --- a/tools/tailor/docs/how-to/override-a-base-per-axis.md +++ b/tools/tailor/docs/how-to/override-a-base-per-axis.md @@ -13,6 +13,7 @@ base: outputs: - format: cosi config: + previewFeatures: [input-image-oci] # required by IC for the edge cell's OCI base os: hostname: gizmo ``` diff --git a/tools/tailor/docs/how-to/pin-the-ic-version.md b/tools/tailor/docs/how-to/pin-the-ic-version.md index ebefe1a8f8..9bcc53fac1 100644 --- a/tools/tailor/docs/how-to/pin-the-ic-version.md +++ b/tools/tailor/docs/how-to/pin-the-ic-version.md @@ -1,71 +1,34 @@ -# Pin the Image Customizer version +# Select an Image Customizer tag -Pin Image Customizer in `tailor.yaml` under `toolchains:`. +Choose the workspace's default Image Customizer container in `tailor.yaml`: ```yaml schemaVersion: 1 - toolchains: - default: ic-1.3 + default: ic entries: - - name: ic-1.3 + - name: ic container: mcr.microsoft.com/azurelinux/imagecustomizer version: "1.3.0" + pull: missing ``` -`tag` defaults to `version`, or to `latest` when neither `tag` nor `version` is set. Use `tag` if the registry tag is not the version string: - -```yaml -toolchains: - default: nightly - entries: - - name: nightly - container: mcr.microsoft.com/azurelinux/imagecustomizer - tag: latest -``` - -An image can select a non-default toolchain: - -```yaml -# db/image.yaml -name: db -toolchain: ic-1.3 -``` +`version` is SemVer metadata and supplies the registry tag if `tag` is absent. An explicit +`tag` takes precedence; with neither field, the tag is `latest`. Images may choose another named +toolchain with `toolchain: `. `pull: missing` (default) normally uses a local image, then +resolves/pulls from the registry if absent; `always` requests a pull, while `never` normally +requires a local image. The policy applies to this container source, not separately declared +registry base images. -## Freeze and refresh the lockfile *(preview)* - -> **Preview feature.** `tailor lock`, `tailor update`, and `tailor build --locked` are gated behind -> the `lock-preview` Cargo feature and are **absent from default builds** (lockfile enforcement is not -> yet implemented). Install with `cargo install --features lock-preview` to use the commands below. - -`tailor.lock` records the exact digest each toolchain (and base image) resolves to, so every machine -builds the same inputs. - -```bash -# First time — resolve everything and write the lock: -tailor lock - -# Add a new image/toolchain later — lock keeps existing pins and resolves only the new inputs: -tailor lock - -# Deliberately move every pin to the latest digest: -tailor update -``` - -`lock` is a **freeze**: inputs already pinned keep their digests, so re-running it is idempotent and -never silently moves a pin. `update` re-resolves **everything** to the latest digest — use it when you -intend to move up. - -## Build reproducibly from the lock +Tags, **including version-shaped tags**, can move. Selecting `version: "1.3.0"` or +`tag: "1.3.0"` does not guarantee identical bytes on later machines or runs. To inspect the +current resolution, run `tailor resolve` with a reachable engine (it may contact registries): ```bash -tailor build --locked +tailor resolve ``` -`--locked` requires a complete lock and fails on any missing entry or registry drift, so a CI build -never silently resolves something new. - -> **Local images.** A local-only Image Customizer image (a fresh `docker build` that was never pushed, -> so it has no registry digest) can't be pinned and is omitted from `tailor.lock`; reproducibility then -> depends on the local image tag you built. A local image that *does* carry a repo digest is still -> locked. See [Use a locally-built Image Customizer image](use-a-local-ic-image.md). +It prints a YAML summary of resolved inputs to stdout without building or writing files. +Results from a later run may differ if a tag changes. See +[Pull policy](../reference/tailor-yaml.md#pull-policy) and +the [CLI reference](../reference/cli.md#tailor-resolve-images). diff --git a/tools/tailor/docs/how-to/set-the-build-directory.md b/tools/tailor/docs/how-to/set-the-build-directory.md index 48910fac9c..fa84c16feb 100644 --- a/tools/tailor/docs/how-to/set-the-build-directory.md +++ b/tools/tailor/docs/how-to/set-the-build-directory.md @@ -25,7 +25,8 @@ specific filesystem without changing the committed `tailor.yaml`. ## Safety guard tailor refuses a build directory that is the filesystem root, a protected system directory such as -`/etc`, `/usr`, or `/var`, or `$HOME`. Use a dedicated scratch directory instead. +`/etc`, `/usr`, or `/var`, `$HOME`, or an ancestor of the current working directory. The check +resolves symlinks and does **not** require a separate filesystem. Use a dedicated scratch directory. Related: [Use a tools dir for sealed images](use-a-tools-dir.md); see the [`tailor.yaml` reference](../reference/tailor-yaml.md). diff --git a/tools/tailor/docs/how-to/sign-an-image.md b/tools/tailor/docs/how-to/sign-an-image.md index aebc600eb8..34a16928f7 100644 --- a/tools/tailor/docs/how-to/sign-an-image.md +++ b/tools/tailor/docs/how-to/sign-an-image.md @@ -48,20 +48,23 @@ signing: | Backend | Required fields | Use | | --- | --- | --- | -| `local-test-ca` | none | MVP / CI. A self-signed CA + leaf minted per build. Not a production trust root. | -| `keypair` | `key`, `cert` | Bring your own Secure Boot key + certificate (PEM). | +| `local-test-ca` | `openssl` and `sbsign` on the host | MVP / CI. A self-signed CA + leaf minted per build. Not a production trust root. | +| `keypair` | `key`, `cert` (PEM); `openssl` and `sbsign` on the host | Bring your own Secure Boot key + certificate. | | `azure-key-vault` | `vault`, `certificate` | Remote signing — configuration only; execution is a later milestone. | ## 3. Opt an image in -Set `signing:` on the image — `true` for the workspace default profile, or a profile id. The image -still authors its own `output.artifacts` (that is what tells IC which boot artifacts to extract): +Add `signing:` to a buildable image — `true` for the workspace default profile, or a profile id. +Keep its `base`, output, and other IC config fields; the excerpt below also adds +`output.artifacts` **and IC's `output-artifacts` preview flag** (distinct from tailor's +workspace `signing` opt-in): ```yaml -# image.yaml +# appliance/image.yaml (relevant fields) name: appliance signing: true # or `signing: byo` config: + previewFeatures: [output-artifacts] output: artifacts: items: [ukis, shim, systemd-boot, verity-hash] @@ -93,7 +96,9 @@ error: signing preflight failed — fix every prerequisite below, then rebuild: What the preflight checks per backend: -- **`local-test-ca`** — always ready (the CA + leaf are minted at sign time). +- **Both implemented backends** — `openssl` and `sbsign` are on the host `PATH`, even if the + extracted image contains only one artifact type. +- **`local-test-ca`** — no key files required; the CA + leaf are minted at sign time. - **`keypair`** — the `key` and `cert` files exist, are readable, and are PEM. - **`azure-key-vault`** — configuration completeness; preflight also reports that execution is not yet available for this backend. @@ -118,3 +123,5 @@ declared `output.artifacts`, sign them on the host, and re-inject them so the fi into an image. - `local-test-ca` mints fresh keys each build, so its signed outputs are intentionally not reproducible; use `keypair` (a fixed cert identity) for reproducible production builds. +- `tailor clean` only removes the default `.ca_cert.pem`; a `publishCaCert` override must be + cleaned up separately. diff --git a/tools/tailor/docs/how-to/skip-experimental-cells.md b/tools/tailor/docs/how-to/skip-experimental-cells.md new file mode 100644 index 0000000000..24d8e59e85 --- /dev/null +++ b/tools/tailor/docs/how-to/skip-experimental-cells.md @@ -0,0 +1,36 @@ +# Skip experimental cells in bulk runs + +Exclude an entire image from no-argument bulk commands: + +```yaml +# experimental/image.yaml +name: experimental +skip: true +base: { path: ./input.img } +config: + os: + hostname: experimental +``` + +`tailor build` and `tailor matrix` omit it, but `tailor build experimental --dry-run` selects +it explicitly. `tailor list` marks the image `(skip)`. + +To skip just one matrix value, set `skip` in its fragment: + +```yaml +# experimental/by-channel/edge.yaml +skip: true +``` + +Bulk selection drops edge cells. Request them by pinning the skipped coordinate: + +```bash +tailor build experimental -s channel=edge --dry-run +``` + +A selector on a different axis alone does not unskip edge. A more-specific fragment may use +`skip: false` to override the skipped value. This controls *selection*, not whether the YAML is +valid; `tailor validate experimental -s channel=edge` still renders that cell. Dependencies needed +by an explicitly requested consumer are selected even when their producer cells are skipped. + +See [Image definition](../reference/image-yaml.md#fragments). diff --git a/tools/tailor/docs/how-to/track-build-inputs.md b/tools/tailor/docs/how-to/track-build-inputs.md new file mode 100644 index 0000000000..30f12d0c27 --- /dev/null +++ b/tools/tailor/docs/how-to/track-build-inputs.md @@ -0,0 +1,31 @@ +# Track local build inputs + +Tailor hashes `rpmSources` and `extraDependencies` when deciding whether a cell needs rebuilding. +An IC config path alone is only a string: add files it references to `extraDependencies`. + +```yaml +# appliance/image.yaml +name: appliance +base: { path: ../bases/input.img } +rpmSources: + - ./rpms + - ./repos/updates.repo +extraDependencies: + - ./scripts/configure.sh +config: + scripts: + postCustomization: + - path: scripts/configure.sh +``` + +Paths in `image.yaml` resolve from that image's directory. A changed file in `scripts` now +invalidates the cell's stamp; edits to RPM sources do likewise. Changes to merged IC config, +toolchain/base identity, output settings, and declared producer `inputs` also re-fingerprint the +cell. Directory hashes ignore RPM `repodata` for `rpmSources`. + +Use `tailor build appliance --force` to rerun independently of the stamp. `--dry-run` previews +invocations without resolving a registry digest or writing a build stamp; `tailor validate` checks +the tailor configuration offline but does **not** run Image Customizer to validate its opaque +`config:` tree. + +See [Image definition](../reference/image-yaml.md#interpolation-and-local-inputs). diff --git a/tools/tailor/docs/how-to/use-a-local-ic-image.md b/tools/tailor/docs/how-to/use-a-local-ic-image.md index 5ae3f4b76c..95b5ca3e6c 100644 --- a/tools/tailor/docs/how-to/use-a-local-ic-image.md +++ b/tools/tailor/docs/how-to/use-a-local-ic-image.md @@ -2,7 +2,7 @@ Sometimes the toolchain (or a tools-dir source) is an image you build **locally** and never push to a registry — for example an Image Customizer image with extra host tools layered in, tagged -`my-ic:local`. tailor runs such images directly: no registry, no `localhost:5000` +`my-ic:local`. tailor runs such images directly: no registry, no local registry workaround. ## 1. Reference the local image @@ -30,21 +30,20 @@ tailor build ``` Under `pull: missing`, tailor inspects the local daemon, finds `my-ic:local`, and runs -it **without a registry pull**. Under `pull: never`, tailor requires the image to be present locally -(or a locked digest) and never contacts a registry — use it to guarantee an offline build. +it **without a registry pull** in the normal local-image flow. `pull: never` normally requires +a local image instead of fetching the toolchain. Registry **bases** resolve independently, so +this setting alone does not make the entire build offline. -## 2. How the image is identified (and whether it locks) +## 2. How the image is identified -tailor inspects the local image and picks the most reproducible identifier available: +tailor inspects the local image and chooses an identifier for this run: - If the image carries a **`RepoDigest`** (it was pulled from, or pushed to, a registry at some - point), tailor runs it as `container@sha256:…` and that digest is **recorded in `tailor.lock`**. + point), tailor runs it as `container@sha256:…`. - If the image is **local-only** (no `RepoDigest` — a fresh `docker build` that was never pushed), - tailor runs it by its image **`Id`**. An `Id` is not portable across machines, so it is - **intentionally omitted from `tailor.lock`**: a locked build cannot pin something another machine - cannot fetch. + tailor runs it by its image **`Id`**. That identifier is not portable across machines. -This applies identically to `toolsDirSources` and to an image's inline `toolchain:` / `toolsDir.source`. +This also applies to inline `toolchain:` and `toolsDir.source` images. ## 3. Match the architecture @@ -59,8 +58,8 @@ error: toolchain `local-ic` local image is `amd64` but cell `gizmo_arm64_cosi` t Build cells whose arch the local image can serve (for example select the matching arch, see [Cross-arch building](cross-arch-building.md)), or provide a local image for the other arch. -## 4. Make a reproducible build +## 4. Use the image on another machine -To pin a local image in the lockfile, give it a `RepoDigest` by pushing (or re-pulling) it through a -registry your builders can reach, then `tailor lock`. A purely local `Id`-based image stays usable -but is never locked — see [Pull policy](../reference/tailor-yaml.md#pull-policy). +Build or distribute the same toolchain image on each builder, or push it to a registry they can +access. A shared tag alone does not guarantee identical bytes across machines. See +[Pull policy](../reference/tailor-yaml.md#pull-policy). diff --git a/tools/tailor/docs/reference/cli.md b/tools/tailor/docs/reference/cli.md index 396e730198..5fae5ad9e1 100644 --- a/tools/tailor/docs/reference/cli.md +++ b/tools/tailor/docs/reference/cli.md @@ -4,17 +4,22 @@ Global options: | Option | Meaning | | --- | --- | -| `--manifest ` | Path to `tailor.yaml`, or a directory to search from. Default: walk up from the current directory. | +| `--manifest ` | Path to `tailor.yaml` (uses its parent as the search start), or a directory to search upward from. Default: current directory. | | `--engine ` | Container engine for this invocation. Overrides `runtime.engine`. See [Select a container engine](../how-to/select-a-container-engine.md). | | `--host ` | Engine endpoint for this invocation (`unix://…` or `tcp://…`). Overrides `runtime.host` and `DOCKER_HOST` / `CONTAINER_HOST`. | | `--log-dir ` | Persist each cell's full IC debug log to `/.log`. | -| `--ic-log-level ` | Set IC's own log level, independent of `-v`/`-q`. | -| `--strict` | Promote authority/confinement warnings to errors. | +| `--ic-log-level ` | Set IC's own log level, independent of `-v`/`-q` (default: `debug`; overrides `runtime.logLevel`). | +| `--strict` | Accepted, but currently unused; does **not** promote warnings to errors. | | `-v`, `--verbose` | Increase verbosity; repeatable. | | `-q`, `--quiet` | Decrease verbosity; repeatable. | | `--timestamps ` | Leading timestamp on status/log lines (default elapsed). | | `--version` | Print version, including commit/build metadata. | +These options are global. `--engine`/`--host` take precedence over manifest and environment +engine settings; `--log-dir` takes precedence over `TAILOR_LOG_DIR`, then `runtime.logDir`. +Without a manifest, a standalone `image.yaml` in the search directory is used. Run +`tailor --help` for the installed binary's command-specific flags. + ## `tailor init [base|simple|advanced]` Scaffold a project. Omitted template is `base`. @@ -46,14 +51,16 @@ name the image and pass `--cell` — `tailor build ` is shorthand for | --- | --- | | `-s`, `--select AXIS=VALUE` | Constrain matrix axes. Repeatable. Comma-separated axis pairs are accepted, for example `-s variant=full,arch=amd64`. | | `--cell SLUG` | Select exact cells by slug. Repeatable. | -| `--locked` | *(preview, `lock-preview` feature)* Require a complete `tailor.lock`; fail on missing entries or registry drift. Absent from default builds — see [`tailor lock`](#tailor-lock-preview). | | `--force` | Ignore incremental up-to-date checks. | | `--arch ARCH` | Restrict build to architecture(s). Repeatable. | | `--output-dir PATH` | Output directory. Default: `/artifacts`. | -| `--build-dir-base PATH` | Override `runtime.buildDirBase`: place each cell's build scratch under this directory (which must not be `/`, a system directory, or `$HOME`). Lets CI point scratch at a specific filesystem without editing the committed `tailor.yaml`. | +| `--build-dir-base PATH` | Override `runtime.buildDirBase`: per-cell scratch under this directory; rejects `/`, protected system dirs, `$HOME`, and ancestors of the current directory. A separate filesystem is not required. | | `--dry-run` | Render each selected container/IC invocation without running it. | | `--clones N` | Build N clones of each cell — distinct artifacts sharing all meaningful content but differing in incidental details (fresh UUIDs, timestamps), each published as `_clone`. Default: `1`. | +Builds run sequentially. A hidden `--jobs` argument is accepted but has no scheduling effect; do +not use it to request parallel builds. + ## `tailor convert --to ` Convert a single image file to another format via Image Customizer (`convert`) — no workspace or @@ -65,7 +72,7 @@ config required. Writes the output beside the input by default (or to `-o`), own | `-o`, `--output PATH` | Output path. Default: the input's name with the target extension, beside the input. | | `--container REF` | The Image Customizer image to run. Default: `mcr.microsoft.com/azurelinux/imagecustomizer:latest`. | | `--arch ARCH` | `amd64` (default) or `arm64` — drives `--platform linux/`. | -| `--build-dir-base PATH` | Host base for IC scratch. Default: a unique dir under the system temp dir. Must not be `/`. | +| `--build-dir-base PATH` | Explicit host base for IC scratch; without it, IC uses container-internal `/tmp`. Host paths must pass the same safety guard as build scratch. | | `--dry-run` | Render the container invocation without running it. | See [Convert an image format](../how-to/convert-an-image-format.md). @@ -94,9 +101,10 @@ Print one selected cell slug per line. Equivalent to `tailor matrix --format slu ## `tailor explain ` -Print the **merge order** for each selected cell: the ordered list of fragment files that merge into it -(base first, later files win), each annotated with why it applies and any `$include`d libraries. This makes -the fragment precedence model legible. Add `--with-config` to also print the merged Image Customizer +Print the **merge order** for each selected cell: the ordered list of applicable fragment files +(base first), each annotated with its *path* predicate and any `$include`d libraries. An inline +`match:` filters applicability but does not appear in the reason label or change precedence. Add +`--with-config` to also print the merged Image Customizer config. Pass an image name to explain its selected cells, or a cell slug to explain that cell directly. Accepts `-s/--select` and `--cell`; read-only and offline. @@ -122,7 +130,8 @@ List images and toolchains. ## `tailor render [images...]` -Write golden snapshots for selected cells. Accepts `-s/--select` and `--cell`. +Write merged IC config snapshots to `/.rendered/.yaml` for selected cells. +Offline; does not run IC. Accepts `-s/--select` and `--cell`. ## `tailor export [images...]` @@ -148,34 +157,26 @@ export: | `--cell SLUG` | Select exact cells by slug. Repeatable. | `tailor export` writes each `.yaml` and prunes any stale `*.yaml` in the output directory that no -selected cell produces. Only static cells are exportable — the config YAML itself carries no -tools-dir, base, rpm-source, or signing details (those are Image Customizer invocation arguments the -consuming pipeline supplies). - -## `tailor lock` *(preview)* - -> **Preview feature.** `lock`, `update`, and `build --locked` are gated behind the `lock-preview` -> Cargo feature and are **absent from default builds** (lockfile *enforcement* is not yet -> implemented). Build with `cargo install --features lock-preview` (or `cargo build --features -> lock-preview`) to enable them. - -Resolve registry inputs and write `tailor.lock` without building. Inputs already pinned in the -current lock **keep their digests** — only new or unpinned inputs are resolved, so re-running `lock` -is idempotent and never silently moves an existing pin. Use it to freeze a reproducible set. - -## `tailor update` *(preview)* - -Re-resolve **every** input to its latest digest and rewrite `tailor.lock`, ignoring the existing -pins. Use it to deliberately refresh to newer base images / toolchains. Preview feature — see -[`tailor lock`](#tailor-lock-preview). +selected cell produces. The config YAML carries no tools-dir, base, rpm-source, or signing arguments +(the pipeline must supply those). `${inputs.}` producer paths are **not** resolved by export; +avoid exporting those configs unchanged to a pipeline without substituting concrete paths. ## `tailor resolve [images...]` -Resolve digests/hashes and print the lockfile content without writing it. +Inspect the selected images' inputs and print a YAML summary of resolved toolchain, named +tools-dir, and registry-base digests (plus an explicitly configured janitor image). Requires a +reachable container engine and may contact registries. Prints to stdout without building or +writing a file; local-only images without registry digests are omitted from the summary. ## `tailor clean [images...]` -Remove generated artifacts and build stamps for selected cells. Accepts `-s/--select` and `--cell`. +Use the container janitor to remove selected cells' default, **uncompressed** artifacts from +`/artifacts` (and the default `.ca_cert.pem` path for signed cells). +An explicit signing profile `publishCaCert` path is **not** removed. Accepts `-s/--select` and +`--cell`. +Requires a reachable engine. It does **not** remove build stamps, clones, compressed `.zst` outputs, +managed `.artifacts` directories, or outputs from `build --output-dir`; delete those separately. +To regenerate after cleaning, use `build --force` if the stamp still marks a cell up to date. ## `tailor bases list` diff --git a/tools/tailor/docs/reference/directives.md b/tools/tailor/docs/reference/directives.md index e53f632cf5..286af462a7 100644 --- a/tools/tailor/docs/reference/directives.md +++ b/tools/tailor/docs/reference/directives.md @@ -12,6 +12,28 @@ Fragments apply after `image.yaml`. Local `by-/.yaml` fragments app A later `$set` wins over an earlier `$set`. A plain reassignment after a `$set` still conflicts. +## Field-specific merging + +Only `base`, `outputs`, `params`, `rpmSources`, `extraParams`, `skip`, `config`, and the +predicate-only `match` are read from `by-*/*.yaml` fragments. `name`, `toolchain`, `toolsDir`, +`matrix`, `selectors`, `features`, `operation`, `outputArtifacts`, `signing`, +`extraDependencies`, `dependsOn`, and `inputs` belong in `image.yaml`; putting them in a +fragment does **not** change the cell. + +| Fragment field | Behavior | +| --- | --- | +| `config` | Deep-merge IC mappings; lists append; differing plain scalars conflict. Directives below apply, including `$include` in the `config:` tree. | +| `base` | Merge mappings with the same rules. Exactly one source kind must remain; use `$set` on the whole `base` to switch kinds. | +| `outputs` | Append output specs by default; use `$replace` (or `$set`) on the whole list to replace it. | +| `params` | Repeat the same scalar value freely; different values for one name error. Values are compared as strings, not overridden with `$set`. | +| `rpmSources`, `extraParams` | Concatenate base and matched fragment lists in merge order; no list directives. | +| `skip` | Last specified boolean wins (`skip: false` can undo an earlier `true`). | +| `match` | Filters this fragment together with its path predicate; never merged into the config. | + +`$include` resolves **only inside `config:`**, relative to the image directory. It does not +include a `base`, `outputs`, or another top-level tailor field. See +[Fragments and inline match](image-yaml.md#fragments). + ## Directives | Directive | Valid position | Value | Purpose | @@ -22,8 +44,9 @@ A later `$set` wins over an earlier `$set`. A plain reassignment after a `$set` | `$prepend` | List field value | list | Insert items **before** the inherited list. | | `$append` | List field value | list | Insert items **after** the inherited list. | | `$unset` | Mapping value | the bare token `$unset` | Remove the inherited key entirely. | -| `$include` | Mapping value or list item | path string | Splice a shared YAML file at that position. | +| `$include` | Mapping value or list item **inside `config:`** | path string | Splice a shared YAML file at that position. | | `$select` | — | — | **Reserved, not implemented** — use [`by-/.yaml` fragments](image-yaml.md#fragments) instead. | +| `$rename` | — | — | **Unsupported**; rejected by the merger. | `$prepend`, `$append`, and `$remove` may share one mapping (e.g. trim the inherited list and add to both ends at once); `$set` and `$replace` are exclusive and cannot be combined with the others. @@ -98,8 +121,9 @@ config: Remove several keys by annotating each; remove a whole subtree by unsetting it at its own level (`scripts: $unset`). `$unset` is resolved at merge time before interpolation, so a field can never hold the -literal string `$unset` (as with YAML's reserved `null`/`~`). A later fragment may set a removed key again. +literal string `$unset`. A later fragment may set a removed key again. The mapping synonym `key: { $unset: true }` is tolerated; `{ $unset: false }` is an error. +Unlike `$unset`, YAML `null`/`~` is an ordinary scalar for merge purposes, not a removal directive. ## `$include` diff --git a/tools/tailor/docs/reference/image-yaml.md b/tools/tailor/docs/reference/image-yaml.md index f15b7c95a7..550a1ab068 100644 --- a/tools/tailor/docs/reference/image-yaml.md +++ b/tools/tailor/docs/reference/image-yaml.md @@ -6,22 +6,48 @@ An image definition lives in an `image.yaml`. The top level belongs to tailor. T | --- | --- | --- | --- | | `name` | string | yes | Image id used by CLI and slugs. Use `[A-Za-z0-9.-]+`; `_` is reserved as the slug separator. | | `skip` | bool | no | Default `false`. When `true`, the image is excluded from **bulk** selection — any command run with no image named (`build`, `matrix`, `slugs`, `validate`, `render`, `export`, `clean`, …), so a pipeline that runs those over all images never picks it up. Build it by naming it explicitly (`tailor build `); `tailor list` marks it `(skip)`. Useful for an experimental config that isn't ready to build in CI. **Also valid in a `by-/.yaml` fragment** — there it drops every cell that picks that value from bulk selection, unless the run pins that value (`-s =`) or names the cell (`--cell `); a non-pinning selector like `-s arch=amd64` does *not* resurrect it. Fragment `skip` merges last-wins (a more-specific fragment may set `skip: false`). | -| `toolchain` | string or `{name, container, version?, tag?, pull?}` | no | Workspace toolchain name or inline standalone toolchain. Defaults to workspace default or built-in `latest`; `pull` defaults to `missing`. | +| `toolchain` | string or `{name, container, version?, tag?, pull?}` | no | Workspace toolchain name or inline toolchain. Defaults to workspace default or built-in `latest`; `pull` defaults to `missing`. | | `toolsDir` | `{source}` | no | Tailor-managed IC `--tools-dir`. `source` is a `toolsDirSources` name or inline `{container, tag?, pull?}`. Always bound writable as a per-cell copy under `runtime.buildDirBase` (which defaults to `/.tailor/build`). | | `matrix` | ordered map `axis: [values]` | no | User-defined axes; their cartesian product is the candidate cells. Omit for one cell. Declaration order controls slug order and fragment precedence — order axes widest → most-specific (so `arch` is first). | | `selectors` | `{ include?, exclude? }` | no | Which cells of the `matrix:` product to build. Lists of **selectors** (sub-cubes); `include` is an allowlist, `exclude` a denylist. Requires `matrix:`; omitted ⇒ the full product. | -| `outputs` | list of output specs | no | Defaults from workspace or built-in `cosi`. One artifact per cell × output. | +| `outputs` | list of `{format, cosiCompressionLevel?, compression?}` | no | Defaults from workspace or built-in `cosi`. One artifact per cell × output. | | `base` | one of `path`, `oci`, `azureLinux`, `ref`, `image` | conditional | Exactly one base resolves per cell. `ref: ` references a `baseImages:` slot; `image: ` bases on another workspace image's output (see [Inter-image dependencies](#inter-image-dependencies)). | | `features` | string list | no | Enables matching `by-feature/.yaml` fragments. Does not multiply cells. | | `params` | scalar map | no | Values interpolated into `config:` strings with `${name}`. Params may reference other params. | | `rpmSources` | path list | no | Each path is a directory of RPMs or a `.repo` file; passed as IC `--rpm-source`. | | `operation` | `customize` or `convert` | no | Default: `customize`. | +| `outputArtifacts` | `managed`, `scratch`, or `strip` | no | Policy for unsigned cells using IC's `output-artifacts` preview flag. Overrides `defaults.outputArtifacts`; see [Staging extracted artifacts](#staging-extracted-artifacts). | | `signing` | `true` or profile id | no | Opt in to the signed-image pipeline. `true` ⇒ the workspace `signing.default` profile; a string ⇒ that named profile; omitted ⇒ unsigned. See [Sign an image](../how-to/sign-an-image.md). | | `extraDependencies` | path list | no | Extra files/directories to hash for incremental checks; use for IC-config-referenced assets. | | `dependsOn` | string list | no | Order-only build dependencies on other workspace images. See [Inter-image dependencies](#inter-image-dependencies). | | `inputs` | list of `{name, image, output?, cell?}` | no | Named producer artifacts embedded in `config:` via `${inputs.}`. See [Embedding an artifact with `inputs:`](#embedding-an-artifact-with-inputs). | | `extraParams` | list of `{param, value?}` | no | Extra Image Customizer command-line flags, appended verbatim after every flag tailor manages. For experimental/non-standard IC builds. See [Extra params](#extra-params). | -| `config` | mapping or path string | conditional | Required for `customize`, forbidden for `convert`. Opaque IC config. | +| `config` | mapping | conditional | Inline IC config for `customize`. Path-string configs are not supported; `convert` does not use a config. | + +Top-level keys are checked: an unknown tailor field is an error. Tailor does not validate the +Image Customizer schema *inside* `config:`; use IC for that check. + +## Interpolation and local inputs + +`params` values may be strings, numbers, or booleans. `${axis}` and `${param}` expand in **string +values** of `config:` (not mapping keys); parameters may reference other parameters. Unknown names, +unterminated expressions, and parameter cycles are errors. For example: + +```yaml +matrix: + arch: [amd64, arm64] +params: + hostLabel: "node-${arch}" +config: + os: + hostname: "${hostLabel}" +``` + +`base.oci.platform: "linux/${arch}"` also expands per cell. Relative `base.path`, +`rpmSources`, and `extraDependencies` paths resolve from the image directory (for an inline +workspace image, the workspace root). `extraDependencies` tracks IC-referenced scripts or files for +incremental builds; `rpmSources` accepts RPM directories or `.repo` files and is passed as +`--rpm-source`. Neither changes the IC config. ## Tools dir @@ -32,7 +58,7 @@ tools-dir is always bound **writable** as a per-cell disposable copy under `runt cannot work). `runtime.buildDirBase` defaults to `/.tailor/build`, so no configuration is needed; set it to override the location. Inline sources accept the same `pull: always | missing | never` policy as workspace `toolsDirSources`; local-only -images without a `RepoDigest` run by image `Id` and are not lockable. The inline IC +images without a `RepoDigest` run by local image `Id` (not portable across machines). The inline IC `config.previewFeatures` list must include `tools-dir`. ```yaml @@ -60,6 +86,33 @@ tailor exports the source container to `runtime.imageCacheDir/tools-dirs//.artifacts`, keep and chown the extracted files. | +| `scratch` | Rewrite its `path` to tailor-owned scratch beside the image definition; reclaim after the run. | +| `strip` | Remove the block and its `output-artifacts` preview flag before calling IC. | + +The declared `path` is replaced for `managed` and `scratch`. Without the IC preview flag, tailor +does not apply this policy. Signing has its own extraction/re-injection flow; see +[Sign an image](../how-to/sign-an-image.md). `tailor render` and `export` write the *authored* +config, not the temporary working copy used during execution. + ## Extra params `extraParams:` passes extra flags straight through to Image Customizer, for experimental or @@ -131,8 +184,8 @@ and [Cross-arch building](../how-to/cross-arch-building.md). ## Fragments -Per-cell deltas live in `by-*/` files whose **path** is the condition — no inline `match:` needed. A -fragment applies to a cell when its path predicate holds: +Per-cell deltas live in `by-*/` files. Their **path** supplies a condition; an optional inline +`match:` adds another condition (it does not replace the path predicate): | Path | Applies when | Kind | | --- | --- | --- | @@ -146,12 +199,41 @@ several values (a disjunction, in the axis's declared value order); a directory takes exactly one value per axis, positionally, with the axes in matrix-declared order. `image.yaml` is the base (applies to every cell). -Apply order is merge precedence (later wins for scalars, extends lists). Fragments are sorted by: **arity** -(more axes apply later — a composite refines the singles it builds on), then **axis-declaration order** -(cross-axis precedence follows the matrix), then **breadth** (a broader disjunction applies before a -narrower single value on the same axis, so the more specific one wins). Run `tailor explain --cell -` to print the exact merge order for a cell. See [Merge directives](directives.md) and -[Merge model](../explanation/merge-model.md) for the full model. +### Inline `match:` + +Inside a `by-*/*.yaml` fragment, a leaf mapping tests axis values (equality for a string, +membership for a list); multiple keys in one leaf are ANDed. The special `feature` key tests the +image's `features:` list (a list value matches **any** named feature). Combine leaves with +`all: [, ...]`, `any: [, ...]`, and `not: `; nesting is supported. +Missing axes and disabled features do not match their leaf. + +```yaml +# by-arch/amd64.yaml +match: + all: + - any: + - channel: stable + - feature: diagnostics + - not: + variant: minimal +config: + os: + hostname: + $set: diagnostic-amd64 +``` + +This applies only to `amd64` cells (the path), with a non-`minimal` variant and either +`channel=stable` or the `diagnostics` feature. Inline `match:` is **not** a top-level +`image.yaml` field, nor a substitute for declaring axes/features. It changes which cells receive +this fragment, **not** its sort order: even complex matches retain their path-based precedence. +`tailor explain --cell ` lists only fragments that actually apply; its reason label +describes the path predicate. See [Field-specific merging](directives.md#field-specific-merging) +for which top-level fields a fragment may contribute. + +Apply order is merge precedence; **conflicting plain scalars error** rather than silently +overriding. Fragments are sorted by **arity** (multi-axis composites after single-axis and feature +fragments), then **axis-declaration order** (features follow matrix axes at the same arity), then +**breadth** (broader disjunctions first). See [Merge model](../explanation/merge-model.md). ## Base sources @@ -179,10 +261,10 @@ base: ref: baremetal # a named slot in tailor.yaml `baseImages:` ``` -For an `oci` or `azureLinux` base, tailor resolves the registry digest and passes IC a digest-pinned -`--image oci:@sha256:…` (so the build is reproducible). Image Customizer downloads OCI input -images behind a **preview feature**, and tailor never edits your IC `config:`, so you must enable it -yourself in the image's `config:`: +For an `oci` or `azureLinux` base, tailor resolves a registry digest for the run and passes IC +`--image oci:@sha256:…`. A tag may resolve to different bytes on a later run. Image +Customizer downloads OCI inputs behind a **preview feature**, and tailor never edits your IC +`config:`, so you must enable it yourself in the image's `config:`: ```yaml config: @@ -235,8 +317,8 @@ references none of its output. (An `image` base or input already implies the edg ### Embedding an artifact with `inputs:` -To consume a producer's artifact *inside* `config:` (e.g. as an IC `additionalFiles` source or a -local `rpmSources` entry), declare it in `inputs:` and reference it by name as `${inputs.}`: +To consume a producer's artifact *inside* `config:` (e.g. as an IC `additionalFiles` source), +declare it in `inputs:` and reference it by name as `${inputs.}`: ```yaml inputs: @@ -257,6 +339,7 @@ config: fingerprint — so a producer rebuild rebuilds this image, and you never hand-write a `../…/artifacts/…` path. A `${inputs.}` that names no declared input is an error. `inputs` entries whose kind is `image` add a build-order edge; each input `name` is unique per image. +Substitution applies only to `config:` values, **not** to `rpmSources` or other tailor-level paths. ## Output spec @@ -264,12 +347,12 @@ entries whose kind is `image` add a build-order edge; each input `name` is uniqu outputs: - format: cosi cosiCompressionLevel: 6 - name: "${name}-${arch}" - format: vhd-fixed compression: zstd # tailor compresses the artifact → .vhd.zst ``` -`format` is required. `cosiCompressionLevel`, `compression`, and `name` are optional. +`format` is required. `cosiCompressionLevel` and `compression` are optional; there is no output +`name` field. The cell slug determines the artifact name. ### `compression` @@ -286,4 +369,3 @@ uncompressed original. This is a **tailor** step, not an IC feature, and is inde breaks bootability), and the `pxe-*` outputs (a directory / an already-gzipped tar) — `validate` rejects those combinations. It applies to the raw disk-image formats: `vhd`, `vhd-fixed`, `vhdx`, `qcow2`, `raw`, `baremetal-image`. Changing it re-fingerprints the cell, so the artifact rebuilds. - diff --git a/tools/tailor/docs/reference/output-formats.md b/tools/tailor/docs/reference/output-formats.md index d5c0f4d0dc..94c5bf7db4 100644 --- a/tools/tailor/docs/reference/output-formats.md +++ b/tools/tailor/docs/reference/output-formats.md @@ -28,6 +28,9 @@ Optional output fields: | `cosiCompressionLevel` | COSI compression level. tailor passes IC `--cosi-compression-level` when set. | | `compression` | Post-build compression tailor applies to the artifact (`zstd` ⇒ `..zst`). Not an IC feature; see below. | +There is no `name` output field: filenames come from the cell slug. Both `vhd` and `vhd-fixed` +use `.vhd`; select a producer output by **format**, not extension. + ### Compression `compression: zstd` makes tailor compress the finished artifact: IC writes the raw image, then tailor diff --git a/tools/tailor/docs/reference/tailor-yaml.md b/tools/tailor/docs/reference/tailor-yaml.md index 6f6eaa724e..c3ef715f3e 100644 --- a/tools/tailor/docs/reference/tailor-yaml.md +++ b/tools/tailor/docs/reference/tailor-yaml.md @@ -27,12 +27,12 @@ defaults: | `toolchains.entries` | list of `{name, container, version?, tag?, pull?}` | yes | Named toolchain definitions. Each `name` must be unique. `tag` defaults to `version`, else `latest`; `pull` defaults to `missing`. | | `toolsDirSources` | list of `{name, container, tag?, pull?}` | no | Named tools-dir sources. Each `name` must be unique. `tag` defaults to `latest`; `pull` defaults to `missing`. Images opt in with `toolsDir.source`. | | `runtime.engine` | enum | no | Container engine: `docker` (default), `podman`, or `auto`. See [Select a container engine](../how-to/select-a-container-engine.md). | -| `runtime.host` | string | no | Explicit engine endpoint (`unix://…`, a bare socket path, or `tcp://…`), overriding the engine default and `DOCKER_HOST` / `CONTAINER_HOST`. | +| `runtime.host` | string | no | Engine endpoint (`unix://…`, a bare socket path, or `tcp://…`); used after `--host` and the selected engine's `DOCKER_HOST` / `CONTAINER_HOST`, before the engine default. | | `runtime.privileged` | bool | no | Default `true`; IC requires privileged container execution. | | `runtime.mounts.hostRoot` | path | no | Default `/host`; namespace prefix for translated host paths. tailor no longer binds host `/` there. | | `runtime.mounts.dev` | bool | no | Default `true`; bind `/dev:/dev`. | | `runtime.mounts.extraPaths` | list of extra mount objects | no | Additional paths exposed under `hostRoot`. `access` defaults to `ro`; use `rw` only for explicit writable carve-outs. Relative paths resolve against the workspace root. | -| `runtime.buildDirBase` | path | no | Host filesystem base for per-cell IC build dirs (`/`). Omit to use the default under the output dir (`/.tailor/build`). Must not be `/`, a system directory, or `$HOME`. | +| `runtime.buildDirBase` | path | no | Host filesystem base for per-cell IC build dirs (`/`). Omit to use `/.tailor/build`. Must not be `/`, a system directory, `$HOME`, or an ancestor of the current directory. | | `runtime.logLevel` | enum | no | IC log level: `panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace`. | | `runtime.logDir` | path | no | Directory for on-disk IC log files (one per cell). Off by default (logs stream to the console). Overridden, highest precedence first, by the `--log-dir` flag then the `TAILOR_LOG_DIR` environment variable. | | `runtime.imageCacheDir` | path | no | Cache for registry base images. Default: `/.tailor/cache`. Required by IC for `oci`/`azureLinux` bases — tailor supplies the default so they build out of the box. | @@ -40,7 +40,7 @@ defaults: | `signing.default` | string | no | Signing profile used when an image says `signing: true`. See [Sign an image](../how-to/sign-an-image.md). | | `signing.profiles` | map of `{backend, …}` | no | Named signing profiles. `backend` is `local-test-ca` (optional `publishCaCert`: where to write the enrollable CA certificate), `keypair` (needs `key`+`cert`), or `azure-key-vault` (needs `vault`+`certificate`). | | `defaults.outputs` | output list | no | Inherited by images without `outputs`. | -| `defaults.outputArtifacts` | enum | no | Default `output.artifacts` staging policy for images that don't set their own: `managed` (default — relocate the extracted artifacts to the output dir and keep them), `scratch` (treat as signing scratch: extract, then reclaim), or `strip` (drop the `output.artifacts` block so IC never extracts). A per-image `outputArtifacts` overrides this. | +| `defaults.outputArtifacts` | enum | no | Default `output.artifacts` policy for unsigned images: `managed` (default; keep extracted files), `scratch` (reclaim), or `strip` (disable extraction). Per-image `outputArtifacts` overrides it; only applies when IC `config.previewFeatures` includes `output-artifacts`. See [Staging extracted artifacts](image-yaml.md#staging-extracted-artifacts). | | `export.outputDir` | path | cond | Committed directory for `tailor export` output (one `.yaml` per cell), relative to the workspace root. Required to run `tailor export` argument-free. See [Export](#export). | | `export.scope` | enum | no | What `tailor export` emits. Defaults to `configsOnly` (the only value today); omit it. | | `export.images` | list of strings | no | Restrict `tailor export` to a subset of images. Default: all images. | @@ -54,14 +54,15 @@ out-of-workspace inputs. The old whole-host `-v /:/host` bind is never emitted. Toolchains and tools-dir sources support `pull: always | missing | never`: -- `always` resolves the image from its registry and pulls before use. -- `missing` (default) uses a local image when present, otherwise resolves and pulls from the registry. - If `tailor.lock` already pins a digest for the named source, the locked digest wins. -- `never` requires a locked digest or a local image and never contacts the registry. +- `always` requests a pull before use. +- `missing` (default) normally uses an available local image; otherwise it resolves and pulls + from the registry. +- `never` requires an available local image in ordinary use; do not treat it as a whole-build + offline guarantee. Registry bases are resolved separately from the toolchain pull policy. -Local images that expose a `RepoDigest` are lockable and run as `container@sha256:…`. Local-only -images without a `RepoDigest` run by their image `Id` and are intentionally omitted from -`tailor.lock`. +Local images with a `RepoDigest` run as `container@sha256:…`; images without one run by local +image `Id`, which is not portable across machines. Tags, including version-like tags, may move +between runs. See [Select an Image Customizer tag](../how-to/pin-the-ic-version.md). A local toolchain image serves the architecture it was built for. tailor reads that architecture during resolution and **fails fast, before any container run,** when it cannot provide a selected @@ -72,8 +73,8 @@ error: toolchain `local-ic` local image is `amd64` but cell `gizmo_arm64_cosi` t no local image for that arch and pull policy won't fetch it ``` -`pull: never` with no local image and no locked digest fails the same way — never silently reaching a -registry. See [Use a locally-built Image Customizer image](../how-to/use-a-local-ic-image.md). +See [Use a locally-built Image Customizer image](../how-to/use-a-local-ic-image.md) for a +local-only workflow. A toolchain entry with every optional key set: @@ -105,7 +106,8 @@ runtime: `toolsDirSources:` is a list of named container root filesystems that tailor can export, cache, bind, and pass to IC as `--tools-dir`. This is useful for sealed/minimal images whose target root does not contain a package manager. The cache is tailor-owned under `runtime.imageCacheDir/tools-dirs/` -and is bound read-only by default. tailor never passes `--tools-dir /`. +and stays as a shared cache; tailor copies it into per-cell disposable scratch under +`runtime.buildDirBase` and binds **that copy writable** for IC. tailor never passes `--tools-dir /`. ```yaml toolsDirSources: @@ -125,8 +127,7 @@ toolsDirSources: | `tag` | string | no | Registry tag. Defaults to `latest` when `container` has no tag or digest. | | `pull` | `always`\|`missing`\|`never` | no | Pull policy. Defaults to `missing`; see [Pull policy](#pull-policy). | -Run `tailor lock` to pin named tools-dir source digests in `tailor.lock`. Local-only tools-dir -images without a registry digest are usable by local image `Id`, but are not lockable. +Local-only tools-dir images without a registry digest are usable by their local image `Id`. ## Base-image catalogue diff --git a/tools/tailor/docs/tutorials/getting-started.md b/tools/tailor/docs/tutorials/getting-started.md index c91678374a..a96b55454f 100644 --- a/tools/tailor/docs/tutorials/getting-started.md +++ b/tools/tailor/docs/tutorials/getting-started.md @@ -54,17 +54,35 @@ base: version: "3.0" variant: minimal-os config: + previewFeatures: + - input-image-oci os: hostname: solo + bootloader: + resetType: hard-reset packages: install: - openssh-server services: enable: - sshd + storage: + bootType: efi + disks: + - partitionTableType: gpt + maxSize: 4G + partitions: + - { id: esp, type: esp, size: 8M } + - { id: rootfs, size: grow } + filesystems: + - { deviceId: esp, type: fat32, mountPoint: { path: /boot/efi, options: "umask=0077" } } + - { deviceId: rootfs, type: ext4, mountPoint: { path: / } } ``` -Top-level keys are tailor's. Everything under `config:` is Image Customizer configuration and is passed through opaquely. +This is the complete scaffold, with the storage lists shown in compact YAML. The +`input-image-oci` opt-in lets IC fetch the Azure Linux base; the growing root partition and +`hard-reset` bootloader setting support package installation. Top-level keys are tailor's; +`config:` passes through to IC. ## 4. Validate @@ -88,11 +106,33 @@ Expected shape: ```text 1 cell(s) (dry-run) -... -imagecustomizer ... --config-file ... --output-image-format cosi ... + +docker run \ + ... + mcr.microsoft.com/azurelinux/imagecustomizer:latest \ + customize \ + --config-file ... \ + ... + --output-image-format cosi \ + ... +``` + +`--dry-run` prints the container/IC invocation without starting a container or contacting an +engine. The host paths depend on your directory. + +## 6. Build the image (optional) + +With a reachable Docker daemon and registry access, run: + +```bash +tailor build ``` -`--dry-run` renders the build plan — the container/Image Customizer invocation — and prints it without starting a container. It contacts no container engine, so it works with no Docker daemon. Remove `--dry-run` when you have Docker daemon access and want to build the artifact. +The generated output is `artifacts/solo_amd64_cosi.cosi`. To check it: + +```bash +test -f artifacts/solo_amd64_cosi.cosi +``` ## Next step diff --git a/tools/tailor/docs/tutorials/your-first-matrix.md b/tools/tailor/docs/tutorials/your-first-matrix.md index 3930fbc455..1972d28439 100644 --- a/tools/tailor/docs/tutorials/your-first-matrix.md +++ b/tools/tailor/docs/tutorials/your-first-matrix.md @@ -120,21 +120,24 @@ tailor explain gizmo -s variant=full,arch=amd64,channel=edge --with-config Expected shape: ```text -gizmo: 1 cell(s) +cell gizmo_full_amd64_edge_cosi (arch=amd64, channel=edge, variant=full) -── gizmo_full_amd64_edge_cosi ── +merge order (top = base, bottom wins): + 1 image.yaml base + 2 by-variant/full.yaml variant=full + 3 by-arch/amd64.yaml arch=amd64 + 4 by-channel/edge.yaml channel=edge + +merged config: +previewFeatures: +- input-image-oci os: hostname: gizmo - packages: - install: - - openssh-server - - grub2-efi-x64 - - vim - - git - - gizmo-edge + ... ``` -The exact config depends on your edits. The important point: `explain --with-config` shows the fully merged IC config for selected cells (plain `explain` shows only the ordered list of fragment files that merge into each cell). +The full config also includes the merged package list and storage layout. `explain --with-config` +prints it after the fragment order; plain `explain` omits that last section. ## 6. Dry-run one selected cell