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
10 changes: 1 addition & 9 deletions .github/workflows/quay_binaries_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
arch: [amd64, arm64, ppc64le, s390x]
arch: [amd64, arm64]
steps:

- name: Checkout code
Expand Down Expand Up @@ -75,23 +75,17 @@ jobs:
# Load all arch images and capture their IDs
AMD64_ID=$(buildah pull oci-archive:oadp-cli-image-amd64/oadp-cli-amd64.tar)
ARM64_ID=$(buildah pull oci-archive:oadp-cli-image-arm64/oadp-cli-arm64.tar)
PPC64LE_ID=$(buildah pull oci-archive:oadp-cli-image-ppc64le/oadp-cli-ppc64le.tar)
S390X_ID=$(buildah pull oci-archive:oadp-cli-image-s390x/oadp-cli-s390x.tar)

# Tag the loaded images using their IDs
buildah tag $AMD64_ID ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-amd64
buildah tag $ARM64_ID ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-arm64
buildah tag $PPC64LE_ID ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-ppc64le
buildah tag $S390X_ID ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-s390x

- name: Create and push multi-arch manifest (version tag)
if: github.ref_name != 'oadp-dev'
run: |
buildah manifest create ${{ env.IMAGE_REPO }}:${{ github.ref_name }}
buildah manifest add ${{ env.IMAGE_REPO }}:${{ github.ref_name }} ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-amd64
buildah manifest add ${{ env.IMAGE_REPO }}:${{ github.ref_name }} ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-arm64
buildah manifest add ${{ env.IMAGE_REPO }}:${{ github.ref_name }} ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-ppc64le
buildah manifest add ${{ env.IMAGE_REPO }}:${{ github.ref_name }} ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-s390x
buildah manifest push --all ${{ env.IMAGE_REPO }}:${{ github.ref_name }}

- name: Create and push multi-arch manifest (latest tag)
Expand All @@ -100,6 +94,4 @@ jobs:
buildah manifest create ${{ env.IMAGE_REPO }}:latest
buildah manifest add ${{ env.IMAGE_REPO }}:latest ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-amd64
buildah manifest add ${{ env.IMAGE_REPO }}:latest ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-arm64
buildah manifest add ${{ env.IMAGE_REPO }}:latest ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-ppc64le
buildah manifest add ${{ env.IMAGE_REPO }}:latest ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-s390x
buildah manifest push --all ${{ env.IMAGE_REPO }}:latest
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
4. Verify installation: `kubectl oadp --help`

### Supported Platforms
- Linux (amd64, arm64, ppc64le, s390x)
- Linux (amd64, arm64)
- macOS (amd64, arm64)
- Windows (amd64, arm64)

Expand Down
10 changes: 4 additions & 6 deletions Containerfile.download
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ RUN go mod download && go mod verify

COPY . .

