Skip to content

Add scratch OS support#578

Open
bschwedler wants to merge 16 commits into
mainfrom
feat/scratch-os-channel
Open

Add scratch OS support#578
bschwedler wants to merge 16 commits into
mainfrom
feat/scratch-os-channel

Conversation

@bschwedler

Copy link
Copy Markdown
Contributor

Enables images that don't vary by OS — no OS suffix in tags or Containerfile names. The artifactOs field handles the dev-channel case where the channel API has no scratch-native artifact entries.

@bschwedler bschwedler requested a review from ianpittwood as a code owner June 9, 2026 18:22
@bschwedler bschwedler linked an issue Jun 9, 2026 that may be closed by this pull request
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Test Results

1 733 tests  +22   1 732 ✅ +21   8m 11s ⏱️ -36s
    1 suites ± 0       0 💤 ± 0 
    1 files   ± 0       1 ❌ + 1 

For more details on these failures, see this check.

Results for commit 094db41. ± Comparison against base commit ddaf099.

♻️ This comment has been updated with latest results.

@ianpittwood ianpittwood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of this is making sense, but the branch seems like it was polluted with another change

Comment thread .claude/settings.json
Comment thread .github/workflows/bakery-build-pr.yml
Comment thread posit-bakery/posit_bakery/cli/ci.py
Comment thread posit-bakery/posit_bakery/config/image/version_os.py Outdated
Comment thread posit-bakery/posit_bakery/config/image/version_os.py Outdated
Introduce `_OSLESS_NAMES` (containing "scratch") in `shared.py`.
The `default_factory` lambdas for `ExtensionField` and
`TagDisplayNameField` now return `""` when the OS name is in that
set. The `pattern` validators are relaxed from `+` (one-or-more) to
`*` (zero-or-more) so explicitly-supplied empty strings are also
accepted.
Move the inline name→BuildOS resolution logic from the field validator
into a module-level function so Task 3 can reuse it without duplication.
No behavior change; all existing tests pass.
Adds an optional `artifactOs` field to `ImageVersionOS` for dev versions
that use a scratch OS but need to resolve artifact download URLs against a
real OS (e.g. ubuntu-24.04).

The `artifact_build_os` property returns the resolved `BuildOS` for
`artifactOs` when set, falling back to `buildOS` otherwise.

Also fixes trailing-hyphen stripping in `_resolve_name_to_build_os` so
that hyphenated OS names like "ubuntu-24.04" resolve correctly, and
restores the comment explaining int conversion before max() for numeric
ordering.
The assertion `Containerfile.{previous_os.extension}` produced
`Containerfile.` when scratch OS has an empty extension field.

Fall back to the lowercased OS name when extension is empty, matching
the actual rendered filename (`Containerfile.scratch`).
Consolidates the four duplicated OS validators from version.py,
dev_version/base.py, and matrix.py into a single mixin with generic
messages. Adds a fifth validator, error_untaggable_os, which must
run after make_single_os_primary (declaration order enforces this)
to catch non-primary OSes with an empty tagDisplayName.
Wire OSValidatorMixin into ImageVersion, BaseImageDevelopmentVersion,
and ImageMatrix, removing the four duplicate @field_validator("os")
methods from each class body.

- Add sourceType to the mixin's name-guard so dev version classes
  (which have no name/namePattern field) still trigger warnings and
  errors correctly
- Update all test message assertions to match the mixin's generic
  messages ("image configuration" instead of class-specific strings)
- Add three new tests for error_untaggable_os behaviour on scratch OS
build() called self.image_os.platforms directly at the docker.build
call site, crashing with AttributeError when image_os is None.
build_platforms was already computed safely with a null guard two
lines earlier — use it consistently.

Adds two tests:
- scratch OS (empty extension) + no variant produces a plain Containerfile
- build() uses DEFAULT_PLATFORMS instead of crashing when image_os is None
Replace the three buildOS call sites in get_version(),
get_url_by_os(), and _resolve_os_urls() with
artifact_build_os. This ensures scratch OS entries (which
have family=UNKNOWN) delegate artifact resolution to their
configured artifactOs instead of failing.
The scratch OS now uses empty extension/tagDisplayName, producing
a bare Containerfile (no suffix) and OS-unsuffixed tags (only
version and :latest, no :scratch or :1.0.0-scratch).

- Rename Containerfile.scratch → Containerfile and
  Containerfile.scratch.jinja2 → Containerfile.jinja2 in the
  barebones fixture
- Update test_config.py assertions to use bare Containerfile
  instead of Containerfile.scratch
- Drop OS-suffix tags from get/tags/barebones expected JSON
- Add multiversion context and default matrix testdata
Commit 456788a2 updated the barebones bakery.yaml to use empty extension
for Scratch OS and renamed the Containerfile, but the bake plan JSON
fixtures still referenced Containerfile.scratch and included OS-suffix
tags (1.0.0-scratch, scratch). Update the three bake testdata fixtures
to match the new Scratch OS behavior.
- test_dgoss_environment: use full dict equality after delenv(GITHUB_ACTIONS)
  so extra keys (e.g. GH_TOKEN leaking in CI) cannot go undetected
- test_resolve_os_urls_uses_artifact_os_for_scratch: capture _resolve_os_urls()
  return value and assert artifactDownloadURL was set, not just that the mock
  was called with the right BuildOS
- Rename fail-fast Containerfile.scratch.{min,std} to Containerfile.{min,std}
  so bakery finds them now that Scratch OS defaults to empty extension; fixes
  test_building_images_from_a_project_using_sequential_build_with_failfast
  failing with BakeryFileError under just test-all
Python 3.14 raises DeprecationWarning for invalid escape sequences
processed through Jinja2's unicode-escape codec. The string literal
'\d' inside the Jinja2 template triggers this; '\\d' makes Jinja2
decode it to \d correctly without the warning.
Move _OSLESS_NAMES and the scratch-aware default_factory out of
shared.py into version_os.py. The shared ExtensionField/
TagDisplayNameField are used by ImageVariant too; keeping the
scratch check there would silently produce empty extension/
tagDisplayName for any variant named "scratch", risking tag and
Containerfile collisions.

- shared.py: restore original default_factory and + patterns
- version_os.py: own _OSLESS_NAMES and * patterns locally
@bschwedler bschwedler force-pushed the feat/scratch-os-channel branch from d221142 to a46ea62 Compare June 11, 2026 19:46
Lines 76 and 91 of version_os.py had inline pattern strings for
extension and tagDisplayName validation. Extract them to named
constants in const.py alongside the existing tag-suffix pattern.
Duplicate OS entries that share the same name/extension/tagDisplayName
but have different artifactOs values represent a config conflict —
the set-based deduplication in deduplicate_os would silently drop one,
producing incorrect artifact URL resolution. Detect this case and raise
a ValueError instead of collapsing silently.
@bschwedler bschwedler requested a review from ianpittwood June 11, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support OS-less images in Bakery

2 participants