Skip to content

Commit 03c4a27

Browse files
author
Sven Siegmund
committed
feat: add subcommand update-actions
1 parent 72f42cc commit 03c4a27

File tree

5 files changed

+191
-12
lines changed

5 files changed

+191
-12
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies = [
2121
"gitpython>=3.1.45",
2222
"importlib-resources>=5.10 ; python_full_version < '3.12'",
2323
"platformdirs>=4.4.0",
24+
"requests>=2.32.5",
2425
"ruamel-yaml>=0.18.15",
2526
"semver>=3.0.4",
2627
"tomli-w>=1.2.0",

src/ci_starter/__init__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
from importlib.metadata import version as get_version
33
from logging import getLogger
44
from pathlib import Path
5+
from typing import TYPE_CHECKING
56

67
from .constants import BASE_WORKFLOW_ASSET_PATH, HELPER_SCRIPT_ASSET_PATH
78
from .git_helpers import get_repo_name
89
from .presets import DISTRIBUTION_ARTIFACTS_DIR, LOCKFILE_ARTIFACT
910
from .semantic_release_config import SemanticReleaseConfiguration
10-
from .utils import from_yaml, get_asset
11+
from .utils import dump, from_yaml, get_actions, get_asset, update_step_data
12+
13+
if TYPE_CHECKING:
14+
from .action import Action
1115

1216
__version__ = get_version(__package__)
1317

@@ -45,4 +49,17 @@ def generate_reusable_workflow(asset_path: Path) -> dict:
4549

4650

4751
def update_actions(workflows_path: Path) -> None:
48-
pass
52+
actions: dict[str, Action] = {}
53+
54+
for file in workflows_path.rglob("*.yml"):
55+
data = from_yaml(file)
56+
57+
for action in get_actions(data):
58+
if action.name not in actions:
59+
action.update()
60+
actions[action.name] = action
61+
updated_action = actions[action.name]
62+
63+
data = update_step_data(data, updated_action)
64+
65+
dump(data, file)

src/ci_starter/action.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
1+
from os import getenv
12
from re import compile
23
from typing import Self
34

4-
from semver import VersionInfo
5+
from requests import get
6+
from semver.version import Version
57

68
from .errors import ActionNotParsableError
79

810

911
class Action:
1012
PATTERN = compile(r"(?P<owner>[\w_-]+)/(?P<repo>[\w_-]+)@(?P<commit>[\S]+)")
13+
TOKEN = getenv("CI_STARTER_GH_API_TOKEN")
1114

12-
def __init__(self, owner: str, repo: str, commit: str, version: VersionInfo | None = None) -> None:
15+
def __init__(self, owner: str, repo: str, commit: str, version: Version | None = None) -> None:
1316
self.owner: str = owner
1417
self.repo: str = repo
1518
self.commit: str = commit
16-
self.version: VersionInfo = version
19+
self.version: Version = version
1720

18-
def to_text(self):
19-
text = f"{self.owner}/{self.repo}@{self.commit}"
20-
return text
21+
@property
22+
def name(self) -> str:
23+
return f"{self.owner}/{self.repo}"
24+
25+
def to_text(self) -> str:
26+
return f"{self.name}@{self.commit}"
2127

2228
@classmethod
23-
def from_text(cls, text: str, version: VersionInfo | None = None) -> Self:
29+
def from_text(cls, text: str, version: Version | None = None) -> Self:
2430
match = cls.PATTERN.search(text)
2531
if not match:
2632
raise ActionNotParsableError(text)
@@ -29,9 +35,46 @@ def from_text(cls, text: str, version: VersionInfo | None = None) -> Self:
2935

3036
return action
3137

38+
def update(self) -> None:
39+
response = get(self.url, headers=self.header)
40+
if not response.ok:
41+
response.raise_for_status()
42+
43+
data = response.json()
44+
current_release = data[0]
45+
46+
current_version = current_release["name"].removeprefix("v")
47+
current_commit = current_release["commit"]["sha"]
48+
49+
self.version = Version.parse(current_version)
50+
self.commit = current_commit
51+
52+
def update_from_other(self, other: Self) -> None:
53+
if not self.name == other.name:
54+
raise ValueError(
55+
f"can update only from an {self.__class__.__name__} "
56+
f"of name '{self.name}', not '{other.name}'"
57+
)
58+
self.commit = other.commit
59+
self.version = other.version
60+
61+
@property
62+
def url(self) -> str:
63+
return f"https://api.github.com/repos/{self.owner}/{self.repo}/tags"
64+
65+
@property
66+
def header(self) -> dict[str, str]:
67+
result = {
68+
"User-Agent": __package__,
69+
"Accept": "application.vnd.github+json",
70+
}
71+
if self.TOKEN:
72+
result.update(Authorization=f"token {self.TOKEN}")
73+
return result
74+
3275
def __repr__(self) -> str:
3376
string = (
34-
f"{self.__class__.__name__}(user={self.user}, repo={self.repo}, "
77+
f"{self.__class__.__name__}(owner={self.owner}, repo={self.repo}, "
3578
f"commit={self.commit}, version={self.version})"
3679
)
3780
return string

src/ci_starter/utils.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections.abc import Iterable, Mapping
22
from pathlib import Path
33
from sys import version_info
4-
from typing import TYPE_CHECKING, TextIO
4+
from typing import TYPE_CHECKING, Any, TextIO
55

66
from ruamel.yaml import YAML as Yaml
77

@@ -39,7 +39,7 @@ def step_yaml() -> Yaml:
3939
return yaml
4040

4141

42-
def from_yaml(s: str) -> dict:
42+
def from_yaml(s: str | Path) -> dict:
4343
yaml = step_yaml()
4444
obj = yaml.load(s)
4545
return obj
@@ -62,3 +62,24 @@ def get_actions(workflow: Mapping) -> Iterable[Action]:
6262
yield from get_actions_from_list(v)
6363
elif isinstance(v, Mapping):
6464
yield from get_actions(v)
65+
66+
67+
def get_steps_from_list(lst: list) -> Iterable[Step]:
68+
for item in lst:
69+
if isinstance(item, Step):
70+
yield item
71+
72+
73+
def get_steps(workflow: Mapping) -> Iterable[Step]:
74+
for v in workflow.values():
75+
if isinstance(v, list):
76+
yield from get_steps_from_list(v)
77+
elif isinstance(v, Mapping):
78+
yield from get_steps(v)
79+
80+
81+
def update_step_data(data: Mapping[str, Any], action: Action) -> dict[str, Any]:
82+
for step in get_steps(data):
83+
if step.uses.name == action.name:
84+
step.uses = action
85+
return data

0 commit comments

Comments
 (0)