Skip to content

Commit 7ea38ed

Browse files
committed
RHOAIENG-15333: chore(ci): create a Python script that reads Workbench image manifests and outputs snippet suited for inclusion in Docs
Co-authored-by: Guilherme Caponetto <[email protected]> RHOAIENG-15333: chore(ci): create a Python script that reads Workbench image manifests and outputs snippet suited for inclusion in Docs
1 parent 22364e2 commit 7ea38ed

File tree

5 files changed

+392
-20
lines changed

5 files changed

+392
-20
lines changed

.github/workflows/docs.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
"name": "Docs (release notes)"
3+
"on":
4+
"push":
5+
"pull_request":
6+
"workflow_dispatch":
7+
8+
permissions:
9+
contents: read
10+
11+
env:
12+
poetry_version: '1.8.3'
13+
14+
jobs:
15+
generate-releasenotes:
16+
name: Generate list of images for release notes
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Cache poetry in ~/.local
22+
uses: actions/cache/restore@v4
23+
id: cache-poetry-restore
24+
with:
25+
path: ~/.local
26+
key: "${{ runner.os }}-local-${{ env.poetry_version }}"
27+
28+
- name: Install poetry
29+
if: steps.cache-poetry-restore.outputs.cache-hit != 'true'
30+
run: pipx install poetry==${{ env.poetry_version }}
31+
env:
32+
PIPX_HOME: /home/runner/.local/pipx
33+
PIPX_BIN_DIR: /home/runner/.local/bin
34+
35+
- name: Check poetry is installed correctly
36+
run: poetry env info
37+
38+
- name: Save cache
39+
if: steps.cache-poetry-restore.outputs.cache-hit != 'true'
40+
uses: actions/cache/save@v4
41+
with:
42+
path: ~/.local
43+
key: ${{ steps.cache-poetry-restore.outputs.cache-primary-key }}
44+
45+
- name: Set up Python
46+
id: setup-python
47+
uses: actions/setup-python@v5
48+
with:
49+
python-version: '3.12'
50+
cache: 'poetry'
51+
52+
- name: Configure poetry
53+
run: poetry env use "${{ steps.setup-python.outputs.python-path }}"
54+
55+
- name: Install deps
56+
run: poetry install --sync
57+
58+
- name: Run the release notes script
59+
run: |
60+
set -Eeuxo pipefail
61+
poetry run ci/package_versions.py | tee ${GITHUB_STEP_SUMMARY}

