Skip to content

Commit 9297de6

Browse files
committed
Add a script to update pulp-cli/glue in plugins
1 parent db2f2b3 commit 9297de6

File tree

3 files changed

+180
-2
lines changed

3 files changed

+180
-2
lines changed

.github/workflows/cookiecutter.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: "Set up Python"
2525
uses: "actions/setup-python@v6"
2626
with:
27-
python-version: "3.11"
27+
python-version: "3.13"
2828
- name: "Setup git"
2929
run: |
3030
git config user.name pulpbot

cookiecutter/ci/{{ cookiecutter.__project_name }}/.github/workflows/cookiecutter.yml

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- name: "Set up Python"
2727
uses: "actions/setup-python@v6"
2828
with:
29-
python-version: "3.11"
29+
python-version: "3.13"
3030
- name: "Setup git"
3131
run: |
3232
git config user.name pulpbot
@@ -63,4 +63,59 @@ jobs:
6363
GH_TOKEN: "${{ secrets.RELEASE_TOKEN }}"
6464
continue-on-error: true
6565
{%- endraw %}
66+
{%- if cookiecutter.app_label %}
67+
update-dependencies:
68+
runs-on: "ubuntu-latest"
69+
steps:
70+
- uses: "actions/checkout@v5"
71+
with:
72+
repository: "pulp/pulp-cli"
73+
path: "pulp-cli"
74+
- uses: "actions/checkout@v5"
75+
with:
76+
{%- raw %}
77+
token: "${{ secrets.RELEASE_TOKEN }}"
78+
{%- endraw %}
79+
path: "pulp-cli{{ cookiecutter.__app_label_suffix }}"
80+
- name: "Set up Python"
81+
uses: "actions/setup-python@v6"
82+
with:
83+
python-version: "3.13"
84+
- name: "Setup git"
85+
run: |
86+
git config user.name pulpbot
87+
git config user.email [email protected]
88+
- name: "Install python dependencies"
89+
run: |
90+
pip install packaging tomlkit
91+
- name: "Apply cookiecutter templates"
92+
run: |
93+
../pulp-cli/cookiecutter/update_pulp_cli.py
94+
if [ "$(git status --porcelain)" ]
95+
then
96+
git add .
97+
git commit -m "Update CLI and GLUE"
98+
fi
99+
- name: "Create Pull Request"
100+
uses: "peter-evans/create-pull-request@v7"
101+
id: "create_pr"
102+
with:
103+
{%- raw %}
104+
token: "${{ secrets.RELEASE_TOKEN }}"
105+
{%- endraw %}
106+
title: "Update CLI and GLUE"
107+
body: ""
108+
branch: "update_cli"
109+
delete-branch: true
110+
path: "pulp-cli{{ cookiecutter.__app_label_suffix }}"
111+
{%- raw %}
112+
- name: "Mark PR automerge"
113+
run: |
114+
gh pr merge --rebase --auto "${{ steps.create_pr.outputs.pull-request-number }}"
115+
if: "steps.create_pr.outputs.pull-request-number"
116+
env:
117+
GH_TOKEN: "${{ secrets.RELEASE_TOKEN }}"
118+
continue-on-error: true
119+
{%- endraw %}
120+
{%- endif %}
66121
...

