Skip to content
Merged
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
12 changes: 5 additions & 7 deletions Containerfile.download
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
97 changes: 79 additions & 18 deletions cmd/downloads/server.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,56 @@
package main

import (
"embed"
"fmt"
"html/template"
"io/fs"
"log"
"net/http"
"os"
"path/filepath"
"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)

Expand All @@ -40,40 +69,72 @@ func listBinaries(w http.ResponseWriter, r *http.Request) {
return
}

w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, "<html><head><title>kubectl-oadp Downloads</title></head><body>")
fmt.Fprintf(w, "<h1>kubectl-oadp Binary Downloads</h1>")
fmt.Fprintf(w, "<p>Download pre-built binaries for your platform:</p><ul>")

var linuxFiles, darwinFiles, windowsFiles []archiveFile
for _, file := range files {
name := filepath.Base(file)
info, err := os.Stat(file)
if err != nil {
continue
}
size := float64(info.Size()) / (1024 * 1024) // MB
fmt.Fprintf(w, `<li><a href="/download/%s">%s</a> (%.2f MB)</li>`, name, name, size)
size := float64(info.Size()) / (1024 * 1024)
osName, arch := parsePlatform(name)
checksum := readChecksum(file + ".sha256")
af := archiveFile{Name: name, Size: size, OS: osName, Arch: arch, Checksum: checksum}
switch osName {
case "linux":
linuxFiles = append(linuxFiles, af)
case "darwin":
darwinFiles = append(darwinFiles, af)
case "windows":
windowsFiles = append(windowsFiles, af)
default:
linuxFiles = append(linuxFiles, af)
}
}

fmt.Fprintf(w, "</ul>")
fmt.Fprintf(w, "<h3>Installation:</h3>")
fmt.Fprintf(w, "<pre>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/</pre>")
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
}

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
Expand Down
Loading
Loading