Skip to content

Commit b3e1845

Browse files
authored
add workflow to release and tag collection
workflow to release collection
1 parent 8f6e9f3 commit b3e1845

File tree

7 files changed

+424
-0
lines changed

7 files changed

+424
-0
lines changed

.github/workflows/release-branch.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Release
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
release_branch:
7+
description: The release base branch
8+
required: true
9+
type: string
10+
11+
jobs:
12+
release:
13+
env:
14+
source_path: "./source"
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: write
18+
pull-requests: write
19+
20+
steps:
21+
- name: setup python
22+
uses: actions/setup-python@v4
23+
with:
24+
python-version: "3.9"
25+
26+
- name: Install python required libraries
27+
run: pip install -U pygithub semver tox pyyaml
28+
shell: bash
29+
30+
- name: Download python script
31+
run: >-
32+
curl -o create_release_branch.py
33+
https://raw.githubusercontent.com/abikouo/github_actions/release_v2/scripts/create_release_branch.py
34+
35+
- name: Compute release version and create release branch
36+
id: compute-version
37+
run: >-
38+
python3 ./create_release_branch.py
39+
env:
40+
REPOSITORY_NAME: ${{ github.repository }}
41+
RELEASE_BRANCH: ${{ inputs.release_branch }}
42+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
44+
- name: Checkout the repository
45+
uses: actions/checkout@v3
46+
with:
47+
path: ${{ env.source_path }}
48+
fetch-depth: "0"
49+
if: ${{ (steps.compute-version.outputs.release_version != '') }}
50+
51+
- name: Prepare release
52+
run: tox -e prepare_release -vv
53+
shell: bash
54+
working-directory: ${{ env.source_path }}
55+
env:
56+
RELEASE_VERSION: ${{ steps.compute-version.outputs.release_version }}
57+
if: ${{ (steps.compute-version.outputs.release_version != '') }}
58+
59+
- name: Download python script used to update galaxy file
60+
run: >-
61+
curl -o update_galaxy_file.py
62+
https://raw.githubusercontent.com/abikouo/github_actions/release_v2/scripts/update_galaxy_file.py
63+
if: ${{ (steps.compute-version.outputs.release_version != '') }}
64+
65+
- name: Compute release version and create release branch
66+
run: >-
67+
python3 ./update_galaxy_file.py
68+
env:
69+
COLLECTION_PATH: ${{ env.source_path }}
70+
RELEASE_VERSION: ${{ steps.compute-version.outputs.release_version }}
71+
if: ${{ (steps.compute-version.outputs.release_version != '') }}
72+
73+
- name: Create Pull Request
74+
uses: peter-evans/create-pull-request@v5
75+
with:
76+
token: ${{ secrets.GITHUB_TOKEN }}
77+
path: ${{ env.source_path }}
78+
commit-message: "Release ${{ steps.compute-version.outputs.release_version }}"
79+
base: ${{ inputs.release_branch }}
80+
branch: "prepare_release_${{ steps.compute-version.outputs.release_version }}"
81+
title: "Prepare release ${{ steps.compute-version.outputs.release_version }}"
82+
body: |
83+
Release ${{ steps.compute-version.outputs.release_version }}
84+
if: ${{ (steps.compute-version.outputs.release_version != '') }}

.github/workflows/release-tag.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: tagging
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
branch_name:
7+
description: The branch name to create tag from.
8+
type: string
9+
default: ${{ github.base_ref }}
10+
release_publish:
11+
default: true
12+
description: Publish a new github release.
13+
type: boolean
14+
release_prefix:
15+
default: "Release"
16+
description: Prefix name of the release to create.
17+
type: string
18+
secrets:
19+
# due to https://github.com/ad-m/github-push-action/issues/32 we are not using default GITHUB_TOKEN
20+
gh_token:
21+
required: true
22+
23+
jobs:
24+
push:
25+
if: "github.event.pull_request.merged == true"
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: write
29+
pull-requests: write
30+
31+
steps:
32+
- name: Checkout the repository
33+
uses: actions/checkout@v3
34+
with:
35+
ref: ${{ inputs.branch_name }}
36+
token: ${{ secrets.gh_token }}
37+
if: "contains(github.event.pull_request.labels.*.name, 'ok-to-tag')"
38+
39+
- name: setup python
40+
uses: actions/setup-python@v4
41+
with:
42+
python-version: "3.9"
43+
if: "contains(github.event.pull_request.labels.*.name, 'ok-to-tag')"
44+
45+
- name: install python libraries
46+
run: pip3 install yq pygithub
47+
shell: bash
48+
if: "contains(github.event.pull_request.labels.*.name, 'ok-to-tag')"
49+
50+
- name: extract tag name from 'galaxy.yml'
51+
id: read-tag
52+
run: echo "release_tag=$(yq -r '.version' 'galaxy.yml')" >> $GITHUB_OUTPUT
53+
shell: bash
54+
if: "contains(github.event.pull_request.labels.*.name, 'ok-to-tag')"
55+
56+
- name: create tag
57+
run: |
58+
curl -o create_github_tag.py https://raw.githubusercontent.com/abikouo/github_actions/release_v2/scripts/create_github_tag.py
59+
python3 ./create_github_tag.py --repository ${{ github.repository }} --tag ${{ steps.read-tag.outputs.release_tag }} --branch ${{ inputs.branch_name }}
60+
env:
61+
GITHUB_TOKEN: ${{ secrets.gh_token }}
62+
if: "contains(github.event.pull_request.labels.*.name, 'ok-to-tag')"
63+
64+
- name: Parse release content
65+
run: |
66+
curl -o create_github_release.py https://raw.githubusercontent.com/abikouo/github_actions/release_v2/scripts/create_github_release.py
67+
python3 ./create_github_release.py --repository ${{ github.repository }} --release-tag ${{ steps.read-tag.outputs.release_tag }} --release-name "${{ inputs.release_prefix }} ${{ steps.read-tag.outputs.release_tag }}"
68+
env:
69+
GITHUB_TOKEN: ${{ secrets.gh_token }}
70+
if: ${{ (inputs.release_publish) && contains(github.event.pull_request.labels.*.name, 'ok-to-tag') }}

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ repos:
7474
- PyYAML
7575
- pygithub
7676
- pytest
77+
- semver
7778

