Skip to content

Commit 8e80775

Browse files
mprycclaude
andcommitted
Upstream: <carry>: Use clean binary names in download server
Replace versioned binary names (oadp-vmdp_${VERSION}_${os}_${arch}) with clean names (oadp-vmdp_${os}_${arch}) so users can directly curl/wget binaries without version/commit hash in the filename. Switch from single sha256sum.txt to per-file .sha256 sidecar checksums. Add LICENSE as a downloadable file on the download server page. Similar to migtools/oadp-cli#174 Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Michal Pryc <mpryc@redhat.com>
1 parent 17b230f commit 8e80775

4 files changed

Lines changed: 64 additions & 64 deletions

File tree

Containerfile.download

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@ RUN go mod download && go mod verify
5252
COPY . .
5353

5454
# Build oadp-vmdp binaries for all target platforms as direct executables
55-
# Binaries are statically linked so no archive wrapping is needed
56-
# SHA256 checksums are generated for integrity verification
55+
# with clean names (no version/commit hash) for direct curl/wget download.
56+
# Per-file SHA256 checksums are generated for integrity verification.
5757
RUN mkdir -p /archives && \
5858
for platform in linux/amd64 linux/arm64 windows/amd64 windows/arm64; do \
5959
os=$(echo $platform | cut -d'/' -f1); \
6060
arch=$(echo $platform | cut -d'/' -f2); \
6161
if [ "$os" = "windows" ]; then \
62-
out_name="oadp-vmdp_${VERSION}_${os}_${arch}.exe"; \
62+
out_name="oadp-vmdp_${os}_${arch}.exe"; \
6363
else \
64-
out_name="oadp-vmdp_${VERSION}_${os}_${arch}"; \
64+
out_name="oadp-vmdp_${os}_${arch}"; \
6565
fi; \
6666
echo "Building oadp-vmdp for ${os}/${arch}..."; \
6767
CGO_ENABLED=0 GOOS=$os GOARCH=$arch \
@@ -73,8 +73,9 @@ RUN mkdir -p /archives && \
7373
-X github.com/kopia/kopia/repo.BuildGitHubRepo=github.com/openshift/oadp-vmdp" \
7474
-o /archives/$out_name \
7575
. ; \
76+
sha256sum /archives/$out_name > /archives/$out_name.sha256; \
7677
done && \
77-
cd /archives && sha256sum oadp-vmdp_* > sha256sum.txt && \
78+
cp LICENSE /archives/LICENSE && \
7879
rm -rf /root/.cache/go-build /tmp/*
7980

8081
# Build the download server for the TARGET platform (the arch this container will run on)

cmd/downloads/server.go

Lines changed: 43 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import (
1515
)
1616

1717
const (
18-
checksumFile = "sha256sum.txt"
1918
binaryPrefix = "oadp-vmdp_"
19+
licenseFile = "LICENSE"
2020
bytesPerMB = 1024 * 1024
2121
minPlatformParts = 3
2222
readTimeout = 10 * time.Second
@@ -30,7 +30,7 @@ var templateFS embed.FS
3030
var staticFS embed.FS
3131

3232
var (
33-
archiveDir = getEnv("ARCHIVE_DIR", "/archives")
33+
binaryDir = getEnv("ARCHIVE_DIR", "/archives")
3434
port = getEnv("PORT", "8080")
3535
pageTemplate = template.Must(template.ParseFS(templateFS, "templates/index.html"))
3636
)
@@ -43,7 +43,7 @@ func getEnv(key, fallback string) string {
4343
return fallback
4444
}
4545

46-
type archiveFile struct {
46+
type binaryFile struct {
4747
Name string
4848
Size float64
4949
OS string
@@ -52,12 +52,12 @@ type archiveFile struct {
5252
}
5353

5454
func main() {
55-
files, err := listBinaryFiles()
55+
files, err := discoverBinaries()
5656
if err != nil || len(files) == 0 {
57-
log.Fatal("No downloadable files found in ", archiveDir)
57+
log.Fatal("No binaries found in ", binaryDir)
5858
}
5959

60-
log.Printf("Found %d downloadable files", len(files))
60+
log.Printf("Found %d binaries", len(files))
6161

6262
staticContent, err := fs.Sub(staticFS, "static")
6363
if err != nil {
@@ -69,7 +69,7 @@ func main() {
6969
http.HandleFunc("/download/", downloadBinary)
7070

7171
log.Printf("Starting server on port %s", port)
72-
log.Printf("Serving files from %s", archiveDir)
72+
log.Printf("Serving binaries from %s", binaryDir)
7373

7474
srv := &http.Server{
7575
Addr: ":" + port,
@@ -82,66 +82,47 @@ func main() {
8282
}
8383
}
8484

85-
// listBinaryFiles returns all downloadable files (excludes sha256sum.txt and .sha256 sidecars).
86-
func listBinaryFiles() ([]string, error) {
87-
entries, err := os.ReadDir(archiveDir)
85+
// discoverBinaries finds oadp-vmdp binaries (excluding .sha256 and LICENSE files).
86+
func discoverBinaries() ([]string, error) {
87+
entries, err := os.ReadDir(binaryDir)
8888
if err != nil {
89-
return nil, fmt.Errorf("reading archive directory: %w", err)
89+
return nil, fmt.Errorf("reading binary directory: %w", err)
9090
}
9191

92-
var files []string
92+
var binaries []string
9393

9494
for _, e := range entries {
95-
if e.IsDir() {
95+
name := e.Name()
96+
97+
if e.IsDir() || strings.HasSuffix(name, ".sha256") || name == licenseFile {
9698
continue
9799
}
98100

99-
name := e.Name()
100-
101-
if strings.HasPrefix(name, binaryPrefix) &&
102-
!strings.HasSuffix(name, ".sha256") &&
103-
name != checksumFile {
104-
files = append(files, filepath.Join(archiveDir, name))
101+
if strings.HasPrefix(name, binaryPrefix) {
102+
binaries = append(binaries, filepath.Join(binaryDir, name))
105103
}
106104
}
107105

108-
return files, nil
106+
return binaries, nil
109107
}
110108

111-
// readChecksum reads a SHA256 checksum from a .sha256 sidecar file or
112-
// falls back to looking up the filename in sha256sum.txt.
109+
// readChecksum reads a SHA256 checksum from a per-file .sha256 sidecar file.
113110
func readChecksum(filePath string) string {
114-
// Try per-file .sha256 sidecar first (oadp-cli style).
115-
data, err := os.ReadFile(filePath + ".sha256") //nolint:gosec // path is constructed from archiveDir constant
116-
if err == nil {
117-
fields := strings.Fields(string(data))
118-
if len(fields) > 0 {
119-
return fields[0]
120-
}
121-
}
122-
123-
// Fall back to sha256sum.txt.
124-
sumFile := filepath.Join(filepath.Dir(filePath), checksumFile)
125-
126-
data, err = os.ReadFile(sumFile) //nolint:gosec // path is constructed from archiveDir constant
111+
data, err := os.ReadFile(filePath + ".sha256") //nolint:gosec // path is constructed from binaryDir constant
127112
if err != nil {
128113
return ""
129114
}
130115

131-
base := filepath.Base(filePath)
132-
133-
for line := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") {
134-
fields := strings.Fields(line)
135-
if len(fields) == 2 && fields[1] == base {
136-
return fields[0]
137-
}
116+
fields := strings.Fields(string(data))
117+
if len(fields) > 0 {
118+
return fields[0]
138119
}
139120

140121
return ""
141122
}
142123

143124
// parsePlatform extracts OS and architecture from a filename like
144-
// oadp-vmdp_v1.0.0_linux_amd64 or oadp-vmdp_v1.0.0_windows_arm64.exe.
125+
// oadp-vmdp_linux_amd64 or oadp-vmdp_windows_arm64.exe.
145126
func parsePlatform(filename string) (string, string) {
146127
name := strings.TrimSuffix(filename, ".exe")
147128

@@ -154,13 +135,18 @@ func parsePlatform(filename string) (string, string) {
154135
}
155136

156137
func listBinaries(w http.ResponseWriter, _ *http.Request) {
157-
files, err := listBinaryFiles()
138+
files, err := discoverBinaries()
158139
if err != nil {
159-
http.Error(w, "Error listing files", http.StatusInternalServerError)
140+
http.Error(w, "Error listing binaries", http.StatusInternalServerError)
160141
return
161142
}
162143

163-
var linuxFiles, windowsFiles []archiveFile
144+
hasLicense := false
145+
if _, err := os.Stat(filepath.Join(binaryDir, licenseFile)); err == nil {
146+
hasLicense = true
147+
}
148+
149+
var linuxFiles, windowsFiles []binaryFile
164150

165151
for _, file := range files {
166152
name := filepath.Base(file)
@@ -174,20 +160,21 @@ func listBinaries(w http.ResponseWriter, _ *http.Request) {
174160
osName, arch := parsePlatform(name)
175161
checksum := readChecksum(file)
176162

177-
af := archiveFile{Name: name, Size: size, OS: osName, Arch: arch, Checksum: checksum}
163+
bf := binaryFile{Name: name, Size: size, OS: osName, Arch: arch, Checksum: checksum}
178164

179165
switch osName {
180166
case "linux":
181-
linuxFiles = append(linuxFiles, af)
167+
linuxFiles = append(linuxFiles, bf)
182168
case "windows":
183-
windowsFiles = append(windowsFiles, af)
169+
windowsFiles = append(windowsFiles, bf)
184170
}
185171
}
186172

187173
data := struct {
188-
LinuxFiles []archiveFile
189-
WindowsFiles []archiveFile
190-
}{linuxFiles, windowsFiles}
174+
LinuxFiles []binaryFile
175+
WindowsFiles []binaryFile
176+
HasLicense bool
177+
}{linuxFiles, windowsFiles, hasLicense}
191178

192179
w.Header().Set("Content-Type", "text/html")
193180

@@ -199,18 +186,18 @@ func listBinaries(w http.ResponseWriter, _ *http.Request) {
199186
func downloadBinary(w http.ResponseWriter, r *http.Request) {
200187
filename := filepath.Base(r.URL.Path[len("/download/"):])
201188

202-
// Security: only allow known file prefixes and the checksum file.
189+
// Security: only allow known file prefixes and the LICENSE file.
203190
if filepath.Dir(filename) != "." {
204191
http.Error(w, "Invalid filename", http.StatusBadRequest)
205192
return
206193
}
207194

208-
if !strings.HasPrefix(filename, binaryPrefix) && filename != checksumFile {
195+
if !strings.HasPrefix(filename, binaryPrefix) && filename != licenseFile {
209196
http.Error(w, "Invalid filename", http.StatusBadRequest)
210197
return
211198
}
212199

213-
filePath := filepath.Join(archiveDir, filename)
200+
filePath := filepath.Join(binaryDir, filename)
214201

215202
if _, err := os.Stat(filePath); os.IsNotExist(err) {
216203
http.Error(w, "File not found", http.StatusNotFound)
@@ -219,7 +206,7 @@ func downloadBinary(w http.ResponseWriter, r *http.Request) {
219206

220207
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
221208

222-
if filename == checksumFile {
209+
if filename == licenseFile {
223210
w.Header().Set("Content-Type", "text/plain")
224211
} else {
225212
w.Header().Set("Content-Type", "application/octet-stream")

cmd/downloads/templates/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@
7272
</div>
7373
{{end}}
7474

75+
{{if .HasLicense}}
76+
<div class="section">
77+
<div class="section-header">License</div>
78+
<div style="padding: 0.85rem 1.25rem; display: flex; align-items: center; justify-content: space-between;">
79+
<span style="font-size: 0.9rem;">LICENSE</span>
80+
<a class="download-btn" href="/download/LICENSE">View License</a>
81+
</div>
82+
</div>
83+
{{end}}
84+
7585
<div class="install-section">
7686
<h3>Installation</h3>
7787

konflux.Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ ARG BUILD_DATE=unknown
1616
ARG BUILDTAGS=
1717

1818
# Build oadp-vmdp binaries for all target platforms as direct executables
19+
# with clean names (no version/commit hash) for direct curl/wget download.
1920
RUN mkdir -p /archives && \
2021
for platform in linux/amd64 linux/arm64 windows/amd64 windows/arm64; do \
2122
os=$(echo $platform | cut -d'/' -f1); \
2223
arch=$(echo $platform | cut -d'/' -f2); \
2324
if [ "$os" = "windows" ]; then \
24-
out_name="oadp-vmdp_${VERSION}_${os}_${arch}.exe"; \
25+
out_name="oadp-vmdp_${os}_${arch}.exe"; \
2526
else \
26-
out_name="oadp-vmdp_${VERSION}_${os}_${arch}"; \
27+
out_name="oadp-vmdp_${os}_${arch}"; \
2728
fi; \
2829
echo "Building oadp-vmdp for ${os}/${arch}..."; \
2930
CGO_ENABLED=0 GOOS=$os GOARCH=$arch \
@@ -35,8 +36,9 @@ RUN mkdir -p /archives && \
3536
-X github.com/kopia/kopia/repo.BuildGitHubRepo=github.com/openshift/oadp-vmdp" \
3637
-o /archives/$out_name \
3738
. ; \
39+
sha256sum /archives/$out_name > /archives/$out_name.sha256; \
3840
done && \
39-
cd /archives && sha256sum oadp-vmdp_* > sha256sum.txt && \
41+
cp LICENSE /archives/LICENSE && \
4042
rm -rf /root/.cache/go-build /tmp/*
4143

4244
# Build the download server (FIPS-compliant)

0 commit comments

Comments
 (0)