ci/package_versions.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python3
2+
3+
from __future__ import annotations
4+
5+
import dataclasses
6+
import glob
7+
import io
8+
import json
9+
import pathlib
10+
import typing
11+
import unittest
12+
13+
import yaml
14+
15+
import package_versions_selftestdata
16+
17+
"""Generates the workbench software listings for https://access.redhat.com/articles/rhoai-supported-configs
18+
using the Markdown variant described at https://access.redhat.com/articles/7056942"""
19+
20+
"""
21+
TODO:
22+
* separate reading data and printing output
23+
so that output can be properly sorted (by opendatahub.io/notebook-image-order probably)
24+
* don't repeat image name when printing multiple tags for it
25+
* run this in red-hat-data-services repo so we also have (or not have) Habana image
26+
* diff it with what's in the knowledge base now, to check if outputs match
27+
"""
28+
29+
ROOT_DIR = pathlib.Path(__file__).parent.parent
30+
31+
32+
# unused for now
33+
@dataclasses.dataclass
34+
class Manifest:
35+
_data: any
36+
37+
@property
38+
def name(self) -> str:
39+
return self._data['metadata']['annotations']['opendatahub.io/notebook-image-name']
40+
41+
@property
42+
def order(self) -> int:
43+
return int(self._data['metadata']['annotations']['opendatahub.io/notebook-image-order'])
44+
45+
@property
46+
def tags(self) -> list[Tag]:
47+
return [Tag(tag) for tag in self._data['spec']['tags']]
48+
49+
50+
@dataclasses.dataclass()
51+
class Tag:
52+
_data: any
53+
54+
@property
55+
def name(self) -> str:
56+
return self._data['name']
57+
58+
@property
59+
def recommended(self) -> bool:
60+
if 'opendatahub.io/workbench-image-recommended' not in self._data['annotations']:
61+
return False
62+
return self._data['annotations']['opendatahub.io/workbench-image-recommended'] == 'true'
63+
64+
@property
65+
def outdated(self) -> bool:
66+
if 'opendatahub.io/image-tag-outdated' not in self._data['annotations']:
67+
return False
68+
return self._data['annotations']['opendatahub.io/image-tag-outdated'] == 'true'
69+
70+
@property
71+
def sw_general(self) -> list[typing.TypedDict("Software", {"name": str, "version": str})]:
72+
return json.loads(self._data['annotations']['opendatahub.io/notebook-software'])
73+
74+
@property
75+
def sw_python(self) -> list[typing.TypedDict("Software", {"name": str, "version": str})]:
76+
return json.loads(self._data['annotations']['opendatahub.io/notebook-python-dependencies'])
77+
78+
79+
def main():
80+
pathname = 'manifests/base/*.yaml'
81+
# pathname = 'manifests/overlays/additional/*.yaml'
82+
imagestreams: list[Manifest] = []
83+
for fn in glob.glob(pathname, root_dir=ROOT_DIR):
84+
# there may be more than one yaml document in a file (e.g. rstudio buildconfigs)
85+
with (open(ROOT_DIR / fn, 'rt') as fp):
86+
for data in yaml.safe_load_all(fp):
87+
if 'kind' not in data or data['kind'] != 'ImageStream':
88+
continue
89+
if 'labels' not in data['metadata']:
90+
continue
91+
if ('opendatahub.io/notebook-image' not in data['metadata']['labels'] or
92+
data['metadata']['labels']['opendatahub.io/notebook-image'] != 'true'):
93+
continue
94+
imagestream = Manifest(data)
95+
imagestreams.append(imagestream)
96+
97+
tabular_data: list[tuple[str, str, str]] = []
98+
99+
# todo(jdanek): maybe we want to change to sorting by `imagestream.order`
100+
# for imagestream in sorted(imagestreams, key=lambda imagestream: imagestream.order):
101+
for imagestream in sorted(imagestreams, key=lambda imagestream: imagestream.name):
102+
name = imagestream.name
103+
104+
prev_tag = None
105+
for tag in imagestream.tags:
106+
if tag.outdated:
107+
continue
108+
109+
tag_name = tag.name
110+
recommended = tag.recommended
111+
112+
sw_general = tag.sw_general
113+
sw_python = tag.sw_python
114+
115+
software: list[str] = []
116+
for item in sw_general:
117+
sw_name: str
118+
sw_version: str
119+
sw_name, sw_version = item['name'], item['version']
120+
sw_version = sw_version.lstrip("v")
121+
122+
# do not allow duplicates when general and python lists both contain e.g. TensorFlow
123+
if sw_name in set(item['name'] for item in sw_python):
124+
continue
125+
software.append(f"{sw_name} {sw_version}")
126+
for item in sw_python:
127+
sw_name: str
128+
sw_version: str
129+
sw_name, sw_version = item['name'], item['version']
130+
sw_version = sw_version.lstrip("v")
131+
software.append(f"{sw_name}: {sw_version}")
132+
133+
maybe_techpreview = "" if name not in ('code-server',) else " (Technology Preview)"
134+
maybe_recommended = "" if not recommended or len(imagestream.tags) == 1 else ' (Recommended)'
135+
136+
tabular_data.append((
137+
f'{name}{maybe_techpreview}' if not prev_tag else '',
138+
f'{tag_name}{maybe_recommended}',
139+
', '.join(software)
140+
))
141+
142+
prev_tag = tag
143+
144+
print('| Image name | Image version | Preinstalled packages |')
145+
print('|------------|---------------|-----------------------|')
146+
for row in tabular_data:
147+
print(f'| {row[0]} | {row[1]} | {row[2]} |')
148+
149+
print()
150+
151+
print('## Source')
152+
print()
153+
print('_mouse hover reveals copy button in top right corner of the box_')
154+
print()
155+
print('```markdown')
156+
print('Image name | Image version | Preinstalled packages')
157+
print('--------- | ---------')
158+
for row in tabular_data:
159+
print(f'{row[0]} | {row[1]} | {row[2]}')
160+
print('```')
161+
162+
class TestManifest(unittest.TestCase):
163+
_data = yaml.safe_load(io.StringIO(package_versions_selftestdata.imagestream))
164+
manifest = Manifest(_data)
165+
166+
def test_name(self):
167+
assert self.manifest.name == "Minimal Python"
168+
169+
def test_order(self):
170+
assert self.manifest.order == 1
171+
172+
def test_tag_name(self):
173+
assert self.manifest.tags[0].name == "2024.2"
174+
175+
def test_tag_recommended(self):
176+
assert self.manifest.tags[0].recommended is True
177+
178+
def test_tag_sw_general(self):
179+
assert self.manifest.tags[0].sw_general == [{'name': 'Python', 'version': 'v3.11'}]
180+
181+
def test_tag_sw_python(self):
182+
assert self.manifest.tags[0].sw_python == [{'name': 'JupyterLab', 'version': '4.2'}]
183+
184+
185+
if __name__ == '__main__':
186+
main()

