Skip to content

Add positron daily channel to bakery product map#567

Open
bschwedler wants to merge 46 commits into
worktree-dev-build-dispatchfrom
positron-init-preview-stream
Open

Add positron daily channel to bakery product map#567
bschwedler wants to merge 46 commits into
worktree-dev-build-dispatchfrom
positron-init-preview-stream

Conversation

@bschwedler

Copy link
Copy Markdown
Contributor

Adds POSITRON to ProductEnum and wires its daily CDN (cdn.posit.co/positron/dailies/pwb/{arch}/releases.json) into product_release_channel_url_map, enabling workbench-positron-init images to be built from the Positron daily channel.

The ReleaseChannelPath.get() call now formats channel_url with resolver metadata before fetching, which is how the arch-parameterized positron URL template gets resolved. The arch mapping handles both amd64 (Debian/Ubuntu) and x86_64 (RHEL) identifiers.

Stacked on:

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Test Results

1 635 tests   1 631 ✅  6m 39s ⏱️
    1 suites      0 💤
    1 files        4 ❌

For more details on these failures, see this check.

Results for commit 9706fbb.

♻️ This comment has been updated with latest results.

Base automatically changed from dev-stream-deprecation-tests to worktree-dev-build-dispatch June 5, 2026 15:15

@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.

This creates a bit of weirdness where Positron is considered as both a dependency and a product. We'll need to make it live in one of those worlds. I would vote for keeping it a dependency and finding a way to implement dev version or prerelease behaviors with dependency matrix builds.

I would still like a --latest flag for building matrix builds to single out the latest image only. Maybe a similar concept could be implemented for --pre-release to build the an image with dependency pre-releases if defined/available?

@ianpittwood

Copy link
Copy Markdown
Contributor

Another idea, we could move it fully into the product world and make a section for matrix.productConstraints that would work through the product resolver workflow.

@bschwedler bschwedler force-pushed the worktree-dev-build-dispatch branch 2 times, most recently from 2de6f5f to 5d99ecf Compare June 8, 2026 15:24
bschwedler added 15 commits June 8, 2026 10:54
'channel' is the established term in package distribution
(apt, conda, Homebrew, Chrome). ReleaseStreamEnum kept as
a deprecated alias for backward compatibility during migration.

PRODUCT_RELEASE_CHANNEL_SUPPORT_MAP likewise replaces
PRODUCT_RELEASE_STREAM_SUPPORT_MAP, with the old name kept
as a deprecated alias.
'channel' is the established term in package distribution
(apt, conda, Homebrew, Chrome). The bakery.yaml 'stream' key is
accepted with a deprecation warning during the migration period.

- Rename stream field → channel on ImageDevelopmentVersionFromProductStream
- Add model_validator to migrate 'stream' → 'channel' with a warning
- Update metadata key release_stream → release_channel in base.py
  and the filter lookup in version.py
- Update all test fixtures and test data to use channel=
Update stale ReleaseStreamEnum references left behind after the
stream→channel rename landed. No behaviour change — ReleaseStreamEnum
remains a deprecated alias pointing at ReleaseChannelEnum.

- base.py: import and get_release_stream return type
- version.py: import and matches_dev_filter parameter (dev_stream→dev_channel)
- test_version.py: import, parametrize arg name, test method docstring
- test_stream.py: remove unused ReleaseStreamEnum import, rename
  test_as_image_version_metadata_contains_release_stream, add edge-case
  test asserting channel wins when both stream and channel are supplied
dev_channel replaces dev_stream as the primary field.
dev_stream kept as a deprecated read-only property for any
existing code that reads it during the migration period.
--dev-stream is now hidden and emits a warning; --dev-channel
is the primary flag. Both are accepted during the migration period.
Product repos that still pass dev-stream continue to work unchanged.
The shared workflow translates both to --dev-channel for bakery.
Remove dev-stream from shared workflows in Phase 0.5c after all
product repos have migrated.
image_target.py:375 still read 'release_stream' from metadata after
the rename to 'release_channel', causing the Stream tag context
variable to always be empty for dev versions. Update both the source
and the corresponding test.
Two tests added to main after branching used {"release_stream": ...}
metadata and ReleaseStreamEnum directly; both needed to use
{"release_channel": ...} and ReleaseChannelEnum after Phase 0.5a.

