diff --git a/.github/workflows/bakery-build-native.yml b/.github/workflows/bakery-build-native.yml
index 0a9d9f54..73d57d08 100644
--- a/.github/workflows/bakery-build-native.yml
+++ b/.github/workflows/bakery-build-native.yml
@@ -27,6 +27,21 @@ on:
default: "exclude"
required: false
type: string
+ image-version:
+ description: "Filter to a specific image version (e.g. product version from upstream dispatch)"
+ default: ""
+ required: false
+ type: string
+ dev-stream:
+ description: "Filter dev versions to a specific release stream (e.g. 'daily', 'preview')"
+ default: ""
+ required: false
+ type: string
+ dev-channel:
+ description: "Upstream channel reference (e.g. branch codename for Workbench preview)"
+ default: ""
+ required: false
+ type: string
push:
description: "Whether to push images to registries [default: false]"
default: false
@@ -105,17 +120,31 @@ jobs:
env:
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ IMAGE_VERSION: ${{ inputs.image-version }}
CONTEXT: ${{ inputs.context }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
run: |
- echo "platform_matrix=$(bakery ci matrix --quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --context "$CONTEXT" | jq --compact-output .)" >> $GITHUB_OUTPUT
+ ARGS=(--quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --context "$CONTEXT")
+ [[ -n "$IMAGE_VERSION" ]] && ARGS+=(--image-version "$IMAGE_VERSION")
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ echo "platform_matrix=$(bakery ci matrix "${ARGS[@]}" | jq --compact-output .)" >> $GITHUB_OUTPUT
- name: Images by Version
id: images-by-version
env:
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ IMAGE_VERSION: ${{ inputs.image-version }}
CONTEXT: ${{ inputs.context }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
run: |
- echo "versions_matrix=$(bakery ci matrix --quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --exclude platform --context "$CONTEXT" | jq --compact-output .)" >> $GITHUB_OUTPUT
+ ARGS=(--quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --exclude platform --context "$CONTEXT")
+ [[ -n "$IMAGE_VERSION" ]] && ARGS+=(--image-version "$IMAGE_VERSION")
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ echo "versions_matrix=$(bakery ci matrix "${ARGS[@]}" | jq --compact-output .)" >> $GITHUB_OUTPUT
build-test:
name: "Build/Test ${{ matrix.img.image }}:${{ matrix.img.version }} (${{ matrix.img.platform }})"
@@ -209,24 +238,22 @@ jobs:
IMAGE_PLATFORM: ${{ matrix.img.platform }}
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
REGISTRY: ghcr.io/${{ github.repository_owner }}
NORMALIZED_PLATFORM: ${{ steps.normalize-platform.outputs.platform }}
CONTEXT: ${{ inputs.context }}
# Cache-to is conditional on --push (handled by bakery internally)
run: |
- bakery build \
- --strategy build --pull \
- --retry "$RETRY" \
- --image-name "^${IMAGE_NAME}$" \
- --image-version "$IMAGE_VERSION" \
- --image-platform "$IMAGE_PLATFORM" \
- --dev-versions "$DEV_VERSIONS" \
- --matrix-versions "$MATRIX_VERSIONS" \
- --cache-registry "$REGISTRY" \
- --temp-registry "$REGISTRY" \
- --metadata-file "./${IMAGE_NAME}-${IMAGE_VERSION}-${NORMALIZED_PLATFORM}-metadata.json" \
- --context "$CONTEXT" \
- --push
+ ARGS=(--strategy build --pull --retry "$RETRY")
+ ARGS+=(--image-name "^${IMAGE_NAME}$" --image-version "$IMAGE_VERSION" --image-platform "$IMAGE_PLATFORM")
+ ARGS+=(--dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS")
+ ARGS+=(--cache-registry "$REGISTRY" --temp-registry "$REGISTRY")
+ ARGS+=(--metadata-file "./${IMAGE_NAME}-${IMAGE_VERSION}-${NORMALIZED_PLATFORM}-metadata.json")
+ ARGS+=(--context "$CONTEXT" --push)
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ bakery build "${ARGS[@]}"
- name: Test
env:
IMAGE_NAME: ${{ matrix.img.image }}
@@ -234,19 +261,20 @@ jobs:
IMAGE_PLATFORM: ${{ matrix.img.platform }}
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
NORMALIZED_PLATFORM: ${{ steps.normalize-platform.outputs.platform }}
CONTEXT: ${{ inputs.context }}
run: |
+ ARGS=(--image-name "^${IMAGE_NAME}$" --image-version "$IMAGE_VERSION" --image-platform "$IMAGE_PLATFORM")
+ ARGS+=(--dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS")
+ ARGS+=(--metadata-file "./${IMAGE_NAME}-${IMAGE_VERSION}-${NORMALIZED_PLATFORM}-metadata.json")
+ ARGS+=(--context "$CONTEXT")
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
GOSS_PATH=${GITHUB_WORKSPACE}/tools/goss \
DGOSS_PATH=${GITHUB_WORKSPACE}/tools/dgoss \
- bakery run dgoss \
- --image-name "^${IMAGE_NAME}$" \
- --image-version "$IMAGE_VERSION" \
- --image-platform "$IMAGE_PLATFORM" \
- --dev-versions "$DEV_VERSIONS" \
- --matrix-versions "$MATRIX_VERSIONS" \
- --metadata-file "./${IMAGE_NAME}-${IMAGE_VERSION}-${NORMALIZED_PLATFORM}-metadata.json" \
- --context "$CONTEXT"
+ bakery run dgoss "${ARGS[@]}"
- name: Upload Metadata
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -346,13 +374,12 @@ jobs:
CONTEXT: ${{ inputs.context }}
REGISTRY: ghcr.io/${{ github.repository_owner }}
PUSH: ${{ inputs.push }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
run: |
- if [ "$PUSH" = "true" ]; then PUSH_FLAG=""; else PUSH_FLAG="--dry-run"; fi
- bakery ci merge \
- --context "$CONTEXT" \
- --temp-registry "$REGISTRY" \
- $PUSH_FLAG \
- *-metadata.json
+ ARGS=(--context "$CONTEXT" --temp-registry "$REGISTRY")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ if [ "$PUSH" != "true" ]; then ARGS+=(--dry-run); fi
+ bakery ci merge "${ARGS[@]}" *-metadata.json
readme:
name: Push READMEs
@@ -379,8 +406,8 @@ jobs:
CONTEXT: ${{ inputs.context }}
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
run: |
- bakery ci readme \
- --context "$CONTEXT" \
- --dev-versions "$DEV_VERSIONS" \
- --matrix-versions "$MATRIX_VERSIONS"
+ ARGS=(--context "$CONTEXT" --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ bakery ci readme "${ARGS[@]}"
diff --git a/.github/workflows/bakery-build.yml b/.github/workflows/bakery-build.yml
index 9f33329a..fc81cb59 100644
--- a/.github/workflows/bakery-build.yml
+++ b/.github/workflows/bakery-build.yml
@@ -28,6 +28,21 @@ on:
default: "exclude"
required: false
type: string
+ image-version:
+ description: "Filter to a specific image version (e.g. product version from upstream dispatch)"
+ default: ""
+ required: false
+ type: string
+ dev-stream:
+ description: "Filter dev versions to a specific release stream (e.g. 'daily', 'preview')"
+ default: ""
+ required: false
+ type: string
+ dev-channel:
+ description: "Upstream channel reference (e.g. branch codename for Workbench preview)"
+ default: ""
+ required: false
+ type: string
push:
description: "Whether to push images to registries [default: false]"
default: false
@@ -95,9 +110,16 @@ jobs:
env:
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ IMAGE_VERSION: ${{ inputs.image-version }}
CONTEXT: ${{ inputs.context }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
run: |
- echo "matrix=$(bakery ci matrix --quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --context "$CONTEXT" | jq --compact-output .)" >> $GITHUB_OUTPUT
+ ARGS=(--quiet --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS" --context "$CONTEXT")
+ [[ -n "$IMAGE_VERSION" ]] && ARGS+=(--image-version "$IMAGE_VERSION")
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ echo "matrix=$(bakery ci matrix "${ARGS[@]}" | jq --compact-output .)" >> $GITHUB_OUTPUT
build:
name: "${{ matrix.img.image }}:${{ matrix.img.version }}"
@@ -174,17 +196,18 @@ jobs:
IMAGE_VERSION: ${{ matrix.img.version }}
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
REGISTRY: ghcr.io/${{ github.repository_owner }}
CONTEXT: ${{ inputs.context }}
run: |
- bakery build --load --pull \
- --retry "$RETRY" \
- --image-name "^${IMAGE_NAME}$" \
- --image-version "$IMAGE_VERSION" \
- --dev-versions "$DEV_VERSIONS" \
- --matrix-versions "$MATRIX_VERSIONS" \
- --cache-registry "$REGISTRY" \
- --context "$CONTEXT"
+ ARGS=(--load --pull --retry "$RETRY")
+ ARGS+=(--image-name "^${IMAGE_NAME}$" --image-version "$IMAGE_VERSION")
+ ARGS+=(--dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS")
+ ARGS+=(--cache-registry "$REGISTRY" --context "$CONTEXT")
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ bakery build "${ARGS[@]}"
- name: Test
env:
@@ -192,16 +215,18 @@ jobs:
IMAGE_VERSION: ${{ matrix.img.version }}
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
CONTEXT: ${{ inputs.context }}
run: |
+ ARGS=(--image-name "^${IMAGE_NAME}$" --image-version "$IMAGE_VERSION")
+ ARGS+=(--dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS")
+ ARGS+=(--context "$CONTEXT")
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
GOSS_PATH=${GITHUB_WORKSPACE}/tools/goss \
DGOSS_PATH=${GITHUB_WORKSPACE}/tools/dgoss \
- bakery run dgoss \
- --image-name "^${IMAGE_NAME}$" \
- --image-version "$IMAGE_VERSION" \
- --dev-versions "$DEV_VERSIONS" \
- --matrix-versions "$MATRIX_VERSIONS" \
- --context "$CONTEXT"
+ bakery run dgoss "${ARGS[@]}"
- name: Push
# Since this is a reusable workflow, we need to be very explicit about
@@ -215,15 +240,17 @@ jobs:
IMAGE_VERSION: ${{ matrix.img.version }}
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ DEV_STREAM: ${{ inputs.dev-stream }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
CONTEXT: ${{ inputs.context }}
run: |
- bakery build --push --pull \
- --retry "$RETRY" \
- --image-name "^${IMAGE_NAME}$" \
- --image-version "$IMAGE_VERSION" \
- --dev-versions "$DEV_VERSIONS" \
- --matrix-versions "$MATRIX_VERSIONS" \
- --context "$CONTEXT"
+ ARGS=(--push --pull --retry "$RETRY")
+ ARGS+=(--image-name "^${IMAGE_NAME}$" --image-version "$IMAGE_VERSION")
+ ARGS+=(--dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS")
+ ARGS+=(--context "$CONTEXT")
+ [[ -n "$DEV_STREAM" ]] && ARGS+=(--dev-stream "$DEV_STREAM")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ bakery build "${ARGS[@]}"
readme:
name: Push READMEs
@@ -250,8 +277,8 @@ jobs:
CONTEXT: ${{ inputs.context }}
DEV_VERSIONS: ${{ inputs.dev-versions }}
MATRIX_VERSIONS: ${{ inputs.matrix-versions }}
+ DEV_CHANNEL: ${{ inputs.dev-channel }}
run: |
- bakery ci readme \
- --context "$CONTEXT" \
- --dev-versions "$DEV_VERSIONS" \
- --matrix-versions "$MATRIX_VERSIONS"
+ ARGS=(--context "$CONTEXT" --dev-versions "$DEV_VERSIONS" --matrix-versions "$MATRIX_VERSIONS")
+ [[ -n "$DEV_CHANNEL" ]] && ARGS+=(--value "channel=$DEV_CHANNEL")
+ bakery ci readme "${ARGS[@]}"
diff --git a/posit-bakery/docs/cross-repo-workflows.qmd b/posit-bakery/docs/cross-repo-workflows.qmd
index a2e1ec1e..07a07a02 100644
--- a/posit-bakery/docs/cross-repo-workflows.qmd
+++ b/posit-bakery/docs/cross-repo-workflows.qmd
@@ -56,53 +56,44 @@ posit-platform is a future option once the per-product chains are stable.
%%| fig-cap: "Production Release Flow"
graph TD
subgraph "Product Repos"
- CONNECT_PROD["posit-dev/connect"]
- WORKBENCH_PROD["rstudio/rstudio-pro"]
- PPM_PROD["rstudio/package-manager"]
+ CONNECT_PROD["connect"]
+ WORKBENCH_PROD["rstudio-pro"]
+ PPM_PROD["package-manager"]
end
- CONNECT_BOT["posit-connect-projects"]
- WORKBENCH_BOT["workbench-ide-release"]
- PPM_BOT["posit-ppm"]
-
- CONNECT_PROD -.-> CONNECT_BOT
- WORKBENCH_PROD -.-> WORKBENCH_BOT
- PPM_PROD -.-> PPM_BOT
-
- CONNECT_BOT -.->|"workflow_dispatch release.yml
(version)"| IMG_CONNECT
- WORKBENCH_BOT -.->|"workflow_dispatch release.yml
(version)"| IMG_WORKBENCH
- PPM_BOT -.->|"workflow_dispatch release.yml
(version)"| IMG_PM
-
- SHARED["posit-dev/images-shared
bakery-build-native
bakery-build
product-release
clean"]
+ CONNECT_PROD -.->|"dispatch release.yml
version"| IMG_CONNECT
+ WORKBENCH_PROD -.->|"dispatch release.yml
version"| IMG_WORKBENCH
+ PPM_PROD -.->|"dispatch release.yml
version"| IMG_PM
subgraph "Image Repos"
- IMG_CONNECT["posit-dev/images-connect
production
content
release"]
- IMG_WORKBENCH["posit-dev/images-workbench
production
session
release"]
- IMG_PM["posit-dev/images-package-manager
production
release"]
+ IMG_CONNECT["images-connect"]
+ IMG_WORKBENCH["images-workbench"]
+ IMG_PM["images-package-manager"]
end
- IMG_CONNECT -.->|workflow_call| SHARED
- IMG_WORKBENCH -.->|workflow_call| SHARED
- IMG_PM -.->|workflow_call| SHARED
+ IMG_CONNECT -.->|"workflow_call
bakery-build-native.yml"| SHARED
+ IMG_WORKBENCH -.->|"workflow_call
bakery-build-native.yml"| SHARED
+ IMG_PM -.->|"workflow_call
bakery-build-native.yml"| SHARED
- IMG_CONNECT -->|push| DOCKERHUB
- IMG_CONNECT -->|push| GHCR
- IMG_WORKBENCH -->|push| DOCKERHUB
- IMG_WORKBENCH -->|push| GHCR
- IMG_PM -->|push| DOCKERHUB
- IMG_PM -->|push| GHCR
+ subgraph images-shared
+ SHARED["bakery-build-native.yml
product-release.yml"]
+ end
+
+ IMG_CONNECT -->|push| REGISTRIES
+ IMG_WORKBENCH -->|push| REGISTRIES
+ IMG_PM -->|push| REGISTRIES
- DOCKERHUB["Docker Hub"]
- GHCR["GHCR"]
+ REGISTRIES["Docker Hub + GHCR"]
- IMG_CONNECT -.->|"workflow_dispatch product-release.yml"| HELM
- IMG_WORKBENCH -.->|"workflow_dispatch product-release.yml"| HELM
- IMG_PM -.->|"workflow_dispatch product-release.yml"| HELM
+ IMG_CONNECT -.->|"dispatch product-release.yml
product, version"| HELM_WF
+ IMG_WORKBENCH -.->|"dispatch product-release.yml
product, version"| HELM_WF
+ IMG_PM -.->|"dispatch product-release.yml
product, version"| HELM_WF
- HELM["rstudio/helm
product-release
chart-releaser"]
- HELM -->|Flux sync| K8S
+ subgraph helm
+ HELM_WF["product-release.yml"]
+ end
- K8S["K8s Dogfood Sites"]
+ HELM_WF -->|Flux sync| K8S["K8s Dogfood Sites"]
```
## Development / Preview Flow
@@ -111,32 +102,28 @@ graph TD
%%| fig-cap: "Development / Preview Flow"
graph TD
subgraph "Product Repos"
- CONNECT_PROD["posit-dev/connect"]
- WORKBENCH_PROD["rstudio/rstudio-pro"]
- PPM_PROD["rstudio/package-manager"]
+ CONNECT_PROD["connect"]
+ WORKBENCH_PROD["rstudio-pro"]
+ PPM_PROD["package-manager"]
end
- CONNECT_BOT["posit-connect-projects"]
- WORKBENCH_BOT["workbench-ide-release"]
- PPM_BOT["posit-ppm"]
+ CONNECT_PROD -.->|"dispatch development.yml
version"| IMG_CONNECT
+ WORKBENCH_PROD -.->|"dispatch development.yml
version, stream, channel"| IMG_WORKBENCH
+ PPM_PROD -.->|"dispatch development.yml
version, stream"| IMG_PM
- CONNECT_PROD -.-> CONNECT_BOT
- WORKBENCH_PROD -.-> WORKBENCH_BOT
- PPM_PROD -.-> PPM_BOT
-
- CONNECT_BOT -.->|"workflow_dispatch development.yml
(version)"| IMG_CONNECT
- WORKBENCH_BOT -.->|"workflow_dispatch development.yml
(version)"| IMG_WORKBENCH
- PPM_BOT -.->|"workflow_dispatch development.yml
(version)"| IMG_PM
-
- SHARED["posit-dev/images-shared
bakery-build-native
bakery-build"]
+ subgraph "Image Repos"
+ IMG_CONNECT["images-connect
development.yml"]
+ IMG_WORKBENCH["images-workbench
development.yml"]
+ IMG_PM["images-package-manager
development.yml"]
+ end
- IMG_CONNECT["posit-dev/images-connect
development"]
- IMG_WORKBENCH["posit-dev/images-workbench
development"]
- IMG_PM["posit-dev/images-package-manager
development"]
+ IMG_CONNECT -.->|"workflow_call
dev-versions, image-version,
dev-stream"| SHARED
+ IMG_WORKBENCH -.->|"workflow_call
dev-versions, image-version,
dev-stream, dev-channel"| SHARED
+ IMG_PM -.->|"workflow_call
dev-versions, image-version,
dev-stream"| SHARED
- IMG_CONNECT -.->|workflow_call| SHARED
- IMG_WORKBENCH -.->|workflow_call| SHARED
- IMG_PM -.->|workflow_call| SHARED
+ subgraph images-shared
+ SHARED["bakery-build-native.yml"]
+ end
IMG_CONNECT -->|preview push| GHCR
IMG_WORKBENCH -->|preview push| GHCR
@@ -144,13 +131,9 @@ graph TD
GHCR["GHCR
connect-preview
workbench-preview
workbench-session-init-preview
package-manager-preview"]
- GHCR --> K8S
- GHCR --> FUZZBUCKET
- GHCR --> EKS_REF
-
- K8S["K8s Dogfood Sites"]
- FUZZBUCKET["Fuzzbucket
IDE Automation"]
- EKS_REF["EKS Reference Architecture"]
+ GHCR --> K8S["K8s Dogfood Sites"]
+ GHCR --> FUZZBUCKET["Fuzzbucket
IDE Automation"]
+ GHCR --> EKS_REF["EKS Reference Architecture"]
```
## Per-Product Diagrams
@@ -162,38 +145,53 @@ graph TD
```{mermaid}
%%| fig-cap: "Connect Production Flow"
graph TD
- PROD["posit-dev/connect
release-scripts.yml"]
- BOT["posit-connect-projects"]
+ subgraph connect
+ SCRIPTS["release-scripts.yml
publish_release.py"]
+ end
+
+ SCRIPTS -.->|"workflow_dispatch
version"| REL
- PROD -.-> BOT
- BOT -.->|"workflow_dispatch release.yml
(version)"| IMG_RELEASE
+ subgraph images-connect
+ REL["release.yml"]
+ PROD_WF["production.yml"]
+ CONTENT["content.yml"]
+ HELM_JOB["update-helm job"]
+ end
- SHARED["posit-dev/images-shared
bakery-build-native
product-release"]
+ REL -.->|"workflow_call
version, images"| PRODUCT_REL
- IMG_RELEASE["posit-dev/images-connect
release"]
- IMG_PROD["posit-dev/images-connect
production"]
- IMG_CONTENT["posit-dev/images-connect
content"]
+ subgraph images-shared
+ PRODUCT_REL["product-release.yml"]
+ SHARED["bakery-build-native.yml"]
+ end
- IMG_RELEASE -.->|workflow_call| SHARED
- IMG_PROD -.->|workflow_call| SHARED
- IMG_CONTENT -.->|workflow_call| SHARED
+ REL -->|"PR merge triggers"| PROD_WF
+ REL -->|"PR merge triggers"| CONTENT
- IMG_RELEASE -->|"merge to main"| IMG_PROD
- IMG_RELEASE -->|"merge to main"| IMG_CONTENT
+ PROD_WF -.->|"workflow_call
dev-versions=exclude"| SHARED
+ CONTENT -.->|"workflow_call
matrix-versions=only"| SHARED
- IMG_PROD -->|push| DOCKERHUB
- IMG_PROD -->|push| GHCR
- IMG_CONTENT -->|push| DOCKERHUB
- IMG_CONTENT -->|push| GHCR
+ PROD_WF --> HELM_JOB
+ HELM_JOB -.->|"workflow_dispatch
product=connect, version"| HELM_WF
- DOCKERHUB["Docker Hub
rstudio/rstudio-connect
rstudio/rstudio-connect-content-init"]
- GHCR["GHCR"]
+ PROD_WF -->|push| DOCKERHUB["Docker Hub"]
+ PROD_WF -->|push| GHCR["GHCR"]
+ CONTENT -->|push| DOCKERHUB
+ CONTENT -->|push| GHCR
- IMG_PROD -.->|"workflow_dispatch product-release.yml"| HELM
- HELM["rstudio/helm
product-release
chart-releaser"]
- HELM -->|Flux sync| K8S
+ subgraph helm
+ HELM_WF["product-release.yml"]
+ end
- K8S["K8s Dogfood Sites"]
+ HELM_WF -->|Flux sync| K8S["K8s Dogfood Sites"]
+
+ click SCRIPTS "https://github.com/posit-dev/connect/blob/main/.github/workflows/release-scripts.yml" _blank
+ click REL "https://github.com/posit-dev/images-connect/blob/main/.github/workflows/release.yml" _blank
+ click PROD_WF "https://github.com/posit-dev/images-connect/blob/main/.github/workflows/production.yml" _blank
+ click CONTENT "https://github.com/posit-dev/images-connect/blob/main/.github/workflows/content.yml" _blank
+ click PRODUCT_REL "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/product-release.yml" _blank
+ click SHARED "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/bakery-build-native.yml" _blank
+ click HELM_WF "https://github.com/rstudio/helm/blob/main/.github/workflows/product-release.yml" _blank
```
#### Development {.unnumbered}
@@ -201,25 +199,28 @@ graph TD
```{mermaid}
%%| fig-cap: "Connect Development Flow"
graph TD
- PROD["posit-dev/connect
ci.yml (push to main)"]
- BOT["posit-connect-projects"]
-
- PROD -.-> BOT
- BOT -.->|"workflow_dispatch development.yml
(version)"| IMG_DEV
-
- SHARED["posit-dev/images-shared
bakery-build-native"]
+ subgraph connect
+ CI["ci.yml"]
+ end
- IMG_DEV["posit-dev/images-connect
development"]
+ CI -.->|"workflow_dispatch
version"| DEV
- IMG_DEV -.->|workflow_call| SHARED
+ subgraph images-connect
+ DEV["development.yml"]
+ end
- IMG_DEV -->|preview push| GHCR
+ DEV -.->|"workflow_call
dev-versions, image-version,
dev-stream"| SHARED
- GHCR["GHCR
connect-preview"]
+ subgraph images-shared
+ SHARED["bakery-build-native.yml"]
+ end
- GHCR --> K8S
+ DEV -->|preview push| GHCR["GHCR
connect-preview"]
+ GHCR --> K8S["K8s Dogfood Sites"]
- K8S["K8s Dogfood Sites"]
+ click CI "https://github.com/posit-dev/connect/blob/main/.github/workflows/ci.yml" _blank
+ click DEV "https://github.com/posit-dev/images-connect/blob/main/.github/workflows/development.yml" _blank
+ click SHARED "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/bakery-build-native.yml" _blank
```
### Workbench
@@ -229,38 +230,56 @@ graph TD
```{mermaid}
%%| fig-cap: "Workbench Production Flow"
graph TD
- PROD["rstudio/rstudio-pro
release-all.yml"]
- BOT["workbench-ide-release"]
+ subgraph rstudio-pro
+ RELEASE_ALL["release-all.yml"]
+ UPDATE_IMG["release-update-images-workbench.yml"]
+ RELEASE_ALL --> UPDATE_IMG
+ end
+
+ UPDATE_IMG -.->|"workflow_dispatch
version"| REL
- PROD -.-> BOT
- BOT -.->|"workflow_dispatch release.yml
(version)"| IMG_RELEASE
+ subgraph images-workbench
+ REL["release.yml"]
+ PROD_WF["production.yml"]
+ SESSION["session.yml"]
+ HELM_JOB["update-helm job"]
+ end
- SHARED["posit-dev/images-shared
bakery-build-native
product-release"]
+ REL -.->|"workflow_call
version, images"| PRODUCT_REL
- IMG_RELEASE["posit-dev/images-workbench
release"]
- IMG_PROD["posit-dev/images-workbench
production"]
- IMG_SESSION["posit-dev/images-workbench
session"]
+ subgraph images-shared
+ PRODUCT_REL["product-release.yml"]
+ SHARED["bakery-build-native.yml"]
+ end
- IMG_RELEASE -.->|workflow_call| SHARED
- IMG_PROD -.->|workflow_call| SHARED
- IMG_SESSION -.->|workflow_call| SHARED
+ REL -->|"PR merge triggers"| PROD_WF
+ REL -->|"PR merge triggers"| SESSION
- IMG_RELEASE -->|"merge to main"| IMG_PROD
- IMG_RELEASE -->|"merge to main"| IMG_SESSION
+ PROD_WF -.->|"workflow_call
dev-versions=exclude"| SHARED
+ SESSION -.->|"workflow_call
matrix-versions=only"| SHARED
- IMG_PROD -->|push| DOCKERHUB
- IMG_PROD -->|push| GHCR
- IMG_SESSION -->|push| DOCKERHUB
- IMG_SESSION -->|push| GHCR
+ PROD_WF --> HELM_JOB
+ HELM_JOB -.->|"workflow_dispatch
product=workbench, version"| HELM_WF
- DOCKERHUB["Docker Hub
rstudio/rstudio-workbench
rstudio/r-session-complete"]
- GHCR["GHCR"]
+ PROD_WF -->|push| DOCKERHUB["Docker Hub"]
+ PROD_WF -->|push| GHCR["GHCR"]
+ SESSION -->|push| DOCKERHUB
+ SESSION -->|push| GHCR
- IMG_PROD -.->|"workflow_dispatch product-release.yml"| HELM
- HELM["rstudio/helm
product-release
chart-releaser"]
- HELM -->|Flux sync| K8S
+ subgraph helm
+ HELM_WF["product-release.yml"]
+ end
- K8S["K8s Dogfood Sites"]
+ HELM_WF -->|Flux sync| K8S["K8s Dogfood Sites"]
+
+ click RELEASE_ALL "https://github.com/rstudio/rstudio-pro/blob/main/.github/workflows/release-all.yml" _blank
+ click UPDATE_IMG "https://github.com/rstudio/rstudio-pro/blob/main/.github/workflows/release-update-images-workbench.yml" _blank
+ click REL "https://github.com/posit-dev/images-workbench/blob/main/.github/workflows/release.yml" _blank
+ click PROD_WF "https://github.com/posit-dev/images-workbench/blob/main/.github/workflows/production.yml" _blank
+ click SESSION "https://github.com/posit-dev/images-workbench/blob/main/.github/workflows/session.yml" _blank
+ click PRODUCT_REL "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/product-release.yml" _blank
+ click SHARED "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/bakery-build-native.yml" _blank
+ click HELM_WF "https://github.com/rstudio/helm/blob/main/.github/workflows/product-release.yml" _blank
```
#### Development {.unnumbered}
@@ -268,29 +287,30 @@ graph TD
```{mermaid}
%%| fig-cap: "Workbench Development Flow"
graph TD
- PROD["rstudio/rstudio-pro
release-nightly-test.yml"]
- BOT["workbench-ide-release"]
-
- PROD -.-> BOT
- BOT -.->|"workflow_dispatch development.yml
(version)"| IMG_DEV
-
- SHARED["posit-dev/images-shared
bakery-build-native"]
+ subgraph rstudio-pro
+ NIGHTLY["release-nightly-test.yml"]
+ end
- IMG_DEV["posit-dev/images-workbench
development"]
+ NIGHTLY -.->|"workflow_dispatch
version, stream, channel"| DEV
- IMG_DEV -.->|workflow_call| SHARED
+ subgraph images-workbench
+ DEV["development.yml"]
+ end
- IMG_DEV -->|preview push| GHCR
+ DEV -.->|"workflow_call
dev-versions, image-version,
dev-stream, dev-channel"| SHARED
- GHCR["GHCR
workbench-preview
workbench-session-init-preview"]
+ subgraph images-shared
+ SHARED["bakery-build-native.yml"]
+ end
- GHCR --> K8S
- GHCR --> FUZZBUCKET
- GHCR --> EKS_REF
+ DEV -->|preview push| GHCR["GHCR
workbench-preview
workbench-session-init-preview"]
+ GHCR --> K8S["K8s Dogfood Sites"]
+ GHCR --> FUZZBUCKET["Fuzzbucket
IDE Automation"]
+ GHCR --> EKS_REF["EKS Reference Architecture"]
- K8S["K8s Dogfood Sites"]
- FUZZBUCKET["Fuzzbucket
IDE Automation"]
- EKS_REF["EKS Reference Architecture"]
+ click NIGHTLY "https://github.com/rstudio/rstudio-pro/blob/main/.github/workflows/release-nightly-test.yml" _blank
+ click DEV "https://github.com/posit-dev/images-workbench/blob/main/.github/workflows/development.yml" _blank
+ click SHARED "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/bakery-build-native.yml" _blank
```
### Package Manager
@@ -300,33 +320,46 @@ graph TD
```{mermaid}
%%| fig-cap: "Package Manager Production Flow"
graph TD
- PROD["rstudio/package-manager
ci.yml (tag push)"]
- BOT["posit-ppm"]
+ subgraph package-manager
+ CI["ci.yml (publish job)"]
+ end
+
+ CI -.->|"workflow_dispatch
version"| REL
- PROD -.-> BOT
- BOT -.->|"workflow_dispatch release.yml
(version)"| IMG_RELEASE
+ subgraph images-package-manager
+ REL["release.yml"]
+ PROD_WF["production.yml"]
+ HELM_JOB["update-helm job"]
+ end
- SHARED["posit-dev/images-shared
bakery-build-native
product-release"]
+ REL -.->|"workflow_call
version, images"| PRODUCT_REL
- IMG_RELEASE["posit-dev/images-package-manager
release"]
- IMG_PROD["posit-dev/images-package-manager
production"]
+ subgraph images-shared
+ PRODUCT_REL["product-release.yml"]
+ SHARED["bakery-build-native.yml"]
+ end
- IMG_RELEASE -.->|workflow_call| SHARED
- IMG_PROD -.->|workflow_call| SHARED
+ REL -->|"PR merge triggers"| PROD_WF
+ PROD_WF -.->|"workflow_call
dev-versions=exclude"| SHARED
- IMG_RELEASE -->|"merge to main"| IMG_PROD
+ PROD_WF --> HELM_JOB
+ HELM_JOB -.->|"workflow_dispatch
product=package-manager, version"| HELM_WF
- IMG_PROD -->|push| DOCKERHUB
- IMG_PROD -->|push| GHCR
+ PROD_WF -->|push| DOCKERHUB["Docker Hub"]
+ PROD_WF -->|push| GHCR["GHCR"]
- DOCKERHUB["Docker Hub
rstudio/rstudio-package-manager"]
- GHCR["GHCR"]
+ subgraph helm
+ HELM_WF["product-release.yml"]
+ end
- IMG_PROD -.->|"workflow_dispatch product-release.yml"| HELM
- HELM["rstudio/helm
product-release
chart-releaser"]
- HELM -->|Flux sync| K8S
+ HELM_WF -->|Flux sync| K8S["K8s Dogfood Sites"]
- K8S["K8s Dogfood Sites"]
+ click CI "https://github.com/rstudio/package-manager/blob/main/.github/workflows/ci.yml" _blank
+ click REL "https://github.com/posit-dev/images-package-manager/blob/main/.github/workflows/release.yml" _blank
+ click PROD_WF "https://github.com/posit-dev/images-package-manager/blob/main/.github/workflows/production.yml" _blank
+ click PRODUCT_REL "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/product-release.yml" _blank
+ click SHARED "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/bakery-build-native.yml" _blank
+ click HELM_WF "https://github.com/rstudio/helm/blob/main/.github/workflows/product-release.yml" _blank
```
#### Development {.unnumbered}
@@ -334,25 +367,28 @@ graph TD
```{mermaid}
%%| fig-cap: "Package Manager Development Flow"
graph TD
- PROD["rstudio/package-manager
ci.yml (push to main)"]
- BOT["posit-ppm"]
-
- PROD -.-> BOT
- BOT -.->|"workflow_dispatch development.yml
(version)"| IMG_DEV
-
- SHARED["posit-dev/images-shared
bakery-build-native"]
+ subgraph package-manager
+ CI["ci.yml (publish job)"]
+ end
- IMG_DEV["posit-dev/images-package-manager
development"]
+ CI -.->|"workflow_dispatch
version, stream"| DEV
- IMG_DEV -.->|workflow_call| SHARED
+ subgraph images-package-manager
+ DEV["development.yml"]
+ end
- IMG_DEV -->|preview push| GHCR
+ DEV -.->|"workflow_call
dev-versions, image-version,
dev-stream"| SHARED
- GHCR["GHCR
package-manager-preview"]
+ subgraph images-shared
+ SHARED["bakery-build-native.yml"]
+ end
- GHCR --> K8S
+ DEV -->|preview push| GHCR["GHCR
package-manager-preview"]
+ GHCR --> K8S["K8s Dogfood Sites"]
- K8S["K8s Dogfood Sites"]
+ click CI "https://github.com/rstudio/package-manager/blob/main/.github/workflows/ci.yml" _blank
+ click DEV "https://github.com/posit-dev/images-package-manager/blob/main/.github/workflows/development.yml" _blank
+ click SHARED "https://github.com/posit-dev/images-shared/blob/main/.github/workflows/bakery-build-native.yml" _blank
```
## Reference Tables
diff --git a/posit-bakery/posit_bakery/cli/build.py b/posit-bakery/posit_bakery/cli/build.py
index 9d3bc733..510936cf 100644
--- a/posit-bakery/posit_bakery/cli/build.py
+++ b/posit-bakery/posit_bakery/cli/build.py
@@ -7,7 +7,7 @@
import python_on_whales
import typer
-from posit_bakery.cli.common import with_verbosity_flags, with_temporary_storage
+from posit_bakery.cli.common import with_verbosity_flags, with_temporary_storage, _make_value_map
from posit_bakery.config import BakeryConfig
from posit_bakery.config.config import BakeryConfigFilter, BakerySettings
from posit_bakery.const import DevVersionInclusionEnum, MatrixVersionInclusionEnum
@@ -186,6 +186,20 @@ def build(
rich_help_panel=RichHelpPanelEnum.FILTERS,
),
] = MatrixVersionInclusionEnum.EXCLUDE,
+ dev_stream: Annotated[
+ Optional[str],
+ typer.Option(
+ help="Filter development versions to a specific release stream (e.g. 'daily', 'preview').",
+ rich_help_panel=RichHelpPanelEnum.FILTERS,
+ ),
+ ] = None,
+ value: Annotated[
+ Optional[list[str]],
+ typer.Option(
+ help="Override a devVersion value (key=value). Can be specified multiple times.",
+ rich_help_panel=RichHelpPanelEnum.FILTERS,
+ ),
+ ] = None,
) -> None:
"""Builds images in the context path
@@ -196,6 +210,11 @@ def build(
Requires Docker, Podman, or nerdctl to be installed and running for `--strategy build`.
"""
+ value_map, errors = _make_value_map(value)
+ if errors:
+ for e in errors:
+ log.error(e)
+ raise typer.Exit(code=1)
settings = BakerySettings(
filter=BakeryConfigFilter(
image_name=image_name,
@@ -203,6 +222,8 @@ def build(
image_variant=image_variant,
image_os=image_os,
image_platform=image_platform or [],
+ dev_stream=dev_stream,
+ values=value_map,
),
dev_versions=dev_versions,
matrix_versions=matrix_versions,
diff --git a/posit-bakery/posit_bakery/cli/ci.py b/posit-bakery/posit_bakery/cli/ci.py
index fe822cd9..9380bd5b 100644
--- a/posit-bakery/posit_bakery/cli/ci.py
+++ b/posit-bakery/posit_bakery/cli/ci.py
@@ -1,6 +1,7 @@
import glob
import json
import logging
+import re
import python_on_whales
from enum import Enum
from pathlib import Path
@@ -8,7 +9,7 @@
import typer
-from posit_bakery.cli.common import with_verbosity_flags
+from posit_bakery.cli.common import with_verbosity_flags, _make_value_map
from posit_bakery.config import BakeryConfig
from posit_bakery.config.config import BakerySettings, BakeryConfigFilter
from posit_bakery.const import DevVersionInclusionEnum, MatrixVersionInclusionEnum
@@ -49,6 +50,28 @@ def matrix(
rich_help_panel=RichHelpPanelEnum.FILTERS,
),
] = MatrixVersionInclusionEnum.EXCLUDE,
+ image_version: Annotated[
+ Optional[str],
+ typer.Option(
+ show_default=False,
+ help="The image version to filter to.",
+ rich_help_panel=RichHelpPanelEnum.FILTERS,
+ ),
+ ] = None,
+ dev_stream: Annotated[
+ Optional[str],
+ typer.Option(
+ help="Filter development versions to a specific release stream (e.g. 'daily', 'preview').",
+ rich_help_panel=RichHelpPanelEnum.FILTERS,
+ ),
+ ] = None,
+ value: Annotated[
+ Optional[list[str]],
+ typer.Option(
+ help="Override a devVersion value (key=value). Can be specified multiple times.",
+ rich_help_panel=RichHelpPanelEnum.FILTERS,
+ ),
+ ] = None,
exclude: Annotated[
Optional[list[BakeryCIMatrixFieldEnum]],
typer.Option(help="Fields to exclude splitting the matrix by."),
@@ -85,8 +108,18 @@ def matrix(
exclude = []
try:
+ value_map, errors = _make_value_map(value)
+ if errors:
+ for e in errors:
+ log.error(e)
+ raise typer.Exit(code=1)
settings = BakerySettings(
- filter=BakeryConfigFilter(image_name=image_name),
+ filter=BakeryConfigFilter(
+ image_name=image_name,
+ image_version=re.escape(image_version) if image_version else None,
+ dev_stream=dev_stream,
+ values=value_map,
+ ),
dev_versions=dev_versions,
)
c = BakeryConfig.from_context(context=context, settings=settings)
@@ -142,6 +175,12 @@ def merge(
rich_help_panel="Build Configuration & Outputs",
),
] = None,
+ value: Annotated[
+ Optional[list[str]],
+ typer.Option(
+ help="Override a devVersion value (key=value). Can be specified multiple times.",
+ ),
+ ] = None,
dry_run: Annotated[
bool, typer.Option(help="If set, the merged images will not be pushed to the registry.")
] = False,
@@ -162,7 +201,13 @@ def merge(
}
```
"""
+ value_map, errors = _make_value_map(value)
+ if errors:
+ for e in errors:
+ log.error(e)
+ raise typer.Exit(code=1)
settings = BakerySettings(
+ filter=BakeryConfigFilter(values=value_map),
dev_versions=DevVersionInclusionEnum.INCLUDE,
matrix_versions=MatrixVersionInclusionEnum.INCLUDE,
clean_temporary=False,
@@ -245,6 +290,12 @@ def readme(
rich_help_panel=RichHelpPanelEnum.FILTERS,
),
] = MatrixVersionInclusionEnum.INCLUDE,
+ value: Annotated[
+ Optional[list[str]],
+ typer.Option(
+ help="Override a devVersion value (key=value). Can be specified multiple times.",
+ ),
+ ] = None,
) -> None:
"""Push image READMEs to Docker Hub.
@@ -256,7 +307,13 @@ def readme(
variables to be set with a Personal Access Token (PAT). Organization Access Tokens
cannot update repository descriptions.
"""
+ value_map, errors = _make_value_map(value)
+ if errors:
+ for e in errors:
+ log.error(e)
+ raise typer.Exit(code=1)
settings = BakerySettings(
+ filter=BakeryConfigFilter(values=value_map),
dev_versions=dev_versions,
matrix_versions=matrix_versions,
)
diff --git a/posit-bakery/posit_bakery/cli/common.py b/posit-bakery/posit_bakery/cli/common.py
index c930ff31..e40cdae7 100644
--- a/posit-bakery/posit_bakery/cli/common.py
+++ b/posit-bakery/posit_bakery/cli/common.py
@@ -90,7 +90,7 @@ def wrapper(ctx: typer.Context, *args, **kwargs) -> None:
return wrapper
-def __make_value_map(value: list[str] | None) -> tuple[dict[Any, Any], list[Exception]]:
+def _make_value_map(value: list[str] | None) -> tuple[dict[Any, Any], list[Exception]]:
"""Parses key=value option pairs into a dictionary"""
value_map = dict()
errors = []
diff --git a/posit-bakery/posit_bakery/cli/create.py b/posit-bakery/posit_bakery/cli/create.py
index c42a8da1..cecdd41b 100644
--- a/posit-bakery/posit_bakery/cli/create.py
+++ b/posit-bakery/posit_bakery/cli/create.py
@@ -7,7 +7,7 @@
from posit_bakery import error
from posit_bakery.cli.common import (
- __make_value_map,
+ _make_value_map,
with_verbosity_flags,
__parse_dependency_constraint,
__parse_dependency_versions,
@@ -221,7 +221,7 @@ def version(
└── Containerfile*.jinja2
```
"""
- value_map, errors = __make_value_map(value)
+ value_map, errors = _make_value_map(value)
if errors:
for e in errors:
log.error(e)
@@ -334,7 +334,7 @@ def matrix(
└── Containerfile*.jinja2
```
"""
- value_map, value_errors = __make_value_map(value)
+ value_map, value_errors = _make_value_map(value)
parsed_dependency_constraints = []
dependency_constraint_errors = []
diff --git a/posit-bakery/posit_bakery/cli/run.py b/posit-bakery/posit_bakery/cli/run.py
index 67ccbf65..43d3a29c 100644
--- a/posit-bakery/posit_bakery/cli/run.py
+++ b/posit-bakery/posit_bakery/cli/run.py
@@ -7,7 +7,7 @@
import typer
-from posit_bakery.cli.common import with_verbosity_flags
+from posit_bakery.cli.common import _make_value_map, with_verbosity_flags
from posit_bakery.config import BakeryConfig
from posit_bakery.config.config import BakeryConfigFilter, BakerySettings
from posit_bakery.const import DevVersionInclusionEnum, MatrixVersionInclusionEnum
@@ -97,6 +97,20 @@ def dgoss(
rich_help_panel=RichHelpPanelEnum.FILTERS,
),
] = MatrixVersionInclusionEnum.EXCLUDE,
+ dev_stream: Annotated[
+ Optional[str],
+ typer.Option(
+ help="Filter development versions to a specific release stream (e.g. 'daily', 'preview').",
+ rich_help_panel=RichHelpPanelEnum.FILTERS,
+ ),
+ ] = None,
+ value: Annotated[
+ Optional[list[str]],
+ typer.Option(
+ help="Override a devVersion value (key=value). Can be specified multiple times.",
+ rich_help_panel=RichHelpPanelEnum.FILTERS,
+ ),
+ ] = None,
metadata_file: Annotated[
Optional[Path],
typer.Option(
@@ -130,9 +144,13 @@ def dgoss(
DeprecationWarning,
stacklevel=2,
)
- stderr_console.print(
- "[yellow]Warning: 'bakery run dgoss' is deprecated. Use 'bakery dgoss run' instead.[/yellow]"
- )
+ stderr_console.print("[yellow]Warning: 'bakery run dgoss' is deprecated. Use 'bakery dgoss run' instead.[/yellow]")
+
+ value_map, errors = _make_value_map(value)
+ if errors:
+ for e in errors:
+ log.error(e)
+ raise typer.Exit(code=1)
# Autoselect host architecture platform if not specified.
image_platform = image_platform or SETTINGS.architecture
@@ -145,6 +163,8 @@ def dgoss(
image_variant=image_variant,
image_os=image_os,
image_platform=[image_platform],
+ dev_stream=dev_stream,
+ values=value_map,
),
dev_versions=dev_versions,
matrix_versions=matrix_versions,
diff --git a/posit-bakery/posit_bakery/cli/update.py b/posit-bakery/posit_bakery/cli/update.py
index ed487f11..786e74e6 100644
--- a/posit-bakery/posit_bakery/cli/update.py
+++ b/posit-bakery/posit_bakery/cli/update.py
@@ -5,7 +5,7 @@
import typer
-from posit_bakery.cli.common import __make_value_map, with_verbosity_flags
+from posit_bakery.cli.common import _make_value_map, with_verbosity_flags
from posit_bakery.config import BakeryConfig
from posit_bakery.config.config import BakeryConfigFilter
from posit_bakery.log import stderr_console
@@ -141,7 +141,7 @@ def version(
bakery update version connect 2026.03.1 --target-version 2026.03.0
# Explicitly patches '2026.03.0' to '2026.03.1'
"""
- value_map, errors = __make_value_map(value)
+ value_map, errors = _make_value_map(value)
if errors:
for e in errors:
log.error(e)
diff --git a/posit-bakery/posit_bakery/config/config.py b/posit-bakery/posit_bakery/config/config.py
index 16847925..b69bb54a 100644
--- a/posit-bakery/posit_bakery/config/config.py
+++ b/posit-bakery/posit_bakery/config/config.py
@@ -264,6 +264,20 @@ class BakeryConfigFilter(BaseModel):
image_platform: Annotated[
list[str], Field(description="Name or regex pattern of the image platform to filter by.", default_factory=list)
]
+ dev_stream: Annotated[
+ str | None,
+ Field(
+ description="Development stream to filter by (e.g. 'daily', 'preview').",
+ default=None,
+ ),
+ ]
+ values: Annotated[
+ dict[str, str],
+ Field(
+ description="Key-value pairs to override in devVersion values (e.g. channel=apple-blossom).",
+ default_factory=dict,
+ ),
+ ]
class BakerySettings(BaseModel):
@@ -331,7 +345,10 @@ def __init__(self, config_file: str | Path | os.PathLike, settings: BakerySettin
if self.settings.dev_versions in [DevVersionInclusionEnum.ONLY, DevVersionInclusionEnum.INCLUDE]:
for image in self.model.images:
- image.load_dev_versions()
+ image.load_dev_versions(
+ dev_stream=self.settings.filter.dev_stream,
+ values=self.settings.filter.values,
+ )
image.render_ephemeral_version_files()
if self.settings.clean_temporary:
atexit.register(image.remove_ephemeral_version_files)
diff --git a/posit-bakery/posit_bakery/config/image/dev_version/base.py b/posit-bakery/posit_bakery/config/image/dev_version/base.py
index 4162991f..5f8eaf86 100644
--- a/posit-bakery/posit_bakery/config/image/dev_version/base.py
+++ b/posit-bakery/posit_bakery/config/image/dev_version/base.py
@@ -194,9 +194,10 @@ def all_registries(self) -> list[Registry | BaseRegistry]:
return all_registries
@abc.abstractmethod
- def get_version(self) -> str:
+ def get_version(self, values: dict[str, str] | None = None) -> str:
"""Retrieve the version string for this image development version.
+ :param values: Optional merged values dict (self.values + overrides). If None, uses self.values.
:return: The version string.
"""
raise NotImplementedError("Subclasses must implement get_version method.")
@@ -221,16 +222,21 @@ def add_os_url(self) -> "BaseImageDevelopmentVersion":
return self
- def as_image_version(self):
- """Convert this development version to a standard image version."""
+ def as_image_version(self, value_overrides: dict[str, str] | None = None):
+ """Convert this development version to a standard image version.
+
+ :param value_overrides: Optional key-value pairs to merge on top of self.values.
+ Does not mutate the original values dict.
+ """
+ merged_values = {**self.values, **value_overrides} if value_overrides else self.values
return ImageVersion(
- name=self.get_version(),
- subpath=f".dev-{self.get_version()}".replace(" ", "-").lower(),
+ name=self.get_version(merged_values),
+ subpath=f".dev-{self.get_version(merged_values)}".replace(" ", "-").lower(),
parent=self.parent,
extraRegistries=self.extraRegistries,
overrideRegistries=self.overrideRegistries,
os=self.os,
- values=self.values,
+ values=merged_values,
latest=False,
dependencies=self.parent.resolve_dependency_versions(),
ephemeral=True,
diff --git a/posit-bakery/posit_bakery/config/image/dev_version/env.py b/posit-bakery/posit_bakery/config/image/dev_version/env.py
index f0b00a89..4844cf3f 100644
--- a/posit-bakery/posit_bakery/config/image/dev_version/env.py
+++ b/posit-bakery/posit_bakery/config/image/dev_version/env.py
@@ -51,9 +51,10 @@ def validate_env_vars(cls, v: str, info: ValidationInfo):
return v
- def get_version(self) -> str:
+ def get_version(self, values: dict[str, str] | None = None) -> str:
"""Retrieve the version from the specified environment variable.
+ :param values: Unused. Accepted for ABC compatibility.
:return: The version string from the environment variable.
"""
return _get_value_from_env("versionEnvVar", self.versionEnvVar)
diff --git a/posit-bakery/posit_bakery/config/image/dev_version/stream.py b/posit-bakery/posit_bakery/config/image/dev_version/stream.py
index 0aa0f7ad..169d6eca 100644
--- a/posit-bakery/posit_bakery/config/image/dev_version/stream.py
+++ b/posit-bakery/posit_bakery/config/image/dev_version/stream.py
@@ -27,13 +27,16 @@ def get_primary_os(self) -> ImageVersionOS:
return DEFAULT_OS
- def get_version(self) -> str:
+ def get_version(self, values: dict[str, str] | None = None) -> str:
"""Retrieve the version from the specified product stream.
+ :param values: Optional merged values dict. If None, uses self.values.
:return: The version string from the product stream.
"""
_os = self.get_primary_os()
- result = get_product_artifact_by_stream(self.product, self.stream, _os.buildOS)
+ result = get_product_artifact_by_stream(
+ self.product, self.stream, _os.buildOS, values=values if values is not None else self.values
+ )
return result.version
@@ -44,7 +47,7 @@ def get_url_by_os(self, generalize_architecture: bool = False) -> dict[str, str]
"""
url_by_os = {}
for _os in self.os:
- result = get_product_artifact_by_stream(self.product, self.stream, _os.buildOS)
+ result = get_product_artifact_by_stream(self.product, self.stream, _os.buildOS, values=self.values)
if generalize_architecture:
url_by_os[_os.name] = str(result.architecture_generalized_download_url)
else:
diff --git a/posit-bakery/posit_bakery/config/image/image.py b/posit-bakery/posit_bakery/config/image/image.py
index 744b0660..97fb6b8b 100644
--- a/posit-bakery/posit_bakery/config/image/image.py
+++ b/posit-bakery/posit_bakery/config/image/image.py
@@ -573,10 +573,22 @@ def patch_version(
return patched_image_version
- def load_dev_versions(self):
- """Load the development versions for this image."""
+ def load_dev_versions(self, dev_stream: str | None = None, values: dict[str, str] | None = None):
+ """Load the development versions for this image.
+
+ :param dev_stream: If provided, only load dev versions from this stream.
+ :param values: Key-value pairs to override in each devVersion's values dict.
+ """
for dev_version in self.devVersions:
- image_version = dev_version.as_image_version()
+ if dev_stream is not None and hasattr(dev_version, "stream"):
+ if dev_version.stream.value != dev_stream:
+ log.info(
+ f"Skipping {self.name} dev version {repr(dev_version)}: "
+ f"stream '{dev_version.stream.value}' does not match filter '{dev_stream}'"
+ )
+ continue
+
+ image_version = dev_version.as_image_version(value_overrides=values)
log_message = f"Loaded {self.name} development version from {repr(dev_version)}:\n"
log_message += f" - Version: {image_version.name}\n"
for dep in image_version.dependencies:
diff --git a/posit-bakery/posit_bakery/config/image/posit_product/main.py b/posit-bakery/posit_bakery/config/image/posit_product/main.py
index dab7d276..1412378b 100644
--- a/posit-bakery/posit_bakery/config/image/posit_product/main.py
+++ b/posit-bakery/posit_bakery/config/image/posit_product/main.py
@@ -43,8 +43,15 @@ def __init__(self, stream_url: str, resolver_map: dict[str, resolvers.AbstractRe
def get(self, metadata: dict) -> ReleaseStreamResult:
"""Fetches data from the stream URL and resolves the data using the given resolvers."""
+ try:
+ stream_url = self.stream_url.format_map(metadata)
+ except KeyError as e:
+ raise ValueError(
+ f"Stream URL {self.stream_url!r} contains placeholder {e} "
+ f"not found in metadata. Pass --value {e.args[0]}= to set it."
+ ) from e
session = cached_session()
- response = session.get(self.stream_url)
+ response = session.get(stream_url)
response.raise_for_status()
try:
data = response.json()
@@ -281,7 +288,11 @@ def _make_resolver_metadata(_os: BuildOS, product: ProductEnum):
def get_product_artifact_by_stream(
- product: ProductEnum, stream: ReleaseStreamEnum, os: BuildOS, generalize_arch: bool = True
+ product: ProductEnum,
+ stream: ReleaseStreamEnum,
+ os: BuildOS,
+ generalize_arch: bool = True,
+ values: dict[str, str] | None = None,
) -> ReleaseStreamResult:
"""Fetches the version and download URL for a given product, release stream, and OS."""
if product not in product_release_stream_url_map:
@@ -290,5 +301,7 @@ def get_product_artifact_by_stream(
raise ValueError(f"Stream {stream} is not supported for product {product}.")
metadata = _make_resolver_metadata(os, product)
+ if values:
+ metadata.update(values)
return product_release_stream_url_map[product][stream].get(metadata)
diff --git a/posit-bakery/test/cli/test_common.py b/posit-bakery/test/cli/test_common.py
index 8ffb155b..dcfc2def 100644
--- a/posit-bakery/test/cli/test_common.py
+++ b/posit-bakery/test/cli/test_common.py
@@ -3,7 +3,7 @@
import pytest
from posit_bakery.cli.common import (
- __make_value_map as make_value_map,
+ _make_value_map as make_value_map,
__parse_dependency_constraint as parse_dependency_constraint,
__parse_dependency_versions as parse_dependency_versions,
)
@@ -22,7 +22,7 @@
class TestMakeValueMap:
- """Tests for the __make_value_map function"""
+ """Tests for the _make_value_map function"""
def test_none_input(self):
"""Test that None input returns empty dict with no errors"""
diff --git a/posit-bakery/test/config/image/posit_products/test_main.py b/posit-bakery/test/config/image/posit_products/test_main.py
index 4d3bc283..fdcf407f 100644
--- a/posit-bakery/test/config/image/posit_products/test_main.py
+++ b/posit-bakery/test/config/image/posit_products/test_main.py
@@ -1,3 +1,5 @@
+from unittest.mock import MagicMock, patch
+
import pytest
from posit_bakery.config.image.build_os import SUPPORTED_OS, BuildOS
@@ -6,6 +8,7 @@
_parse_download_json_os_identifier,
_make_resolver_metadata,
get_product_artifact_by_stream,
+ ReleaseStreamPath,
ReleaseStreamResult,
)
@@ -163,6 +166,48 @@
]
+class TestReleaseStreamPath:
+ @pytest.fixture
+ def _mock_session(self):
+ """Patch cached_session to return a mock that accepts any URL."""
+ mock_resp = MagicMock()
+ mock_resp.json.return_value = {}
+ mock_resp.raise_for_status.return_value = None
+ mock_session = MagicMock()
+ mock_session.get.return_value = mock_resp
+ with patch("posit_bakery.config.image.posit_product.main.cached_session", return_value=mock_session):
+ yield mock_session
+
+ def test_static_url(self, _mock_session):
+ """A URL with no placeholders passes through unchanged."""
+ path = ReleaseStreamPath(
+ "https://example.com/daily.json",
+ {"version": "1.0.0", "download_url": "https://example.com/pkg.deb"},
+ )
+ result = path.get({"os": "ubuntu"})
+ assert result.version == "1.0.0"
+ _mock_session.get.assert_called_once_with("https://example.com/daily.json")
+
+ def test_url_with_placeholder(self, _mock_session):
+ """A URL with a {channel} placeholder resolves from metadata."""
+ path = ReleaseStreamPath(
+ "https://dailies.example.com/{channel}/index.json",
+ {"version": "2.0.0", "download_url": "https://example.com/pkg.deb"},
+ )
+ result = path.get({"channel": "apple-blossom"})
+ assert result.version == "2.0.0"
+ _mock_session.get.assert_called_once_with("https://dailies.example.com/apple-blossom/index.json")
+
+ def test_url_with_missing_placeholder_raises(self):
+ """A URL with a placeholder not in metadata raises ValueError."""
+ path = ReleaseStreamPath(
+ "https://dailies.example.com/{channel}/index.json",
+ {"version": "2.0.0", "download_url": "https://example.com/pkg.deb"},
+ )
+ with pytest.raises(ValueError, match="channel"):
+ path.get({})
+
+
class TestReleaseStreamResult:
@pytest.mark.parametrize(
"download_url,expected_url",
diff --git a/posit-bakery/test/config/image/test_image.py b/posit-bakery/test/config/image/test_image.py
index b0812882..8d6ad707 100644
--- a/posit-bakery/test/config/image/test_image.py
+++ b/posit-bakery/test/config/image/test_image.py
@@ -704,6 +704,111 @@ def test_load_dev_versions(self):
assert i.get_version(stream_version).os[0].name == "Ubuntu 22.04"
assert str(i.get_version(stream_version).os[0].artifactDownloadURL) == stream_url
+ def test_load_dev_versions_dev_stream_filter(self):
+ """Test that load_dev_versions filters by dev_stream when provided."""
+ context = Path(__file__).parent.parent.parent / "contexts" / "with-dev-versions"
+ mock_parent = MagicMock(spec=BakeryConfigDocument)
+ mock_parent.path = context
+
+ stream_version = "1.1.0"
+ stream_url = "https://example.com/image-daily.tar.gz"
+ with patch("posit_bakery.config.image.dev_version.stream.get_product_artifact_by_stream") as mock_get:
+ mock_get.return_value = ReleaseStreamResult(version=stream_version, download_url=stream_url)
+ i = Image(
+ name="my-image",
+ parent=mock_parent,
+ devVersions=[
+ {
+ "sourceType": "stream",
+ "product": "package-manager",
+ "stream": "daily",
+ "os": [{"name": "Ubuntu 22.04", "primary": True}],
+ },
+ {
+ "sourceType": "stream",
+ "product": "package-manager",
+ "stream": "preview",
+ "os": [{"name": "Ubuntu 22.04", "primary": True}],
+ },
+ ],
+ versions=[{"name": "1.0.0"}],
+ )
+ i.load_dev_versions(dev_stream="daily")
+
+ # Only the daily stream should be loaded; preview should be skipped.
+ # 1.0.0 (release) + daily dev version = 2 total. Preview is filtered out.
+ assert len(i.versions) == 2
+ assert i.get_version("1.0.0") is not None
+ assert i.get_version(stream_version) is not None
+ assert i.get_version(stream_version).isDevelopmentVersion
+
+ def test_load_dev_versions_values_override(self):
+ """Test that load_dev_versions applies value overrides to dev versions."""
+ context = Path(__file__).parent.parent.parent / "contexts" / "with-dev-versions"
+ mock_parent = MagicMock(spec=BakeryConfigDocument)
+ mock_parent.path = context
+
+ stream_version = "1.1.0"
+ stream_url = "https://example.com/image-daily.tar.gz"
+ with patch("posit_bakery.config.image.dev_version.stream.get_product_artifact_by_stream") as mock_get:
+ mock_get.return_value = ReleaseStreamResult(version=stream_version, download_url=stream_url)
+ i = Image(
+ name="my-image",
+ parent=mock_parent,
+ devVersions=[
+ {
+ "sourceType": "stream",
+ "product": "workbench",
+ "stream": "daily",
+ "os": [{"name": "Ubuntu 22.04", "primary": True}],
+ "values": {"channel": "latest"},
+ },
+ ],
+ versions=[{"name": "1.0.0"}],
+ )
+ i.load_dev_versions(values={"channel": "apple-blossom"})
+
+ # The original devVersion model must not be mutated.
+ assert i.devVersions[0].values["channel"] == "latest"
+ # The override should flow through to the resulting image version.
+ dev_ver = i.get_version(stream_version)
+ assert dev_ver is not None
+ assert dev_ver.values["channel"] == "apple-blossom"
+
+ def test_load_dev_versions_values_override_channel_url(self):
+ """Test that a channel override propagates to get_product_artifact_by_stream as metadata."""
+ context = Path(__file__).parent.parent.parent / "contexts" / "with-dev-versions"
+ mock_parent = MagicMock(spec=BakeryConfigDocument)
+ mock_parent.path = context
+
+ stream_version = "2026.04.0-daily+313.pro27"
+ stream_url = "https://dailies.rstudio.com/rstudio/globemaster-allium/workbench.deb"
+ with patch("posit_bakery.config.image.dev_version.stream.get_product_artifact_by_stream") as mock_get:
+ mock_get.return_value = ReleaseStreamResult(version=stream_version, download_url=stream_url)
+ i = Image(
+ name="workbench",
+ parent=mock_parent,
+ devVersions=[
+ {
+ "sourceType": "stream",
+ "product": "workbench",
+ "stream": "daily",
+ "os": [{"name": "Ubuntu 24.04", "primary": True}],
+ "values": {"channel": "latest"},
+ },
+ ],
+ versions=[{"name": "2026.03.0"}],
+ )
+ i.load_dev_versions(values={"channel": "globemaster-allium"})
+
+ # The original devVersion model must not be mutated.
+ assert i.devVersions[0].values["channel"] == "latest"
+ # The overridden value should be passed to get_product_artifact_by_stream.
+ assert mock_get.called
+ assert any(
+ call.kwargs.get("values", {}).get("channel") == "globemaster-allium" for call in mock_get.call_args_list
+ )
+
def test_render_ephemeral_version_files(self, get_tmpcontext, common_image_variants_objects):
"""Test that render_ephemeral_version_files creates the correct directory structure for an ephemeral version."""
context = get_tmpcontext("basic")