diff --git a/.github/workflows/tag-stable-commit.yml b/.github/workflows/tag-stable-commit.yml new file mode 100644 index 00000000..5c69b886 --- /dev/null +++ b/.github/workflows/tag-stable-commit.yml @@ -0,0 +1,163 @@ +# 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 +jobs: + tag-stable-commit: + # 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 + # 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'); + // 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*([a-z]+)\/stable:\s+([0-9.]+)\+snap([a-f0-9]+)\s/ + ); + if (!m) { + core.setFailed('Failed to parse channel line'); + return; + } + 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(`codeName=${codeName}`); + core.info(`version=${version}`); + 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: + 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`); + } + # 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 + } + ); + + 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; + } + } + + // 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.`); +