From f8e1623179b8526ba002c29cd895922590ffbdae Mon Sep 17 00:00:00 2001 From: Claude <242468646+Claude@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:32:01 -0500 Subject: [PATCH] Add orbit/fleetd version detection and support both singular/plural version fields in bug tagging workflow (#41268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the auto-tag-unreleased-bugs workflow to improve version detection and handling. ## Changes Made - **Orbit/Fleetd Version Detection**: Added support for detecting Orbit and Fleetd versions in addition to Fleet server versions. The workflow now checks `**Orbit version**:` and `**Fleetd version**:` fields (case insensitive) and validates them against orbit-v* tags. - **Optimized API Calls**: The workflow now only fetches the data it needs: - Fetches releases only when checking Fleet server versions - Fetches tags only when checking Orbit/Fleetd versions - This reduces unnecessary GitHub API calls and improves performance - **Singular/Plural Field Support**: Updated regex patterns to match both "version" and "versions" in issue templates (e.g., `**Fleet version**:` and `**Fleet versions**:`). This handles variations in issue template formatting where either singular or plural forms may be used. - **Pagination Support**: Both `listReleases` and `listTags` API calls now use `github.paginate()` to fetch all results instead of just the first 100. This ensures older Orbit/Fleetd versions or Fleet versions won't be misclassified as unreleased when they exist beyond the first page of results. - **Fixed 4.x Handling**: Corrected the logic for handling "4.x" version strings (which represent all 4.x versions). The check now occurs before the empty versions check, preventing issues reporting only "4.x" from being incorrectly tagged as unreleased. ## Testing - ✅ Verified regex patterns match both singular and plural forms for Fleet, Orbit, and Fleetd version fields - ✅ Confirmed the workflow correctly parses versions from various issue formats - ✅ Tested that API optimization only fetches releases or tags based on which version types are present - ✅ Verified pagination logic fetches all releases and tags, not just first 100 - ✅ Tested 4.x handling logic correctly treats it as released The changes maintain backward compatibility with existing issue formats while adding support for Orbit/Fleetd version detection, handling template variations, and ensuring comprehensive version checking through pagination. --------- Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: iansltx <472804+iansltx@users.noreply.github.com> Co-authored-by: lukeheath <2495927+lukeheath@users.noreply.github.com> --- .../workflows/auto-tag-unreleased-bugs.yml | 157 +++++++++++++----- 1 file changed, 113 insertions(+), 44 deletions(-) diff --git a/.github/workflows/auto-tag-unreleased-bugs.yml b/.github/workflows/auto-tag-unreleased-bugs.yml index 6831934450..aeb39d5803 100644 --- a/.github/workflows/auto-tag-unreleased-bugs.yml +++ b/.github/workflows/auto-tag-unreleased-bugs.yml @@ -54,56 +54,115 @@ jobs: // Parse Fleet version from issue body const body = issue.body || ''; - const versionMatch = body.match(/\*\*Fleet version\*\*:\s*(.+)/); + const versionMatch = body.match(/\*\*Fleet versions?\*\*:\s*(.+)/i); + + // Also check for Orbit/Fleetd version (case insensitive) + const orbitMatch = body.match(/\*\*(?:Orbit|Fleetd) versions?\*\*:\s*(.+)/i); if (!versionMatch || !versionMatch[1]) { console.log('No Fleet version found in issue body'); - await tagAsUnreleased(); - return; + + // If no Fleet version but has Orbit/Fleetd version, check that instead + if (orbitMatch && orbitMatch[1]) { + console.log('Found Orbit/Fleetd version, will check that instead'); + } else { + await tagAsUnreleased(); + return; + } } // Extract version, removing any HTML comments - let reportedVersion = versionMatch[1].trim(); + let reportedVersion = versionMatch ? versionMatch[1].trim() : ''; + let orbitVersion = orbitMatch ? orbitMatch[1].trim() : ''; + // Remove HTML comment if present (e.g., "4.62.0 ") reportedVersion = reportedVersion.replace(/\s*\s*/g, '').trim(); + orbitVersion = orbitVersion.replace(/\s*\s*/g, '').trim(); console.log(`Found reported version: ${reportedVersion}`); + if (orbitVersion) { + console.log(`Found Orbit/Fleetd version: ${orbitVersion}`); + } // Treat as unreleased if reported version is RC/main/unknown/todo - if (!reportedVersion || - reportedVersion.trim() === '' || - reportedVersion.toLowerCase().includes('todo') || - reportedVersion.toLowerCase().includes('unknown') || - reportedVersion.toLowerCase().includes('main') || - reportedVersion.toLowerCase().includes('rc')) { + // Check both Fleet version and Orbit/Fleetd version if present + const versionsToCheck = []; + + if (reportedVersion && + reportedVersion.trim() !== '' && + !reportedVersion.toLowerCase().includes('todo') && + !reportedVersion.toLowerCase().includes('unknown') && + !reportedVersion.toLowerCase().includes('main') && + !reportedVersion.toLowerCase().includes('rc') && + reportedVersion !== '4.x') { + versionsToCheck.push({ version: reportedVersion, type: 'fleet' }); + } + + if (orbitVersion && + orbitVersion.trim() !== '' && + !orbitVersion.toLowerCase().includes('todo') && + !orbitVersion.toLowerCase().includes('unknown') && + !orbitVersion.toLowerCase().includes('main') && + !orbitVersion.toLowerCase().includes('rc')) { + versionsToCheck.push({ version: orbitVersion, type: 'orbit' }); + } + + // Special case: "4.x" means all 4.x versions, which is released + if (reportedVersion === '4.x') { + return; + } + + // If no valid versions to check, tag as unreleased + if (versionsToCheck.length === 0) { await tagAsUnreleased(); return; } - - if (reportedVersion === '4.x') { - return; // this is "all 4.x versions" so it's released + + // Determine what we need to fetch based on versions present + const needsFleetReleases = versionsToCheck.some(v => v.type === 'fleet'); + const needsOrbitTags = versionsToCheck.some(v => v.type === 'orbit'); + + // Fetch Fleet releases only if we have a Fleet version to check + let releasedFleetVersions = []; + if (needsFleetReleases) { + const allReleases = await github.paginate(github.rest.repos.listReleases, { + owner: "fleetdm", + repo: "fleet", + per_page: 100 + }); + + // Extract version numbers from Fleet releases + // Fleet releases are tagged as "fleet-v4.X.X" or similar + releasedFleetVersions = allReleases + .map(release => { + // Try to extract from name + const nameMatch = release.name?.match(/(\d+\.\d+\.\d+)/); + if (nameMatch) return nameMatch[1]; + + return null; + }) + .filter(v => v !== null); } - // Fetch most recent 100 releases from the repo; that's realistically enough to match - // any newly created bug - const { data: allReleases } = await github.rest.repos.listReleases({ - owner: "fleetdm", - repo: "fleet", - per_page: 100, - page: 1 - }); + // Fetch tags only if we have an orbit/fleetd version to check + let releasedOrbitVersions = []; + if (needsOrbitTags) { + const allTags = await github.paginate(github.rest.repos.listTags, { + owner: "fleetdm", + repo: "fleet", + per_page: 100 + }); - // Extract version numbers from releases - // Fleet releases are tagged as "fleet-v4.X.X" or similar - const releasedVersions = allReleases - .map(release => { - // Try to extract from name - const nameMatch = release.name?.match(/(\d+\.\d+\.\d+)/); - if (nameMatch) return nameMatch[1]; - - return null; - }) - .filter(v => v !== null); + // Extract orbit/fleetd versions from tags + // Orbit tags are like "orbit-v1.X.X" + releasedOrbitVersions = allTags + .filter(tag => tag.name.match(/^orbit-v\d+\.\d+\.\d+$/)) + .map(tag => { + const match = tag.name.match(/^orbit-v(\d+\.\d+\.\d+)$/); + return match ? match[1] : null; + }) + .filter(v => v !== null); + } // Normalize version for comparison // Remove common prefixes/suffixes and extract core version number @@ -111,26 +170,36 @@ jobs: // First try to extract x.y.z pattern let match = version.match(/v?(\d+\.\d+\.\d+)/); if (match) return match[1]; - + // If no patch version, try x.y pattern and add .0 match = version.match(/v?(\d+\.\d+)(?!\.\d)/); if (match) return match[1] + '.0'; - + return version; }; - // Split version string on "&" to handle multiple versions (e.g., "4.60 & 4.61") - const reportedVersions = reportedVersion.split('&').map(v => v.trim()); - - // Check if ANY of the reported versions matches any released version + // Check if ANY of the reported versions is released let isReleased = false; - for (const version of reportedVersions) { - const normalizedVersion = normalizeVersion(version); - if (releasedVersions.some(releasedVer => releasedVer === normalizedVersion)) { - console.log(`Found released version: ${normalizedVersion}`); - isReleased = true; - break; + for (const versionInfo of versionsToCheck) { + const { version, type } = versionInfo; + + // Split version string on "&" to handle multiple versions (e.g., "4.60 & 4.61") + const versions = version.split('&').map(v => v.trim()); + + for (const v of versions) { + const normalizedVersion = normalizeVersion(v); + + // Check against the appropriate list based on type + const releasedVersions = type === 'orbit' ? releasedOrbitVersions : releasedFleetVersions; + + if (releasedVersions.some(releasedVer => releasedVer === normalizedVersion)) { + console.log(`Found released ${type} version: ${normalizedVersion}`); + isReleased = true; + break; + } } + + if (isReleased) break; } if (isReleased) {