From 5c5a30928d78d82a9c429cffe7fb58a204520579 Mon Sep 17 00:00:00 2001 From: Akos Eros Date: Fri, 2 Oct 2026 09:19:21 +0200 Subject: [PATCH 1/3] feat: Support externaly stored CI badges Add badge for rhoso pattern --- content/patterns/rhoso-gitops/_index.adoc | 5 ++ layouts/ci/single.html | 14 ++++- layouts/partials/ci.html | 9 ++- static/js/dashboard.v2.js | 74 +++++++++++++++++------ 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/content/patterns/rhoso-gitops/_index.adoc b/content/patterns/rhoso-gitops/_index.adoc index 67aaaa74a8..07f2821580 100644 --- a/content/patterns/rhoso-gitops/_index.adoc +++ b/content/patterns/rhoso-gitops/_index.adoc @@ -12,6 +12,11 @@ industries: focus_areas: - DevSecOps aliases: /rhoso-gitops/ +ci: rhoso-gitops +ci_sources: + - base: https://raw.githubusercontent.com/validatedpatterns-sandbox/rhoso-gitops/reporting/ + files: + - rhoso-gitops-baremetal-4.18-stable-badge.json links: github: https://github.com/validatedpatterns-sandbox/rhoso-gitops install: getting-started diff --git a/layouts/ci/single.html b/layouts/ci/single.html index d10c7244f0..bed0baf57d 100644 --- a/layouts/ci/single.html +++ b/layouts/ci/single.html @@ -20,8 +20,18 @@

{{- .Title -}}

