Automated release process

Signed-off-by: Pedro Algarvio <palgarvio@vmware.com>
This commit is contained in:
Pedro Algarvio 2022-08-10 12:48:58 +01:00 committed by Pedro Algarvio
parent c9e42e1504
commit 50dd136e9a
3 changed files with 396 additions and 28 deletions

View file

@ -9,22 +9,20 @@ jobs:
checksums:
name: Update Scripts Checksums
runs-on: ubuntu-latest
if: github.repository == 'saltstack/salt-bootstrap'
steps:
- uses: actions/checkout@v2
if: github.repository == 'saltstack/salt-bootstrap'
with:
ref: stable
- name: Get bootstrap-salt.sh sha256sum
if: github.repository == 'saltstack/salt-bootstrap'
run: |
echo "SH=$(sha256sum bootstrap-salt.sh | awk '{ print $1 }')" >> $GITHUB_ENV
echo "PS1=$(sha256sum bootstrap-salt.ps1 | awk '{ print $1 }')" >> $GITHUB_ENV
echo "BS_VERSION=$(sh bootstrap-salt.sh -v | awk '{ print $4 }')" >> $GITHUB_ENV
- name: Update Checksums
if: github.repository == 'saltstack/salt-bootstrap'
run: |
echo ${{ env.SH }} > bootstrap-salt.sh.sha256
echo ${{ env.PS1 }} > bootstrap-salt.ps1.sha256

View file

