Skip to content

Commit 9fd355b

Browse files
authored
[FAST_BUILD] Merge manifests without pulling images (#2377)
1 parent 9ab935b commit 9fd355b

File tree

6 files changed

+164
-66
lines changed

6 files changed

+164
-66
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Merge single platform tags
2+
3+
env:
4+
REGISTRY: quay.io
5+
PUSH_TO_REGISTRY: ${{ (github.repository_owner == 'jupyter' || github.repository_owner == 'mathbunnyru') && (github.ref == 'refs/heads/main' || github.event_name == 'schedule') }}
6+
7+
on:
8+
workflow_call:
9+
inputs:
10+
image:
11+
description: Image name
12+
required: true
13+
type: string
14+
variant:
15+
description: Variant tag prefix
16+
required: true
17+
type: string
18+
timeout-minutes:
19+
description: Timeout in minutes
20+
default: 5
21+
type: number
22+
secrets:
23+
REGISTRY_USERNAME:
24+
required: true
25+
REGISTRY_TOKEN:
26+
required: true
27+
28+
permissions:
29+
contents: read
30+
31+
jobs:
32+
tag-merge:
33+
runs-on: ubuntu-24.04
34+
timeout-minutes: ${{ inputs.timeout-minutes }}
35+
36+
steps:
37+
- name: Checkout Repo ⚡️
38+
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
39+
40+
- name: Create dev environment 📦
41+
uses: ./.github/actions/create-dev-env
42+
43+
- name: Download aarch64 tags file 🏷
44+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
45+
with:
46+
name: ${{ inputs.image }}-aarch64-${{ inputs.variant }}-tags
47+
path: /tmp/jupyter/tags/
48+
if: ${{ !contains(inputs.variant, 'cuda11') }}
49+
50+
- name: Download x86_64 tags file 🏷
51+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
52+
with:
53+
name: ${{ inputs.image }}-x86_64-${{ inputs.variant }}-tags
54+
path: /tmp/jupyter/tags/
55+
56+
- name: Login to Registry 🔐
57+
if: env.PUSH_TO_REGISTRY == 'true'
58+
run: |
59+
docker login ${{ env.REGISTRY }} \
60+
--username ${{ secrets.REGISTRY_USERNAME }} \
61+
--password ${{ secrets.REGISTRY_TOKEN }} || \
62+
docker login ${{ env.REGISTRY }} \
63+
--username ${{ secrets.REGISTRY_USERNAME }} \
64+
--password ${{ secrets.REGISTRY_TOKEN }}
65+
shell: bash
66+
id: login
67+
68+
- name: Merge tags for the images 🔀
69+
run: |
70+
python3 -m tagging.apps.merge_tags \
71+
--image ${{ inputs.image }} \
72+
--variant ${{ inputs.variant }} \
73+
--tags-dir /tmp/jupyter/tags/
74+
shell: bash
75+
76+
- name: Logout from Registry 🔐
77+
if: always() && env.PUSH_TO_REGISTRY == 'true' && steps.login.outcome == 'success'
78+
run: |
79+
docker logout ${{ env.REGISTRY }}
80+
shell: bash
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Download a Docker image and its tags from GitHub artifacts, apply them, and push the image to the Registry; then merge them
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
image:
7+
description: Image name
8+
required: true
9+
type: string
10+
variant:
11+
description: Variant tag prefix
12+
required: true
13+
type: string
14+
secrets:
15+
REGISTRY_USERNAME:
16+
required: true
17+
REGISTRY_TOKEN:
18+
required: true
19+
20+
permissions:
21+
contents: read
22+
23+
jobs:
24+
tag-push:
25+
uses: ./.github/workflows/docker-tag-push.yml
26+
with:
27+
image: ${{ inputs.image }}
28+
variant: ${{ inputs.variant }}
29+
secrets:
30+
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
31+
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
32+
33+
tag-merge:
34+
uses: ./.github/workflows/docker-tag-merge.yml
35+
needs: tag-push
36+
with:
37+
image: ${{ inputs.image }}
38+
variant: ${{ inputs.variant }}
39+
secrets:
40+
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
41+
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}

.github/workflows/docker-tag-push.yml

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on:
1818
type: string
1919
timeout-minutes:
2020
description: Timeout in minutes
21-
default: 20
21+
default: 15
2222
type: number
2323
secrets:
2424
REGISTRY_USERNAME:
@@ -33,29 +33,22 @@ jobs:
3333
tag-push:
3434
runs-on: ubuntu-24.04
3535
timeout-minutes: ${{ inputs.timeout-minutes }}
36+
strategy:
37+
matrix:
38+
platform: [aarch64, x86_64]
3639

3740
steps:
3841
- name: Checkout Repo ⚡️
3942
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
40-
- name: Free disk space 🧹
41-
uses: ./.github/actions/free-disk-space
4243
- name: Create dev environment 📦
4344
uses: ./.github/actions/create-dev-env
4445

