Skip to content
Merged
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
31 changes: 21 additions & 10 deletions tests/test_prepare_release_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ def test_template_exposes_required_release_entry_points(self) -> None:
content = TEMPLATE.read_text(encoding="utf-8")

self.assertIn("workflow_dispatch:", content)
self.assertIn("pull_request:", content)
self.assertIn("pull_request_target:", content)
self.assertIn("release:", content)
self.assertIn("branch:", content)
self.assertIn("channel:", content)
self.assertIn("ignore_open_backport:", content)
self.assertIn("create_follow_up_milestone:", content)
self.assertIn("mode:", content)
self.assertNotIn("mode:", content)
self.assertNotIn("safe_public_text:", content)

def test_dispatch_help_is_concise_and_explains_risky_inputs(self) -> None:
content = TEMPLATE.read_text(encoding="utf-8")
Expand All @@ -38,30 +39,38 @@ def test_dispatch_help_is_concise_and_explains_risky_inputs(self) -> None:
self.assertIn("Branch and version rules are still validated", content)
self.assertIn("matching backport PR still open", content)
self.assertIn("move remaining open items", content)
self.assertIn("security keeps advisory-private details out of public release text", content)
self.assertIn("Never include private advisory details", content)
self.assertNotIn("advisory-private", content)
self.assertNotIn("private advisory details", content)

def test_template_uses_explicit_release_run_names(self) -> None:
content = TEMPLATE.read_text(encoding="utf-8")

self.assertIn("run-name:", content)
self.assertIn("Prepare release · {0}", content)
self.assertIn("Finalize release · PR #{0}", content)
self.assertIn("Verify release · {0}", content)

def test_template_delegates_all_release_stages_to_versioned_actions(self) -> None:
content = TEMPLATE.read_text(encoding="utf-8")
sha = "18e6c30c33a5d81b0248a7d865536965eaa93329"
sha = "f732578ab87c7bd7d85b60ac1612277149f1153e"

self.assertIn(
f"actions/release-prepare@{sha} # v0.6.3",
f"actions/release-prepare@{sha} # v0.6.29",
content,
)
self.assertIn(
f"actions/release-post-merge@{sha} # v0.6.3",
f"actions/release-post-merge@{sha} # v0.6.29",
content,
)
self.assertIn(
f"actions/release-publication@{sha} # v0.6.3",
f"actions/release-publication@{sha} # v0.6.29",
content,
)

def test_release_mutation_credentials_use_org_variable_and_secret(self) -> None:
def test_release_mutation_credentials_use_org_secret(self) -> None:
content = TEMPLATE.read_text(encoding="utf-8")

self.assertEqual(2, content.count("vars.LIBRECODE_WORKFLOW_APP_ID"))
self.assertNotIn("vars.LIBRECODE_WORKFLOW_APP_ID", content)
self.assertEqual(2, content.count("secrets.LIBRECODE_WORKFLOW_APP_PRIVATE_KEY"))
self.assertNotIn("secrets.LIBRECODE_WORKFLOW_APP_ID", content)

Expand All @@ -79,13 +88,15 @@ def test_template_keeps_permissions_stage_scoped(self) -> None:
self.assertIn("permissions: {}", content)
self.assertIn("actions: read", content)
self.assertIn("contents: read", content)
self.assertIn("issues: write", content)
self.assertIn("pull-requests: read", content)
self.assertNotIn("permissions: write-all", content)
self.assertNotIn("contents: write", content)

def test_post_merge_only_accepts_generated_merged_release_prs(self) -> None:
content = TEMPLATE.read_text(encoding="utf-8")

self.assertIn("github.event_name == 'pull_request_target'", content)
self.assertIn("github.event.pull_request.merged == true", content)
self.assertIn("startsWith(github.event.pull_request.head.ref, 'release-tool/')", content)
self.assertIn("<!-- release-tool:preparation ", content)
Expand Down
36 changes: 13 additions & 23 deletions workflow-templates/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later

name: Prepare release
run-name: >-
${{ github.event_name == 'workflow_dispatch' && format('Prepare release · {0}', inputs.branch)
|| github.event_name == 'pull_request_target' && format('Finalize release · PR #{0}', github.event.pull_request.number)
|| github.event_name == 'release' && format('Verify release · {0}', github.event.release.tag_name)
|| 'Release automation' }}

