Skip to content

Commit 2c1df40

Browse files
authored
Make taggers manifest functions (#2252)
* Make taggers and manifests functions * Add changelog
1 parent 48b0650 commit 2c1df40

17 files changed

+188
-258
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ All image manifests can be found in [the wiki](https://github.com/jupyter/docker
88
Affected: all images.
99

1010
- **Non-breaking:** Add `conda` and `mamba` version taggers ([#2251](https://github.com/jupyter/docker-stacks/pull/2251)).
11+
- **Non-breaking:** Make taggers and manifests functions ([#2252](https://github.com/jupyter/docker-stacks/pull/2252)).
1112

1213
## 2025-02-21
1314

docs/maintaining/tagging.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Tagging and manifest creation
22

3-
The main purpose of the source code in [the `tagging` folder](https://github.com/jupyter/docker-stacks/tree/main/tagging) is to properly write tag files and manifests for single-platform images,
4-
apply these tags, and merge single-platform images into one multi-arch image.
3+
The main purpose of the source code in [the `tagging` folder](https://github.com/jupyter/docker-stacks/tree/main/tagging) is to
4+
properly write tags file, build history line and manifest for a single-platform image,
5+
apply these tags, and then merge single-platform images into one multi-arch image.
56

67
## What is a tag and a manifest
78

@@ -16,9 +17,9 @@ For example, we dump all `conda` packages with their versions into the manifest.
1617

1718
- All images are organized in a hierarchical tree.
1819
More info on [image relationships](../using/selecting.md#image-relationships).
19-
- Classes inherit from `TaggerInterface` and `ManifestInterface` to generate tags and manifest pieces by running commands in Docker containers.
20+
- `TaggerInterface` and `ManifestInterface` are interfaces for functions to generate tags and manifest pieces by running commands in Docker containers.
2021
- Tags and manifests are reevaluated for each image in the hierarchy since values may change between parent and child images.
21-
- To tag an image and create its manifest, run `make hook/<somestack>` (e.g., `make hook/base-notebook`).
22+
- To tag an image and create its manifest and build history line, run `make hook/<somestack>` (e.g., `make hook/base-notebook`).
2223

2324
## Utils
2425

@@ -46,42 +47,42 @@ The prefix of commit hash (namely, 12 letters) is used as an image tag to make i
4647

4748
### Tagger
4849

49-
`Tagger` is a class that can be run inside a docker container to calculate a tag for an image.
50+
`Tagger` is a function that runs commands inside a docker container to calculate a tag for an image.
5051

51-
All the taggers are inherited from `TaggerInterface`:
52+
All the taggers follow `TaggerInterface`:
5253

5354
```{literalinclude} ../../tagging/taggers/tagger_interface.py
5455
:language: py
55-
:start-at: class TaggerInterface
56+
:start-at: TaggerInterface
5657
```
5758

58-
So, the `tag_value(container)` method gets a docker container as an input and returns a tag.
59+
So, the `tagger(container)` gets a docker container as an input and returns a tag.
5960

60-
`SHATagger` example:
61+
`commit_sha_tagger` example:
6162

6263
```{literalinclude} ../../tagging/taggers/sha.py
6364
:language: py
64-
:start-at: class SHATagger
65+
:start-at: def
6566
```
6667

67-
- `taggers/` subdirectory contains all the taggers.
68+
- `taggers/` subdirectory contains all taggers.
6869
- `apps/write_tags_file.py`, `apps/apply_tags.py`, and `apps/merge_tags.py` are Python executable used to write tags for an image, apply tags from a file, and create multi-arch images.
6970

7071
### Manifest
7172

72-
All manifest classes except `BuildInfo` are inherited from `ManifestInterface`
73-
and `markdown_piece(container)` method returns a piece of the build manifest.
73+
All manifest functions except `build_info_manifest` follow `ManifestInterface`
74+
and `manifest(container)` method returns a piece of the build manifest.
7475

7576
```{literalinclude} ../../tagging/manifests/manifest_interface.py
7677
:language: py
77-
:start-at: class ManifestInterface
78+
:start-at: ManifestInterface
7879
```
7980

80-
`AptPackagesManifest` example:
81+
`apt_packages_manifest` example:
8182

8283
```{literalinclude} ../../tagging/manifests/apt_packages.py
8384
:language: py
84-
:start-at: class AptPackagesManifest
85+
:start-at: def
8586
```
8687

8788
- `quoted_output(container, cmd)` simply runs the command inside a container using `DockerRunner.exec_cmd` and wraps it to triple quotes to create a valid markdown piece.

tagging/apps/write_manifest.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from tagging.apps.config import Config
1111
from tagging.hierarchy.get_manifests import get_manifests
1212
from tagging.hierarchy.get_taggers import get_taggers
13-
from tagging.manifests.build_info import BuildInfo, BuildInfoConfig
13+
from tagging.manifests.build_info import BuildInfoConfig, build_info_manifest
1414
from tagging.utils.docker_runner import DockerRunner
1515
from tagging.utils.get_prefix import get_file_prefix, get_tag_prefix
1616
from tagging.utils.git_helper import GitHelper
@@ -27,7 +27,7 @@ def get_build_history_line(config: Config, filename: str, container: Container)
2727

2828
taggers = get_taggers(config.image)
2929
tags_prefix = get_tag_prefix(config.variant)
30-
all_tags = [tags_prefix + "-" + tagger.tag_value(container) for tagger in taggers]
30+
all_tags = [tags_prefix + "-" + tagger(container) for tagger in taggers]
3131

3232
date_column = f"`{BUILD_TIMESTAMP}`"
3333
image_column = MARKDOWN_LINE_BREAK.join(
@@ -64,7 +64,7 @@ def get_manifest(config: Config, commit_hash_tag: str, container: Container) ->
6464
LOGGER.info(f"Calculating manifest file for image: {config.image}")
6565

6666
manifests = get_manifests(config.image)
67-
manifest_names = [manifest.__class__.__name__ for manifest in manifests]
67+
manifest_names = [manifest.__name__ for manifest in manifests]
6868
LOGGER.info(f"Using manifests: {manifest_names}")
6969

7070
build_info_config = BuildInfoConfig(
@@ -77,8 +77,8 @@ def get_manifest(config: Config, commit_hash_tag: str, container: Container) ->
7777

7878
markdown_pieces = [
7979
f"# Build manifest for image: {config.image}:{commit_hash_tag}",
80-
BuildInfo.markdown_piece(build_info_config).get_str(),
81-
*(manifest.markdown_piece(container).get_str() for manifest in manifests),
80+
build_info_manifest(build_info_config).get_str(),
81+
*(manifest(container).get_str() for manifest in manifests),
8282
]
8383
markdown_content = "\n\n".join(markdown_pieces) + "\n"
8484

tagging/apps/write_tags_file.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def get_tags(config: Config) -> list[str]:
2020
tags = [f"{config.full_image()}:{tags_prefix}-latest"]
2121
with DockerRunner(config.full_image()) as container:
2222
for tagger in taggers:
23-
tagger_name = tagger.__class__.__name__
24-
tag_value = tagger.tag_value(container)
23+
tagger_name = tagger.__name__
24+
tag_value = tagger(container)
2525
LOGGER.info(
2626
f"Calculated tag, tagger_name: {tagger_name} tag_value: {tag_value}"
2727
)

tagging/hierarchy/images_hierarchy.py

Lines changed: 32 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,17 @@
22
# Distributed under the terms of the Modified BSD License.
33
from dataclasses import dataclass, field
44

5-
from tagging.manifests.apt_packages import AptPackagesManifest
6-
from tagging.manifests.conda_environment import CondaEnvironmentManifest
7-
from tagging.manifests.julia_packages import JuliaPackagesManifest
5+
from tagging.manifests.apt_packages import apt_packages_manifest
6+
from tagging.manifests.conda_environment import conda_environment_manifest
7+
from tagging.manifests.julia_packages import julia_packages_manifest
88
from tagging.manifests.manifest_interface import ManifestInterface
9-
from tagging.manifests.r_packages import RPackagesManifest
10-
from tagging.manifests.spark_info import SparkInfoManifest
11-
from tagging.taggers.date import DateTagger
12-
from tagging.taggers.sha import SHATagger
9+
from tagging.manifests.r_packages import r_packages_manifest
10+
from tagging.manifests.spark_info import spark_info_manifest
11+
from tagging.taggers import versions
12+
from tagging.taggers.date import date_tagger
13+
from tagging.taggers.sha import commit_sha_tagger
1314
from tagging.taggers.tagger_interface import TaggerInterface
14-
from tagging.taggers.ubuntu_version import UbuntuVersionTagger
15-
from tagging.taggers.versions import (
16-
CondaVersionTagger,
17-
JavaVersionTagger,
18-
JuliaVersionTagger,
19-
JupyterHubVersionTagger,
20-
JupyterLabVersionTagger,
21-
JupyterNotebookVersionTagger,
22-
MambaVersionTagger,
23-
PythonMajorMinorVersionTagger,
24-
PythonVersionTagger,
25-
PytorchVersionTagger,
26-
RVersionTagger,
27-
SparkVersionTagger,
28-
TensorflowVersionTagger,
29-
)
15+
from tagging.taggers.ubuntu_version import ubuntu_version_tagger
3016

3117

3218
@dataclass
@@ -40,55 +26,55 @@ class ImageDescription:
4026
"docker-stacks-foundation": ImageDescription(
4127
parent_image=None,
4228
taggers=[
43-
SHATagger(),
44-
DateTagger(),
45-
UbuntuVersionTagger(),
46-
PythonMajorMinorVersionTagger(),
47-
PythonVersionTagger(),
48-
MambaVersionTagger(),
49-
CondaVersionTagger(),
29+
commit_sha_tagger,
30+
date_tagger,
31+
ubuntu_version_tagger,
32+
versions.python_major_minor_tagger,
33+
versions.python_tagger,
34+
versions.mamba_tagger,
35+
versions.conda_tagger,
5036
],
51-
manifests=[CondaEnvironmentManifest(), AptPackagesManifest()],
37+
manifests=[conda_environment_manifest, apt_packages_manifest],
5238
),
5339
"base-notebook": ImageDescription(
5440
parent_image="docker-stacks-foundation",
5541
taggers=[
56-
JupyterNotebookVersionTagger(),
57-
JupyterLabVersionTagger(),
58-
JupyterHubVersionTagger(),
42+
versions.jupyter_notebook_tagger,
43+
versions.jupyter_lab_tagger,
44+
versions.jupyter_hub_tagger,
5945
],
6046
),
6147
"minimal-notebook": ImageDescription(parent_image="base-notebook"),
6248
"scipy-notebook": ImageDescription(parent_image="minimal-notebook"),
6349
"r-notebook": ImageDescription(
6450
parent_image="minimal-notebook",
65-
taggers=[RVersionTagger()],
66-
manifests=[RPackagesManifest()],
51+
taggers=[versions.r_tagger],
52+
manifests=[r_packages_manifest],
6753
),
6854
"julia-notebook": ImageDescription(
6955
parent_image="minimal-notebook",
70-
taggers=[JuliaVersionTagger()],
71-
manifests=[JuliaPackagesManifest()],
56+
taggers=[versions.julia_tagger],
57+
manifests=[julia_packages_manifest],
7258
),
7359
"tensorflow-notebook": ImageDescription(
74-
parent_image="scipy-notebook", taggers=[TensorflowVersionTagger()]
60+
parent_image="scipy-notebook", taggers=[versions.tensorflow_tagger]
7561
),
7662
"pytorch-notebook": ImageDescription(
77-
parent_image="scipy-notebook", taggers=[PytorchVersionTagger()]
63+
parent_image="scipy-notebook", taggers=[versions.python_tagger]
7864
),
7965
"datascience-notebook": ImageDescription(
8066
parent_image="scipy-notebook",
81-
taggers=[RVersionTagger(), JuliaVersionTagger()],
82-
manifests=[RPackagesManifest(), JuliaPackagesManifest()],
67+
taggers=[versions.r_tagger, versions.julia_tagger],
68+
manifests=[r_packages_manifest, julia_packages_manifest],
8369
),
8470
"pyspark-notebook": ImageDescription(
8571
parent_image="scipy-notebook",
86-
taggers=[SparkVersionTagger(), JavaVersionTagger()],
87-
manifests=[SparkInfoManifest()],
72+
taggers=[versions.spark_tagger, versions.java_tagger],
73+
manifests=[spark_info_manifest],
8874
),
8975
"all-spark-notebook": ImageDescription(
9076
parent_image="pyspark-notebook",
91-
taggers=[RVersionTagger()],
92-
manifests=[RPackagesManifest()],
77+
taggers=[versions.r_tagger],
78+
manifests=[r_packages_manifest],
9379
),
9480
}

tagging/manifests/apt_packages.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22
# Distributed under the terms of the Modified BSD License.
33
from docker.models.containers import Container
44

5-
from tagging.manifests.manifest_interface import ManifestInterface, MarkdownPiece
5+
from tagging.manifests.manifest_interface import MarkdownPiece
66
from tagging.utils.quoted_output import quoted_output
77

88

9-
class AptPackagesManifest(ManifestInterface):
10-
@staticmethod
11-
def markdown_piece(container: Container) -> MarkdownPiece:
12-
return MarkdownPiece(
13-
title="## Apt Packages",
14-
sections=[quoted_output(container, "apt list --installed")],
15-
)
9+
def apt_packages_manifest(container: Container) -> MarkdownPiece:
10+
return MarkdownPiece(
11+
title="## Apt Packages",
12+
sections=[quoted_output(container, "apt list --installed")],
13+
)

tagging/manifests/build_info.py

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,33 @@ def full_image(self) -> str:
2525
return f"{self.registry}/{self.owner}/{self.image}"
2626

2727

28-
class BuildInfo:
28+
def build_info_manifest(config: BuildInfoConfig) -> MarkdownPiece:
2929
"""BuildInfo doesn't fall under common interface, and we run it separately"""
30-
31-
@staticmethod
32-
def markdown_piece(config: BuildInfoConfig) -> MarkdownPiece:
33-
commit_hash = GitHelper.commit_hash()
34-
commit_hash_tag = GitHelper.commit_hash_tag()
35-
commit_message = GitHelper.commit_message()
36-
37-
# Unfortunately, `docker images` doesn't work when specifying `docker.io` as registry
38-
fixed_registry = config.registry + "/" if config.registry != "docker.io" else ""
39-
40-
image_size = docker[
41-
"images",
42-
f"{fixed_registry}{config.owner}/{config.image}:latest",
43-
"--format",
44-
"{{.Size}}",
45-
]().rstrip()
46-
47-
build_info = textwrap.dedent(
48-
f"""\
49-
- Build timestamp: {config.build_timestamp}
50-
- Docker image: `{config.full_image()}:{commit_hash_tag}`
51-
- Docker image size: {image_size}
52-
- Git commit SHA: [{commit_hash}](https://github.com/{config.repository}/commit/{commit_hash})
53-
- Git commit message:
54-
55-
```text
56-
{{message}}
57-
```"""
58-
).format(message=commit_message)
59-
60-
return MarkdownPiece(title="## Build Info", sections=[build_info])
30+
commit_hash = GitHelper.commit_hash()
31+
commit_hash_tag = GitHelper.commit_hash_tag()
32+
commit_message = GitHelper.commit_message()
33+
34+
# Unfortunately, `docker images` doesn't work when specifying `docker.io` as registry
35+
fixed_registry = config.registry + "/" if config.registry != "docker.io" else ""
36+
37+
image_size = docker[
38+
"images",
39+
f"{fixed_registry}{config.owner}/{config.image}:latest",
40+
"--format",
41+
"{{.Size}}",
42+
]().rstrip()
43+
44+
build_info = textwrap.dedent(
45+
f"""\
46+
- Build timestamp: {config.build_timestamp}
47+
- Docker image: `{config.full_image()}:{commit_hash_tag}`
48+
- Docker image size: {image_size}
49+
- Git commit SHA: [{commit_hash}](https://github.com/{config.repository}/commit/{commit_hash})
50+
- Git commit message:
51+
52+
```text
53+
{{message}}
54+
```"""
55+
).format(message=commit_message)
56+
57+
return MarkdownPiece(title="## Build Info", sections=[build_info])

tagging/manifests/conda_environment.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@
22
# Distributed under the terms of the Modified BSD License.
33
from docker.models.containers import Container
44

5-
from tagging.manifests.manifest_interface import ManifestInterface, MarkdownPiece
5+
from tagging.manifests.manifest_interface import MarkdownPiece
66
from tagging.utils.docker_runner import DockerRunner
77
from tagging.utils.quoted_output import quoted_output
88

99

10-
class CondaEnvironmentManifest(ManifestInterface):
11-
@staticmethod
12-
def markdown_piece(container: Container) -> MarkdownPiece:
13-
return MarkdownPiece(
14-
title="## Python Packages",
15-
sections=[
16-
DockerRunner.exec_cmd(container, "python --version"),
17-
quoted_output(container, "conda info"),
18-
quoted_output(container, "mamba info"),
19-
quoted_output(container, "mamba list"),
20-
],
21-
)
10+
def conda_environment_manifest(container: Container) -> MarkdownPiece:
11+
return MarkdownPiece(
12+
title="## Python Packages",
13+
sections=[
14+
DockerRunner.exec_cmd(container, "python --version"),
15+
quoted_output(container, "conda info"),
16+
quoted_output(container, "mamba info"),
17+
quoted_output(container, "mamba list"),
18+
],
19+
)

0 commit comments

Comments
 (0)