Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions posit-bakery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ The [bakery](./posit-bakery/) command line interface (CLI) binds together variou
* [pipx](https://pipx.pypa.io/stable/installation/)
* [docker buildx bake](https://github.com/docker/buildx#installing)
* [just](https://just.systems/man/en/prerequisites.html)
* [gh](https://github.com/cli/cli#installation) (required while repositories are private)

### 3rd Party Tools

| Tool | Used By | Purpose |
Expand All @@ -28,14 +26,7 @@ The [bakery](./posit-bakery/) command line interface (CLI) binds together variou

### CLI Installation

1. Authorize w/ GitHub, since the repository is private

```bash
gh auth login
gh auth setup-git
```

2. Install `bakery` using `pipx`
1. Install `bakery` using `pipx`

```bash
pipx install 'git+https://github.com/posit-dev/images-shared.git@main#subdirectory=posit-bakery&egg=posit-bakery'
Expand Down
6 changes: 4 additions & 2 deletions posit-bakery/posit_bakery/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,9 @@ def build_targets(
:param fail_fast: If True, stop building targets on the first failure.
"""
if strategy == ImageBuildStrategy.BAKE:
bake_plan = BakePlan.from_image_targets(context=self.base_path, image_targets=self.targets)
bake_plan = BakePlan.from_image_targets(
context=self.base_path, image_targets=self.targets, platforms=platforms
)
set_opts = None
if self.settings.temp_registry is not None and push:
set_opts = {
Expand Down Expand Up @@ -955,7 +957,7 @@ def clean_caches(
:param remove_older_than: Optional timedelta to remove caches older than the specified duration.
:param dry_run: If True, print what would be deleted without actually deleting anything.
"""
target_caches = list(set([target.cache_name.split(":")[0] for target in self.targets]))
target_caches = list(set([cn.split(":")[0] for target in self.targets if (cn := target.cache_name())]))
Copy link
Contributor

Choose a reason for hiding this comment

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

This may be the first time I've ever seen the walrus operator used in the real world 😆, clever way of doing it!


errors = []
for target_cache in target_caches:
Expand Down
28 changes: 21 additions & 7 deletions posit-bakery/posit_bakery/image/bake/bake.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,22 @@ def serialize_path(value: Path | str) -> str:
return str(value)

@classmethod
def from_image_target(cls, image_target: ImageTarget) -> "BakeTarget":
"""Create a BakeTarget from an ImageTarget."""
def from_image_target(cls, image_target: ImageTarget, platforms: list[str] | None = None) -> "BakeTarget":
"""Create a BakeTarget from an ImageTarget.

:param image_target: The ImageTarget to create a BakeTarget from.
:param platforms: Optional platform override (e.g., from CLI --platform flag). When provided, this takes
precedence over the image target's OS platform configuration for cache tag generation.
"""
kwargs = {"tags": image_target.tags}
if image_target.cache_name is not None:
kwargs["cache_from"] = [{"type": "registry", "ref": image_target.cache_name}]
kwargs["cache_to"] = [{"type": "registry", "ref": image_target.cache_name, "mode": "max"}]
effective_platforms = platforms or (
image_target.image_os.platforms if image_target.image_os is not None else DEFAULT_PLATFORMS
)
cache_platform = effective_platforms[0] if len(effective_platforms) == 1 else None
cache_name = image_target.cache_name(platform=cache_platform)
if cache_name is not None:
kwargs["cache_from"] = [{"type": "registry", "ref": cache_name}]
kwargs["cache_to"] = [{"type": "registry", "ref": cache_name, "mode": "max"}]
Comment on lines +105 to +109
Copy link
Contributor

Choose a reason for hiding this comment

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

So in theory, bake should handle shared caching correctly for multiplatform builds?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is correct. When we use bake, the multi-platform builds are handled natively.


if image_target.temp_name is not None:
kwargs["tags"] = [image_target.temp_name.rsplit(":", 1)[0]]
Expand Down Expand Up @@ -153,11 +163,15 @@ def update_groups(
return groups

@classmethod
def from_image_targets(cls, context: Path, image_targets: list[ImageTarget]) -> "BakePlan":
def from_image_targets(
cls, context: Path, image_targets: list[ImageTarget], platforms: list[str] | None = None
) -> "BakePlan":
"""Create a BakePlan from a list of ImageTarget objects.

:param context: The absolute path to the build context directory.
:param image_targets: A list of ImageTarget objects to include in the bake plan.
:param platforms: Optional platform override (e.g., from CLI --platform flag). When provided, this takes
precedence over each image target's OS platform configuration for cache tag generation.

:return: A BakePlan object containing the context, groups, and targets.
"""
Expand All @@ -167,7 +181,7 @@ def from_image_targets(cls, context: Path, image_targets: list[ImageTarget]) ->
targets: dict[str, BakeTarget] = {}

for image_target in image_targets:
bake_target = BakeTarget.from_image_target(image_target=image_target)
bake_target = BakeTarget.from_image_target(image_target=image_target, platforms=platforms)
groups = cls.update_groups(
groups=groups,
uid=image_target.uid,
Expand Down
26 changes: 18 additions & 8 deletions posit-bakery/posit_bakery/image/image_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from python_on_whales.components.buildx.imagetools.models import Manifest

from posit_bakery.config.image import ImageVersion, ImageVariant, ImageVersionOS
from posit_bakery.config.image.build_os import DEFAULT_PLATFORMS
from posit_bakery.config.repository import Repository
from posit_bakery.config.tag import TagPattern, TagPatternFilter
from posit_bakery.const import OCI_LABEL_PREFIX, POSIT_LABEL_PREFIX, REGEX_IMAGE_TAG_SUFFIX_ALLOWED_CHARACTERS_PATTERN
Expand Down Expand Up @@ -295,21 +296,27 @@ def labels(self) -> dict[str, str]:

return labels

@property
def cache_name(self) -> str | None:
"""Generate the image name and tag to use for a build cache."""
def cache_name(self, platform: str | None = None) -> str | None:
"""Generate the image name and tag to use for a build cache.

:param platform: Optional platform string (e.g., "linux/amd64") to include in the cache tag
for platform-specific cache differentiation.
"""
if not self.settings.cache_registry:
return None

tag = re.sub(r"[+-].*", "", self.image_version.name)
tag = re.sub(r"\+.*", "", self.image_version.name)

tag = re.sub(REGEX_IMAGE_TAG_SUFFIX_ALLOWED_CHARACTERS_PATTERN, "-", tag).strip("-._")
if self.image_os:
tag += f"-{self.image_os.tagDisplayName}"
if self.image_variant:
tag += f"-{self.image_variant.tagDisplayName}"
cache_name = f"{self.settings.cache_registry}/{self.image_name}/cache:{tag}"
if platform:
platform_suffix = platform.removeprefix("linux/").replace("/", "-")
tag += f"-{platform_suffix}"

return cache_name
return f"{self.settings.cache_registry}/{self.image_name}/cache:{tag}"

@property
def temp_name(self) -> str | None:
Expand Down Expand Up @@ -348,8 +355,11 @@ def build(

cache_from = None
cache_to = None
if self.cache_name is not None:
cache_from = f"type=registry,ref={self.cache_name}"
build_platforms = platforms or (self.image_os.platforms if self.image_os else DEFAULT_PLATFORMS)
cache_platform = build_platforms[0] if build_platforms and len(build_platforms) == 1 else None
cache_name = self.cache_name(platform=cache_platform)
if cache_name is not None:
cache_from = f"type=registry,ref={cache_name}"
cache_to = f"{cache_from},mode=max"

if isinstance(metadata_file, bool) and metadata_file:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@
"cache_from": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/scratch/cache:1.0.0-scratch"
"ref": "ghcr.io/posit-dev/scratch/cache:1.0.0-scratch-amd64"
}
],
"cache_to": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/scratch/cache:1.0.0-scratch",
"ref": "ghcr.io/posit-dev/scratch/cache:1.0.0-scratch-amd64",
"mode": "max"
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@
"cache_from": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-min"
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-min-amd64"
}
],
"cache_to": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-min",
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-min-amd64",
"mode": "max"
}
]
Expand Down Expand Up @@ -113,13 +113,13 @@
"cache_from": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-std"
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-std-amd64"
}
],
"cache_to": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-std",
"ref": "ghcr.io/posit-dev/test-image/cache:1.0.0-ubuntu-22.04-std-amd64",
"mode": "max"
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@
"cache_from": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-min"
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-min-amd64"
}
],
"cache_to": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-min",
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-min-amd64",
"mode": "max"
}
]
Expand Down Expand Up @@ -144,13 +144,13 @@
"cache_from": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-std"
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-std-amd64"
}
],
"cache_to": [
{
"type": "registry",
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-std",
"ref": "ghcr.io/posit-dev/test-multi/cache:1.0.0-ubuntu-22.04-std-amd64",
"mode": "max"
}
]
Expand Down
5 changes: 3 additions & 2 deletions posit-bakery/test/image/test_image_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ def test_build_args_with_build_args(self, basic_standard_image_target):
def test_build_args_cache_registry(self, basic_standard_image_target):
"""Test the build property of an ImageTarget."""
basic_standard_image_target.settings = ImageTargetSettings(cache_registry="ghcr.io/posit-dev")
cache_name = basic_standard_image_target.cache_name(platform="linux/amd64")
expected_build_args = {
"context_path": basic_standard_image_target.context.base_path,
"file": basic_standard_image_target.containerfile,
Expand All @@ -534,8 +535,8 @@ def test_build_args_cache_registry(self, basic_standard_image_target):
"push": False,
"output": {},
"cache": True,
"cache_from": f"type=registry,ref={basic_standard_image_target.cache_name}",
"cache_to": f"type=registry,ref={basic_standard_image_target.cache_name},mode=max",
"cache_from": f"type=registry,ref={cache_name}",
"cache_to": f"type=registry,ref={cache_name},mode=max",
"metadata_file": None,
"platforms": ["linux/amd64"],
}
Expand Down