Skip to content

Publish draft version #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 34 additions & 15 deletions publish-github-release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,52 @@ directly from a Jira release version, or it can use release notes provided direc

This action uses the GitHub CLI to create the release and a Python script to interact with the Jira API.

## Duplicate Release Handling

The action automatically checks for existing releases with the same title before creating a new one:

- **When `draft=true`**: If a release with the same title already exists, the action logs a warning and skips creation without failing.
- **When `draft=false`**: If an existing draft release with the same title is found, it will be published instead of creating a new release. If a published release with the same title already exists, the action will fail with an error.

## Release Workflow Triggering

After creating the GitHub release, this action automatically triggers a release workflow in the caller repository using the GitHub CLI. The action:

- Triggers the specified release workflow (default: `release.yml`) on the specified branch or ref (default: `master`)
- Passes the release tag name, release ID, and dry-run flag (based on the `draft` input) to the triggered workflow
- Monitors the workflow execution and waits for it to complete
- Succeeds if the release workflow completes successfully, or fails if the release workflow fails

This ensures that the entire release process (GitHub release creation + downstream release workflow) succeeds or fails as a unit.

## Prerequisites

To fetch release notes from Jira, the action requires that the repository has the `development/kv/data/jira` token
configured in vault.
This can be done using the SPEED self-service
portal ([more info](https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3553787989/Manage+Vault+Policy+-+SPEED)).

The action also requires a `github_token` with `contents: write` permissions to create the release. The default
`${{ github.token }}` is usually sufficient.
The action also requires a `github_token` with `contents: write`, `id-token:write` and `actions:write` permissions to create the release.

## Inputs

The following inputs can be configured for the action:

| Input | Description | Required | Default |
|--------------------------|------------------------------------------------------------------------------------------------------------------|----------|-----------------------|
| `github_token` | The GitHub token for API calls. | `true` | `${{ github.token }}` |
| `version` | The version number for the new release (e.g., `v1.0.0`). This will also be the tag name. | `true` | |
| `branch` | The branch, commit, or tag to create the release from. | `false` | `master` |
| `draft` | A boolean value to indicate if the release should be a draft. | `false` | `true` |
| `release_notes` | The full markdown content for the release notes. If provided, this is used directly, ignoring Jira inputs. | `false` | `''` |
| `jira_release_name` | The name of the Jira release version. If provided and `release_notes` is empty, notes will be fetched from Jira. | `false` | `''` |
| `jira_project_key` | The Jira project key (e.g., "SONARPHP") to fetch notes from. Required if using `jira_release_name`. | `false` | |
| `jira_user` | Jira user (email) for authentication. Required if using `jira_release_name`. | `false` | |
| `jira_token` | Jira API token for authentication. Required if using `jira_release_name`. | `false` | |
| `issue_types` | Optional comma-separated list of Jira issue types to include in the release notes, in order of appearance. | `false` | `''` |
| `use_sandbox` | Set to `false` to use the Jira production server instead of the sandbox. | `false` | `true` |
| Input | Description | Required | Default |
|------------------------|------------------------------------------------------------------------------------------------------------------|----------|-----------------------|
| `github_token` | The GitHub token for API calls. | `true` | `${{ github.token }}` |
| `version` | The version number for the new release (e.g., `v1.0.0`). This will also be the tag name. | `true` | |
| `branch` | The branch, commit, or tag to create the release from. | `false` | `master` |
| `draft` | A boolean value to indicate if the release should be a draft. | `false` | `true` |
| `release_notes` | The full markdown content for the release notes. If provided, this is used directly, ignoring Jira inputs. | `false` | `''` |
| `jira_release_name` | The name of the Jira release version. If provided and `release_notes` is empty, notes will be fetched from Jira. | `false` | `''` |
| `jira_project_key` | The Jira project key (e.g., "SONARPHP") to fetch notes from. Required if using `jira_release_name`. | `false` | |
| `jira_user` | Jira user (email) for authentication. Required if using `jira_release_name`. | `false` | |
| `jira_token` | Jira API token for authentication. Required if using `jira_release_name`. | `false` | |
| `issue_types` | Optional comma-separated list of Jira issue types to include in the release notes, in order of appearance. | `false` | `''` |
| `use_sandbox` | Set to `false` to use the Jira production server instead of the sandbox. | `false` | `true` |
| `release_workflow` | The filename of the release workflow to trigger in the caller repository. | `false` | `release.yml` |
| `release_workflow_ref` | The branch or ref to trigger the release workflow from. | `false` | `master` |

## Outputs

Expand Down
123 changes: 119 additions & 4 deletions publish-github-release/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ inputs:
description: 'The GitHub token for API calls.'
required: true
default: ${{ github.token }}
wait_for_workflow_name:
description: 'The name or file name of the workflow to wait for upon a non-draft release (e.g., "sonar-release" or "release.yml"). If empty, this step is skipped.'
release_workflow:
description: 'The filename of the release workflow to trigger in the caller repository.'
required: false
default: 'sonar-release'
default: 'release.yml'
release_workflow_ref:
description: 'The branch or ref to trigger the release workflow from.'
required: false
default: 'master'

outputs:
release_url:
Expand Down Expand Up @@ -117,15 +121,126 @@ runs:
run: |
echo "${{ inputs.github_token }}" | gh auth login --with-token

