You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
15 KiB
349 lines
15 KiB
name: _publish-mobile-github-release |
|
|
|
on: |
|
workflow_call: |
|
inputs: |
|
release_name: |
|
description: 'Name prefix of the release to publish (e.g. "Password Manager")' |
|
type: string |
|
required: true |
|
workflow_name: |
|
description: "Name of the workflow to check for previous runs (e.g. publish-github-release.yml)" |
|
type: string |
|
required: true |
|
credentials_filename: |
|
description: 'Name of the credentials file to download from Azure Blob Storage (e.g. "google-play-credentials.json")' |
|
type: string |
|
required: true |
|
check_release_command: |
|
description: > |
|
Shell command to check if a release is already published. |
|
Use $CREDENTIALS_PATH for the path to credentials file. |
|
Example: 'bundle exec fastlane android getLatestVersion serviceCredentialsFile:$CREDENTIALS_PATH' |
|
type: string |
|
required: true |
|
project_type: |
|
description: 'Type of the project (e.g. "android" or "ios")' |
|
type: string |
|
required: true |
|
dry_run: |
|
type: boolean |
|
description: "Run the workflow in dry-run mode without making any changes" |
|
required: false |
|
default: false |
|
|
|
jobs: |
|
publish-release: |
|
name: Publish GitHub Release ${{ inputs.release_name }} |
|
runs-on: ubuntu-24.04 |
|
permissions: |
|
contents: write |
|
id-token: write |
|
actions: write |
|
env: |
|
_DRY_RUN: ${{ inputs.dry_run }} |
|
_WORKFLOW_NAME: ${{ inputs.workflow_name }} |
|
|
|
steps: |
|
- name: Check out repository |
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 |
|
with: |
|
fetch-depth: 0 |
|
persist-credentials: false |
|
|
|
- name: Get latest draft name |
|
id: get_latest_draft |
|
env: |
|
GITHUB_TOKEN: ${{ github.token }} |
|
RELEASE_NAME: ${{ inputs.release_name }} |
|
run: | |
|
latest_release=$( |
|
gh release list --json name,tagName,isDraft,isPrerelease -L 10 \ |
|
--jq "first(.[] | select((.name | test(\"$RELEASE_NAME\"; \"i\")) and (.isDraft == true)))" |
|
) |
|
|
|
is_latest_draft="false" |
|
|
|
if [ "$latest_release" != "null" ] && [ -n "$latest_release" ]; then |
|
is_latest_draft=$(jq -r '.isDraft' <<< "$latest_release") |
|
fi |
|
|
|
echo "is_latest_draft=$is_latest_draft" >> "$GITHUB_OUTPUT" |
|
|
|
if [ "$is_latest_draft" != "true" ]; then |
|
echo "No draft found" |
|
if [ "$_DRY_RUN" != "true" ]; then |
|
echo "Disabling workflow to prevent further runs" |
|
gh workflow disable "$_WORKFLOW_NAME" |
|
fi |
|
exit 0 |
|
fi |
|
|
|
latest_draft_tag=$(jq -r '.tagName' <<< "$latest_release") |
|
echo "latest_draft_tag=$latest_draft_tag" >> "$GITHUB_OUTPUT" |
|
|
|
latest_draft_version_name=$(jq -r '.name' <<< "$latest_release" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') |
|
echo "latest_draft_version_name=$latest_draft_version_name" >> "$GITHUB_OUTPUT" |
|
|
|
latest_draft_version_number=$(jq -r '.name' <<< "$latest_release" | grep -oE '\([0-9]+\)' | sed 's/[()]//g') |
|
echo "latest_draft_version_number=$latest_draft_version_number" >> "$GITHUB_OUTPUT" |
|
|
|
latest_draft_name=$(jq -r '.name' <<< "$latest_release") |
|
echo "latest_draft_name=$latest_draft_name" >> "$GITHUB_OUTPUT" |
|
|
|
# Retrieve the previous run ID and run state to determine the status of the last workflow execution. |
|
# This is done to prevent the workflow from publishing a release that was already published, |
|
# but then deleted and reverted to draft for any reason. |
|
# It ensures the workflow does not process the same release multiple times if it was reverted. |
|
- name: Get previous run ID |
|
id: get_previous_run |
|
env: |
|
GH_TOKEN: ${{ github.token }} |
|
run: | |
|
previous_run_id=$(gh run list --workflow="$_WORKFLOW_NAME" --status=success --limit 1 --json databaseId --jq '.[0].databaseId // empty') |
|
|
|
if [ -n "$previous_run_id" ] && [ "$previous_run_id" != "null" ]; then |
|
echo "Found previous successful scheduled run: $previous_run_id" |
|
echo "previous_run_id=$previous_run_id" >> "$GITHUB_OUTPUT" |
|
echo "has_previous_run=true" >> "$GITHUB_OUTPUT" |
|
else |
|
echo "No previous successful scheduled run found" |
|
echo "has_previous_run=false" >> "$GITHUB_OUTPUT" |
|
fi |
|
|
|
- name: Compose artifact name |
|
id: compose_artifact_name |
|
env: |
|
RELEASE_NAME: ${{ inputs.release_name }} |
|
run: | |
|
artifact_name=$(echo "release-info-$RELEASE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g') |
|
echo "artifact_name=$artifact_name" >> "$GITHUB_OUTPUT" |
|
|
|
- name: Download previous run state |
|
id: previous_state |
|
if: steps.get_previous_run.outputs.has_previous_run == 'true' |
|
continue-on-error: true |
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 |
|
with: |
|
name: ${{ steps.compose_artifact_name.outputs.artifact_name }} |
|
run-id: ${{ steps.get_previous_run.outputs.previous_run_id }} |
|
github-token: ${{ github.token }} |
|
|
|
- name: Parse previous state |
|
id: parse_previous_state |
|
if: steps.get_previous_run.outputs.has_previous_run == 'true' |
|
run: | |
|
if [[ -f "release-info.json" ]]; then |
|
|
|
previous_release_tag=$(jq -r '.release_tag // empty' release-info.json) |
|
previous_initial_state=$(jq -r '.initial_state // empty' release-info.json) |
|
previous_changed_to=$(jq -r '.changed_to_state // empty' release-info.json) |
|
|
|
cat >>"$GITHUB_OUTPUT" <<EOF |
|
previous_release_tag=$previous_release_tag |
|
previous_initial_state=$previous_initial_state |
|
previous_changed_to=$previous_changed_to |
|
previous_timestamp=$previous_timestamp |
|
has_previous_state=true |
|
EOF |
|
|
|
echo "Previous run processed: $previous_release_tag (changed from: $previous_initial_state to: $previous_changed_to)" |
|
else |
|
echo "::warning::No valid release-info.json found in previous artifact" |
|
echo "has_previous_state=false" >> "$GITHUB_OUTPUT" |
|
fi |
|
|
|
- name: Check if release was already processed |
|
id: check_already_processed |
|
env: |
|
CURRENT_RELEASE: ${{ steps.get_latest_draft.outputs.latest_draft_version_name }} |
|
PREVIOUS_RELEASE: ${{ steps.parse_previous_state.outputs.previous_release_tag }} |
|
PREVIOUS_INITIAL_STATE: ${{ steps.parse_previous_state.outputs.previous_initial_state }} |
|
PREVIOUS_CHANGED_TO: ${{ steps.parse_previous_state.outputs.previous_changed_to }} |
|
HAS_PREVIOUS_STATE: ${{ steps.parse_previous_state.outputs.has_previous_state }} |
|
run: | |
|
should_skip=false |
|
|
|
if [[ "$HAS_PREVIOUS_STATE" == "true" && -n "$PREVIOUS_RELEASE" && "$CURRENT_RELEASE" == "$PREVIOUS_RELEASE" ]]; then |
|
if [[ "$PREVIOUS_CHANGED_TO" == "published" || ( "$PREVIOUS_INITIAL_STATE" == "published" && "$PREVIOUS_CHANGED_TO" == "none" ) ]]; then |
|
|
|
cat <<EOF |
|
::error:: Release $CURRENT_RELEASE was already processed and published by this workflow |
|
This suggests the release was manually reverted to draft after being published |
|
Skipping to prevent duplicate processing |
|
EOF |
|
|
|
cat >>"$GITHUB_STEP_SUMMARY" <<EOF |
|
## ::error:: Workflow Skipped |
|
Release \`$CURRENT_RELEASE\` was already processed by this workflow. |
|
To force reprocessing, either: |
|
- Use manual workflow dispatch |
|
- Create a new release version |
|
EOF |
|
|
|
should_skip=true |
|
fi |
|
fi |
|
|
|
echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" |
|
|
|
- name: Configure Ruby |
|
if: steps.check_already_processed.outputs.should_skip == 'false' |
|
uses: ruby/setup-ruby@efbf473cab83af4468e8606cc33eca9281bb213f # v1.256.0 |
|
with: |
|
bundler-cache: true |
|
|
|
- name: Install Fastlane |
|
if: steps.check_already_processed.outputs.should_skip == 'false' |
|
run: | |
|
gem install bundler:2.2.27 |
|
|
|
- name: Log in to Azure |
|
if: steps.check_already_processed.outputs.should_skip == 'false' |
|
uses: bitwarden/gh-actions/azure-login@main |
|
with: |
|
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} |
|
tenant_id: ${{ secrets.AZURE_TENANT_ID }} |
|
client_id: ${{ secrets.AZURE_CLIENT_ID }} |
|
|
|
- name: Download Store credentials |
|
if: steps.check_already_processed.outputs.should_skip == 'false' |
|
env: |
|
ACCOUNT_NAME: bitwardenci |
|
CONTAINER_NAME: mobile |
|
CREDENTIALS_FILE_NAME: ${{ inputs.credentials_filename }} |
|
run: | |
|
mkdir -p ${{ github.workspace }}/secrets |
|
|
|
az storage blob download --account-name "$ACCOUNT_NAME" --container-name "$CONTAINER_NAME" \ |
|
--name "$CREDENTIALS_FILE_NAME" --file "${{ github.workspace }}/secrets/$CREDENTIALS_FILE_NAME" --output none |
|
|
|
- name: Log out from Azure |
|
uses: bitwarden/gh-actions/azure-logout@main |
|
|
|
- name: Get store versions |
|
if: steps.check_already_processed.outputs.should_skip == 'false' && inputs.check_release_command != '' |
|
id: get_store_versions |
|
env: |
|
CREDENTIALS_PATH: ${{ github.workspace }}/secrets/${{ inputs.credentials_filename }} |
|
CHECK_RELEASE_COMMAND: ${{ inputs.check_release_command }} |
|
run: | |
|
echo "Running custom release check command..." |
|
echo "Command: $CHECK_RELEASE_COMMAND" |
|
|
|
OUTPUT=$(eval "$CHECK_RELEASE_COMMAND") |
|
|
|
version_name=$(echo "$OUTPUT" | grep 'version_name: ' | cut -d' ' -f3) |
|
version_number=$(echo "$OUTPUT" | grep 'version_number: ' | cut -d' ' -f3) |
|
|
|
echo "store_version_name=$version_name" >> "$GITHUB_OUTPUT" |
|
echo "store_version_number=$version_number" >> "$GITHUB_OUTPUT" |
|
|
|
- name: Check if version is already released |
|
if: steps.check_already_processed.outputs.should_skip == 'false' |
|
id: check_version |
|
env: |
|
PROJECT_TYPE: ${{ inputs.project_type }} |
|
LATEST_DRAFT_VERSION_NAME: ${{ steps.get_latest_draft.outputs.latest_draft_version_name }} |
|
LATEST_DRAFT_VERSION_NUMBER: ${{ steps.get_latest_draft.outputs.latest_draft_version_number }} |
|
STORE_VERSION_NAME: ${{ steps.get_store_versions.outputs.store_version_name }} |
|
STORE_VERSION_NUMBER: ${{ steps.get_store_versions.outputs.store_version_number }} |
|
run: | |
|
if [ "$PROJECT_TYPE" == "ios" ]; then |
|
if [ "$LATEST_DRAFT_VERSION_NAME" == "$STORE_VERSION_NAME" ] && [ "$LATEST_DRAFT_VERSION_NUMBER" == "$STORE_VERSION_NUMBER" ]; then |
|
echo "iOS: Version name $LATEST_DRAFT_VERSION_NAME and version number $LATEST_DRAFT_VERSION_NUMBER is already released on store" |
|
echo "version_released=true" >> "$GITHUB_OUTPUT" |
|
else |
|
echo "iOS: Version $LATEST_DRAFT_VERSION_NAME ($LATEST_DRAFT_VERSION_NUMBER) is not released on store. Latest version in the store is $STORE_VERSION_NAME ($STORE_VERSION_NUMBER)" |
|
echo "version_released=false" >> "$GITHUB_OUTPUT" |
|
fi |
|
else |
|
if [ "$LATEST_DRAFT_VERSION_NUMBER" == "$STORE_VERSION_NUMBER" ]; then |
|
echo "Version $LATEST_DRAFT_VERSION_NUMBER is already released on store" |
|
echo "version_released=true" >> "$GITHUB_OUTPUT" |
|
else |
|
echo "Version $LATEST_DRAFT_VERSION_NUMBER is not released on store. Latest version in the store is $STORE_VERSION_NUMBER, with version name: $STORE_VERSION_NAME" |
|
echo "version_released=false" >> "$GITHUB_OUTPUT" |
|
fi |
|
fi |
|
|
|
- name: Make GitHub release latest and non-pre-release |
|
id: make_release_latest |
|
if: steps.check_version.outputs.version_released == 'true' && inputs.dry_run != true |
|
env: |
|
TAG: ${{ steps.get_latest_draft.outputs.latest_draft_tag }} |
|
GH_TOKEN: ${{ github.token }} |
|
PRODUCT: ${{ inputs.release_name }} |
|
run: | |
|
if [ "$PRODUCT" = "Password Manager" ]; then |
|
gh release edit "$TAG" --prerelease=false --latest --draft=false |
|
else |
|
gh release edit "$TAG" --prerelease=false --draft=false |
|
fi |
|
|
|
- name: Disable workflow if published |
|
if: ${{ steps.make_release_latest.conclusion == 'success' }} |
|
env: |
|
GH_TOKEN: ${{ github.token }} |
|
run: | |
|
if [ "$_DRY_RUN" = "true" ]; then |
|
echo "Dry run mode - skipping gh workflow disable command" |
|
gh workflow list "$_WORKFLOW_NAME" |
|
else |
|
echo "Disabling workflow to prevent further runs" |
|
gh workflow disable "$_WORKFLOW_NAME" |
|
fi |
|
|
|
- name: Create workflow state artifact |
|
env: |
|
IS_LATEST_DRAFT: ${{ steps.get_latest_draft.outputs.is_latest_draft }} |
|
LATEST_DRAFT_VERSION_NAME: ${{ steps.get_latest_draft.outputs.latest_draft_version_name }} |
|
PREVIOUS_RELEASE_TAG: ${{ steps.parse_previous_state.outputs.previous_release_tag }} |
|
SHOULD_SKIP: ${{ steps.check_already_processed.outputs.should_skip }} |
|
VERSION_RELEASED: ${{ steps.check_version.outputs.version_released }} |
|
run: | |
|
if [ -f "release-info.json" ]; then |
|
echo "release-info.json already exists, removing it" |
|
rm -f release-info.json |
|
fi |
|
|
|
if [ "$IS_LATEST_DRAFT" == "true" ]; then |
|
release_tag="$LATEST_DRAFT_VERSION_NAME" |
|
else |
|
release_tag="$PREVIOUS_RELEASE_TAG" |
|
fi |
|
|
|
if [ "$SHOULD_SKIP" == "true" ]; then |
|
initial_state="draft" |
|
changed_to_state="none" |
|
elif [ "$IS_LATEST_DRAFT" == "true" ] && [ "$SHOULD_SKIP" == "false" ]; then |
|
initial_state="draft" |
|
if [ "$VERSION_RELEASED" == "true" ]; then |
|
changed_to_state="published" |
|
else |
|
changed_to_state="none" |
|
fi |
|
elif [ "$IS_LATEST_DRAFT" == "false" ]; then |
|
initial_state="published" |
|
changed_to_state="none" |
|
fi |
|
|
|
json=$(jq -n \ |
|
--arg release_tag "$release_tag" \ |
|
--arg initial_state "$initial_state" \ |
|
--arg changed_to_state "$changed_to_state" \ |
|
'{release_tag: $release_tag, initial_state: $initial_state, changed_to_state: $changed_to_state}') |
|
|
|
echo "$json" > release-info.json |
|
|
|
cat >>"$GITHUB_STEP_SUMMARY" <<EOF |
|
\`\`\`json |
|
$json |
|
\`\`\` |
|
EOF |
|
|
|
- name: Upload workflow state artifact |
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 |
|
with: |
|
name: ${{ steps.compose_artifact_name.outputs.artifact_name }} |
|
path: release-info.json
|
|
|