Skip to content

Commit 5a11fca

Browse files
authored
feat: Add update-manifests-images script for automated release preparation (#662)
This commit implements issue #657 by adding automation for updating image tags in Kubernetes manifests during the release process. Changes: - Add releasing/update-manifests-images.py script that updates image tags in kustomization.yaml files for all 6 Notebooks v1 components - Add releasing/README.md with comprehensive release process documentation The script uses the correct ghcr.io image paths from the existing kustomization files and only updates the newTag field, preserving the original image registry paths. Components updated by the script: - Jupyter Web App (ghcr.io/kubeflow/notebooks/jupyter-web-app) - Tensorboards Web App (ghcr.io/kubeflow/kubeflow/tensorboards-web-app) - Volumes Web App (ghcr.io/kubeflow/kubeflow/volumes-web-app) - Notebook Controller (ghcr.io/kubeflow/kubeflow/notebook-controller) - PVC Viewer Controller (ghcr.io/kubeflow/kubeflow/pvcviewer-controller) - Tensorboard Controller (ghcr.io/kubeflow/kubeflow/tensorboard-controller) Closes #657 Signed-off-by: Asaad Balum <asaad.balum@gmail.com>
1 parent e4c3bc1 commit 5a11fca

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed

releasing/README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Releasing
2+
3+
This folder contains scripts and instructions for releasing images and manifests
4+
for the components of this repository.
5+
6+
## Release Process
7+
8+
The Notebooks Working Group release process follows the Kubeflow [release timeline](https://github.com/kubeflow/community/blob/master/releases/handbook.md#timeline)
9+
and the [release versioning policy](https://github.com/kubeflow/community/blob/master/releases/handbook.md#versioning-policy),
10+
as defined in the [Kubeflow release handbook](https://github.com/kubeflow/community/blob/master/releases/handbook.md).
11+
12+
## Steps for releasing
13+
14+
### Prepare (MINOR RELEASE)
15+
16+
1. Create a new release branch from `notebooks-v1`:
17+
18+
```sh
19+
RELEASE_BRANCH="v1.10-branch"
20+
git checkout -b $RELEASE_BRANCH origin/notebooks-v1
21+
# OR: git checkout -b $RELEASE_BRANCH upstream/notebooks-v1
22+
23+
# push the release branch to the upstream repository
24+
git push origin $RELEASE_BRANCH
25+
# OR: git push upstream $RELEASE_BRANCH
26+
```
27+
28+
### Prepare (PATCH RELEASE)
29+
30+
1. Check out the release branch for the version you are releasing:
31+
32+
```sh
33+
RELEASE_BRANCH="v1.10-branch"
34+
git checkout $RELEASE_BRANCH
35+
```
36+
37+
2. Cherry-pick the changes you want to include in the patch release (if they have not been added via a PR):
38+
39+
```sh
40+
# cherry-pick the commit
41+
# NOTE: you could also use an IDE to make this easier
42+
git cherry-pick HASH_OF_COMMIT
43+
44+
# push the changes to the release branch
45+
git push origin $RELEASE_BRANCH
46+
# OR: git push upstream $RELEASE_BRANCH
47+
```
48+
49+
### Create Release (ALL RELEASES)
50+
51+
1. Update the image tags in the manifests to the new version:
52+
53+
```sh
54+
VERSION="v1.10.0-rc.0" # for a release candidate
55+
# VERSION="v1.10.0" # for a final release
56+
57+
# Ensure required Python dependency is installed
58+
pip install ruamel.yaml
59+
60+
# Run the release script
61+
python3 releasing/update-manifests-images.py $VERSION
62+
```
63+
64+
2. Bump version in `releasing/version/VERSION` file:
65+
66+
```sh
67+
echo "$VERSION" > releasing/version/VERSION
68+
```
69+
70+
3. Create a PR into the release branch with the changes from steps 1 and 2:
71+
72+
- The PR should be titled `chore: Release vX.X.X-rc.X` or `chore: Release vX.X.X`.
73+
- This is to trigger the GitHub Actions tests, and ensure a release is possible.
74+
- The only changes should be the image tags in the manifests and the VERSION bump.
75+
- __WARNING:__ the "example notebook servers" builds are not triggered on PRs (they need to push to a registry as they `FROM` each other).
76+
Check the last commit which updated `components/example-notebook-servers/**` and ensure that its build succeeded.
77+
If not, STOP, and fix the build for the "example notebook servers" before merging the release PR.
78+
- Once the tests pass, merge the PR (this will trigger the release builds).
79+
80+
4. Create a tag in the release branch:
81+
82+
```sh
83+
# check out the release branch at the commit that was merged
84+
git checkout $RELEASE_BRANCH
85+
# NOTE: make sure you are at the current HEAD of the release branch
86+
# which should be the commit from the PR merged in the previous step
87+
88+
# create the tag
89+
# NOTE: this is a signed tag, so you must have set up your GPG key
90+
git tag -s "$VERSION" -m "$VERSION"
91+
92+
# verify the tag signature
93+
git verify-tag "$VERSION"
94+
95+
# push the tag
96+
git push origin "$VERSION"
97+
# OR: git push upstream "$VERSION"
98+
```
99+
100+
5. Create a new release on GitHub:
101+
102+
- Go to the [releases page](https://github.com/kubeflow/notebooks/releases).
103+
- Click `"Draft a new release"`.
104+
- Enter the tag you just created in the `"Choose a tag"` box.
105+
- If this is an RC release:
106+
- check the `"This is a pre-release"` box
107+
- make a basic description of the release but don't include the full release notes.
108+
- If this is a final release:
109+
- Set the `"Previous tag"` to the previous non-RC release and click `"Generate release notes"`.
110+
- Try and format the release notes like the previous releases.
111+
- Click `"Publish release"`.
112+
113+
## Components Updated by Script
114+
115+
The `update-manifests-images.py` script updates image tags for the following Notebooks v1 components:
116+
117+
- **Jupyter Web App** - `components/crud-web-apps/jupyter/manifests/base/kustomization.yaml`
118+
- **Tensorboards Web App** - `components/crud-web-apps/tensorboards/manifests/base/kustomization.yaml`
119+
- **Volumes Web App** - `components/crud-web-apps/volumes/manifests/base/kustomization.yaml`
120+
- **Notebook Controller** - `components/notebook-controller/config/base/kustomization.yaml`
121+
- **PVC Viewer Controller** - `components/pvcviewer-controller/config/base/kustomization.yaml`
122+
- **Tensorboard Controller** - `components/tensorboard-controller/config/base/kustomization.yaml`
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import logging
5+
import sys
6+
import ruamel.yaml
7+
8+
log = logging.getLogger(__name__)
9+
10+
11+
class YAMLEmitterNoVersionDirective(ruamel.yaml.emitter.Emitter):
12+
def write_version_directive(self, version_text):
13+
pass
14+
15+
def expect_document_start(self, first=False):
16+
if not isinstance(self.event, ruamel.yaml.events.DocumentStartEvent):
17+
return super().expect_document_start(first=first)
18+
version = self.event.version
19+
self.event.version = None
20+
ret = super().expect_document_start(first=first)
21+
self.event.version = version
22+
return ret
23+
24+
25+
class YAML(ruamel.yaml.YAML):
26+
def __init__(self, *args, **kwargs):
27+
super().__init__(*args, **kwargs)
28+
self.version = (1, 1)
29+
self.Emitter = YAMLEmitterNoVersionDirective
30+
self.preserve_quotes = True
31+
32+
33+
yaml = YAML()
34+
35+
applications = [
36+
{
37+
"name": "Jupyter Web App",
38+
"kustomization": (
39+
"components/crud-web-apps/jupyter/"
40+
"manifests/base/kustomization.yaml"
41+
),
42+
"images": [
43+
{
44+
"name": "ghcr.io/kubeflow/notebooks/jupyter-web-app",
45+
"newName": "ghcr.io/kubeflow/notebooks/jupyter-web-app",
46+
},
47+
],
48+
},
49+
{
50+
"name": "Tensorboards Web App",
51+
"kustomization": (
52+
"components/crud-web-apps/tensorboards/"
53+
"manifests/base/kustomization.yaml"
54+
),
55+
"images": [
56+
{
57+
"name": "ghcr.io/kubeflow/kubeflow/tensorboards-web-app",
58+
"newName": "ghcr.io/kubeflow/kubeflow/tensorboards-web-app",
59+
},
60+
],
61+
},
62+
{
63+
"name": "Volumes Web App",
64+
"kustomization": (
65+
"components/crud-web-apps/volumes/"
66+
"manifests/base/kustomization.yaml"
67+
),
68+
"images": [
69+
{
70+
"name": "ghcr.io/kubeflow/kubeflow/volumes-web-app",
71+
"newName": "ghcr.io/kubeflow/kubeflow/volumes-web-app",
72+
},
73+
],
74+
},
75+
{
76+
"name": "Notebook Controller",
77+
"kustomization": (
78+
"components/notebook-controller/"
79+
"config/base/kustomization.yaml"
80+
),
81+
"images": [
82+
{
83+
"name": "ghcr.io/kubeflow/kubeflow/notebook-controller",
84+
"newName": "ghcr.io/kubeflow/kubeflow/notebook-controller",
85+
},
86+
],
87+
},
88+
{
89+
"name": "PVC Viewer Controller",
90+
"kustomization": (
91+
"components/pvcviewer-controller/"
92+
"config/base/kustomization.yaml"
93+
),
94+
"images": [
95+
{
96+
"name": "ghcr.io/kubeflow/kubeflow/pvcviewer-controller",
97+
"newName": "ghcr.io/kubeflow/kubeflow/pvcviewer-controller",
98+
},
99+
],
100+
},
101+
{
102+
"name": "Tensorboard Controller",
103+
"kustomization": (
104+
"components/tensorboard-controller/"
105+
"config/base/kustomization.yaml"
106+
),
107+
"images": [
108+
{
109+
"name": "ghcr.io/kubeflow/kubeflow/tensorboard-controller",
110+
"newName": "ghcr.io/kubeflow/kubeflow/tensorboard-controller",
111+
},
112+
],
113+
},
114+
]
115+
116+
117+
def update_manifests_images(applications, tag):
118+
for application in applications:
119+
log.info("Updating manifests for app %s", application["name"])
120+
with open(application["kustomization"], "r") as file:
121+
kustomize = yaml.load(file)
122+
123+
images = kustomize.get("images", [])
124+
for target_image in application["images"]:
125+
found = False
126+
for image in images:
127+
if image["name"] == target_image["name"]:
128+
image["newName"] = target_image["newName"]
129+
image["newTag"] = tag
130+
found = True
131+
break
132+
if not found:
133+
images.append({
134+
"name": target_image["name"],
135+
"newName": target_image["newName"],
136+
"newTag": tag})
137+
kustomize["images"] = images
138+
139+
with open(application["kustomization"], "w") as file:
140+
yaml.dump(kustomize, file)
141+
142+
143+
def parse_args():
144+
parser = argparse.ArgumentParser("Update image tags in manifests.")
145+
parser.add_argument("tag", type=str, help="Image tag to use.")
146+
return parser.parse_args()
147+
148+
149+
def main():
150+
logging.basicConfig(level=logging.INFO)
151+
args = parse_args()
152+
update_manifests_images(applications, args.tag)
153+
154+
155+
if __name__ == "__main__":
156+
sys.exit(main())

0 commit comments

Comments
 (0)