45-
- name: Download aarch64 image tar and apply tags 🏷
46+
- name: Download image tar and apply tags 🏷
4647
uses: ./.github/actions/apply-single-tags
4748
with:
4849
image: ${{ inputs.image }}
4950
variant: ${{ inputs.variant }}
50-
platform: aarch64
51-
if: ${{ !contains(inputs.variant, 'cuda11') }}
52-
53-
- name: Download x86_64 image tar and apply tags 🏷
54-
uses: ./.github/actions/apply-single-tags
55-
with:
56-
image: ${{ inputs.image }}
57-
variant: ${{ inputs.variant }}
58-
platform: x86_64
51+
platform: ${{ matrix.platform }}
5952

6053
- name: Login to Registry 🔐
6154
if: env.PUSH_TO_REGISTRY == 'true'
@@ -76,14 +69,6 @@ jobs:
7669
docker push --all-tags ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ inputs.image }}
7770
shell: bash
7871

79-
- name: Merge tags for the images 🔀
80-
run: |
81-
python3 -m tagging.apps.merge_tags \
82-
--image ${{ inputs.image }} \
83-
--variant ${{ inputs.variant }} \
84-
--tags-dir /tmp/jupyter/tags/
85-
shell: bash
86-
8772
- name: Logout from Registry 🔐
8873
if: always() && env.PUSH_TO_REGISTRY == 'true' && steps.login.outcome == 'success'
8974
run: |

.github/workflows/docker.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ on:
1515
# We use local reusable workflows to make architecture clean and simple
1616
# https://docs.github.com/en/actions/sharing-automations/reusing-workflows
1717
- ".github/workflows/docker-build-test-upload.yml"
18-
- ".github/workflows/docker-tag-push.yml"
18+
- ".github/workflows/docker-tag-push-merge.yml"
1919
- ".github/workflows/docker-wiki-update.yml"
2020

2121
# We use local composite actions to combine multiple workflow steps within one action
@@ -39,7 +39,7 @@ on:
3939
paths:
4040
- ".github/workflows/docker.yml"
4141
- ".github/workflows/docker-build-test-upload.yml"
42-
- ".github/workflows/docker-tag-push.yml"
42+
- ".github/workflows/docker-tag-push-merge.yml"
4343
- ".github/workflows/docker-wiki-update.yml"
4444

4545
- ".github/actions/apply-single-tags/action.yml"
@@ -376,7 +376,7 @@ jobs:
376376
needs: [aarch64-base, x86_64-base, aarch64-minimal, x86_64-minimal]
377377

378378
tag-push:
379-
uses: ./.github/workflows/docker-tag-push.yml
379+
uses: ./.github/workflows/docker-tag-push-merge.yml
380380
with:
381381
image: ${{ matrix.image }}
382382
variant: ${{ matrix.variant }}
@@ -443,7 +443,7 @@ jobs:
443443
if: ${{ !contains(github.event.pull_request.title, '[FAST_BUILD]') }}
444444

445445
tag-push-fast:
446-
uses: ./.github/workflows/docker-tag-push.yml
446+
uses: ./.github/workflows/docker-tag-push-merge.yml
447447
with:
448448
image: ${{ matrix.image }}
449449
variant: ${{ matrix.variant }}

docs/using/custom-images.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Our repository provides several customization points:
2121
- `REGISTRY`, `OWNER` (part of `env` in some GitHub workflows) - these allow to properly tag and refer to images during following steps:
2222
- [`build-test-upload`](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/docker-build-test-upload.yml)
2323
- [`contributed-recipes`](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/contributed-recipes.yml)
24-
- [`tag-push`](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/docker-tag-push.yml)
24+
- [`tag-push-merge`](https://github.com/jupyter/docker-stacks/blob/main/.github/workflows/docker-tag-push-merge.yml)
2525

2626
These customization points can't be changed during runtime.
2727
Read more about [Docker build arguments](https://docs.docker.com/build/building/variables/#arg-usage-example) and [GitHub environment variables for a single workflow](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#defining-environment-variables-for-a-single-workflow).

tagging/apps/merge_tags.py

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,15 @@
1616
from tagging.apps.config import Config
1717
from tagging.utils.get_platform import ALL_PLATFORMS
1818
from tagging.utils.get_prefix import get_file_prefix_for_platform
19-
from tagging.utils.git_helper import GitHelper
2019

2120
docker = plumbum.local["docker"]
2221

2322
LOGGER = logging.getLogger(__name__)
2423

2524

26-
def read_local_tags_from_files(config: Config) -> tuple[list[str], set[str]]:
25+
def read_local_tags_from_files(config: Config) -> set[str]:
2726
LOGGER.info(f"Read tags from file(s) for image: {config.image}")
2827

29-
all_local_tags = []
3028
merged_local_tags = set()
3129
for platform in ALL_PLATFORMS:
3230
LOGGER.info(f"Reading tags for platform: {platform}")
@@ -42,73 +40,67 @@ def read_local_tags_from_files(config: Config) -> tuple[list[str], set[str]]:
4240

4341
LOGGER.info(f"Tag file: {path} found")
4442
for tag in path.read_text().splitlines():
45-
all_local_tags.append(tag)
4643
merged_local_tags.add(tag.replace(platform + "-", ""))
4744

4845
LOGGER.info(f"Tags read for image: {config.image}")
49-
return all_local_tags, merged_local_tags
46+
return merged_local_tags
5047

5148

5249
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4))
53-
def pull_tag(tag: str) -> None:
54-
LOGGER.info(f"Pulling tag: {tag}")
55-
docker["pull", tag] & plumbum.FG
56-
LOGGER.info(f"Tag {tag} pulled successfully")
50+
def inspect_manifest(tag: str) -> None:
51+
LOGGER.info(f"Inspecting manifest for tag: {tag}")
52+
docker["buildx", "imagetools", "inspect", tag] & plumbum.FG
53+
LOGGER.info(f"Manifest {tag} exists")
5754

