Skip to content
Open
Show file tree
Hide file tree
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
8 changes: 4 additions & 4 deletions Dockerfile.downloads
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM quay.io/openshift/origin-cli-artifacts:4.18 AS origincli
FROM quay.io/openshift/origin-cli-artifacts:4.22 AS origincli

FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.22-openshift-4.18 AS gobuilder
FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22 AS gobuilder
Comment on lines +1 to +3
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="$(fd '^Dockerfile\.downloads$' | head -n1)"

echo "== FROM lines =="
awk '/^FROM / { print NR ":" $0 }' "$FILE"

echo
echo "== Unpinned FROM lines (missing `@sha256`) =="
awk '/^FROM / && $2 !~ /@sha256:/ { print NR ":" $0 }' "$FILE"

echo
echo "== Mutable latest tags =="
awk '/^FROM / && $2 ~ /:latest($|[[:space:]])/ { print NR ":" $0 }' "$FILE"

Repository: openshift/console

Length of output: 639


🏁 Script executed:

#!/bin/bash
# Find all Dockerfiles and check their FROM line patterns
find . -name "Dockerfile*" -type f | head -20

Repository: openshift/console

Length of output: 439


🏁 Script executed:

#!/bin/bash
# Check other Dockerfiles for digest pinning patterns
find . -name "Dockerfile*" -type f -exec grep -H "^FROM" {} + | head -30

Repository: openshift/console

Length of output: 2300


🏁 Script executed:

#!/bin/bash
# Check console-operator Dockerfiles for digest pinning patterns
find . -name "Dockerfile*" -type f -exec grep -H "^FROM" {} + | head -30

Repository: openshift/console-operator

Length of output: 767


🏁 Script executed:

#!/bin/bash
# Check context around the Dockerfile.downloads file to understand the UBI choice
cat -n Dockerfile.downloads | head -25

Repository: openshift/console

Length of output: 997


🌐 Web query:

registry.access.redhat.com/ubi9/ubi-minimal latest stable versions tags

💡 Result:

As of May 15, 2026, the latest stable version of the Red Hat Universal Base Image 9 Minimal (ubi9-minimal) is 9.7 [1][2]. The Red Hat Container Registry uses a tagging system that includes both major/minor version tags and specific build-based tags [1]. You can access the image using the following primary tags: - latest: Always points to the most recent stable release of the image [1]. - 9.7: Points to the current stable minor release [1]. - 9.7-: Points to a specific, immutable build of the 9.7 release (e.g., 9.7-1778562320) [1]. Additionally, Red Hat provides corresponding source images for these tags, which can be accessed by appending -source to the tag (e.g., 9.7-source) [1][2]. To view the most up-to-date list of available tags, you can check the Red Hat Ecosystem Catalog [2] or the official Red Hat repository on Docker Hub [1][3]. For programmatic access or to mirror images, it is recommended to use the skopeo tool to list tags directly from the registry [2][4].

Citations:


Replace mutable :latest tag on Line 17 with a stable version tag.

Line 17 uses registry.access.redhat.com/ubi9/ubi-minimal:latest, which is mutable and inconsistent with the versioned approach elsewhere in the Dockerfile. Replace with a stable version tag like 9.7 to ensure reproducible builds and align with the versioned tags used on lines 1 and 3.

