diff --git a/.github/workflows/quay_binaries_push.yml b/.github/workflows/quay_binaries_push.yml index d1fe756..799798b 100644 --- a/.github/workflows/quay_binaries_push.yml +++ b/.github/workflows/quay_binaries_push.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - arch: [amd64, arm64, ppc64le, s390x] + arch: [amd64, arm64] steps: - name: Checkout code @@ -75,14 +75,10 @@ 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' @@ -90,8 +86,6 @@ jobs: 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) @@ -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 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3a0867a..f98ae8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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) diff --git a/Containerfile.download b/Containerfile.download index 613636b..049922e 100644 --- a/Containerfile.download +++ b/Containerfile.download @@ -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/ && \ @@ -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 diff --git a/Makefile b/Makefile index 517e265..94d3865 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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" @@ -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 @@ -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 diff --git a/cmd/downloads/server.go b/cmd/downloads/server.go index b766f95..bf1b886 100644 --- a/cmd/downloads/server.go +++ b/cmd/downloads/server.go @@ -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")) ) @@ -31,7 +31,7 @@ func getEnv(key, fallback string) string { return fallback } -type archiveFile struct { +type binaryFile struct { Name string Size float64 OS string @@ -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 { @@ -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) @@ -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 { @@ -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] @@ -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) diff --git a/cmd/downloads/templates/index.html b/cmd/downloads/templates/index.html index a552ad9..d7cdfa6 100644 --- a/cmd/downloads/templates/index.html +++ b/cmd/downloads/templates/index.html @@ -100,29 +100,32 @@ {{end}} + {{if .HasLicense}} +
+
License
+
+ LICENSE + View License +
+
+ {{end}} +

Installation

