From 871667475dbb2a21e7b6332ba657c106487378f2 Mon Sep 17 00:00:00 2001 From: Joseph Date: Tue, 17 Mar 2026 14:17:42 -0400 Subject: [PATCH 1/5] Redesign download server with templated UI and Red Hat branding Replace inline HTML with Go html/template and separated CSS using embed.FS. Group downloads by OS (Linux/macOS/Windows) in table layout with architecture badges, SHA256 checksums (click to copy), and per-command copy buttons. Use official Red Hat brand colors. Also optimize Containerfile to clean build cache within RUN steps and include .sha256 checksum files in the image. Signed-off-by: Joseph --- Containerfile.download | 12 +- cmd/downloads/server.go | 97 +++++++++-- cmd/downloads/static/style.css | 254 +++++++++++++++++++++++++++++ cmd/downloads/templates/index.html | 149 +++++++++++++++++ 4 files changed, 487 insertions(+), 25 deletions(-) create mode 100644 cmd/downloads/static/style.css create mode 100644 cmd/downloads/templates/index.html diff --git a/Containerfile.download b/Containerfile.download index d350b84..613636b 100644 --- a/Containerfile.download +++ b/Containerfile.download @@ -15,15 +15,13 @@ COPY . . RUN make release-archives && \ mkdir -p /archives && \ - mv *.tar.gz /archives/ + mv *.tar.gz *.sha256 /archives/ && \ + rm -rf /root/.cache/go-build /tmp/* release-build/ # Build the download server for the TARGET platform (the arch this container will run on) -RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o download-server ./cmd/downloads/server.go - -# Clean up to reduce layer size -RUN go clean -cache -modcache -testcache && \ - rm -rf /root/.cache/go-build && \ - rm -rf /go/pkg +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o download-server ./cmd/downloads/ && \ + go clean -cache -modcache -testcache && \ + rm -rf /root/.cache/go-build /go/pkg FROM registry.access.redhat.com/ubi9/ubi-minimal:latest diff --git a/cmd/downloads/server.go b/cmd/downloads/server.go index 47ac9be..b766f95 100644 --- a/cmd/downloads/server.go +++ b/cmd/downloads/server.go @@ -1,7 +1,10 @@ package main import ( + "embed" "fmt" + "html/template" + "io/fs" "log" "net/http" "os" @@ -9,19 +12,45 @@ import ( "strings" ) -const ( - archiveDir = "/archives" - port = "8080" +//go:embed templates/*.html +var templateFS embed.FS + +//go:embed static/* +var staticFS embed.FS + +var ( + archiveDir = getEnv("ARCHIVE_DIR", "/archives") + port = getEnv("PORT", "8080") + pageTemplate = template.Must(template.ParseFS(templateFS, "templates/index.html")) ) +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} + +type archiveFile struct { + Name string + Size float64 + OS string + Arch string + Checksum string +} + func main() { - // Verify archives exist files, err := filepath.Glob(filepath.Join(archiveDir, "*.tar.gz")) if err != nil || len(files) == 0 { log.Fatal("No archives found in ", archiveDir) } log.Printf("Found %d archives", len(files)) + staticContent, err := fs.Sub(staticFS, "static") + if err != nil { + log.Fatal("Failed to load static files: ", err) + } + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticContent)))) http.HandleFunc("/", listBinaries) http.HandleFunc("/download/", downloadBinary) @@ -40,32 +69,65 @@ func listBinaries(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "text/html") - fmt.Fprintf(w, "kubectl-oadp Downloads") - fmt.Fprintf(w, "

kubectl-oadp Binary Downloads

") - fmt.Fprintf(w, "

Download pre-built binaries for your platform:

") - fmt.Fprintf(w, "

Installation:

") - fmt.Fprintf(w, "
tar -xzf kubectl-oadp_*.tar.gz\n")
-	fmt.Fprintf(w, "chmod +x kubectl-oadp\n")
-	fmt.Fprintf(w, "sudo mv kubectl-oadp /usr/local/bin/
") + data := struct { + LinuxFiles []archiveFile + DarwinFiles []archiveFile + WindowsFiles []archiveFile + }{linuxFiles, darwinFiles, windowsFiles} + + w.Header().Set("Content-Type", "text/html") + if err := pageTemplate.Execute(w, data); err != nil { + log.Printf("Template error: %v", err) + } +} + +func readChecksum(path string) string { + data, err := os.ReadFile(path) + if err != nil { + return "" + } + fields := strings.Fields(string(data)) + if len(fields) > 0 { + return fields[0] + } + return "" +} + +func parsePlatform(filename string) (string, string) { + name := strings.TrimSuffix(filename, ".tar.gz") + parts := strings.Split(name, "_") + if len(parts) >= 3 { + return parts[len(parts)-2], parts[len(parts)-1] + } + return "unknown", "unknown" } func downloadBinary(w http.ResponseWriter, r *http.Request) { filename := filepath.Base(r.URL.Path[len("/download/"):]) - // Security: ensure filename is just the archive name if filepath.Dir(filename) != "." || !strings.HasSuffix(filename, ".tar.gz") { http.Error(w, "Invalid filename", http.StatusBadRequest) return @@ -73,7 +135,6 @@ func downloadBinary(w http.ResponseWriter, r *http.Request) { filePath := filepath.Join(archiveDir, filename) - // Verify file exists if _, err := os.Stat(filePath); os.IsNotExist(err) { http.Error(w, "Archive not found", http.StatusNotFound) return diff --git a/cmd/downloads/static/style.css b/cmd/downloads/static/style.css new file mode 100644 index 0000000..d2c2435 --- /dev/null +++ b/cmd/downloads/static/style.css @@ -0,0 +1,254 @@ +/* Red Hat Brand Colors (https://ux.redhat.com/foundations/color/) */ +:root { + --rh-red: #ee0000; + --rh-red-dark: #a60000; + --rh-black: #151515; + --rh-white: #ffffff; + --rh-gray-10: #f2f2f2; + --rh-gray-20: #e0e0e0; + --rh-gray-30: #c7c7c7; + --rh-gray-50: #707070; + --rh-gray-60: #4d4d4d; + --rh-gray-90: #1f1f1f; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: "Red Hat Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background: var(--rh-gray-10); + color: var(--rh-black); + min-height: 100vh; +} + +.header { + background: var(--rh-red); + padding: 2.5rem 0; + color: var(--rh-white); +} + +.container { + max-width: 860px; + margin: 0 auto; + padding: 0 1.5rem; +} + +.logo { + font-size: 1.6rem; + font-weight: 700; +} + +.logo-link { + color: var(--rh-white); + text-decoration: none; +} + +.logo-link:hover { text-decoration: underline; } + +.subtitle { + color: rgba(255,255,255,0.8); + margin-top: 0.4rem; + font-size: 0.95rem; +} + +.binary-note { + margin-top: 0.5rem; + font-size: 0.8rem; + color: rgba(255,255,255,0.65); +} + +.binary-note code { + background: rgba(255,255,255,0.15); + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; + font-size: 0.8rem; +} + +.content { + padding: 2rem 0; +} + +.section { + background: var(--rh-white); + border: 1px solid var(--rh-gray-20); + border-radius: 0.75rem; + overflow: hidden; + margin-bottom: 1.5rem; +} + +.section-header { + padding: 0.85rem 1.25rem; + background: var(--rh-gray-10); + border-bottom: 1px solid var(--rh-gray-20); + font-weight: 600; + font-size: 0.9rem; + color: var(--rh-black); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.section-header .os-icon { + font-size: 1.1rem; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th { + text-align: left; + padding: 0.6rem 1.25rem; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--rh-gray-50); + border-bottom: 1px solid var(--rh-gray-20); + background: var(--rh-gray-10); +} + +td { + padding: 0.85rem 1.25rem; + border-bottom: 1px solid var(--rh-gray-20); + font-size: 0.9rem; +} + +tr:last-child td { border-bottom: none; } +tr:hover td { background: var(--rh-gray-10); } + +.arch-badge { + display: inline-block; + background: var(--rh-gray-10); + color: var(--rh-gray-60); + padding: 0.2rem 0.6rem; + border-radius: 0.35rem; + font-size: 0.8rem; + font-weight: 500; + font-family: "Red Hat Mono", "SF Mono", Consolas, monospace; + border: 1px solid var(--rh-gray-20); +} + +.size { + color: var(--rh-gray-50); + font-size: 0.85rem; +} + +.checksum { + font-family: "Red Hat Mono", "SF Mono", Consolas, monospace; + font-size: 0.7rem; + color: var(--rh-gray-50); + word-break: break-all; + max-width: 220px; + cursor: pointer; + position: relative; +} + +.checksum:hover { color: var(--rh-gray-60); } + +.checksum .copy-hint { + display: none; + position: absolute; + top: -1.6rem; + left: 0; + background: var(--rh-black); + color: var(--rh-white); + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; + font-size: 0.65rem; + white-space: nowrap; +} + +.checksum:hover .copy-hint { display: block; } + +.download-btn { + display: inline-flex; + align-items: center; + gap: 0.35rem; + background: var(--rh-red); + color: var(--rh-white); + padding: 0.45rem 0.9rem; + border-radius: 0.4rem; + text-decoration: none; + font-size: 0.82rem; + font-weight: 500; + transition: background 0.15s; +} + +.download-btn:hover { background: var(--rh-red-dark); } + +.install-section { + margin-top: 0.5rem; +} + +.install-section h3 { + font-size: 1rem; + font-weight: 600; + color: var(--rh-black); + margin-bottom: 0.75rem; +} + +.code-block { + background: var(--rh-gray-90); + border-radius: 0.5rem; + padding: 0.75rem 0; + overflow-x: auto; + font-family: "Red Hat Mono", "SF Mono", Consolas, monospace; + font-size: 0.85rem; + line-height: 1.4; +} + +.code-line { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.3rem 1.25rem; +} + +.code-line:hover { background: rgba(255,255,255,0.04); } + +.code-line .comment { + color: var(--rh-gray-50); + padding-top: 0.4rem; +} + +.code-line .cmd { + color: var(--rh-gray-20); + font-family: inherit; +} + +.copy-btn { + background: transparent; + border: 1px solid var(--rh-gray-60); + color: var(--rh-gray-50); + padding: 0.2rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.7rem; + font-family: "Red Hat Text", -apple-system, sans-serif; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + flex-shrink: 0; + margin-left: 1rem; +} + +.copy-btn:hover { + border-color: var(--rh-gray-30); + color: var(--rh-gray-20); + background: rgba(255,255,255,0.08); +} + +.footer { + text-align: center; + padding: 2rem 0; + color: var(--rh-gray-50); + font-size: 0.8rem; + border-top: 1px solid var(--rh-gray-20); +} + +.footer a { + color: var(--rh-red); + text-decoration: none; +} + +.footer a:hover { text-decoration: underline; } diff --git a/cmd/downloads/templates/index.html b/cmd/downloads/templates/index.html new file mode 100644 index 0000000..1b1bc23 --- /dev/null +++ b/cmd/downloads/templates/index.html @@ -0,0 +1,149 @@ + + + + + + OADP CLI Downloads + + + +
+
+ +

OpenShift API for Data Protection — Command Line Interface

+

CLI for managing backups and restores with the OADP Operator — works as a kubectl / oc plugin

+
+
+ +
+ {{if .LinuxFiles}} +
+
🐧 Linux
+ + + + + + + + + + + + {{range .LinuxFiles}} + + + + + + + + {{end}} + +
BinaryArchitectureSizeSHA256
{{.Name}}{{.Arch}}{{printf "%.2f" .Size}} MB{{if .Checksum}}Click to copy{{slice .Checksum 0 16}}...{{else}}—{{end}}Download
+
+ {{end}} + + {{if .DarwinFiles}} +
+
macOS
+ + + + + + + + + + + + {{range .DarwinFiles}} + + + + + + + + {{end}} + +
BinaryArchitectureSizeSHA256
{{.Name}}{{.Arch}}{{printf "%.2f" .Size}} MB{{if .Checksum}}Click to copy{{slice .Checksum 0 16}}...{{else}}—{{end}}Download
+
+ {{end}} + + {{if .WindowsFiles}} +
+
💻 Windows
+ + + + + + + + + + + + {{range .WindowsFiles}} + + + + + + + + {{end}} + +
BinaryArchitectureSizeSHA256
{{.Name}}{{.Arch}}{{printf "%.2f" .Size}} MB{{if .Checksum}}Click to copy{{slice .Checksum 0 16}}...{{else}}—{{end}}Download
+
+ {{end}} + +
+

Installation

+
+
+ # Extract the archive +
+
+ tar -xzf kubectl-oadp_*.tar.gz + +
+
+ # Make it executable +
+
+ chmod +x kubectl-oadp + +
+
+ # Move to your PATH +
+
+ sudo mv kubectl-oadp /usr/local/bin/ + +
+
+ # Verify it works +
+
+ oc oadp --help + +
+
+
+ +
+ + + + From 05310b9d74a1b2e7e91f896759ff63116638d252 Mon Sep 17 00:00:00 2001 From: Joseph Date: Tue, 17 Mar 2026 14:17:50 -0400 Subject: [PATCH 2/5] Add Konflux hermetic Containerfile for download server Uses the OSBS golang builder with FIPS-compliant build flags (CGO_ENABLED=1, strictfipsruntime). Dependencies are prefetched by the Konflux pipeline via Hermeto, so no go mod download is needed. Includes Red Hat metadata labels and license copy. Signed-off-by: Joseph --- konflux.Containerfile.download | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 konflux.Containerfile.download diff --git a/konflux.Containerfile.download b/konflux.Containerfile.download new file mode 100644 index 0000000..9d0ea8b --- /dev/null +++ b/konflux.Containerfile.download @@ -0,0 +1,42 @@ +# Konflux hermetic build for the kubectl-oadp download server +# Dependencies are prefetched by the Konflux pipeline (Hermeto) and injected +# into the build context before this Containerfile runs. + +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.24 AS builder + +COPY . /workspace +WORKDIR /workspace + +ENV GOEXPERIMENT=strictfipsruntime + +# Build release archives for all platforms +RUN make release-archives && \ + mkdir -p /archives && \ + mv *.tar.gz *.sha256 /archives/ && \ + rm -rf /root/.cache/go-build /tmp/* release-build/ + +# Build the download server (FIPS-compliant) +RUN CGO_ENABLED=1 GOOS=linux go build -mod=mod -a -tags strictfipsruntime \ + -o /workspace/bin/download-server ./cmd/downloads/ && \ + go clean -cache -modcache -testcache && \ + rm -rf /root/.cache/go-build /go/pkg + +FROM registry.redhat.io/ubi9/ubi:latest + +RUN dnf -y install openssl && dnf -y reinstall tzdata && dnf clean all + +COPY --from=builder /archives /archives +COPY --from=builder /workspace/bin/download-server /usr/local/bin/download-server +COPY LICENSE /licenses/ + +EXPOSE 8080 + +USER 65532:65532 + +ENTRYPOINT ["/usr/local/bin/download-server"] + +LABEL description="OADP CLI - Binary Download Server" +LABEL io.k8s.description="OADP CLI - Binary Download Server" +LABEL io.k8s.display-name="OADP CLI Downloads" +LABEL io.openshift.tags="oadp,migration,backup" +LABEL summary="Serves pre-built kubectl-oadp binaries for all platforms" From 3981ec4a97dddf222a4bb9189ca5e263b351fd08 Mon Sep 17 00:00:00 2001 From: Joseph Date: Wed, 18 Mar 2026 09:40:29 -0400 Subject: [PATCH 3/5] Replace emoji icons with OS logos for Linux and Windows sections Add RHEL technology icon (Red RGB) and Windows logo SVGs to static assets. Update section headers to use img tags with proper sizing instead of Unicode emoji characters. Signed-off-by: Joseph --- cmd/downloads/static/rhel-icon.svg | 89 +++++++++++++++++++++++++++ cmd/downloads/static/style.css | 7 ++- cmd/downloads/static/windows-logo.svg | 1 + cmd/downloads/templates/index.html | 4 +- 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 cmd/downloads/static/rhel-icon.svg create mode 100644 cmd/downloads/static/windows-logo.svg diff --git a/cmd/downloads/static/rhel-icon.svg b/cmd/downloads/static/rhel-icon.svg new file mode 100644 index 0000000..747ea2c --- /dev/null +++ b/cmd/downloads/static/rhel-icon.svg @@ -0,0 +1,89 @@ +Red Hat Enterprise Linux icon +RHEL, Linux platforms, CentOS + + + + + 2024-03-01T15:26:42.608Z + pending + TRA6a7371ad-8c7a-4614-800d-505632e2746e + Icon + 2024-03-01T15:26:42.608Z + square + true + true + pending + 2024-07-13T00:10:25.737Z + rhcc-audience:internal + no + Technology icon + DER6a7371ad-8c7a-4614-800d-505632e2746e + pmeilleu@redhat.com + Red + yes + + + rhcc-product:red-hat-enterprise-linux + + + + + Technology icon + + + image/svg+xml + 2024-05-10T14:11:28.447Z + + + Red Hat Enterprise Linux icon + + + + + RHEL, Linux platforms, CentOS + + + Activate + Activate + 2024-10-02T19:52:51.076Z + workflow-process-service + Activate + workflow-process-service + false + 2024-10-02T19:52:51.076Z + workflow-process-service + 2024-10-02T19:52:51.076Z + 38 + 38 + + + Use technology icons to represent Red Hat products and components. Do not remove the icon from the bounding shape. + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cmd/downloads/static/style.css b/cmd/downloads/static/style.css index d2c2435..4dae33e 100644 --- a/cmd/downloads/static/style.css +++ b/cmd/downloads/static/style.css @@ -89,7 +89,12 @@ body { } .section-header .os-icon { - font-size: 1.1rem; + font-size: 1.4rem; +} + +.section-header .os-logo { + height: 1.6rem; + width: auto; } table { diff --git a/cmd/downloads/static/windows-logo.svg b/cmd/downloads/static/windows-logo.svg new file mode 100644 index 0000000..2c7392e --- /dev/null +++ b/cmd/downloads/static/windows-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cmd/downloads/templates/index.html b/cmd/downloads/templates/index.html index 1b1bc23..ad17225 100644 --- a/cmd/downloads/templates/index.html +++ b/cmd/downloads/templates/index.html @@ -18,7 +18,7 @@
{{if .LinuxFiles}}
-
🐧 Linux
+
Linux
@@ -74,7 +74,7 @@ {{if .WindowsFiles}}
-
💻 Windows
+
Windows
From 89ed878167715c10d3a02aa9b9d4bad6d5619da1 Mon Sep 17 00:00:00 2001 From: Joseph Date: Wed, 18 Mar 2026 15:42:17 -0400 Subject: [PATCH 4/5] Update golang version to 1.25 in download server containerfile Signed-off-by: Joseph --- konflux.Containerfile.download | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/konflux.Containerfile.download b/konflux.Containerfile.download index 9d0ea8b..e660981 100644 --- a/konflux.Containerfile.download +++ b/konflux.Containerfile.download @@ -2,7 +2,7 @@ # Dependencies are prefetched by the Konflux pipeline (Hermeto) and injected # into the build context before this Containerfile runs. -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.24 AS builder +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.25 AS builder COPY . /workspace WORKDIR /workspace From 51c301b588195039c40752056e18254db9ac0367 Mon Sep 17 00:00:00 2001 From: Joseph Date: Thu, 19 Mar 2026 11:29:12 -0400 Subject: [PATCH 5/5] Remove OS icons from download server section headers Replace icon+label section headers with plain text labels: "Download for Linux & Unix", "Download for macOS", and "Download for Windows". Delete unused rhel-icon.svg and windows-logo.svg assets. Signed-off-by: Joseph --- cmd/downloads/static/rhel-icon.svg | 89 --------------------------- cmd/downloads/static/windows-logo.svg | 1 - cmd/downloads/templates/index.html | 6 +- 3 files changed, 3 insertions(+), 93 deletions(-) delete mode 100644 cmd/downloads/static/rhel-icon.svg delete mode 100644 cmd/downloads/static/windows-logo.svg diff --git a/cmd/downloads/static/rhel-icon.svg b/cmd/downloads/static/rhel-icon.svg deleted file mode 100644 index 747ea2c..0000000 --- a/cmd/downloads/static/rhel-icon.svg +++ /dev/null @@ -1,89 +0,0 @@ -Red Hat Enterprise Linux icon -RHEL, Linux platforms, CentOS - - - - - 2024-03-01T15:26:42.608Z - pending - TRA6a7371ad-8c7a-4614-800d-505632e2746e - Icon - 2024-03-01T15:26:42.608Z - square - true - true - pending - 2024-07-13T00:10:25.737Z - rhcc-audience:internal - no - Technology icon - DER6a7371ad-8c7a-4614-800d-505632e2746e - pmeilleu@redhat.com - Red - yes - - - rhcc-product:red-hat-enterprise-linux - - - - - Technology icon - - - image/svg+xml - 2024-05-10T14:11:28.447Z - - - Red Hat Enterprise Linux icon - - - - - RHEL, Linux platforms, CentOS - - - Activate - Activate - 2024-10-02T19:52:51.076Z - workflow-process-service - Activate - workflow-process-service - false - 2024-10-02T19:52:51.076Z - workflow-process-service - 2024-10-02T19:52:51.076Z - 38 - 38 - - - Use technology icons to represent Red Hat products and components. Do not remove the icon from the bounding shape. - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cmd/downloads/static/windows-logo.svg b/cmd/downloads/static/windows-logo.svg deleted file mode 100644 index 2c7392e..0000000 --- a/cmd/downloads/static/windows-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/cmd/downloads/templates/index.html b/cmd/downloads/templates/index.html index ad17225..a552ad9 100644 --- a/cmd/downloads/templates/index.html +++ b/cmd/downloads/templates/index.html @@ -18,7 +18,7 @@
{{if .LinuxFiles}}
-
Linux
+
Download for Linux & Unix
@@ -46,7 +46,7 @@ {{if .DarwinFiles}}
-
macOS
+
Download for macOS
@@ -74,7 +74,7 @@ {{if .WindowsFiles}}
-
Windows
+
Download for Windows