on:
workflow_dispatch:
Expand Down Expand Up @@ -38,19 +43,7 @@ on:
required: true
default: true
type: boolean
mode:
description: normal for regular releases; security keeps advisory-private details out of public release text.
required: true
default: normal
type: choice
options:
- normal
- security
safe_public_text:
description: Public wording used only in security mode. Never include private advisory details.
required: false
type: string
pull_request:
pull_request_target:
types: [closed]
release:
types: [published]
Expand Down Expand Up @@ -85,29 +78,26 @@ jobs:
RELEASE_BRANCH: ${{ inputs.branch }}
run: |
set -euo pipefail
git fetch --unshallow --prune origin "+refs/heads/${RELEASE_BRANCH}:refs/remotes/origin/${RELEASE_BRANCH}" "+refs/tags/*:refs/tags/*"
git fetch --quiet --unshallow --prune origin "+refs/heads/${RELEASE_BRANCH}:refs/remotes/origin/${RELEASE_BRANCH}" "+refs/tags/*:refs/tags/*"

- name: Prepare release
uses: LibreCodeCoop/github-workflows/actions/release-prepare@18e6c30c33a5d81b0248a7d865536965eaa93329 # v0.6.3
uses: LibreCodeCoop/github-workflows/actions/release-prepare@f732578ab87c7bd7d85b60ac1612277149f1153e # v0.6.29
with:
branch: ${{ inputs.branch }}
ref: ${{ inputs.ref }}
version: ${{ inputs.version }}
channel: ${{ inputs.channel }}
mode: ${{ inputs.mode }}
safe-public-text: ${{ inputs.safe_public_text }}
ignore-open-backport: ${{ inputs.ignore_open_backport }}
create-follow-up-milestone: ${{ inputs.create_follow_up_milestone }}
config-path: .nextcloud-release.yml
actor: ${{ github.actor }}
github-token: ${{ secrets.GITHUB_TOKEN }}
app-id: ${{ vars.LIBRECODE_WORKFLOW_APP_ID }}
app-private-key: ${{ secrets.LIBRECODE_WORKFLOW_APP_PRIVATE_KEY }}

post_merge:
name: Finalize release and prepare draft
if: >-
github.event_name == 'pull_request' &&
github.event_name == 'pull_request_target' &&
github.event.pull_request.merged == true &&
startsWith(github.event.pull_request.head.ref, 'release-tool/') &&
contains(github.event.pull_request.body, '<!-- release-tool:preparation ')
Expand All @@ -116,6 +106,7 @@ jobs:
permissions:
actions: read
contents: read
issues: write
pull-requests: read
steps:
- name: Checkout merged release branch
Expand All @@ -131,17 +122,16 @@ jobs:
RELEASE_BRANCH: ${{ github.event.pull_request.base.ref }}
run: |
set -euo pipefail
git fetch --unshallow --prune origin "+refs/heads/${RELEASE_BRANCH}:refs/remotes/origin/${RELEASE_BRANCH}" "+refs/tags/*:refs/tags/*"
git fetch --quiet --unshallow --prune origin "+refs/heads/${RELEASE_BRANCH}:refs/remotes/origin/${RELEASE_BRANCH}" "+refs/tags/*:refs/tags/*"

- name: Finalize merged release
uses: LibreCodeCoop/github-workflows/actions/release-post-merge@18e6c30c33a5d81b0248a7d865536965eaa93329 # v0.6.3
uses: LibreCodeCoop/github-workflows/actions/release-post-merge@f732578ab87c7bd7d85b60ac1612277149f1153e # v0.6.29
with:
pull-request-number: ${{ github.event.pull_request.number }}
merger: ${{ github.event.pull_request.merged_by.login }}
config-path: .nextcloud-release.yml
prepare-workflow-path: .github/workflows/prepare-release.yml
github-token: ${{ secrets.GITHUB_TOKEN }}
app-id: ${{ vars.LIBRECODE_WORKFLOW_APP_ID }}
app-private-key: ${{ secrets.LIBRECODE_WORKFLOW_APP_PRIVATE_KEY }}

verify_publication:
Expand All @@ -161,7 +151,7 @@ jobs:
ref: ${{ github.event.release.tag_name }}

- name: Verify publication
uses: LibreCodeCoop/github-workflows/actions/release-publication@18e6c30c33a5d81b0248a7d865536965eaa93329 # v0.6.3
uses: LibreCodeCoop/github-workflows/actions/release-publication@f732578ab87c7bd7d85b60ac1612277149f1153e # v0.6.29
with:
github-release-id: ${{ github.event.release.id }}
config-path: .nextcloud-release.yml
Expand Down
Loading