Also fix image_target.py release_stream property to read
"release_channel" from metadata — ceb2bd8 updated the template
context string but missed the property that drives UID generation.
Rename release_stream property → release_channel, update import
from ReleaseStreamEnum → ReleaseChannelEnum, rename local variable,
update docstrings and error messages. Update callers in config.py
and affected tests.
BakerySettings(dev_stream=...) previously silently dropped the value
since dev_stream is a property, not a field. A model_validator(before)
now migrates it to dev_channel with a DeprecationWarning.

When both dev_stream and dev_channel are passed, dev_channel wins and
dev_stream is discarded. Two new tests cover both paths.
- Rename stream.py → channel.py; test_stream.py → test_channel.py
- Rename get_release_stream() → get_release_channel() in base.py and channel.py
- Rename ReleaseStreamResult → ReleaseChannelResult in main.py
- Rename ReleaseStreamPath → ReleaseChannelPath in main.py
- Rename get_product_artifact_by_stream → get_product_artifact_by_channel
- Rename product_release_stream_url_map → product_release_channel_url_map
- Rename stream_url → channel_url in ReleaseChannelPath
- Rename stream parameter → channel in get_product_artifact_by_channel
- Update all imports, patch paths, and call sites across test files
- Fix test names, local variables, and comments in test_image_target.py
  and test_version.py
Finishes the rename left incomplete in the initial commits:

- Rename ImageDevelopmentVersionFromProductStream →
  ImageDevelopmentVersionFromProductChannel
- Rename {{ Stream }} tag template variable → {{ Channel }}
- Rename ReleaseStreamEnum map keys → ReleaseChannelEnum in main.py
- Update field description "product stream" → "product channel"
- Rename test classes, methods, and variables accordingly
@bschwedler bschwedler force-pushed the worktree-dev-build-dispatch branch from e754ad3 to 56b82eb Compare June 8, 2026 15:55
@bschwedler bschwedler force-pushed the positron-init-preview-stream branch 4 times, most recently from 5af4cc6 to 8626fc7 Compare June 8, 2026 16:39
- tag.py: kwargs.get("Stream") → kwargs.get("Channel") in the
  render() guard. Since tag_template_values now uses "Channel" as the
  key, the old check always returned None, silently skipping every
  channel-based tag pattern and producing no channel tags.
- test_image.py: test_load_dev_versions was using the deprecated
  "stream" key; updated to "channel" since this isn't a compat test.
- test_image_target.py: stale "Stream" in a comment → "Channel".
bschwedler added 27 commits June 8, 2026 12:23
When set by a dispatch spec, pinned_version bypasses CDN discovery
and is forwarded to the stream resolver as version_override. For
template streams (PPM) this renders the URL offline; for manifest
streams (Connect, Workbench) this asserts manifest version matches.
DispatchVersionMismatchError is re-raised explicitly to escape the
per-OS drop logic in _resolve_os_urls.
The dispatched version is pinned onto the matching stream dev version
before load_dev_versions() runs, bypassing CDN discovery. Raises
ValueError on ambiguity (multiple matching dev versions with no
channel to disambiguate).

- Add DevBuildSpec field to BakerySettings
- Add _apply_dev_spec() module-level helper
- Call _apply_dev_spec() per image when dev_spec is set, before
  load_dev_versions()
Accepts a JSON payload via flag or BAKERY_DEV_SPEC env var.
Inert when absent; only active on workflow dispatch.
When both dev_spec.channel and --dev-channel are set and differ,
the pinned version would be silently excluded by the dev-channel
filter. Raise a clear ValueError instead. Also remove unused
json import in build.py.
Tests for both `bakery build` and `bakery ci matrix`, covering flag
and env var delivery, JSON parse errors, schema validation (extra
fields forbidden), and absence (dev_spec is None).

Follows the CliRunner + BakeryConfig.from_context mock pattern from
test_clean.py. A shared helper handles both positional (build) and
keyword (ci matrix) call_args extraction.
Product repos pass dev-version + dev-channel; the workflow
assembles a JSON dev-spec via jq and passes --dev-spec to
bakery ci matrix and bakery build. If dev-spec is passed
directly it takes precedence. Logic is inert when dev-version
is empty (scheduled and PR runs).