{{ partial "spinner.html" . }} diff --git a/layouts/partials/ci.html b/layouts/partials/ci.html index aaad0b04b0..837d5824a8 100644 --- a/layouts/partials/ci.html +++ b/layouts/partials/ci.html @@ -1,10 +1,15 @@ -
{{ partial "spinner.html" . }}
diff --git a/static/js/dashboard.v2.js b/static/js/dashboard.v2.js index 0b33a9cb4b..3861853c45 100644 --- a/static/js/dashboard.v2.js +++ b/static/js/dashboard.v2.js @@ -4,17 +4,18 @@ class Badge { - constructor(base, key, date) { + constructor(base, key, date, useJsonMetadata = true) { this.base = base this.key = key this._json = null + this.useJsonMetadata = useJsonMetadata this.operator = 'N/A' this.date = date.substr(0, 10) this.pattern = '' this.platform = '' this.version = '' - if (isNewCiBadgeBase(base)) { + if (this.useJsonMetadata) { return } @@ -36,7 +37,7 @@ class Badge { applyJson(json) { this._json = json - if (!isNewCiBadgeBase(this.base)) { + if (!this.useJsonMetadata) { return this } @@ -85,6 +86,14 @@ class Badge { // Still present in badge buckets but no longer in the CI matrix (omit from dashboard) var CI_DASHBOARD_EXCLUDED_OCP_VERSIONS = ['4.19'] +// Only the old bucket uses filename parsing, new CI badge bucket / external raw badges — hydrate metadata from JSON, not filename. +function usesJsonMetadata(base) { + if (base == null || base === '' || String(base).indexOf('storage.googleapis.com/vp-results') !== -1 ) { + return false + } + return true +} + function excludeRetiredOcpVersionsFromDashboard(badges) { if (!badges || badges.length === 0) { return badges || [] @@ -157,14 +166,6 @@ var CI_PATTERN_DOC_SLUG = { vsk: 'virtualization-starter-kit' } -// New CI badge bucket (S3 keys under ci-badges/) — distinct from legacy vp-results filenames. - -var NEW_CI_BADGE_KEY_PREFIX = "https://vp-qe-ci-badges" - -function isNewCiBadgeBase(base) { - return base != null && base.startsWith(NEW_CI_BADGE_KEY_PREFIX) -} - function normalizeInfraProvider(provider) { if (provider == null || provider === '') { return null @@ -318,6 +319,9 @@ function stringForKey(key) { if (key == 'nutanix') { return 'Nutanix' } + if (key == 'rhoso-gitops') { + return 'RHOSO GitOps' + } return key } @@ -632,7 +636,8 @@ function platformDisplayName(platform) { 'azr': 'Azure', 'gcp': 'Google Cloud', 'nutanix': 'Nutanix', - 'intel': 'On-prem (Intel)' + 'intel': 'On-prem (Intel)', + 'baremetal': 'Baremetal', } return names[platform] || stringForKey(platform) } @@ -1063,7 +1068,7 @@ function renderPatternCard(pattern, platformBadges, comboBadges, tracker) { var platformColor = tracker.platforms[b.platform] || 'unavailable' html += '' html += '' - html += '' + stringForKey(b.platform) + '' + html += '' + platformDisplayName(b.platform) + '' html += '' }) html += '' @@ -1283,12 +1288,8 @@ function getBadges(xmlText, bucket_url, badge_set) { for (let i = 0; i < l; i++) { let key = entries[i].childNodes[0].nodeValue - if (badge_set == "GA" && key.endsWith("stable-badge.json")) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else if (badge_set == "early" && (key.endsWith("prerelease-badge.json") || key.endsWith("nightly-badge.json") || key.endsWith("operator-badge.json"))) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); - } else if (badge_set == "all" && key.endsWith("-badge.json")) { - badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]))); + if (badgeKeyMatchesSet(key, badge_set)) { + badges.push(new Badge(bucket_url, key, getBadgeDate(entries[i]), usesJsonMetadata(bucket_url))) } else { console.log("Skipping: " + key); } @@ -1379,6 +1380,36 @@ function getBucketOptions(input) { return options } +function badgeKeyMatchesSet(key, badge_set) { + if (badge_set === 'GA' && key.endsWith('stable-badge.json')) { + return true + } + if (badge_set === 'early' && (key.endsWith('prerelease-badge.json') || key.endsWith('nightly-badge.json') || key.endsWith('operator-badge.json'))) { + return true + } + if (badge_set === 'all' && key.endsWith('-badge.json')) { + return true + } + return false +} + +function createBadgesFromExternalSources(sources, badge_set) { + const badges = [] + const sourceList = sources || [] + sourceList.forEach(function (source) { + const base = String(source.base || '').replace(/\/$/, '') + const files = source.files || [] + files.forEach(function (file) { + if (!badgeKeyMatchesSet(file, badge_set)) { + return + } + // Date filled from JSON during enrich + badges.push(new Badge(base, file, '1970-01-01T00:00:00Z')) + }) + }) + return badges +} + function fetchBucketBadges(bucket, inputs) { return new Promise(function (resolve) { var req = new XMLHttpRequest() @@ -1450,6 +1481,11 @@ function obtainBadges(inputs) { } allBadges.push.apply(allBadges, badges || []) }) + var ciSources = createBadgesFromExternalSources(inputs.ci_sources, options.get('sets')) + if (ciSources.length > 0) { + console.log('Got ' + ciSources.length + ' external badges') + allBadges.push.apply(allBadges, ciSources) + } console.log('All badges:', allBadges) return enrichBadgesFromJson(allBadges) From c6f929515bb96894f0e499a7b9c5e75d3199639c Mon Sep 17 00:00:00 2001 From: Akos Eros Date: Tue, 27 Oct 2026 16:17:08 +0100 Subject: [PATCH 2/3] feat: Add check to not show any pattern-repo url as a link For now we whitelist github with https, and vp orgs for pattern-repo urls. --- static/js/dashboard.v2.js | 40 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/static/js/dashboard.v2.js b/static/js/dashboard.v2.js index 3861853c45..d7e82b96db 100644 --- a/static/js/dashboard.v2.js +++ b/static/js/dashboard.v2.js @@ -86,6 +86,44 @@ class Badge { // Still present in badge buckets but no longer in the CI matrix (omit from dashboard) var CI_DASHBOARD_EXCLUDED_OCP_VERSIONS = ['4.19'] +// patternRepo comes from external badge JSON; only link to known GitHub orgs. +var TRUSTED_PATTERN_REPO_PATH_PREFIXES = [ + '/validatedpatterns/', + '/validatedpatterns-sandbox/' +] + +// Returns true when repoUrl is an https://github.com URL under a trusted org. +function isTrustedPatternRepo(repoUrl) { + if (repoUrl == null || repoUrl === '') { + return false + } + try { + var url = new URL(String(repoUrl).trim()) + if (url.protocol !== 'https:') { + return false + } + if (url.username !== '' || url.password !== '') { + return false + } + if (url.hostname.toLowerCase() !== 'github.com') { + return false + } + var path = url.pathname.toLowerCase() + if (path.length > 1 && path.charAt(path.length - 1) !== '/') { + path += '/' + } + for (var i = 0; i < TRUSTED_PATTERN_REPO_PATH_PREFIXES.length; i++) { + var prefix = TRUSTED_PATTERN_REPO_PATH_PREFIXES[i] + if (path.indexOf(prefix) === 0 && path.length > prefix.length) { + return true + } + } + return false + } catch (e) { + return false + } +} + // Only the old bucket uses filename parsing, new CI badge bucket / external raw badges — hydrate metadata from JSON, not filename. function usesJsonMetadata(base) { if (base == null || base === '' || String(base).indexOf('storage.googleapis.com/vp-results') !== -1 ) { @@ -509,7 +547,7 @@ function renderBadgeHtml(badge, field) { } let envLink = encodeURI(badge_url) - let branchLink = json_obj.patternRepo != null ? encodeURI(json_obj.patternRepo) : null + let branchLink = isTrustedPatternRepo(json_obj.patternRepo) ? encodeURI(json_obj.patternRepo) : null let badgeText = '' badgeText += '' + envLabel + '' if (branchLink != null) { From 2ff7df09c574f6390c29a74ca88c4b29f7e93c3e Mon Sep 17 00:00:00 2001 From: Akos Eros Date: Tue, 27 Oct 2026 16:22:28 +0100 Subject: [PATCH 3/3] fix: Add pr preview site to list so it will generate real badges --- static/js/dashboard.v2.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/dashboard.v2.js b/static/js/dashboard.v2.js index d7e82b96db..11b2b5c37f 100644 --- a/static/js/dashboard.v2.js +++ b/static/js/dashboard.v2.js @@ -886,6 +886,7 @@ function renderDashboardTableRow(badge) { function isProductionSite() { var host = window.location.hostname return host === 'validatedpatterns.io' || + host === 'netlify.app' || host === 'localhost' || host === '127.0.0.1' }