Create Release PR #230
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Create Release PR | |
on: | |
workflow_dispatch: | |
inputs: | |
re_release: | |
description: 'Re-Release same version with fixes' | |
required: false | |
default: false | |
type: boolean | |
schedule: | |
- cron: "0 0 * * *" | |
jobs: | |
checks: | |
name: "Create Release PR" | |
permissions: | |
contents: write | |
pull-requests: write | |
runs-on: ubuntu-latest | |
if: github.repository == 'openwallet-foundation/acapy-plugins' | |
outputs: | |
current_available_version: ${{ steps.current_available_version.outputs.version }} | |
current_global_version: ${{ steps.current_global_version.outputs.version }} | |
upgrade_available: ${{ steps.current_global_version.outputs.available }} | |
lint_plugins: ${{ steps.lint_plugins.outputs.lint_plugins }} | |
unit_test_plugins: ${{ steps.unit_test_plugins.outputs.unit_test_plugins }} | |
integration_test_plugins: ${{ steps.integration_test_plugins.outputs.integration_test_plugins }} | |
integration_test_exit_code: ${{ steps.integration_test_plugins.outputs.integration_test_exit_code }} | |
all_potential_upgrades: ${{ steps.all_potential_upgrades.outputs.all_potential_upgrades }} | |
pr_body: ${{ steps.prepare_pr.outputs.pr_body }} | |
tag_version: ${{ steps.prepare_pr.outputs.tag_version }} | |
defaults: | |
run: | |
working-directory: . | |
steps: | |
#---------------------------------------------- | |
# Check out repo | |
#---------------------------------------------- | |
- uses: actions/checkout@v4 | |
#---------------------------------------------- | |
# Setup python | |
#---------------------------------------------- | |
- uses: actions/setup-python@v5 | |
with: | |
python-version: '3.12' | |
#---------------------------------------------- | |
# Install poetry | |
#---------------------------------------------- | |
- name: Install poetry | |
run: pipx install poetry | |
id: setup-poetry | |
#---------------------------------------------- | |
# Get the latest version of acapy-agent from pypi | |
#---------------------------------------------- | |
- name: Get latest acapy-agent version | |
id: current_available_version | |
run: | | |
remote_version=$(pip index versions acapy-agent) | |
version=$(grep -oP '(?<=Available versions: ).*?(?=$|,)' <<< "$remote_version") | |
echo current_available_version=$version >> $GITHUB_OUTPUT | |
echo "Remote version = $version" | |
#---------------------------------------------- | |
# Check the latest version from plugins_global lock file | |
#---------------------------------------------- | |
- name: Get global acapy-agent version from plugins repo | |
id: current_global_version | |
run: | | |
cd plugin_globals | |
lock_version=$(grep -A1 'name = "acapy-agent"' poetry.lock | grep -v 'name = "acapy-agent"') | |
version=$(grep -oP '(?<=").*?(?=")' <<< "$lock_version") | |
echo current_global_version=$version >> $GITHUB_OUTPUT | |
echo "Global version = $version" | |
#---------------------------------------------- | |
# Re-Release Check | |
#---------------------------------------------- | |
- name: Re-Release Check | |
if: github.event.inputs.re_release == 'true' | |
run: | | |
echo "Re-Release manually requested. Skipping upgrade available check." | |
#---------------------------------------------- | |
# Check if acapy-agent upgrade available | |
# If the global version is greater than or equal to the remote version, exit | |
#---------------------------------------------- | |
- name: Check if acapy-agent upgrade available | |
id: upgrade_available | |
if: github.event.inputs.re_release != 'true' | |
run: | | |
current_available_version="${{steps.current_available_version.outputs.current_available_version}}" | |
echo "Remote version = $current_available_version" | |
current_global_version="${{steps.current_global_version.outputs.current_global_version}}" | |
echo "Global version = $current_global_version" | |
# Compare versions | |
sem_version () { | |
echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; | |
} | |
if [ $(sem_version $current_global_version) -ge $(sem_version $current_available_version) ]; then | |
echo "Version of acapy-agent is up to date" | |
exit 0 | |
fi | |
echo available=true >> $GITHUB_OUTPUT | |
echo "Detected acapy-agent upgrade available..." | |
#---------------------------------------------- | |
# Update global acapy-agent version in lock file | |
#---------------------------------------------- | |
- name: Update global acapy version | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
run: | | |
python repo_manager.py 3 | |
echo "Update global acapy version" | |
#---------------------------------------------- | |
# Update all plugins with acapy-agent and global and local dependencies | |
#---------------------------------------------- | |
- name: Run global updates | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
run: | | |
python repo_manager.py 2 | |
echo "Upgrade all plugins" | |
#---------------------------------------------- | |
# Get all potential upgrades | |
#---------------------------------------------- | |
- name: Get all potential upgrades | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
id: all_potential_upgrades | |
run: | | |
declare -a potential_upgrades=() | |
echo "Checking for potential upgrades" | |
upgraded_plugins=$(python repo_manager.py 5) | |
echo "Upgraded plugins: $upgraded_plugins" | |
for dir in ./*/; do | |
current_folder=$(basename "$dir") | |
if [[ $current_folder == "plugin_globals" ]]; then | |
continue | |
fi | |
found=false | |
for plugin in ${upgraded_plugins}; do | |
if [[ $current_folder == *"$plugin"* ]]; then | |
found=true | |
break | |
fi | |
done | |
if [[ $found != true ]]; then | |
potential_upgrades+=("$current_folder") | |
fi | |
done | |
echo "Potential upgrades: ${potential_upgrades[*]}" | |
echo all_potential_upgrades=${potential_upgrades[*]} >> $GITHUB_OUTPUT | |
#---------------------------------------------- | |
# Collect plugins that fail linting | |
#---------------------------------------------- | |
- name: Lint plugins | |
id: lint_plugins | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
continue-on-error: true | |
run: | | |
declare -a failed_plugins=() | |
for dir in ./*/; do | |
current_folder=$(basename "$dir") | |
if [[ $current_folder == "plugin_globals" ]]; then | |
continue | |
fi | |
cd $current_folder | |
# If the plugin fails to install then consider it as a failed plugin and skip linting | |
if ! poetry install --no-interaction --no-root --all-extras $i | |
then | |
echo "plugin $current_folder failed to install" | |
failed_plugins+=("$current_folder") | |
cd .. | |
continue | |
fi | |
# Run the lint check | |
if poetry run ruff check .; then | |
echo "plugin $current_folder passed lint check" | |
else | |
echo "plugin $current_folder failed lint check" | |
failed_plugins+=("$current_folder") | |
fi | |
cd .. | |
done | |
echo lint_plugins=${failed_plugins[*]} >> $GITHUB_OUTPUT | |
#---------------------------------------------- | |
# Collect plugins that fail unit tests | |
#---------------------------------------------- | |
- name: Unit Test Plugins | |
id: unit_test_plugins | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
continue-on-error: true | |
run: | | |
declare -a failed_plugins=() | |
for dir in ./*/; do | |
current_folder=$(basename "$dir") | |
if [[ $current_folder == "plugin_globals" ]]; then | |
continue | |
fi | |
cd $current_folder | |
# If the plugin fails to install then consider it as a failed plugin and skip unit tests | |
if ! poetry install --no-interaction --no-root --all-extras $i | |
then | |
echo "plugin $current_folder failed to install" | |
failed_plugins+=("$current_folder") | |
cd .. | |
continue | |
fi | |
# Run the unit test check | |
if poetry run pytest; then | |
echo "plugin $current_folder passed unit test check" | |
else | |
echo "plugin $current_folder failed unit test check" | |
failed_plugins+=("$current_folder") | |
fi | |
cd .. | |
done | |
echo unit_test_plugins=${failed_plugins[*]} >> $GITHUB_OUTPUT | |
# ---------------------------------------------- | |
# Install docker compose | |
# ---------------------------------------------- | |
- name: Initialize Docker Compose | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
uses: isbang/[email protected] | |
# ---------------------------------------------- | |
# Collect plugins that fail integration tests | |
# ---------------------------------------------- | |
- name: Integration Test Plugins | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
id: integration_test_plugins | |
continue-on-error: true | |
run: | | |
trap 'echo "integration_test_exit_code=$?" >> "$GITHUB_OUTPUT"' EXIT | |
declare -a failed_plugins=() | |
# lite plugins should be skipped during integration tests | |
readarray -t lite_plugin_array < lite_plugins | |
elementIn () { | |
local e match="$1" | |
shift | |
for e; do [[ "$e" == "$match" ]] && return 0; done | |
return 1 | |
} | |
for dir in ./*/; do | |
current_folder=$(basename "$dir") | |
# Skip plugin_globals and lite plugins | |
if [[ "$current_folder" == "plugin_globals" ]] ; then | |
continue | |
fi | |
if elementIn "$current_folder" "${lite_plugin_array[@]}"; then | |
continue | |
fi | |
cd $current_folder/integration | |
docker compose down --remove-orphans | |
docker compose build | |
if ! docker compose run tests $i | |
then | |
echo "plugin $current_folder failed integration test check" | |
failed_plugins+=("$current_folder") | |
continue | |
fi | |
echo "plugin $current_folder passed integration test check" | |
cd ../.. | |
done | |
echo integration_test_plugins=${failed_plugins[*]} >> $GITHUB_OUTPUT | |
# ---------------------------------------------- | |
# Prepare Pull Request | |
# ---------------------------------------------- | |
- name: Prepare Pull Request | |
id: prepare_pr | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
run: | | |
echo "Merging failed plugins" | |
failed_plugins=() | |
# Merge all failed plugins | |
potential_upgrades=(${{ steps.all_potential_upgrades.outputs.all_potential_upgrades }}) | |
echo "All potential upgrades: ${{ steps.all_potential_upgrades.outputs.all_potential_upgrades }}" | |
lint_plugins=(${{steps.lint_plugins.outputs.lint_plugins}}) | |
unit_test_plugins=(${{steps.unit_test_plugins.outputs.unit_test_plugins}}) | |
integration_test_plugins=(${{steps.integration_test_plugins.outputs.integration_test_plugins}}) | |
for plugin in "${lint_plugins[@]}"; do | |
if [[ ! " ${failed_plugins[@]} " =~ " $plugin " ]]; then | |
failed_plugins+=("$plugin") | |
fi | |
done | |
for plugin in "${unit_test_plugins[@]}"; do | |
if [[ ! " ${failed_plugins[@]} " =~ " $plugin " ]]; then | |
failed_plugins+=("$plugin") | |
fi | |
done | |
for plugin in "${integration_test_plugins[@]}"; do | |
if [[ ! " ${failed_plugins[@]} " =~ " $plugin " ]]; then | |
failed_plugins+=("$plugin") | |
fi | |
done | |
echo "Failed plugins: ${failed_plugins[*]}" | |
# Get release for the branch name and docs | |
release_version="${{steps.current_available_version.outputs.current_available_version}}" | |
echo "Remote version = $release_version" | |
# Configure the git bot | |
git config --global user.name 'Release Bot' | |
git config --global user.email '[email protected]' | |
# Add all the changed files and then remove the failed plugins | |
git add . | |
for plugin in "${failed_plugins[@]}"; do | |
git restore --staged $plugin | |
git checkout -- $plugin | |
done | |
# Get the release notes and update the plugin decription with acapy version | |
body=$(python repo_manager.py 4) | |
# Determine the release version via tags | |
get_tags_output=$(git tag -n0 "*$release_version*") | |
echo "Tag output: ${get_tags_output}" | |
tags_num=0 | |
for item in ${get_tags_output}; do | |
tags_num=$((tags_num+1)) | |
done | |
tag_version="" | |
if [ $tags_num -eq 0 ] | |
then | |
tag_version=$release_version | |
else | |
tag_version="$release_version.$tags_num" | |
fi | |
# Update the RELEASES.md file with the release notes | |
# Remove the Plugin Release Status heading | |
sed -i '/# Plugin Release Status/d' RELEASES.md | |
# Add a marker for the insertion of the upgraded plugins section | |
body=$(printf '%s \n*' "${body}") | |
echo "$body" | cat - RELEASES.md > temp && mv temp RELEASES.md | |
# Check if there are any upgrades | |
upgraded_plugins=($(python repo_manager.py 5)) | |
has_upgrades=false | |
for plugin in ${potential_upgrades[@]}; do | |
for upgrade in ${upgraded_plugins[@]}; do | |
if [[ $plugin == *"$upgrade"* ]]; then | |
has_upgrades=true | |
break | |
fi | |
done | |
done | |
if [ "$has_upgrades" = true ] | |
then | |
echo "Upgrades detected. Committing the changes" | |
else | |
echo "No upgrades detected. Skipping commit and release" | |
exit 1 | |
fi | |
# Update the release notes with the upgraded plugins. | |
# For replacing with the sed command we need to double escape the newline and tab characters. | |
details=$(printf '\n### Plugins Upgraded For ACA-Py Release %s \n - ' "$release_version") | |
double_escape_details=$(printf '\\n### Plugins Upgraded For ACA-Py Release %s \\n - ' "$release_version") | |
# For replacing the first occurence of '*' with the details | |
count=${#upgraded_plugins[*]} | |
for i in $(seq 0 "$(("$count" - 2))" ); | |
do | |
details=$(printf '%s %s \n - ' "$details" "${upgraded_plugins[$i]}") | |
double_escape_details=$(printf '%s %s \\n - ' "$double_escape_details" "${upgraded_plugins[$i]}") | |
done | |
details=$(printf '%s %s \n' "$details" "${upgraded_plugins[$count - 1]}") | |
double_escape_details=$(printf '%s %s \n' "$double_escape_details" "${upgraded_plugins[$count - 1]}") | |
# Replace the first occurence of '*' with the details | |
sed -i "0,/*/s//$(printf '%s ' ${double_escape_details})/" RELEASES.md | |
# Replace the release version with the release tag | |
sed -i "0,/v$release_version/{s/v$release_version/v$tag_version/}" RELEASES.md | |
body=${body/v$release_version/v$tag_version} | |
# Add the heading for the Plugin Release Status and RELEASES.md | |
heading="# Plugin Release Status" | |
# Remove the * insertion marker from the body | |
body=$(echo "$body" | sed 's/\*//g') | |
body=$(printf '%s\n%s' "${heading}" "${body}") | |
printf '%s\n%s\n' "${heading}" "$(cat RELEASES.md)" > RELEASES.md | |
# Prepare the PR body | |
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) | |
echo "pr_body<<$EOF" >> $GITHUB_OUTPUT | |
echo "$body $details" >> $GITHUB_OUTPUT | |
echo "$EOF" >> $GITHUB_OUTPUT | |
# Set the tag version output | |
echo tag_version=$tag_version >> $GITHUB_OUTPUT | |
#---------------------------------------------- | |
# Create Release PR | |
#---------------------------------------------- | |
- name: Create PR | |
if: ${{ steps.upgrade_available.outputs.available == 'true' && github.event.inputs.re_release != 'true'}} | |
uses: peter-evans/create-pull-request@v7 | |
with: | |
author: Release Bot <[email protected]> | |
committer: Release Bot <[email protected]> | |
token: ${{ secrets.GITHUB_TOKEN }} | |
commit-message: "Release v${{ steps.prepare_pr.outputs.tag_version }} Upgrades" | |
title: "Release for acapy-agent v${{ steps.prepare_pr.outputs.tag_version }}" | |
body: "${{ steps.prepare_pr.outputs.pr_body }}" | |
branch: "release-v${{ steps.prepare_pr.outputs.tag_version }}" | |
base: "main" | |
draft: false | |
signoff: true | |
delete-branch: true |