RUN make release-archives && \
mkdir -p /archives && \
mv *.tar.gz *.sha256 /archives/ && \
rm -rf /root/.cache/go-build /tmp/* release-build/
RUN make release-binaries && \
rm -rf /root/.cache/go-build /tmp/*

# 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/ && \
Expand All @@ -25,8 +23,8 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o download-ser

FROM registry.access.redhat.com/ubi9/ubi-minimal:latest

# Only copy compressed tarballs (much smaller than raw binaries)
COPY --from=builder /archives /archives
# Copy binaries, checksums, and LICENSE
COPY --from=builder /app/release-binaries /archives

# Copy the download server
COPY --from=builder /app/download-server /usr/local/bin/download-server
Expand Down
37 changes: 34 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ LDFLAGS := -X github.com/vmware-tanzu/velero/pkg/buildinfo.Version=$(VERSION) \

# Centralized platform definitions to avoid duplication
# Matches architectures supported by Kubernetes: https://kubernetes.io/releases/download/#binaries
PLATFORMS = linux/amd64 linux/arm64 linux/ppc64le linux/s390x darwin/amd64 darwin/arm64 windows/amd64 windows/arm64
PLATFORMS = linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64

# Platform variables for multi-arch builds
# Usage: make build PLATFORM=linux/amd64
Expand Down Expand Up @@ -50,8 +50,6 @@ help: ## Show this help message
@echo "Build with different platforms:"
@echo " make build PLATFORM=linux/amd64"
@echo " make build PLATFORM=linux/arm64"
@echo " make build PLATFORM=linux/ppc64le"
@echo " make build PLATFORM=linux/s390x"
@echo " make build PLATFORM=darwin/amd64"
@echo " make build PLATFORM=darwin/arm64"
@echo " make build PLATFORM=windows/amd64"
Expand All @@ -67,6 +65,7 @@ help: ## Show this help message
@echo "Release commands:"
@echo " make release-build # Build binaries for all platforms"
@echo " make release-archives # Create tar.gz archives for all platforms"
@echo " make release-binaries # Create clean-named binaries for download server"

# Build targets
.PHONY: build
Expand Down Expand Up @@ -514,6 +513,38 @@ release-archives: release-build ## Create tar.gz archives with SHA256 checksums
release: release-archives ## Build and create release archives for all platforms
@echo "🚀 Release build complete! Archives ready for distribution."

.PHONY: release-binaries
release-binaries: release-build ## Copy binaries with clean names and SHA256 checksums (for download server)
@echo "Creating clean-named binaries for download server..."
@if [ ! -f LICENSE ]; then \
echo "❌ LICENSE file not found! Please ensure LICENSE file exists."; \
exit 1; \
fi
@mkdir -p release-binaries
@cp LICENSE release-binaries/LICENSE
@for platform in $(PLATFORMS); do \
GOOS=$$(echo $$platform | cut -d'/' -f1); \
GOARCH=$$(echo $$platform | cut -d'/' -f2); \
if [ -n "$(VERSION)" ]; then \
version_suffix="_$(VERSION)"; \
else \
version_suffix=""; \
fi; \
if [ "$$GOOS" = "windows" ]; then \
platform_binary="$(BINARY_NAME)$${version_suffix}_$${GOOS}_$${GOARCH}.exe"; \
clean_name="$(BINARY_NAME)_$${GOOS}_$${GOARCH}.exe"; \
else \
platform_binary="$(BINARY_NAME)$${version_suffix}_$${GOOS}_$${GOARCH}"; \
clean_name="$(BINARY_NAME)_$${GOOS}_$${GOARCH}"; \
fi; \
echo "Copying $$clean_name..."; \
cp $$platform_binary release-binaries/$$clean_name; \
sha256sum release-binaries/$$clean_name > release-binaries/$$clean_name.sha256; \
echo "✅ $$clean_name"; \
done
@echo "✅ All binaries and checksums ready in release-binaries/"
@ls -la release-binaries/

# Optimized krew-manifest generation using Python script for better reliability
.PHONY: krew-manifest
krew-manifest: release-archives ## Generate Krew plugin manifest with SHA256 checksums
Expand Down
81 changes: 58 additions & 23 deletions cmd/downloads/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var templateFS embed.FS
var staticFS embed.FS

var (
archiveDir = getEnv("ARCHIVE_DIR", "/archives")
binaryDir = getEnv("ARCHIVE_DIR", "/archives")
port = getEnv("PORT", "8080")
pageTemplate = template.Must(template.ParseFS(templateFS, "templates/index.html"))
)
Expand All @@ -31,7 +31,7 @@ func getEnv(key, fallback string) string {
return fallback
}

type archiveFile struct {
type binaryFile struct {
Name string
Size float64
OS string
Expand All @@ -40,11 +40,11 @@ type archiveFile struct {
}

func main() {
files, err := filepath.Glob(filepath.Join(archiveDir, "*.tar.gz"))
files, err := discoverBinaries()
if err != nil || len(files) == 0 {
log.Fatal("No archives found in ", archiveDir)
log.Fatal("No binaries found in ", binaryDir)
}
log.Printf("Found %d archives", len(files))
log.Printf("Found %d binaries", len(files))

staticContent, err := fs.Sub(staticFS, "static")
if err != nil {
Expand All @@ -55,21 +55,45 @@ func main() {
http.HandleFunc("/download/", downloadBinary)

log.Printf("Starting server on port %s", port)
log.Printf("Serving archives from %s", archiveDir)
log.Printf("Serving binaries from %s", binaryDir)

if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}

// discoverBinaries finds kubectl-oadp binaries (excluding .sha256 and LICENSE files).
func discoverBinaries() ([]string, error) {
entries, err := os.ReadDir(binaryDir)
if err != nil {
return nil, err
}
var binaries []string
for _, e := range entries {
name := e.Name()
if e.IsDir() || strings.HasSuffix(name, ".sha256") || name == "LICENSE" {
continue
}
if strings.HasPrefix(name, "kubectl-oadp_") {
binaries = append(binaries, filepath.Join(binaryDir, name))
}
}
return binaries, nil
}

func listBinaries(w http.ResponseWriter, r *http.Request) {
files, err := filepath.Glob(filepath.Join(archiveDir, "*.tar.gz"))
files, err := discoverBinaries()
if err != nil {
http.Error(w, "Error listing archives", http.StatusInternalServerError)
http.Error(w, "Error listing binaries", http.StatusInternalServerError)
return
}

var linuxFiles, darwinFiles, windowsFiles []archiveFile
hasLicense := false
if _, err := os.Stat(filepath.Join(binaryDir, "LICENSE")); err == nil {
hasLicense = true
}

var linuxFiles, darwinFiles, windowsFiles []binaryFile
for _, file := range files {
name := filepath.Base(file)
info, err := os.Stat(file)
Expand All @@ -79,24 +103,25 @@ func listBinaries(w http.ResponseWriter, r *http.Request) {
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}
bf := binaryFile{Name: name, Size: size, OS: osName, Arch: arch, Checksum: checksum}
switch osName {
case "linux":
linuxFiles = append(linuxFiles, af)
linuxFiles = append(linuxFiles, bf)
case "darwin":
darwinFiles = append(darwinFiles, af)
darwinFiles = append(darwinFiles, bf)
case "windows":
windowsFiles = append(windowsFiles, af)
windowsFiles = append(windowsFiles, bf)
default:
linuxFiles = append(linuxFiles, af)
linuxFiles = append(linuxFiles, bf)
}
}

data := struct {
LinuxFiles []archiveFile
DarwinFiles []archiveFile
WindowsFiles []archiveFile
}{linuxFiles, darwinFiles, windowsFiles}
LinuxFiles []binaryFile
DarwinFiles []binaryFile
WindowsFiles []binaryFile
HasLicense bool
}{linuxFiles, darwinFiles, windowsFiles, hasLicense}

w.Header().Set("Content-Type", "text/html")
if err := pageTemplate.Execute(w, data); err != nil {
Expand All @@ -117,7 +142,7 @@ func readChecksum(path string) string {
}

func parsePlatform(filename string) (string, string) {
name := strings.TrimSuffix(filename, ".tar.gz")
name := strings.TrimSuffix(filename, ".exe")
parts := strings.Split(name, "_")
if len(parts) >= 3 {
return parts[len(parts)-2], parts[len(parts)-1]
Expand All @@ -128,20 +153,30 @@ func parsePlatform(filename string) (string, string) {
func downloadBinary(w http.ResponseWriter, r *http.Request) {
filename := filepath.Base(r.URL.Path[len("/download/"):])

if filepath.Dir(filename) != "." || !strings.HasSuffix(filename, ".tar.gz") {
if filepath.Dir(filename) != "." {
http.Error(w, "Invalid filename", http.StatusBadRequest)
return
}

filePath := filepath.Join(archiveDir, filename)
// Allow downloading LICENSE or any kubectl-oadp binary
if filename != "LICENSE" && !strings.HasPrefix(filename, "kubectl-oadp_") {
http.Error(w, "Invalid filename", http.StatusBadRequest)
return
}

filePath := filepath.Join(binaryDir, filename)

if _, err := os.Stat(filePath); os.IsNotExist(err) {
http.Error(w, "Archive not found", http.StatusNotFound)
http.Error(w, "File not found", http.StatusNotFound)
return
}

w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
w.Header().Set("Content-Type", "application/gzip")
if filename == "LICENSE" {
w.Header().Set("Content-Type", "text/plain")
} else {
w.Header().Set("Content-Type", "application/octet-stream")
}

http.ServeFile(w, r, filePath)
log.Printf("Downloaded: %s from %s", filename, r.RemoteAddr)
Expand Down
27 changes: 15 additions & 12 deletions cmd/downloads/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,29 +100,32 @@
</div>
{{end}}

{{if .HasLicense}}
<div class="section">
<div class="section-header">License</div>
<div style="padding: 0.85rem 1.25rem; display: flex; align-items: center; justify-content: space-between;">
<span style="font-size: 0.9rem;">LICENSE</span>
<a class="download-btn" href="/download/LICENSE">View License</a>
</div>
</div>
{{end}}

<div class="install-section">
<h3>Installation</h3>
<div class="code-block">
<div class="code-line">
<span class="comment"># Extract the archive</span>
</div>
<div class="code-line">
<code class="cmd">tar -xzf kubectl-oadp_*.tar.gz</code>
<button class="copy-btn" onclick="copyCmd(this)" data-cmd="tar -xzf kubectl-oadp_*.tar.gz">Copy</button>
</div>
<div class="code-line">
<span class="comment"># Make it executable</span>
</div>
<div class="code-line">
<code class="cmd">chmod +x kubectl-oadp</code>
<button class="copy-btn" onclick="copyCmd(this)" data-cmd="chmod +x kubectl-oadp">Copy</button>
<code class="cmd">chmod +x kubectl-oadp_*</code>
<button class="copy-btn" onclick="copyCmd(this)" data-cmd="chmod +x kubectl-oadp_*">Copy</button>
</div>
<div class="code-line">
<span class="comment"># Move to your PATH</span>
<span class="comment"># Rename and move to your PATH</span>
</div>
<div class="code-line">
<code class="cmd">sudo mv kubectl-oadp /usr/local/bin/</code>
<button class="copy-btn" onclick="copyCmd(this)" data-cmd="sudo mv kubectl-oadp /usr/local/bin/">Copy</button>
<code class="cmd">sudo mv kubectl-oadp_* /usr/local/bin/kubectl-oadp</code>
<button class="copy-btn" onclick="copyCmd(this)" data-cmd="sudo mv kubectl-oadp_* /usr/local/bin/kubectl-oadp">Copy</button>
</div>
<div class="code-line">
<span class="comment"># Verify it works</span>
Expand Down
Loading
Loading