From 37df7d1a97ee7000708d3645e1d747b920abd85c Mon Sep 17 00:00:00 2001 From: Sharon Koech Date: Tue, 29 Jul 2025 15:21:29 +0300 Subject: [PATCH 1/4] Add workkflow for tagging stable commit Signed-off-by: Sharon Koech --- .github/workflows/tag-stable-commit.yml | 146 ++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 .github/workflows/tag-stable-commit.yml diff --git a/.github/workflows/tag-stable-commit.yml b/.github/workflows/tag-stable-commit.yml new file mode 100644 index 000000000..42eef4120 --- /dev/null +++ b/.github/workflows/tag-stable-commit.yml @@ -0,0 +1,146 @@ +# This workflow runs biweekly + +name: Tag stable MicroCeph commit + +# Controls when the action will run. Workflow runs when there is a new stable channel +# promoted on Snapcraft +on: +# Allows you to run this workflow manually from the Actions tab + workflow_dispatch: null +# schedule: +# - cron: '0 0 * * MON,THU' # Runs biweekly on Tuesdays and Thursdays +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +# This workflow contains a few jobs; the first one is called "tag-stable-commit" +jobs: + tag-stable-commit: + # The type of runner that the job will run on + runs-on: ubuntu-latest + permissions: + contents: read # Needed for commit search via the API + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Runs a single command using the runners shell + - name: Checkout repository + uses: actions/checkout@v4 +# ------------------------------------------------------------ + # 1. Install the MicroCeph snap +# ------------------------------------------------------------ + - name: Install MicroCeph snap + run: | + sudo snap install microceph +# ----------------------------------------------------------- + # 2. Find the first name under “channels:” + # 3. and then parse and +# ----------------------------------------------------------- + + - name: Extract channel information + id: snap + uses: actions/github-script@v7 + with: + script: | + const { execSync } = require('child_process'); + // Run `snap info microceph` + const info = execSync('snap info microceph', { encoding: 'utf-8' }); + const lines = info.split('\n'); + // Find the "channels:" header (ignoring indentation) + const headerIdx = lines.findIndex(l => l.trim().startsWith('channels:')); + if (headerIdx === -1) { + core.setFailed('Could not find "channels:" in snap info output'); + return; + } + // Get the first non-empty channel line after the header + const channelRaw = lines.slice(headerIdx + 1) + .find(l => l.includes('/stable:')); + if (!channelRaw) { + core.setFailed('Could not find a "/stable:" channel line'); + return; + } + core.info(`Channel line: "${channelRaw}"`); + // Parse version, and commit ID + const m = channelRaw.match( + /^\s*([^/]+)\/stable:\s+([^+\s]+)\+snap([a-f0-9]+)\s/ + ); + if (!m) { + core.setFailed('Failed to parse channel line'); + return; + } + const [, flavour, version, commit] = m; + core.setOutput('flavour', flavour); + core.setOutput('version', version); + core.setOutput('commit', commit); + core.info(`flavour=${flavour}`); + core.info(`version=${version}`); + core.info(`commit=${commit}`); + # ------------------------------------------------------------------------ + # 4. Verify commit exists in the repo and print commit message first line + # ------------------------------------------------------------------------ + - name: Verify commit exists + uses: actions/github-script@v7 + with: + script: | + const target = '${{ steps.snap.outputs.commit }}'.slice(0, 7); // 7-char prefix + core.info(`Looking for a commit starting with "${target}" …`); + const commits = await github.paginate( + github.rest.repos.listCommits, + { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + } + ); + const hit = commits.find(c => c.sha.startsWith(target)); + if (hit) { + core.info(`✅ Found commit: ${hit.sha} — ${hit.html_url}`); + // Print first line of the commit message + const firstLine = hit.commit.message.split('\n')[0]; + core.info(`Commit message first line: "${firstLine}"`); + } else { + core.setFailed(`❌ No commit starting with "${target}" found in the repository`); + } + - name: Move latest tag to verified commit + uses: actions/github-script@v7 + with: + script: | + const target = '${{ steps.snap.outputs.commit }}'.slice(0, 7); + core.info(`Looking for a commit starting with "${target}" …`); + const commits = await github.paginate( + github.rest.repos.listCommits, + { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + } + ); + const hit = commits.find(c => c.sha.startsWith(target)); + if (!hit) { + core.setFailed(`❌ No commit starting with "${target}" found in the repository`); + return; + } + core.info(`✅ Found commit: ${hit.sha} — ${hit.html_url}`); + const firstLine = hit.commit.message.split('\n')[0]; + core.info(`Commit message first line: "${firstLine}"`); + // Fetch latest tag (sorted by commit recency) + const tags = await github.paginate( + github.rest.repos.listTags, + { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + } + ); + if (!tags.length) { + core.setFailed('❌ No tags found in the repository'); + return; + } + const latestTag = tags[0]; + const refName = `tags/${latestTag.name}`; + core.info(`🔁 Moving latest tag "${latestTag.name}" from ${latestTag.commit.sha} → ${hit.sha}`); + await github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: refName, + sha: hit.sha, + force: true + }); + core.info(`✅ Tag "${latestTag.name}" now points to ${hit.sha}`); + \ No newline at end of file From 922b2e759d8a601f87d17bcf19ced4793fcc82b9 Mon Sep 17 00:00:00 2001 From: Sharon Koech Date: Mon, 4 Aug 2025 19:27:38 +0300 Subject: [PATCH 2/4] Apply changes after review Signed-off-by: Sharon Koech --- .github/workflows/tag-stable-commit.yml | 62 ++++++++++++++----------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/.github/workflows/tag-stable-commit.yml b/.github/workflows/tag-stable-commit.yml index 42eef4120..3ca51aa81 100644 --- a/.github/workflows/tag-stable-commit.yml +++ b/.github/workflows/tag-stable-commit.yml @@ -9,30 +9,20 @@ on: workflow_dispatch: null # schedule: # - cron: '0 0 * * MON,THU' # Runs biweekly on Tuesdays and Thursdays -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -# This workflow contains a few jobs; the first one is called "tag-stable-commit" jobs: tag-stable-commit: # The type of runner that the job will run on runs-on: ubuntu-latest permissions: contents: read # Needed for commit search via the API - # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Runs a single command using the runners shell - name: Checkout repository uses: actions/checkout@v4 -# ------------------------------------------------------------ - # 1. Install the MicroCeph snap -# ------------------------------------------------------------ + # Install the MicroCeph snap - name: Install MicroCeph snap run: | sudo snap install microceph -# ----------------------------------------------------------- - # 2. Find the first name under “channels:” - # 3. and then parse and -# ----------------------------------------------------------- - + # Find the first name under “channels:” that includes "/stable:" and parse , and - name: Extract channel information id: snap uses: actions/github-script@v7 @@ -58,26 +48,44 @@ jobs: core.info(`Channel line: "${channelRaw}"`); // Parse version, and commit ID const m = channelRaw.match( - /^\s*([^/]+)\/stable:\s+([^+\s]+)\+snap([a-f0-9]+)\s/ + /^\s*([a-z]+)\/stable:\s+([0-9.]+)\+snap([a-f0-9]+)\s/i ); if (!m) { core.setFailed('Failed to parse channel line'); return; } - const [, flavour, version, commit] = m; - core.setOutput('flavour', flavour); + const [, codeName, version, commit] = m; + + const minCodeNameLength = 1; // At least 1 char + const minVersionLength = 6; // e.g. '19.2.0' or more + const minCommitLength= 7; // typical short git commit hash length + + // Add minimum length requirement to validate output variables + if (!codeName || codeName.length < minCodeNameLength) { + core.setFailed(`Invalid codeName: "${codeName}"`); + return; + } + if (!version || version.length < minVersionLength) { + core.setFailed(`Invalid version: "${version}"`); + return; + } + if (!commit || commit.length < minCommitLength) { + core.setFailed(`Invalid commit: "${commit}"`); + return; + } + + core.setOutput('codeName', codeName); core.setOutput('version', version); core.setOutput('commit', commit); - core.info(`flavour=${flavour}`); + core.info(`codeName=${codeName}`); core.info(`version=${version}`); core.info(`commit=${commit}`); - # ------------------------------------------------------------------------ - # 4. Verify commit exists in the repo and print commit message first line - # ------------------------------------------------------------------------ + + # Verify commit exists in the repo and print commit message first line - name: Verify commit exists uses: actions/github-script@v7 with: - script: | + script: | const target = '${{ steps.snap.outputs.commit }}'.slice(0, 7); // 7-char prefix core.info(`Looking for a commit starting with "${target}" …`); const commits = await github.paginate( @@ -90,12 +98,12 @@ jobs: ); const hit = commits.find(c => c.sha.startsWith(target)); if (hit) { - core.info(`✅ Found commit: ${hit.sha} — ${hit.html_url}`); + core.info(`Found commit: ${hit.sha} — ${hit.html_url}`); // Print first line of the commit message const firstLine = hit.commit.message.split('\n')[0]; core.info(`Commit message first line: "${firstLine}"`); } else { - core.setFailed(`❌ No commit starting with "${target}" found in the repository`); + core.setFailed(`No commit starting with "${target}" found in the repository`); } - name: Move latest tag to verified commit uses: actions/github-script@v7 @@ -113,10 +121,10 @@ jobs: ); const hit = commits.find(c => c.sha.startsWith(target)); if (!hit) { - core.setFailed(`❌ No commit starting with "${target}" found in the repository`); + core.setFailed(`No commit starting with "${target}" found in the repository`); return; } - core.info(`✅ Found commit: ${hit.sha} — ${hit.html_url}`); + core.info(`Found commit: ${hit.sha} — ${hit.html_url}`); const firstLine = hit.commit.message.split('\n')[0]; core.info(`Commit message first line: "${firstLine}"`); // Fetch latest tag (sorted by commit recency) @@ -129,12 +137,12 @@ jobs: } ); if (!tags.length) { - core.setFailed('❌ No tags found in the repository'); + core.setFailed('No tags found in the repository'); return; } const latestTag = tags[0]; const refName = `tags/${latestTag.name}`; - core.info(`🔁 Moving latest tag "${latestTag.name}" from ${latestTag.commit.sha} → ${hit.sha}`); + core.info(`Moving latest tag "${latestTag.name}" from ${latestTag.commit.sha} → ${hit.sha}`); await github.rest.git.updateRef({ owner: context.repo.owner, repo: context.repo.repo, @@ -142,5 +150,5 @@ jobs: sha: hit.sha, force: true }); - core.info(`✅ Tag "${latestTag.name}" now points to ${hit.sha}`); + core.info(`Tag "${latestTag.name}" now points to ${hit.sha}`); \ No newline at end of file From 80493ad581848eb981593bb1282e9147565b1e82 Mon Sep 17 00:00:00 2001 From: Sharon Koech Date: Wed, 6 Aug 2025 10:24:39 +0300 Subject: [PATCH 3/4] Modify last step to create or update tags Signed-off-by: Sharon Koech --- .github/workflows/tag-stable-commit.yml | 99 ++++++++++++++----------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/.github/workflows/tag-stable-commit.yml b/.github/workflows/tag-stable-commit.yml index 3ca51aa81..1a1450225 100644 --- a/.github/workflows/tag-stable-commit.yml +++ b/.github/workflows/tag-stable-commit.yml @@ -14,7 +14,7 @@ jobs: # The type of runner that the job will run on runs-on: ubuntu-latest permissions: - contents: read # Needed for commit search via the API + contents: write # Needed for creating tags steps: - name: Checkout repository uses: actions/checkout@v4 @@ -22,7 +22,8 @@ jobs: - name: Install MicroCeph snap run: | sudo snap install microceph - # Find the first name under “channels:” that includes "/stable:" and parse , and + # Find the first name under "channels:" that includes "/stable:" + # and parse , and - name: Extract channel information id: snap uses: actions/github-script@v7 @@ -48,7 +49,7 @@ jobs: core.info(`Channel line: "${channelRaw}"`); // Parse version, and commit ID const m = channelRaw.match( - /^\s*([a-z]+)\/stable:\s+([0-9.]+)\+snap([a-f0-9]+)\s/i + /^\s*([a-z]+)\/stable:\s+([0-9.]+)\+snap([a-f0-9]+)\s/ ); if (!m) { core.setFailed('Failed to parse channel line'); @@ -87,7 +88,7 @@ jobs: with: script: | const target = '${{ steps.snap.outputs.commit }}'.slice(0, 7); // 7-char prefix - core.info(`Looking for a commit starting with "${target}" …`); + core.info(`Looking for a commit starting with "${target}" ...`); const commits = await github.paginate( github.rest.repos.listCommits, { @@ -98,57 +99,65 @@ jobs: ); const hit = commits.find(c => c.sha.startsWith(target)); if (hit) { - core.info(`Found commit: ${hit.sha} — ${hit.html_url}`); + core.info(`Found commit: ${hit.sha} - ${hit.html_url}`); // Print first line of the commit message const firstLine = hit.commit.message.split('\n')[0]; core.info(`Commit message first line: "${firstLine}"`); } else { core.setFailed(`No commit starting with "${target}" found in the repository`); } - - name: Move latest tag to verified commit + # Create or update tag pointing to the verified commit + # If the tag doesn't exist, create it + # If the tag exists, but it points to a different commit, update it (move it forward) + # If it already exists but points to the correct commit, do nothing + - name: Create or update stable tag for verified commit uses: actions/github-script@v7 with: script: | - const target = '${{ steps.snap.outputs.commit }}'.slice(0, 7); - core.info(`Looking for a commit starting with "${target}" …`); - const commits = await github.paginate( - github.rest.repos.listCommits, - { - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100 - } - ); - const hit = commits.find(c => c.sha.startsWith(target)); - if (!hit) { - core.setFailed(`No commit starting with "${target}" found in the repository`); - return; - } - core.info(`Found commit: ${hit.sha} — ${hit.html_url}`); - const firstLine = hit.commit.message.split('\n')[0]; - core.info(`Commit message first line: "${firstLine}"`); - // Fetch latest tag (sorted by commit recency) + const commitSha = '${{ steps.snap.outputs.commit }}'; + const codeName = '${{ steps.snap.outputs.codeName }}'; + const version = '${{ steps.snap.outputs.version }}'; + const stableTag = `v${version}+${codeName}`; + core.info(`Proposed stable tag: ${stableTag}`); + + // Get existing tags const tags = await github.paginate( - github.rest.repos.listTags, - { - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100 - } - ); - if (!tags.length) { - core.setFailed('No tags found in the repository'); - return; + github.rest.repos.listTags, + { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + } + ); + + const existingTag = tags.find(t => t.name === stableTag); + + if (existingTag) { + core.info(`Tag "${stableTag}" already exists.`); + if (existingTag.commit.sha === commitSha) { + core.info(`It already points to the correct commit (${commitSha}). Nothing to do.`); + return; + } else { + core.info(`Tag "${stableTag}" points to a different commit (${existingTag.commit.sha}). Updating to ${commitSha}...`); + await github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${stableTag}`, + sha: commitSha, + force: true + }); + core.info(`Tag "${stableTag}" updated to point to ${commitSha}.`); + return; + } } - const latestTag = tags[0]; - const refName = `tags/${latestTag.name}`; - core.info(`Moving latest tag "${latestTag.name}" from ${latestTag.commit.sha} → ${hit.sha}`); - await github.rest.git.updateRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: refName, - sha: hit.sha, - force: true + + // Create tag if it doesn't exist + core.info(`Creating new tag "${stableTag}" pointing to commit ${commitSha}`); + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${stableTag}`, + sha: commitSha }); - core.info(`Tag "${latestTag.name}" now points to ${hit.sha}`); + core.info(`Tag "${stableTag}" created successfully.`); \ No newline at end of file From 0caeeac9098bf9b79f15997c71bf61deceafaa47 Mon Sep 17 00:00:00 2001 From: Sharon Koech Date: Thu, 7 Aug 2025 17:58:46 +0300 Subject: [PATCH 4/4] Add changes after testing Signed-off-by: Sharon Koech --- .github/workflows/tag-stable-commit.yml | 166 +++++++++++++----------- 1 file changed, 89 insertions(+), 77 deletions(-) diff --git a/.github/workflows/tag-stable-commit.yml b/.github/workflows/tag-stable-commit.yml index 1a1450225..67f6ccdea 100644 --- a/.github/workflows/tag-stable-commit.yml +++ b/.github/workflows/tag-stable-commit.yml @@ -5,31 +5,39 @@ name: Tag stable MicroCeph commit # Controls when the action will run. Workflow runs when there is a new stable channel # promoted on Snapcraft on: -# Allows you to run this workflow manually from the Actions tab - workflow_dispatch: null -# schedule: -# - cron: '0 0 * * MON,THU' # Runs biweekly on Tuesdays and Thursdays + pull_request: + types: [opened, synchronize, reopened] + branches: + - main + # Allow manual trigger from Actions UI + workflow_dispatch: + schedule: + - cron: '0 0 * * MON,THU' # Runs on Tuesdays and Thursdays at midnight UTC + jobs: tag-stable-commit: - # The type of runner that the job will run on + # The type of runner that the job will run on runs-on: ubuntu-latest permissions: contents: write # Needed for creating tags steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + # Install the MicroCeph snap - - name: Install MicroCeph snap - run: | - sudo snap install microceph - # Find the first name under "channels:" that includes "/stable:" - # and parse , and - - name: Extract channel information - id: snap - uses: actions/github-script@v7 - with: - script: | - const { execSync } = require('child_process'); + - name: Install MicroCeph snap + run: | + sudo snap install microceph + + # Find the first name under "channels:" that includes "/stable:" + # and parse , and + - name: Extract channel information + id: snap + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { execSync } = require('child_process'); // Run `snap info microceph` const info = execSync('snap info microceph', { encoding: 'utf-8' }); const lines = info.split('\n'); @@ -64,17 +72,17 @@ jobs: // Add minimum length requirement to validate output variables if (!codeName || codeName.length < minCodeNameLength) { core.setFailed(`Invalid codeName: "${codeName}"`); - return; - } + return; + } if (!version || version.length < minVersionLength) { core.setFailed(`Invalid version: "${version}"`); - return; - } + return; + } if (!commit || commit.length < minCommitLength) { core.setFailed(`Invalid commit: "${commit}"`); - return; - } - + return; + } + core.setOutput('codeName', codeName); core.setOutput('version', version); core.setOutput('commit', commit); @@ -83,9 +91,11 @@ jobs: core.info(`commit=${commit}`); # Verify commit exists in the repo and print commit message first line - - name: Verify commit exists - uses: actions/github-script@v7 - with: + - name: Verify commit exists + id: verify + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const target = '${{ steps.snap.outputs.commit }}'.slice(0, 7); // 7-char prefix core.info(`Looking for a commit starting with "${target}" ...`); @@ -103,61 +113,63 @@ jobs: // Print first line of the commit message const firstLine = hit.commit.message.split('\n')[0]; core.info(`Commit message first line: "${firstLine}"`); + core.setOutput('full_sha', hit.sha); // Output full SHA } else { core.setFailed(`No commit starting with "${target}" found in the repository`); } - # Create or update tag pointing to the verified commit - # If the tag doesn't exist, create it - # If the tag exists, but it points to a different commit, update it (move it forward) - # If it already exists but points to the correct commit, do nothing - - name: Create or update stable tag for verified commit - uses: actions/github-script@v7 - with: - script: | - const commitSha = '${{ steps.snap.outputs.commit }}'; - const codeName = '${{ steps.snap.outputs.codeName }}'; - const version = '${{ steps.snap.outputs.version }}'; - const stableTag = `v${version}+${codeName}`; - core.info(`Proposed stable tag: ${stableTag}`); - // Get existing tags - const tags = await github.paginate( - github.rest.repos.listTags, - { - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100 - } + # Create or update tag pointing to the verified commit + # If the tag doesn't exist, create it + # If the tag exists, but it points to a different commit, update it (move it forward) + # If it already exists but points to the correct commit, do nothing + - name: Create or update stable tag for verified commit + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const commitSha = '${{ steps.verify.outputs.full_sha }}'; + const codeName = '${{ steps.snap.outputs.codeName }}'; + const version = '${{ steps.snap.outputs.version }}'; + const stableTag = `v${version}+${codeName}`; + core.info(`Proposed stable tag: ${stableTag}`); + + // Get existing tags + const tags = await github.paginate( + github.rest.repos.listTags, + { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + } ); - const existingTag = tags.find(t => t.name === stableTag); + const existingTag = tags.find(t => t.name === stableTag); - if (existingTag) { - core.info(`Tag "${stableTag}" already exists.`); - if (existingTag.commit.sha === commitSha) { - core.info(`It already points to the correct commit (${commitSha}). Nothing to do.`); - return; - } else { - core.info(`Tag "${stableTag}" points to a different commit (${existingTag.commit.sha}). Updating to ${commitSha}...`); - await github.rest.git.updateRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${stableTag}`, - sha: commitSha, - force: true - }); - core.info(`Tag "${stableTag}" updated to point to ${commitSha}.`); - return; + if (existingTag) { + core.info(`Tag "${stableTag}" already exists.`); + if (existingTag.commit.sha === commitSha) { + core.info(`It already points to the correct commit (${commitSha}). Nothing to do.`); + return; + } else { + core.info(`Tag "${stableTag}" points to a different commit (${existingTag.commit.sha}). Updating to ${commitSha}...`); + await github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${stableTag}`, + sha: commitSha, + force: true + }); + core.info(`Tag "${stableTag}" updated to point to ${commitSha}.`); + return; + } } - } - // Create tag if it doesn't exist - core.info(`Creating new tag "${stableTag}" pointing to commit ${commitSha}`); - await github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `refs/tags/${stableTag}`, - sha: commitSha - }); - core.info(`Tag "${stableTag}" created successfully.`); - \ No newline at end of file + // Create tag if it doesn't exist + core.info(`Creating new tag "${stableTag}" pointing to commit ${commitSha}`); + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${stableTag}`, + sha: commitSha + }); + core.info(`Tag "${stableTag}" created successfully.`); \ No newline at end of file