Suggested change
-FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
+FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.downloads` around lines 1 - 3, Replace the mutable UBI image tag
used in the Dockerfile by updating the image reference
"registry.access.redhat.com/ubi9/ubi-minimal:latest" to a stable versioned tag
(for example "registry.access.redhat.com/ubi9/ubi-minimal:9.7") so builds are
reproducible and consistent with the other versioned images like
"quay.io/openshift/origin-cli-artifacts:4.22" and
"registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.22";
locate the line containing the
"registry.access.redhat.com/ubi9/ubi-minimal:latest" image and replace ":latest"
with the chosen stable version.


ENV HOME=/go/src/github.com/openshift/console/
RUN mkdir -p ${HOME}
Expand All @@ -14,7 +14,7 @@ COPY .git/ /${HOME}.git/
WORKDIR ${HOME}
RUN ./build-downloads.sh

FROM registry.ci.openshift.org/ocp/4.18:base-rhel9
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
RUN mkdir -p /opt/downloads

COPY --from=gobuilder /go/src/github.com/openshift/console/bin/downloads /opt/downloads
Expand All @@ -30,7 +30,7 @@ CMD ["/opt/downloads/downloads", "--config-path=/opt/downloads/defaultArtifacts
LABEL \
io.k8s.display-name="CLI Artifacts Downloads Server" \
io.k8s.description="This is a component of OpenShift Container Platform and provides a Golang server that serves 'oc' binaries for various platforms." \
maintainer="Marek Ziska <mziska@redhat.com>" \
maintainer="Jakub Hadvig <jhadvig@redhat.com>" \
License="Apache 2.0" \
vendor="Red Hat" \
io.openshift.tags="openshift,cli-artifacts" \
26 changes: 25 additions & 1 deletion cmd/downloads/config/defaultArtifactsConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ defaultArtifactsConfig:
- arch: amd64
operatingSystem: linux
path: /usr/share/openshift/linux_amd64/oc
- arch: amd64
operatingSystem: linux
path: /usr/share/openshift/linux_amd64/oc.rhel8
- arch: amd64
operatingSystem: linux
path: /usr/share/openshift/linux_amd64/oc.rhel9
- arch: amd64
operatingSystem: mac
path: /usr/share/openshift/mac/oc
Expand All @@ -11,12 +17,30 @@ defaultArtifactsConfig:
- arch: arm64
operatingSystem: linux
path: /usr/share/openshift/linux_arm64/oc
- arch: arm64
operatingSystem: linux
path: /usr/share/openshift/linux_arm64/oc.rhel8
- arch: arm64
operatingSystem: linux
path: /usr/share/openshift/linux_arm64/oc.rhel9
- arch: arm64
operatingSystem: mac
path: /usr/share/openshift/mac_arm64/oc
- arch: ppc64le
operatingSystem: linux
path: /usr/share/openshift/linux_ppc64le/oc
- arch: ppc64le
operatingSystem: linux
path: /usr/share/openshift/linux_ppc64le/oc.rhel8
- arch: ppc64le
operatingSystem: linux
path: /usr/share/openshift/linux_ppc64le/oc.rhel9
- arch: s390x
operatingSystem: linux
path: /usr/share/openshift/linux_s390x/oc
- arch: s390x
operatingSystem: linux
path: /usr/share/openshift/linux_s390x/oc.rhel8
- arch: s390x
operatingSystem: linux
path: /usr/share/openshift/linux_s390x/oc
path: /usr/share/openshift/linux_s390x/oc.rhel9
84 changes: 69 additions & 15 deletions cmd/downloads/config/downloads_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"text/template"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -55,6 +58,8 @@ type DownloadsServerConfig struct {
Spec []ArtifactSpec
TempDir string
TemplateHTML *template.Template
// archivesReady is closed once all background archive creation goroutines complete.
archivesReady chan struct{}
}

const indexFileName = "index.html"
Expand Down Expand Up @@ -210,6 +215,18 @@ func addFileToZip(zw *zip.Writer, filename string) error {
return nil
}

// displayName returns a human-readable label like "amd64 linux" or "amd64 linux - RHEL 8".
func displayName(arch, os, basename string) string {
name := fmt.Sprintf("%s %s", arch, os)
base := strings.TrimSuffix(basename, ".exe")
if strings.HasSuffix(base, ".rhel8") {
name += " - RHEL 8"
} else if strings.HasSuffix(base, ".rhel9") {
name += " - RHEL 9"
}
return name
}

func configureArchivePath(pathToTargetFile string) string {
if filepath.Ext(pathToTargetFile) == ".exe" {
// Remove the .exe extension from the path
Expand Down Expand Up @@ -323,29 +340,66 @@ func (downloadsConfig *DownloadsServerConfig) generateDirFileContents() ([]ListI
return nil, err
}

err = createArchive(artifactPath, ".tar")
if err != nil {
return nil, err
}
err = createArchive(artifactPath, ".zip")
if err != nil {
return nil, err
}

pathToArchive := configureArchivePath(artifactPath)
// append new entry in the list of links to artifacts
relPath := filepath.Join(spec.Arch, spec.OperatingSystem, basename)
relArchivePath := configureArchivePath(relPath)
content = append(content, ListItemLink{
Type: Binary,
URL: artifactPath,
Name: fmt.Sprintf("%s %s", spec.Arch, spec.OperatingSystem),
TarURL: fmt.Sprintf("%s.tar", pathToArchive),
ZipURL: fmt.Sprintf("%s.zip", pathToArchive),
URL: relPath,
Name: displayName(spec.Arch, spec.OperatingSystem, basename),
TarURL: fmt.Sprintf("%s.tar", relArchivePath),
ZipURL: fmt.Sprintf("%s.zip", relArchivePath),
})
}

return content, nil
}

// CreateArchivesInBackground spawns goroutines to create tar and zip archives
// for every artifact concurrently. It closes archivesReady when all archives
// have been written so that Handler() can unblock waiting requests.
func (c *DownloadsServerConfig) CreateArchivesInBackground() {
c.archivesReady = make(chan struct{})
var wg sync.WaitGroup

for _, spec := range c.Spec {
basename := filepath.Base(spec.Path)
artifactPath := filepath.Join(c.TempDir, spec.Arch, spec.OperatingSystem, basename)

wg.Add(2)
go func(p string) {
defer wg.Done()
if err := createArchive(p, ".tar"); err != nil {
klog.Errorf("Failed to create tar archive for %s: %v", p, err)
}
}(artifactPath)
go func(p string) {
defer wg.Done()
if err := createArchive(p, ".zip"); err != nil {
klog.Errorf("Failed to create zip archive for %s: %v", p, err)
}
}(artifactPath)
}

go func() {
wg.Wait()
close(c.archivesReady)
klog.Info("All archives created successfully")
}()
Comment on lines +383 to +387
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Success message logged unconditionally, even when archive creation fails.

If any createArchive call fails, the error is logged but archivesReady is still closed and the "All archives created successfully" message is emitted. This can mask failures during incident investigation.

Consider tracking failure state and adjusting the final log accordingly.

🛠️ Proposed fix
 func (c *DownloadsServerConfig) CreateArchivesInBackground() {
 	c.archivesReady = make(chan struct{})
 	var wg sync.WaitGroup
+	var errCount int32
+	var errMu sync.Mutex

 	for _, spec := range c.Spec {
 		basename := filepath.Base(spec.Path)
 		artifactPath := filepath.Join(c.TempDir, spec.Arch, spec.OperatingSystem, basename)

 		wg.Add(2)
 		go func(p string) {
 			defer wg.Done()
 			if err := createArchive(p, ".tar"); err != nil {
 				klog.Errorf("Failed to create tar archive for %s: %v", p, err)
+				errMu.Lock()
+				errCount++
+				errMu.Unlock()
 			}
 		}(artifactPath)
 		go func(p string) {
 			defer wg.Done()
 			if err := createArchive(p, ".zip"); err != nil {
 				klog.Errorf("Failed to create zip archive for %s: %v", p, err)
+				errMu.Lock()
+				errCount++
+				errMu.Unlock()
 			}
 		}(artifactPath)
 	}

 	go func() {
 		wg.Wait()
 		close(c.archivesReady)
-		klog.Info("All archives created successfully")
+		if errCount > 0 {
+			klog.Warningf("Archive creation completed with %d failures", errCount)
+		} else {
+			klog.Info("All archives created successfully")
+		}
 	}()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/downloads/config/downloads_config.go` around lines 383 - 387, The final
goroutine currently always logs "All archives created successfully" after
wg.Wait() and closing c.archivesReady; change it to track whether any
createArchive call failed (e.g., set a shared failure flag via an atomic.Bool or
send errors to an errs channel read before logging) and only emit the success
message when no failures were recorded; otherwise log a distinct error/warning
indicating that one or more archive creations failed. Update the goroutine that
calls wg.Wait() and close(c.archivesReady) to inspect that failure indicator (or
aggregated errors) and choose the appropriate log message, referencing
createArchive, c.archivesReady, and the wg.Wait() goroutine.

}

// Handler returns an http.Handler that serves files from TempDir. Requests for
// .tar or .zip archives block until background archive creation is complete.
func (c *DownloadsServerConfig) Handler() http.Handler {
fs := http.FileServer(http.Dir(c.TempDir))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ext := filepath.Ext(r.URL.Path)
if ext == ".tar" || ext == ".zip" {
<-c.archivesReady
}
fs.ServeHTTP(w, r)
})
}

// setupArtifactsDirectory creates the root HTML file and directories, files and archives for artifacts
func (artifactsConfig *DownloadsServerConfig) setupArtifactsDirectory() error {
// symlink file in the temporary directory that points to the openshift license
Expand Down
Loading