From 4de5b72ed563a854caee9585384ddd793fffed3a Mon Sep 17 00:00:00 2001 From: Jan Richter Date: Thu, 8 Oct 2026 10:50:32 +0200 Subject: [PATCH 1/2] fix: quay tag resolution for rhdh version --- docs/changelog.md | 8 +++- package.json | 2 +- src/deployment/rhdh/deployment.ts | 64 ++++++++++++++++--------------- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 1caf38f..de75aa4 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,13 @@ All notable changes to this project will be documented in this file. -## [2.2.2] - Current +## [2.2.3] - Current + +### Fixed + +- Errors when resolving helm chart versions due to the desired tags not fitting into the API call. Pagination has been added to search the whole quay repo instead of relying on an arbitrary limit. + +## [2.2.2] ### Fixed diff --git a/package.json b/package.json index 4ddb4ff..429d47a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@red-hat-developer-hub/e2e-test-utils", - "version": "2.2.2", + "version": "2.2.3", "description": "Test utilities for RHDH E2E tests", "license": "Apache-2.0", "repository": { diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index a7d1b55..bd82b15 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -445,25 +445,15 @@ export class RHDHDeployment { // Semantic versions (e.g., 1.2, 1.10) if (/^(\d+(\.\d+)?)$/.test(resolvedVersion)) { - const response = await fetch( - "https://quay.io/api/v1/repository/rhdh/chart/tag/?onlyActiveTags=true&limit=600", - ); - - if (!response.ok) - throw new Error( - `Failed to fetch chart versions: ${response.statusText}`, - ); - - const data = (await response.json()) as { tags: Array<{ name: string }> }; - const matching = data.tags + const matchingTags = (await this._getQuayTags("rhdh", "chart")) .map((t) => t.name) .filter((name) => name.startsWith(`${resolvedVersion}-`)) .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); - const latest = matching.at(-1); - if (!latest) - throw new Error(`No chart version found for ${resolvedVersion}`); - return latest; + const latest = matchingTags.at(-1); + if (latest) return latest; + + throw new Error(`No chart version found for ${resolvedVersion}`); } // CI build versions (e.g., 1.2.3-CI) @@ -478,30 +468,19 @@ export class RHDHDeployment { */ private async _resolveVersionFromNextTag(): Promise { // Fetch all active tags in a single API call - const response = await fetch( - "https://quay.io/api/v1/repository/rhdh/rhdh-hub-rhel9/tag/?onlyActiveTags=true&limit=75", - ); - - if (!response.ok) { - throw new Error(`Failed to fetch image tags: ${response.statusText}`); - } - - // Use Record to avoid snake_case linting issues with Quay API response - const data = (await response.json()) as { - tags: Array>; - }; + const tags = await this._getQuayTags("rhdh", "rhdh-hub-rhel10"); // Find the "next" tag and get its digest - const nextTag = data.tags.find((t) => t["name"] === "next"); + const nextTag = tags.find((t) => t["name"] === "next"); if (!nextTag) { - throw new Error('No "next" tag found in rhdh-hub-rhel9 repository'); + throw new Error('No "next" tag found in rhdh-hub-rhel10 repository'); } const digest = nextTag["manifest_digest"] as string; this._log(`"next" tag digest: ${digest}`); // Find semantic version tag (e.g., "1.10") with the same digest - const semanticVersionTag = data.tags.find( + const semanticVersionTag = tags.find( (t) => t["manifest_digest"] === digest && /^\d+\.\d+$/.test(t["name"] as string), @@ -516,6 +495,31 @@ export class RHDHDeployment { return semanticVersionTag["name"] as string; } + private async _getQuayTags(org: string, repo: string) { + const tags = []; + for (let page = 1; ; page++) { + const response = await fetch( + `https://quay.io/api/v1/repository/${org}/${repo}/tag/?onlyActiveTags=true&limit=600&page=${page}`, + ); + + if (!response.ok) + throw new Error( + `Failed to fetch tags for ${org}/${repo}: ${response.statusText}`, + ); + + /* eslint-disable @typescript-eslint/naming-convention */ + const data = (await response.json()) as { + tags: Array<{ name: string; manifest_digest: string }>; + has_additional: boolean; + }; + /* eslint-enable @typescript-eslint/naming-convention */ + tags.push(...data.tags); + + if (!data.has_additional) break; + } + return tags; + } + private _buildDeploymentConfig(input: DeploymentOptions): DeploymentConfig { // Default to "next" if RHDH_VERSION not set const version = input.version ?? process.env.RHDH_VERSION ?? "next"; From 2b7a5afaac277c247c22eb647660b4064d6692f7 Mon Sep 17 00:00:00 2001 From: Jan Richter Date: Thu, 8 Oct 2026 13:09:15 +0200 Subject: [PATCH 2/2] reduce the amount of API calls --- src/deployment/rhdh/deployment.ts | 49 ++++++++++++++----------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/src/deployment/rhdh/deployment.ts b/src/deployment/rhdh/deployment.ts index bd82b15..5018b07 100644 --- a/src/deployment/rhdh/deployment.ts +++ b/src/deployment/rhdh/deployment.ts @@ -445,7 +445,9 @@ export class RHDHDeployment { // Semantic versions (e.g., 1.2, 1.10) if (/^(\d+(\.\d+)?)$/.test(resolvedVersion)) { - const matchingTags = (await this._getQuayTags("rhdh", "chart")) + const matchingTags = ( + await this._getQuayTags("rhdh", "chart", `${resolvedVersion}-`) + ) .map((t) => t.name) .filter((name) => name.startsWith(`${resolvedVersion}-`)) .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); @@ -463,43 +465,36 @@ export class RHDHDeployment { } /** - * Resolve the semantic version from the "next" tag by looking up the - * downstream image (rhdh-hub-rhel9) and finding tags with the same digest. + * Resolve the semantic version from the "next" tag by looking + * at the version in package.json in rhdh core repo */ private async _resolveVersionFromNextTag(): Promise { - // Fetch all active tags in a single API call - const tags = await this._getQuayTags("rhdh", "rhdh-hub-rhel10"); + const RHDH_PACKAGE_URL = + "https://raw.githubusercontent.com/redhat-developer/rhdh/refs/heads/main/package.json"; + const response = await fetch(RHDH_PACKAGE_URL); - // Find the "next" tag and get its digest - const nextTag = tags.find((t) => t["name"] === "next"); - if (!nextTag) { - throw new Error('No "next" tag found in rhdh-hub-rhel10 repository'); + if (!response.ok) { + throw new Error(`Could not find semantic version for "next"`); } + const packageJson = JSON.parse(await response.text()); + const version = packageJson.version as string; - const digest = nextTag["manifest_digest"] as string; - this._log(`"next" tag digest: ${digest}`); - - // Find semantic version tag (e.g., "1.10") with the same digest - const semanticVersionTag = tags.find( - (t) => - t["manifest_digest"] === digest && - /^\d+\.\d+$/.test(t["name"] as string), - ); - - if (!semanticVersionTag) { - throw new Error( - `Could not find semantic version tag for "next" (digest: ${digest})`, - ); + // only use major and minor, since the image tags don't use micro + const shortVersion = version.match(/^(\d+\.\d+)/); + if (!shortVersion) { + throw new Error(`Invalid semantic version ${version}`); } - - return semanticVersionTag["name"] as string; + return shortVersion[1]; } - private async _getQuayTags(org: string, repo: string) { + private async _getQuayTags(org: string, repo: string, tagFilter?: string) { const tags = []; for (let page = 1; ; page++) { + const tagNameFilter = tagFilter + ? `filter_tag_name=like:${tagFilter}&` + : ""; const response = await fetch( - `https://quay.io/api/v1/repository/${org}/${repo}/tag/?onlyActiveTags=true&limit=600&page=${page}`, + `https://quay.io/api/v1/repository/${org}/${repo}/tag/?${tagNameFilter}onlyActiveTags=true&limit=600&page=${page}`, ); if (!response.ok)