5855

59-
def pull_missing_tags(merged_tag: str, all_local_tags: list[str]) -> list[str]:
60-
existing_platform_tags = []
56+
def find_platform_tags(merged_tag: str) -> list[str]:
57+
platform_tags = []
6158

6259
for platform in ALL_PLATFORMS:
6360
platform_tag = merged_tag.replace(":", f":{platform}-")
64-
if platform_tag in all_local_tags:
65-
LOGGER.info(
66-
f"Tag {platform_tag} already exists locally, not pulling it from registry"
67-
)
68-
existing_platform_tags.append(platform_tag)
69-
continue
70-
71-
LOGGER.warning(f"Trying to pull: {platform_tag} from registry")
61+
LOGGER.warning(f"Trying to inspect: {platform_tag} in the registry")
7262
try:
73-
pull_tag(platform_tag)
74-
existing_platform_tags.append(platform_tag)
75-
LOGGER.info(f"Tag {platform_tag} pulled successfully")
63+
inspect_manifest(platform_tag)
64+
platform_tags.append(platform_tag)
65+
LOGGER.info(f"Tag {platform_tag} found successfully")
7666
except RetryError:
77-
LOGGER.warning(f"Pull failed, tag {platform_tag} doesn't exist")
67+
LOGGER.warning(f"Manifest for tag {platform_tag} doesn't exist")
7868

79-
return existing_platform_tags
69+
return platform_tags
8070

8171

82-
def merge_tags(
83-
merged_tag: str, all_local_tags: list[str], push_to_registry: bool
84-
) -> None:
72+
def merge_tags(merged_tag: str, push_to_registry: bool) -> None:
8573
LOGGER.info(f"Trying to merge tag: {merged_tag}")
8674

87-
existing_platform_tags = pull_missing_tags(merged_tag, all_local_tags)
75+
platform_tags = find_platform_tags(merged_tag)
76+
if not platform_tags:
77+
assert not push_to_registry, (
78+
f"No platform tags found for merged tag: {merged_tag}, "
79+
"and push to registry is enabled. "
80+
"Cannot create a manifest for a non-existing image."
81+
)
82+
LOGGER.info(
83+
f"Not running merge for tag: {merged_tag} as no platform tags found"
84+
)
85+
return
86+
8887
args = [
8988
"buildx",
9089
"imagetools",
9190
"create",
92-
*existing_platform_tags,
91+
*platform_tags,
9392
"--tag",
9493
merged_tag,
9594
]
9695
if not push_to_registry:
9796
args.append("--dry-run")
9897

99-
commit_hash_tag = GitHelper.commit_hash_tag()
100-
if not push_to_registry and merged_tag.endswith(commit_hash_tag):
101-
LOGGER.info(
102-
f"Not running merge for tag: {merged_tag} as it's a commit SHA tag and it wasn't pushed to registry"
103-
)
104-
return
105-
10698
LOGGER.info(f"Running command: {' '.join(args)}")
10799
docker[args] & plumbum.FG
108100
if push_to_registry:
109-
LOGGER.info(f"Pushed merged tag: {merged_tag} to registry")
101+
LOGGER.info(f"Pushed merged tag: {merged_tag}")
110102
else:
111-
LOGGER.info(f"Skipping push for tag: {merged_tag}")
103+
LOGGER.info(f"Skipped push for tag: {merged_tag}")
112104

113105

114106
if __name__ == "__main__":
@@ -119,8 +111,8 @@ def merge_tags(
119111

120112
LOGGER.info(f"Merging tags for image: {config.image}")
121113

122-
all_local_tags, merged_local_tags = read_local_tags_from_files(config)
114+
merged_local_tags = read_local_tags_from_files(config)
123115
for tag in merged_local_tags:
124-
merge_tags(tag, all_local_tags, push_to_registry)
116+
merge_tags(tag, push_to_registry)
125117

126118
LOGGER.info(f"Successfully merged tags for image: {config.image}")

0 commit comments

Comments
 (0)