@ -1,45 +1,210 @@
name: Release
name: Cut Release
on:
push:
tags:
- '*'
on: workflow_dispatch
jobs:
bootstrap:
name: Update Release Checksums on Develop
update-develop:
name: Update CHANGELOG.md and bootstrap-salt.sh
runs-on: ubuntu-latest
if: github.repository == 'saltstack/salt-bootstrap'
permissions:
contents: write # To be able to publish the release
steps:
- uses: actions/checkout@v2
if: github.repository == 'saltstack/salt-bootstrap'
- name: Check Branch Triggering Release
run: |
if [ "${{ github.ref_name }}" != "develop" ]
then
echo "This workflow should only be triggered from the develop branch"
exit 1
fi
- uses: actions/checkout@v3
with:
ref: develop
repository: ${{ github.repository }}
- name: Update Git Settings
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot] on behalf of ${{ github.event.sender.login }}"
- name: Set up Python 3.7
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Install Requirements
run: |
python3 -m pip install requests pre-commit
pre-commit install --install-hooks
- name: Update Repository
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 .github/workflows/scripts/cut-release.py --repo ${{ github.repository }}
export CUT_RELEASE_VERSION=$(cat .cut_release_version)
export CUT_RELEASE_CHANGES=$(cat .cut_release_changes)
echo "CUT_RELEASE_VERSION=${CUT_RELEASE_VERSION}" >> $GITHUB_ENV
echo "CUT_RELEASE_CHANGES=${CUT_RELEASE_CHANGES}" >> $GITHUB_ENV
- name: Show Changes
run: |
git status
git diff
- name: Commit Changes
run: |
git commit -am "Update develop branch for the ${CUT_RELEASE_VERSION} release" || \
git commit -am "Update develop branch for the ${CUT_RELEASE_VERSION} release"
- name: Push Changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: develop
- name: Upload Release Details
uses: actions/upload-artifact@v3
with:
name: release-details
path: |
.cut_release_version
.cut_release_changes
merge-develop-into-stable:
name: Merge develop into stable
runs-on: ubuntu-latest
if: github.repository == 'saltstack/salt-bootstrap'
needs: update-develop
permissions:
contents: write # To be able to publish the release
steps:
- uses: actions/checkout@v3
with:
ref: stable
repository: ${{ github.repository }}
fetch-depth: 0
- name: Update Git Settings
run: |
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot] on behalf of @${{ github.event.sender.login }}"
- name: Download Release Details
uses: actions/download-artifact@v3
with:
name: release-details
- name: Update Environment
run: |
export CUT_RELEASE_VERSION=$(cat .cut_release_version)
export CUT_RELEASE_CHANGES=$(cat .cut_release_changes)
echo "CUT_RELEASE_VERSION=${CUT_RELEASE_VERSION}" >> $GITHUB_ENV
echo "CUT_RELEASE_CHANGES=${CUT_RELEASE_CHANGES}" >> $GITHUB_ENV
- name: Merge develop into stable
run: |
git merge --no-ff -m "Merge develop into stable" origin/develop || touch .git-conflicts
if [ -f .git-conflicts ]
then
git diff
for f in $(git status | grep 'both modified' | awk '{ print $3 }')
do
git checkout --theirs $f
git add $f
done
git commit -a -m "Merge develop into stable(auto resolving conflicts to the develop version)"
fi
- name: Tag Release
uses: mathieudutour/github-tag-action@v6.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
custom_tag: ${{ env.CUT_RELEASE_VERSION }}
tag_prefix: ""
create_annotated_tag: true
- name: Push Changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: stable
tags: true
publish-release:
name: Create GitHub Release
runs-on: ubuntu-latest
if: github.repository == 'saltstack/salt-bootstrap'
needs: merge-develop-into-stable
permissions:
contents: write # To be able to publish the release
steps:
- uses: actions/checkout@v3
with:
ref: stable
repository: ${{ github.repository }}
- name: Download Release Details
uses: actions/download-artifact@v3
with:
name: release-details
- name: Update Environment
run: |
export CUT_RELEASE_VERSION=$(cat .cut_release_version)
export CUT_RELEASE_CHANGES=$(cat .cut_release_changes)
echo "CUT_RELEASE_VERSION=${CUT_RELEASE_VERSION}" >> $GITHUB_ENV
echo "CUT_RELEASE_CHANGES=${CUT_RELEASE_CHANGES}" >> $GITHUB_ENV
- name: Create Github Release
uses: softprops/action-gh-release@v1
with:
name: ${{ env.CUT_RELEASE_VERSION }}
tag_name: ${{ env.CUT_RELEASE_VERSION }}
body_path: .cut_release_changes
target_commitish: stable
prerelease: false
generate_release_notes: false
files: |
bootstrap-salt.sh
bootstrap-salt.ps1
LICENSE
update-develop-checksums:
name: Update Release Checksums on Develop
runs-on: ubuntu-latest
if: github.repository == 'saltstack/salt-bootstrap'
needs: publish-release
permissions:
contents: write # For action peter-evans/create-pull-request
pull-requests: write # For action peter-evans/create-pull-request
steps:
- uses: actions/checkout@v3
with:
ref: stable
repository: ${{ github.repository }}
- name: Get bootstrap-salt.sh sha256sum
if: github.repository == 'saltstack/salt-bootstrap'
run: |
echo "SH=$(sha256sum bootstrap-salt.sh | awk '{ print $1 }')" >> $GITHUB_ENV
echo "BS_VERSION=$(sh bootstrap-salt.sh -v | awk '{ print $4 }')" >> $GITHUB_ENV
- uses: actions/checkout@v2
if: github.repository == 'saltstack/salt-bootstrap'
- uses: actions/checkout@v3
with:
ref: develop
repository: ${{ github.repository }}
- name: Set up Python 3.7
if: github.repository == 'saltstack/salt-bootstrap'
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: 3.7
- name: Update Latest Release on README
if: github.repository == 'saltstack/salt-bootstrap'
run: |
python3 .github/workflows/scripts/update-release-shasum.py ${{ env.BS_VERSION }} ${{ env.SH }}
- name: Create Pull Request Against Develop
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v4
with:
title: Update README.rst with ${{ env.BS_VERSION }} release sha256sum
commit-message: Update README.rst with ${{ env.BS_VERSION }} release sha256sum
@ -48,34 +213,36 @@ jobs:
salt:
name: Update Release on Salt Repo
runs-on: ubuntu-latest
if: github.repository == 'saltstack/salt-bootstrap'
needs: update-develop-checksums
permissions:
contents: write # For action peter-evans/create-pull-request
pull-requests: write # For action peter-evans/create-pull-request
steps:
- uses: actions/checkout@v2
if: github.repository == 'saltstack/salt-bootstrap'
- uses: actions/checkout@v3
with:
ref: stable
repository: ${{ github.repository }}
- name: Get bootstrap version
if: github.repository == 'saltstack/salt-bootstrap'
run: |
echo "BS_VERSION=$(sh bootstrap-salt.sh -v | awk '{ print $4 }')" >> $GITHUB_ENV
- uses: actions/checkout@v2
if: github.repository == 'saltstack/salt-bootstrap'
- uses: actions/checkout@v3
with:
repository: saltstack/salt
ref: master
path: salt-checkout
- name: Update bootstrap script on Salt
if: github.repository == 'saltstack/salt-bootstrap'
run: |
cp bootstrap-salt.sh salt-checkout/salt/cloud/deploy/bootstrap-salt.sh
- name: Create Pull Request Against Develop
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v4
with:
title: Update the bootstrap script to v${{ env.BS_VERSION }}
title: "[DO NOT MERGE] Update the bootstrap script to v${{ env.BS_VERSION }}"
path: salt-checkout
commit-message: Update the bootstrap script to v${{ env.BS_VERSION }}
delete-branch: true