cookiecutter/update_pulp_cli.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
3+
# Requirements:
4+
# packaging
5+
# tomlkit
6+
7+
import json
8+
import logging
9+
from packaging.version import Version
10+
from packaging.requirements import Requirement
11+
from packaging.specifiers import Specifier, SpecifierSet
12+
from packaging.utils import canonicalize_name
13+
import urllib.request
14+
15+
import tomlkit
16+
17+
18+
WAREHOUSE = "https://pypi.org"
19+
_logger = logging.getLogger("venia")
20+
21+
22+
def strategy_semver(requirement: Requirement, latest_version: Version) -> Requirement | None:
23+
requirement = Requirement(str(requirement))
24+
upper_bound = None
25+
rest = []
26+
for spec in requirement.specifier:
27+
if spec.operator in {"<", "<=", "=="}:
28+
if upper_bound is not None:
29+
if Version(spec.version) > Version(upper_bound.version):
30+
rest.append(upper_bound)
31+
upper_bound = spec
32+
else:
33+
rest.append(spec)
34+
else:
35+
upper_bound = spec
36+
else:
37+
rest.append(spec)
38+
assert upper_bound is not None
39+
if latest_version < Version(upper_bound.version):
40+
_logger.warn(
41+
f"Dependency on {requirement} cannot be updated to include latest version {latest_version}."
42+
)
43+
else:
44+
if upper_bound.operator == "==":
45+
upper_bound = Specifier(f"=={latest_version}")
46+
elif upper_bound.operator == "<=":
47+
upper_bound = Specifier(f"<={latest_version}")
48+
elif upper_bound.operator == "<":
49+
dots = len([c for c in upper_bound.version if c == "."])
50+
if dots == 0:
51+
new_version = f"{latest_version.major + 1}"
52+
elif dots == 1:
53+
new_version = f"{latest_version.major}.{latest_version.minor + 1}"
54+
elif dots == 2:
55+
new_version = (
56+
f"{latest_version.major}.{latest_version.minor}.{latest_version.micro + 1}"
57+
)
58+
else:
59+
raise RuntimeError("Too many parts for a semver boundary.")
60+
upper_bound = Specifier(f"<{new_version}")
61+
requirement.specifier = SpecifierSet(",".join(map(str, [upper_bound] + rest)))
62+
return requirement
63+
return None
64+
65+
66+
def latest_version(canonical_name: str, allow_prereleases: bool | None = None) -> Version:
67+
with urllib.request.urlopen(f"{WAREHOUSE}/pypi/{canonical_name}/json") as response:
68+
releases = json.loads(response.read())["releases"]
69+
available_versions = sorted(
70+
(
71+
version
72+
for version in (Version(key) for key in releases.keys())
73+
if allow_prereleases or not version.is_prerelease
74+
)
75+
)
76+
return available_versions[-1]
77+
78+
79+
def main() -> None:
80+
pulp_cli_version = latest_version("pulp-cli")
81+
82+
update = False
83+
new_specifier: SpecifierSet | None = None
84+
85+
with open("pyproject.toml", "r") as fp:
86+
pyproject_toml = tomlkit.load(fp)
87+
glue_available = bool(pyproject_toml["tool"]["pulp_cli_template"]["app_label"]) # type: ignore
88+
app_label = str(pyproject_toml["tool"]["pulp_cli_template"]["app_label"]) # type: ignore
89+
90+
for i, dependency in enumerate(pyproject_toml["project"]["dependencies"]): # type: ignore
91+
requirement = Requirement(dependency)
92+
if canonicalize_name(requirement.name, validate=True) == "pulp-cli":
93+
if pulp_cli_version not in requirement.specifier:
94+
new_requirement = strategy_semver(requirement, pulp_cli_version)
95+
if new_requirement is not None:
96+
pyproject_toml["project"]["dependencies"][i] = str( # type:ignore
97+
new_requirement
98+
)
99+
new_specifier = new_requirement.specifier
100+
update = True
101+
break
102+
103+
if update:
104+
if glue_available:
105+
with open(f"pulp-glue-{app_label}/pyproject.toml", "r") as fp:
106+
glue_pyproject_toml = tomlkit.load(fp)
107+
for i, dependency in enumerate(glue_pyproject_toml["project"]["dependencies"]): # type: ignore
108+
requirement = Requirement(dependency)
109+
if canonicalize_name(requirement.name, validate=True) == "pulp-glue":
110+
assert new_specifier is not None
111+
requirement.specifier = new_specifier
112+
glue_pyproject_toml["project"]["dependencies"][i] = str( # type:ignore
113+
requirement
114+
)
115+
with open(f"pulp-glue-{app_label}/pyproject.toml", "w") as fp:
116+
tomlkit.dump(glue_pyproject_toml, fp)
117+
118+
with open("pyproject.toml", "w") as fp:
119+
tomlkit.dump(pyproject_toml, fp)
120+
121+
122+
if __name__ == "__main__":
123+
main()

0 commit comments

Comments
 (0)