7879
- repo: local
7980
hooks:

scripts/create_github_release.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
"""Script to read release content from CHANGELOG.rst file."""
3+
4+
import logging
5+
import os
6+
import re
7+
8+
from argparse import ArgumentParser
9+
from pathlib import PosixPath
10+
11+
from github import Github
12+
13+
14+
FORMAT = "[%(asctime)s] - %(message)s"
15+
logging.basicConfig(format=FORMAT)
16+
logger = logging.getLogger(__file__)
17+
logger.setLevel(logging.DEBUG)
18+
19+
20+
def create_git_release(
21+
repository: str, release_name: str, release_tag: str, release_content: str
22+
) -> None:
23+
"""Create github release on Repository.
24+
25+
:param repository: Github repository name.
26+
:param release_tag: Release tag.
27+
:param release_content: Release description.
28+
:param release_name: The name of the release to create.
29+
"""
30+
access_token = os.environ.get("GITHUB_TOKEN")
31+
32+
gh_client = Github(access_token)
33+
gh_repository = gh_client.get_repo(repository)
34+
gh_repository.create_git_release(release_tag, release_name, release_content)
35+
36+
37+
def parse_release_content(release_version: str) -> str:
38+
"""Parse release content from CHANGELOG.rst.
39+
40+
:param release_version: Release version to parse content.
41+
:returns: The release content found from CHANGELOG.rst
42+
"""
43+
if not PosixPath("CHANGELOG.rst").exists():
44+
logger.error("CHANGELOG.rst does not exist.")
45+
return "..."
46+
47+
release_content = "..."
48+
with PosixPath("CHANGELOG.rst").open(encoding="utf-8") as file_write:
49+
data = file_write.read().splitlines()
50+
idx = 0
51+
start, end = -1, 0
52+
while idx < len(data):
53+
if data[idx].startswith(f"v{release_version}") and data[idx + 1] == "======":
54+
start = idx + 2
55+
idx += 2
56+
elif (
57+
start > 0
58+
and re.match(r"^v[0-9]+\.[0-9]+\.[0-9]+$", data[idx])
59+
and data[idx + 1] == "======"
60+
):
61+
end = idx
62+
break
63+
idx += 1
64+
if start != -1:
65+
release_content = "\n".join(data[start:]) if not end else "\n".join(data[start:end])
66+
return release_content
67+
68+
69+
def main() -> None:
70+
"""Read release content from CHANGELOG.rst for a specific version."""
71+
parser = ArgumentParser(
72+
description="Read release content from CHANGELOG.rst for a specific version."
73+
)
74+
parser.add_argument("--repository", required=True, help="Repository name.")
75+
parser.add_argument("--release-tag", required=True, help="Release tag.")
76+
parser.add_argument("--release-name", required=True, help="Name of the release to create.")
77+
78+
args = parser.parse_args()
79+
80+
release_content = parse_release_content(args.release_tag)
81+
create_git_release(args.repository, args.release_name, args.release_tag, release_content)
82+
83+
84+
if __name__ == "__main__":
85+
main()