203
.github/workflows/scripts/cut-release.py vendored Normal file
View file

@ -0,0 +1,203 @@
#!/usr/bin/env python
import os
import re
import sys
import pathlib
import argparse
import requests
from datetime import datetime
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent.parent.parent
class ClassPropertyDescriptor:
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.fget.__get__(obj, klass)()
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
type_ = type(obj)
return self.fset.__get__(obj, type_)(value)
def setter(self, func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
self.fset = func
return self
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
class Session:
_instance = None
def __init__(self, endpoint=None):
if endpoint is None:
endpoint = "https://api.github.com"
self.endpoint = endpoint
self.session = requests.Session()
self.session.headers.update(
{
"Accept": "application/vnd.github+json",
"Authorization": f"token {os.environ['GITHUB_TOKEN']}",
}
)
@classproperty
def instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def get(self, path, **kwargs):
return self.session.get(f"{self.endpoint}/{path.lstrip('/')}", **kwargs)
def post(self, path, **kwargs):
return self.session.post(f"{self.endpoint}/{path.lstrip('/')}", **kwargs)
def __enter__(self):
self.session.__enter__()
return self
def __exit__(self, *args):
self.session.__exit__(*args)
def get_latest_release(options):
response = Session.instance.get(f"/repos/{options.repo}/releases/latest")
if response.status_code != 404:
return response.json()["tag_name"]
print(
f"Failed to get latest release. HTTP Response:\n{response.text}",
file=sys.stderr,
flush=True,
)
print("Searching tags...", file=sys.stderr, flush=True)
tags = []
page = 0
while True:
page += 1
response = Session.instance.get(
f"/repos/{options.repo}/tags", data={"pre_page": 100, "page": page}
)
repo_tags = response.json()
added_tags = False
for tag in repo_tags:
if tag["name"] not in tags:
tags.append(tag["name"])
added_tags = True
if added_tags is False:
break
return list(sorted(tags))[-1]
def get_generated_changelog(options):
response = Session.instance.post(
f"/repos/{options.repo}/releases/generate-notes",
json={
"tag_name": options.release_tag,
"previous_tag_name": options.previous_tag,
"target_commitish": "develop",
},
)
if response.status_code == 200:
return response.json()
return response.text
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--repo", required=True, help="The <user>/<repo> to use")
parser.add_argument("--release-tag", required=False, help="The tag of the release")
parser.add_argument(
"--previous-tag",
required=False,
help="The previous release tag. If not passed, the GH Api will be queried for it.",
)
changelog_file = REPO_ROOT / "CHANGELOG.md"
if not os.environ.get("GITHUB_TOKEN"):
parser.exit(status=1, message="GITHUB_TOKEN environment variable not set")
options = parser.parse_args()
if not options.release_tag:
options.release_tag = f"v{datetime.utcnow().strftime('%Y.%m.%d')}"
if not options.previous_tag:
options.previous_tag = get_latest_release(options)
print(
f"Creating changelog entries from {options.previous_tag} to {options.release_tag} ...",
file=sys.stderr,
flush=True,
)
changelog = get_generated_changelog(options)
if not isinstance(changelog, dict):
parser.exit(
status=1,
message=f"Unable to generate changelog. HTTP Response:\n{changelog}",
)
cut_release_version = REPO_ROOT / ".cut_release_version"
print(
f"* Writing {cut_release_version.relative_to(REPO_ROOT)} ...",
file=sys.stderr,
flush=True,
)
cut_release_version.write_text(options.release_tag)
cut_release_changes = REPO_ROOT / ".cut_release_changes"
print(
f"* Writing {cut_release_changes.relative_to(REPO_ROOT)} ...",
file=sys.stderr,
flush=True,
)
cut_release_changes.write_text(changelog["body"])
print(
f"* Updating {changelog_file.relative_to(REPO_ROOT)} ...",
file=sys.stderr,
flush=True,
)
changelog_file.write_text(
f"# {changelog['name']}\n\n"
+ changelog["body"]
+ "\n\n"
+ changelog_file.read_text()
)
bootstrap_script_path = REPO_ROOT / "bootstrap-salt.sh"
print(
f"* Updating {bootstrap_script_path.relative_to(REPO_ROOT)} ...",
file=sys.stderr,
flush=True,
)
bootstrap_script_path.write_text(
re.sub(
r'__ScriptVersion="(.*)"',
f'__ScriptVersion="{options.release_tag.lstrip("v")}"',
bootstrap_script_path.read_text(),
)
)
parser.exit(status=0, message="CHANGELOG.md and bootstrap-salt.sh updated\n")
if __name__ == "__main__":
main()