-
- # Extract the archive -
-
- tar -xzf kubectl-oadp_*.tar.gz - -
# Make it executable
- chmod +x kubectl-oadp - + chmod +x kubectl-oadp_* +
- # Move to your PATH + # Rename and move to your PATH
- sudo mv kubectl-oadp /usr/local/bin/ - + sudo mv kubectl-oadp_* /usr/local/bin/kubectl-oadp +
# Verify it works diff --git a/docs/konflux-onboarding.md b/docs/konflux-onboarding.md deleted file mode 100644 index 05cb133..0000000 --- a/docs/konflux-onboarding.md +++ /dev/null @@ -1,228 +0,0 @@ -# Onboarding a New OADP Component to Konflux - -This document describes the end-to-end process for adding a new image component to the OADP release stream, built via Konflux. - -## Overview - -OADP ships as a set of container images built by Red Hat's Konflux build system. The build configuration lives in [openshift-eng/ocp-build-data](https://github.com/openshift-eng/ocp-build-data) on product-specific branches (e.g. `oadp-1.5`, `oadp-1.6`). Source code is pulled from private mirrors in the `openshift-priv` GitHub org. - -``` -Source repo (migtools/oadp-cli) - | - | auto-mirrored - v -Private mirror (openshift-priv/migtools-oadp-cli) - | - | Konflux pulls source + builds image - v -Build config (ocp-build-data oadp-1.6 branch) - | - | image pushed to - v -Delivery repo (registry.redhat.io/oadp/oadp-cli-rhel9) - | - | released via RPA - v -Stage / Prod registries -``` - -## Repositories Involved - -| Repository | Purpose | Where | -|---|---|---| -| **Source repo** (e.g. `migtools/oadp-cli`) | Component source code + `konflux.Dockerfile` | GitHub | -| **openshift/release** | Whitelist config for `openshift-priv` mirror creation | [GitHub](https://github.com/openshift/release) | -| **openshift-priv/migtools-\** | Private mirror of source repo (ART's midstream) | GitHub (restricted) | -| **openshift-eng/ocp-build-data** | Build configuration (image YAMLs, group config, streams) | [GitHub](https://github.com/openshift-eng/ocp-build-data) | -| **releng/pyxis-repo-configs** | Delivery/Comet repo definitions | [GitLab (internal)](https://gitlab.cee.redhat.com/releng/pyxis-repo-configs) | -| **Stage/Prod RPAs** | Release pipeline access for the image | Internal | - -## Step-by-Step Process - -### Step 1: Create the openshift-priv mirror - -**Repo:** `openshift/release` -**File:** `core-services/openshift-priv/_whitelist.yaml` - -Add your repo under the appropriate org section. For `migtools` repos: - -```yaml - migtools: - - oadp-cli # <-- add your repo here - - oadp-non-admin - # ... -``` - -**Reference PR:** [openshift/release#68955](https://github.com/openshift/release/pull/68955) (ART-14080: whitelist MTC & OADP repos) - -After the PR merges, the mirror at `openshift-priv/migtools-` is auto-created within ~3-4 hours. ART can then perform a test build. - -**Naming convention:** repos under `migtools/` are mirrored as `openshift-priv/migtools-`. - -### Step 2: Prepare the source repo - -Your source repo needs a `konflux.Dockerfile` that follows the standard OADP build pattern: - -```dockerfile -FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.25 AS builder - -COPY . /workspace -WORKDIR /workspace - -ENV GOEXPERIMENT=strictfipsruntime - -RUN CGO_ENABLED=1 GOOS=linux go build -mod=readonly -a -tags strictfipsruntime \ - -o /workspace/bin/ .// - -FROM registry.redhat.io/ubi9/ubi:latest - -RUN dnf -y install openssl && dnf -y reinstall tzdata && dnf clean all - -COPY --from=builder /workspace/bin/ /usr/local/bin/ -COPY LICENSE /licenses/ - -LABEL description="" -LABEL io.k8s.description="" -LABEL io.k8s.display-name="" -LABEL io.openshift.tags="oadp,migration,backup" -LABEL summary="" -``` - -Key requirements: -- **Builder image:** `brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.25` -- **FIPS compliance:** `CGO_ENABLED=1`, `GOEXPERIMENT=strictfipsruntime`, `-tags strictfipsruntime` -- **Build flags:** `-mod=readonly` (hermetic builds don't allow network fetches) -- **Runtime base:** `registry.redhat.io/ubi9/ubi:latest` -- **Use `dnf`** not `microdnf` -- ART auto-replaces `dnf` with `microdnf` at build time via ocp-build-data modifications -- **License:** Copy `LICENSE` to `/licenses/` - -### Step 3: Create the delivery repo - -**Repo:** `gitlab.cee.redhat.com/releng/pyxis-repo-configs` -**Path:** `products/oadp/oadp.yaml` - -For OADP, this is the first delivery repo created post-Konflux migration, so the `products/oadp/` directory needs to be created. Reference `products/ocp/` or `products/mta/` for the file format. - -Add an entry for your new image (e.g. `oadp/oadp-cli-rhel9`). - -### Step 4: Add to stage and prod RPAs - -Add the new image as a component in the stage and prod RPA configurations. This allows the image to be released through the pipeline to stage and production registries. - -### Step 5: Add build config to ocp-build-data - -**Repo:** `openshift-eng/ocp-build-data` -**Branch:** `oadp-1.6` (or whichever release stream) - -#### 5a. Create image config - -Create `images/.yml`: - -```yaml -cachito: - packages: - gomod: - - path: . -content: - source: - dockerfile: konflux.Dockerfile - git: - branch: - target: oadp-1.6 - url: git@github.com:openshift-priv/migtools-.git - web: https://github.com/migtools/ - modifications: - - action: replace - match: "dnf -y" - replacement: "microdnf -y" - - action: replace - match: "dnf clean all" - replacement: "microdnf clean all" -distgit: - component: -container - branch: rhaos-{MAJOR}.{MINOR}-rhel-9 -delivery: - delivery_repo_names: - - oadp/ -for_payload: false -enabled_repos: - - rhel-9-appstream-rpms - - rhel-9-baseos-rpms -from: - builder: - - stream: rhel-9-golang - member: base-rhel9 -name: oadp/ -owners: - - oadp-maintainers@redhat.com -dependents: - - oadp-operator -konflux: - cachi2: - lockfile: - rpms: - - tzdata -jira: - project: OADP - component: -container -``` - -#### 5b. Add public_upstreams mapping - -In `group.yml`, add the mapping between the private mirror and public repo: - -```yaml -public_upstreams: - # ... existing entries ... - - private: "https://github.com/openshift-priv/migtools-" - public: "https://github.com/migtools/" -``` - -### Step 6: Wire into the operator bundle - -Add the new image as a `relatedImage` in the OADP operator's CSV/bundle so it ships when the operator is installed. - -## Build System Details - -### How Konflux builds work for OADP - -- **No `.tekton/` or `.konflux/` directories needed** in source repos -- pipelines are managed externally by ART tooling -- **No cachi2 lockfiles needed in-repo** -- cachi2 dependency resolution is handled externally based on ocp-build-data config -- **Hermetic builds:** network is blocked during build. Dependencies are prefetched by Konflux/cachi2 before the build starts -- **Multi-arch:** images are built for x86_64, aarch64, ppc64le, s390x - -### ocp-build-data branch structure - -OADP uses product-level branches, not per-component branches: - -``` -ocp-build-data/ - oadp-1.5/ <-- all OADP 1.5 components - oadp-1.6/ <-- all OADP 1.6 components -``` - -Each branch contains: -- `group.yml` -- shared config (Go version, arches, RHEL repos, Konflux settings, OCP targets) -- `streams.yml` -- base image references (golang builder, UBI, ose-cli) -- `images/` -- per-component build configs -- `releases.yml` -- release assembly config (may be empty) - -### Key config in group.yml - -```yaml -konflux: - cachi2: - enabled: true - gomod_version_patch: true - lockfile: - force: true - sast: - enabled: true - network_mode: hermetic -``` - -## References - -- [ART Konflux onboarding docs](https://art-docs.engineering.redhat.com/konflux/onboard-external-operators/) (internal, requires cert) -- [openshift/release#68955](https://github.com/openshift/release/pull/68955) -- reference PR for whitelist additions -- [openshift-eng/ocp-build-data](https://github.com/openshift-eng/ocp-build-data) -- build configuration repo diff --git a/konflux.Dockerfile b/konflux.Dockerfile index 509166e..5164a74 100644 --- a/konflux.Dockerfile +++ b/konflux.Dockerfile @@ -7,12 +7,10 @@ FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.25 COPY . /workspace WORKDIR /workspace -# Build release archives for all platforms (CGO_ENABLED=0 for cross-platform +# Build release binaries for all platforms (CGO_ENABLED=0 for cross-platform # portability — CLI binaries run on user machines outside the FIPS boundary) -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 (FIPS-compliant, runs in-cluster on RHEL) RUN CGO_ENABLED=1 GOEXPERIMENT=strictfipsruntime GOOS=linux \ @@ -25,7 +23,7 @@ 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/release-binaries /archives COPY --from=builder /workspace/bin/download-server /usr/local/bin/download-server COPY LICENSE /licenses/