scripts/create_github_tag.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env python3
2+
"""Script to create tag on github repository."""
3+
4+
import logging
5+
import os
6+
7+
from argparse import ArgumentParser
8+
9+
from github import Github
10+
11+
12+
FORMAT = "[%(asctime)s] - %(message)s"
13+
logging.basicConfig(format=FORMAT)
14+
logger = logging.getLogger("compute_release_version")
15+
logger.setLevel(logging.DEBUG)
16+
17+
18+
def main() -> None:
19+
"""Create tag and publish to Github repository."""
20+
parser = ArgumentParser(
21+
description="Create tag to Github repository and pull request to default branch."
22+
)
23+
parser.add_argument("--repository", required=True, help="Repository name.")
24+
parser.add_argument("--tag", required=True, help="Name of the tag to create.")
25+
parser.add_argument("--branch", required=True, help="Name of the branch to create tag from.")
26+
27+
args = parser.parse_args()
28+
29+
access_token = os.environ.get("GITHUB_TOKEN")
30+
31+
gh_instance = Github(access_token)
32+
gh_repository = gh_instance.get_repo(args.repository)
33+
34+
commit_sha = gh_repository.get_branch(args.branch).commit.sha
35+
logger.info("Create tag [%s] from commit '%s'", args.tag, commit_sha)
36+
gh_repository.create_git_ref(f"refs/tags/{args.tag}", commit_sha)
37+
38+
# create pull request to default branch
39+
if args.branch != gh_repository.default_branch:
40+
default_branch = gh_repository.default_branch
41+
pr_title = f"Release '{args.tag}' on '{default_branch}'"
42+
body = f"Push changes from Release {args.tag} into default repository branch."
43+
logger.info(
44+
"Create pull request from branch '%s' to default branch '%s'",
45+
args.branch,
46+
default_branch,
47+
)
48+
gh_repository.create_pull(title=pr_title, body=body, head=args.branch, base=default_branch)
49+
50+
51+
if __name__ == "__main__":
52+
main()

scripts/create_release_branch.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
"""Script to compute release version based on release branch."""
3+
4+
import logging
5+
import os
6+
7+
import semver
8+
9+
from github import Github
10+
from github import Repository
11+
12+
13+
FORMAT = "[%(asctime)s] - %(message)s"
14+
logging.basicConfig(format=FORMAT)
15+
logger = logging.getLogger("compute_release_version")
16+
logger.setLevel(logging.DEBUG)
17+
18+
19+
def compute_next_release_version(release_tags: list[str], release_branch: str) -> str:
20+
"""Bump minor or patch version depending on target branch and existing tags.
21+
22+
:param release_tags: list of existing tags from github repository
23+
:param release_branch: Release base branch
24+
:returns: a release version as string
25+
"""
26+
release_version = release_branch.replace("stable-", "") + ".0"
27+
myversion = semver.Version.parse(release_version)
28+
29+
minor_found = False
30+
for item in release_tags:
31+
version_obj = semver.Version.parse(item)
32+
if version_obj.major == myversion.major and version_obj.minor == myversion.minor:
33+
minor_found = True
34+
if semver.compare(str(myversion), str(version_obj)) == -1:
35+
myversion = version_obj
36+
37+
return str(myversion.bump_patch()) if minor_found else str(myversion)
38+
39+
40+
def create_repository_branch(gh_repository: Repository.Repository, release_branch: str) -> None:
41+
"""Create a branch on github repository.
42+
43+
:param gh_repository: Github repository object.
44+
:param release_branch: Name of the branch to create.
45+
"""
46+
repository_branches = [branch.name for branch in gh_repository.get_branches()]
47+
logger.info("Repository branches: %s", repository_branches)
48+
if release_branch not in repository_branches:
49+
default_branch = gh_repository.default_branch
50+
logger.info("Repository default branch: %s", default_branch)
51+
default_branch_obj = gh_repository.get_branch(default_branch)
52+
logger.info("Creating release branch: %s", release_branch)
53+
gh_repository.create_git_ref(
54+
ref="refs/heads/" + release_branch, sha=default_branch_obj.commit.sha
55+
)
56+
57+
58+
def main() -> None:
59+
"""Read boto constraints and update variables accordingly."""
60+
repository = os.environ.get("REPOSITORY_NAME") or ""
61+
access_token = os.environ.get("GITHUB_TOKEN")
62+
release_branch = os.environ.get("RELEASE_BRANCH", "")
63+
64+
logger.info("Repository name -> '%s'", repository)
65+
logger.info("Github token -> '%s'", access_token)
66+
logger.info("Release branch -> '%s'", release_branch)
67+
68+
gh_client = Github(access_token)
69+
gh_repository = gh_client.get_repo(repository)
70+
71+
if release_branch:
72+
repository_tags = [tag.name for tag in gh_repository.get_tags()]
73+
logger.info("Repository tags => %s", repository_tags)
74+
75+
release_version = compute_next_release_version(repository_tags, release_branch)
76+
if release_version:
77+
create_repository_branch(gh_repository, release_branch)
78+
github_output_file = os.environ.get("GITHUB_OUTPUT") or ""
79+
if github_output_file:
80+
with open(github_output_file, "a", encoding="utf-8") as file_write:
81+
file_write.write(f"release_version={release_version}\n")
82+
83+
84+
if __name__ == "__main__":
85+
main()

0 commit comments

Comments
 (0)