Both bakery-build-native.yml and bakery-build.yml receive the
new dev-version and dev-spec inputs, and all steps that call
bakery commands (matrix, build, push) are updated.
In both bakery-build.yml and bakery-build-native.yml matrix steps,
--dev-channel was appended unconditionally whenever DEV_CHANNEL was
set. When DEV_SPEC also carried a channel field, the channel-conflict
guard in _apply_dev_spec raised a ValueError, failing the matrix step.

Restructure the ARGS/spec assembly as an if/elif/elif chain: pass
--dev-spec when a spec is available (explicit or constructed from
DEV_VERSION), or --dev-channel only when no spec is involved. The
channel is already embedded in the constructed spec, so the separate
--dev-channel flag is redundant and conflicting in that path.
Completes the terminology migration started in Phase 0.5a:

- Rename get_release_stream() → get_release_channel() in base.py and channel.py
- Rename ReleaseStreamResult → ReleaseChannelResult in main.py
- Rename ReleaseStreamPath → ReleaseChannelPath in main.py
- Rename get_product_artifact_by_stream → get_product_artifact_by_channel in main.py
- Rename product_release_stream_url_map → product_release_channel_url_map in main.py
- Rename stream_url → channel_url attribute in ReleaseChannelPath
- Rename stream parameter → channel in get_product_artifact_by_channel
- Rename stream.py → channel.py and test_stream.py → test_channel.py
- Update all imports, patch paths, and call sites across test files

Backward-compat shims (--dev-stream CLI flag, sourceType: "stream" YAML
discriminator, and deprecation warning strings) are intentionally unchanged.
- Remove unused generalize_arch parameter from get_product_artifact_by_channel;
  callers control generalization via result.architecture_generalized_download_url
- Remove unused ReleaseStreamEnum import from channel.py
- Update docstrings in main.py, channel.py, base.py, and config.py to use
  "channel" terminology consistently after the stream→channel rename
_apply_dev_spec uses "BakerySettings" as a forward-reference string
annotation, but BakerySettings is defined at line 298 in the same
file — fully resolved before _apply_dev_spec at line 374. There is
no circular import and no from __future__ import annotations in use,
so the quotes serve no purpose. String annotations are only required
when the referenced name is not yet defined at annotation evaluation
time; that condition does not hold here.
The mode="before" validator checked "dev_channel" not in data to
decide whether to migrate dev_stream. Pydantic v2 includes explicitly
passed None values in the raw data dict, so
BakerySettings(dev_stream=DAILY, dev_channel=None) fell into the
elif branch and silently dropped dev_stream, leaving dev_channel=None.

Change the condition to data.get("dev_channel") is None so that an
explicit None is treated as unset. dev_channel still wins when it
carries a real value. Add a regression test for the explicit-None case.
- Move DispatchVersionMismatchError to posit_product/errors.py; update
  imports in main.py and channel.py
- Fix inconsistent .value formatting in version filter error message
Replace manual model_validate_json + bare Exception catch in each
command with a _parse_dev_spec callback on the option. The callback
raises typer.BadParameter on pydantic.ValidationError so the error
message is attributed correctly in --help output.
Removes the copy-pasted _parse_dev_spec callback from build.py and
ci.py and promotes it to cli/common.py as parse_dev_spec.

- Moves pydantic import to file level (was unjustified local import)
- Converts Optional[str] to str | None (modern union syntax)
- Splits model_validate_json into json.loads + model_validate so JSON
  parse errors produce "not valid JSON: <msg>" rather than a verbose
  pydantic ValidationError dump
- Adds an explanatory comment on the # type: ignore[arg-type]: typer
  requires str | None so Click maps it to STRING; the callback
  delivers DevBuildSpec at runtime
Aligns the field name with the version_override= kwarg already used
when forwarding it to get_product_artifact_by_channel, and with the
general pattern of "override" for externally-supplied values layered
onto an existing object.

Also fixes two stale ImageDevelopmentVersionFromProductStream
references in test_channel.py and config.py that survived the
stream→channel rename because they were in newly-added code rather
than edited lines.
ci merge, ci readme, clean cache-registry, clean temp-registry,
get tags, and run dgoss all received the deprecated --dev-stream
alias and coalesce block in the dev-build-dispatch branch, but
only bakery build and ci matrix had CLI coverage for that behavior.

