Skip to content
Open
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
175 changes: 175 additions & 0 deletions .github/workflows/tag-stable-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 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:
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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment not accurate


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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For debugging purposes I'd suggest to run snap info microceph right here


# Find the first name under "channels:" that includes "/stable:"
# and parse <code name>, <release version> and <commit ID>
- 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');
// 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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment not accurate

const channelRaw = lines.slice(headerIdx + 1)
.find(l => l.includes('/stable:'));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finds the first line that has "/stable" in it. Can we assume that this is the correct stable release though, can we rely on the ordering?

When I run snap info microceph I get

...
channels:
  squid/stable:     19.2.0+snapab139d4a1f 2025-06-26 (1393) 117MB -
  squid/candidate:  19.2.0+snap4910cfbd93 2025-07-15 (1407) 117MB -
  squid/beta:       ↑                                             
  squid/edge:       19.2.1+snap54a98b3cec 2025-08-07 (1464) 118MB -
  latest/stable:    18.2.4+snapc9f2b08f92 2024-09-19 (1139) 102MB -
  latest/candidate: ↑                                             
  latest/beta:      ↑                                             
  latest/edge:      19.2.1+snap54a98b3cec 2025-08-07 (1464) 118MB -
  reef/stable:      18.2.4+snapc9f2b08f92 2024-09-02 (1139) 102MB -
  reef/candidate:   18.2.4+snapa97ae91192 2024-11-12 (1234) 102MB -
  reef/beta:        ↑                                             
  reef/edge:        ↑                                             
  quincy/stable:    0+git.4a608fc         2024-01-10  (793)  96MB -
...

So here the first line with squid/stable is indeed correct. Can we rely on that ordering though?

The other issue I see here is that we're only handling the /stable releases in the main branch (ie. the latest major release). It would be great if we could also auto-tag other stable releases -- at the moment this would be squid, reef, quincy. Those would correspond to the branches main, reef and quincy respectively.

Maybe the workflow could loop through all the foo/stable channels, exclude the one named "latest/stable" and look to tag in the main and named branches.

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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is codename here referring to the release channel name ? (like squid, or quincy etc ?).


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}`);
Comment thread
sabaini marked this conversation as resolved.

# Verify commit exists in the repo and print commit message first line
- 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}" ...`);
const commits = await github.paginate(
github.rest.repos.listCommits,
{
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
Comment thread
sabaini marked this conversation as resolved.
}
);
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}"`);
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:
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}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So from what I've read above, if you use v${Version}+${codeName} as the tag, an example tag could look like:
-> v19.2.0+squid right ?

This has a slight problem that the tag will change everytime the underlying ceph point release changes. for example: in future the tag could become:
-> v19.2.2+squid.

Now since the tag is changing based on ceph-release, one of us would have to manually go and pull a new version on the RTD dashboard based on the new tag.

I propose we do not use the full semantic version but just the constant bit (something like v19+squid) as it will stay constant throughout the squid release life. And every time this workflow re-tags a commit, the documentation will update automatically.

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.`);
Loading