ci/package_versions_selftestdata.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
imagestream = """
2+
---
3+
apiVersion: image.openshift.io/v1
4+
kind: ImageStream
5+
metadata:
6+
labels:
7+
opendatahub.io/notebook-image: "true"
8+
annotations:
9+
opendatahub.io/notebook-image-url: "https://github.com//opendatahub-io/notebooks/tree/main/jupyter/minimal"
10+
opendatahub.io/notebook-image-name: "Minimal Python"
11+
opendatahub.io/notebook-image-desc: "Jupyter notebook image with minimal dependency set to start experimenting with Jupyter environment."
12+
opendatahub.io/notebook-image-order: "1"
13+
name: jupyter-minimal-notebook
14+
spec:
15+
lookupPolicy:
16+
local: true
17+
tags:
18+
# N Version of the image
19+
- annotations:
20+
# language=json
21+
opendatahub.io/notebook-software: |
22+
[
23+
{"name": "Python", "version": "v3.11"}
24+
]
25+
# language=json
26+
opendatahub.io/notebook-python-dependencies: |
27+
[
28+
{"name": "JupyterLab","version": "4.2"}
29+
]
30+
openshift.io/imported-from: quay.io/opendatahub/workbench-images
31+
opendatahub.io/workbench-image-recommended: 'true'
32+
opendatahub.io/default-image: "true"
33+
opendatahub.io/notebook-build-commit: $(odh-minimal-notebook-image-commit-n)
34+
from:
35+
kind: DockerImage
36+
name: $(odh-minimal-notebook-image-n)
37+
name: "2024.2"
38+
referencePolicy:
39+
type: Source
40+
# N Version of the image
41+
- annotations:
42+
# language=json
43+
opendatahub.io/notebook-software: |
44+
[
45+
{"name": "Python", "version": "v3.9"}
46+
]
47+
# language=json
48+
opendatahub.io/notebook-python-dependencies: |
49+
[
50+
{"name": "JupyterLab","version": "3.6"},
51+
{"name": "Notebook","version": "6.5"}
52+
]
53+
openshift.io/imported-from: quay.io/opendatahub/workbench-images
54+
opendatahub.io/workbench-image-recommended: 'false'
55+
opendatahub.io/default-image: "true"
56+
opendatahub.io/notebook-build-commit: $(odh-minimal-notebook-image-commit-n-1)
57+
from:
58+
kind: DockerImage
59+
name: $(odh-minimal-notebook-image-n-1)
60+
name: "2024.1"
61+
referencePolicy:
62+
type: Source
63+
"""

0 commit comments

Comments
 (0)