Skip to content

Create Release PR

Create Release PR #230

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