# Check if a release with the same title already exists
EXPECTED_TITLE="${{ inputs.version }}"
EXISTING_RELEASE=$(gh api repos/${{ github.repository }}/releases --jq ".[] | select(.name == \"$EXPECTED_TITLE\")" || echo "")

if [[ -n "$EXISTING_RELEASE" ]]; then
EXISTING_DRAFT=$(echo "$EXISTING_RELEASE" | jq -r '.draft')
EXISTING_TAG=$(echo "$EXISTING_RELEASE" | jq -r '.tag_name')
EXISTING_URL=$(echo "$EXISTING_RELEASE" | jq -r '.html_url')
EXISTING_ID=$(echo "$EXISTING_RELEASE" | jq -r '.id')

if [[ "${{ inputs.draft }}" == "true" ]]; then
# If draft=true and release exists, log warning and do nothing
echo "::warning::A release with title '$EXPECTED_TITLE' already exists. Skipping creation since draft=true."
echo "release_url=${EXISTING_URL}" >> $GITHUB_OUTPUT
echo "release_id=${EXISTING_ID}" >> $GITHUB_OUTPUT
exit 0
else
# If draft=false and existing release is a draft, publish it
if [[ "$EXISTING_DRAFT" == "true" ]]; then
echo "Found existing draft release with title '$EXPECTED_TITLE'. Publishing it instead of creating a new one."
gh release edit "$EXISTING_TAG" --draft=false
echo "release_url=${EXISTING_URL}" >> $GITHUB_OUTPUT
echo "release_id=${EXISTING_ID}" >> $GITHUB_OUTPUT
exit 0
else
# If draft=false and existing release is already published, this is an error
echo "::error::A published release with title '$EXPECTED_TITLE' already exists. Cannot create or publish another release with the same title."
exit 1
fi
fi
fi

# No existing release found, proceed with normal creation
DRAFT_FLAG=""
if [[ "${{ inputs.draft }}" == "true" ]]; then
DRAFT_FLAG="--draft"
fi

RELEASE_URL=$(gh release create "${{ inputs.version }}" \
--target "${{ inputs.branch }}" \
--title "${{ inputs.project_name }} ${{ inputs.version }}" \
--title "${{ inputs.version }}" \
--notes-file "release-notes.md" \
$DRAFT_FLAG)

echo "release_url=${RELEASE_URL}" >> $GITHUB_OUTPUT

# Get the release ID only for published releases
if [[ "${{ inputs.draft }}" != "true" ]]; then
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases/tags/${{ inputs.version }} --jq '.id')
echo "release_id=${RELEASE_ID}" >> $GITHUB_OUTPUT
fi

- name: Trigger Release Workflow
shell: bash
run: |
echo "${{ inputs.github_token }}" | gh auth login --with-token

# Set release ID based on draft status
if [[ "${{ inputs.draft }}" == "true" ]]; then
RELEASE_ID_VALUE="N/A"
else
RELEASE_ID_VALUE="${{ steps.create_release.outputs.release_id }}"
fi

echo "Triggering release workflow '${{ inputs.release_workflow }}' with tag '${{ inputs.version }}', release ID '$RELEASE_ID_VALUE', and dryRun=${{ inputs.draft }}..."

# Trigger the workflow
gh workflow run "${{ inputs.release_workflow }}" \
--repo "${{ github.repository }}" \
--ref "${{ inputs.release_workflow_ref }}" \
-f "releaseTagName=${{ inputs.version }}" \
-f "releaseId=$RELEASE_ID_VALUE" \
-f "dryRun=${{ inputs.draft }}"

echo "Workflow triggered successfully"

# Wait a moment for the workflow to start, then get the run ID
sleep 5

RUN_ID=$(gh run list \
--repo "${{ github.repository }}" \
--workflow "${{ inputs.release_workflow }}" \
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')

if [[ -z "$RUN_ID" ]] || [[ "$RUN_ID" == "null" ]]; then
echo "::error::Failed to get workflow run ID"
exit 1
fi

echo "Monitoring workflow run ID: $RUN_ID"

# Wait for the workflow to complete
while true; do
RUN_STATUS=$(gh run view "$RUN_ID" \
--repo "${{ github.repository }}" \
--json status,conclusion \
--jq '{status: .status, conclusion: .conclusion}')

STATUS=$(echo "$RUN_STATUS" | jq -r '.status')
CONCLUSION=$(echo "$RUN_STATUS" | jq -r '.conclusion')

echo "Workflow status: $STATUS, conclusion: $CONCLUSION"

if [[ "$STATUS" == "completed" ]]; then
if [[ "$CONCLUSION" == "success" ]]; then
echo "✅ Release workflow completed successfully!"
break
else
echo "::error::❌ Release workflow failed with conclusion: $CONCLUSION"
gh run view "$RUN_ID" --repo "${{ github.repository }}" --log-failed
exit 1
fi
elif [[ "$STATUS" == "cancelled" ]] || [[ "$STATUS" == "failure" ]]; then
echo "::error::❌ Release workflow was cancelled or failed"
gh run view "$RUN_ID" --repo "${{ github.repository }}" --log-failed
exit 1
fi

# Wait 15 seconds before checking again
sleep 15
done