Each command gets two tests: --dev-stream alone coalesces to
dev_channel, and --dev-channel takes precedence when both are given.
Both test_dev_spec.py and test_dev_stream_deprecated.py duplicated
the same from_context call-args extraction logic. Extract it to
test/cli/conftest.py as settings_from_call so there is one place
to update if the calling convention changes.
Add ProductEnum.POSITRON and support for DAILY release
channel. The POSITRON_DAILY_CDN_URL_TEMPLATE includes a
{positron_cdn_arch} placeholder that will be formatted at
fetch time by Task 3.
Add POSITRON to product_release_channel_url_map with a DAILY channel
backed by POSITRON_DAILY_CDN_URL_TEMPLATE. The channel URL contains a
{positron_cdn_arch} placeholder resolved via format_map before the
HTTP request, so the URL is arch-aware.

- Add positron_cdn_arch and positron_pkg_arch keys to
  _make_resolver_metadata (amd64 → x86_64/x64)
- Apply format_map(metadata) to channel_url before fetching so
  {positron_cdn_arch} resolves; no-op for static URLs used by
  existing products
- Add POSITRON_DAILY_CDN_URL_TEMPLATE import
- Add 4 parametrized test cases for the new metadata keys
Created positron_daily_x86_64.json and positron_daily_arm64.json
testdata files and added URL pattern matching in the
patch_testdata_response fixture to map requests to the POSITRON_DAILY
CDN endpoints to the local testdata files for unit tests.
The x86_64 identifier is used in some contexts (e.g., RHEL 9) and
should map to the same CDN paths and package architecture as amd64,
not trigger a fallback to x86_64 in the package arch mapping.

Added test coverage for rocky, alma, and rhel with the x86_64
identifier to prevent regression.
Verify that pinned version_override bypasses the network call to
CachedSession by asserting the mock is never called. This prevents
future regressions where the offline path could be broken but the
test would still pass due to CDN returning matching data by
coincidence.
Positron was tracked as both a dependency (via SupportedDependencies.POSITRON
in the matrix) and a product (via ProductEnum.POSITRON for daily preview
builds), creating a dual-world conflict raised in issue #472.

This replaces the product-side Positron code entirely with a generic
ImageDevelopmentVersionFromDependencyPrerelease type (sourceType:
dependency-prerelease) that resolves its version through the existing
dependency constraint infrastructure with prerelease=True. The
Containerfile template handles download URL construction from Image.Version
and values passed via the devVersions values: field.

- Add POSITRON_DAILY_URL_TEMPLATE to dependencies/const.py
- Add prerelease: bool and daily_url() to PositronDependency; update
  _fetch_versions() to append the daily build when prerelease=True
- Add ImageDevelopmentVersionFromDependencyPrerelease (sourceType:
  dependency-prerelease) in dev_version/dependency.py; register in
  DevelopmentVersionTypes union
- Remove ProductEnum.POSITRON, POSITRON_DAILY_CDN_URL_TEMPLATE, and all
  product-side Positron resolver code from posit_product/
- Move positron daily testdata from posit_products/ to dependencies/
- Update conftest mock routing from product const to dependency const
Rename ImageDevelopmentVersionFromDependencyPrerelease to
ImageDevelopmentVersionFromDependency and change sourceType from
"dependency-prerelease" to "dependency".

Add a prerelease: bool = False field to the class, passed through to
the constraint constructor, replacing the hardcoded True. This makes
prerelease intent explicit in bakery.yaml (on the devVersions entry)
rather than implied by the sourceType name.

The constraint class retains its prerelease field as an implementation
detail; the devVersions prerelease field is the single user-facing
point of control for prerelease resolution.
@bschwedler bschwedler force-pushed the worktree-dev-build-dispatch branch from f6f027d to cea46e4 Compare June 8, 2026 17:24
@bschwedler bschwedler force-pushed the positron-init-preview-stream branch from 8626fc7 to 9706fbb Compare June 8, 2026 17:24
@bschwedler bschwedler force-pushed the worktree-dev-build-dispatch branch from cea46e4 to ca1be12 Compare June 8, 2026 20:10
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.

2 participants