From 10090bb8b5c531f9dac75519a6201856b98f14ed Mon Sep 17 00:00:00 2001 From: "D. Bohdan" Date: Thu, 20 Feb 2025 19:44:02 +0000 Subject: [PATCH] build(release): build agent and create ZIP files Switch to SHA-256. --- script/release.go | 184 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 148 insertions(+), 36 deletions(-) diff --git a/script/release.go b/script/release.go index abd387c..386fadb 100644 --- a/script/release.go +++ b/script/release.go @@ -1,38 +1,53 @@ package main import ( - "crypto/sha512" + "archive/zip" + "crypto/sha256" "encoding/hex" + "errors" "fmt" "io" + "io/fs" "os" "os/exec" "path/filepath" ) const ( - checksumFilename = "SHA512SUMS.txt" + checksumFilename = "SHA256SUMS.txt" + dirPerms = 0o755 distDir = "dist" - pkg = "./cmd/pago" + filePerms = 0o644 projectName = "pago" ) +var ( + pkgs = []string{"./cmd/pago", "./cmd/pago-agent"} +) + type BuildTarget struct { os string arch string } func main() { + if err := buildAll(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func buildAll() error { version := os.Getenv("VERSION") if version == "" { - fmt.Fprintln(os.Stderr, "'VERSION' environment variable must be set") - os.Exit(1) + return errors.New("'VERSION' environment variable must be set") } releaseDir := filepath.Join(distDir, version) - if err := os.MkdirAll(releaseDir, 0755); err != nil { - fmt.Fprintf(os.Stderr, "Failed to create release directory: %v\n", err) - os.Exit(1) + checksumFilePath := filepath.Join(releaseDir, checksumFilename) + + if err := os.MkdirAll(releaseDir, dirPerms); err != nil { + return fmt.Errorf("failed to create release directory: %w", err) } targets := []BuildTarget{ @@ -46,23 +61,53 @@ func main() { {"openbsd", "amd64"}, } - for _, target := range targets { - if err := build(releaseDir, target, version); err != nil { - fmt.Fprintf(os.Stderr, "Build failed for %s/%s: %v\n", target.os, target.arch, err) - os.Exit(1) + for i, target := range targets { + if i > 0 { + fmt.Println() } - } -} + fmt.Printf("Building for %s/%s:\n", target.os, target.arch) -func build(dir string, target BuildTarget, version string) error { - fmt.Printf("Building for %s/%s\n", target.os, target.arch) + arch, system := userArchAndSystem(target) + targetDir := filepath.Join( + releaseDir, + fmt.Sprintf("%s-v%s-%s-%s", projectName, version, system, arch), + ) - ext := "" - if target.os == "windows" { - ext = ".exe" + if err := os.MkdirAll(targetDir, dirPerms); err != nil { + return fmt.Errorf("failed to create target directory: %w", err) + } + + var buildErrors []error + for _, pkg := range pkgs { + outputPath, err := build(target, targetDir, pkg) + if err != nil { + buildErrors = append(buildErrors, fmt.Errorf("build failed for %s on %s/%s: %w", pkg, target.os, target.arch, err)) + } + + if err := appendChecksum(checksumFilePath, outputPath); err != nil { + return err + } + } + + if len(buildErrors) > 0 { + return errors.Join(buildErrors...) + } + + zipPath := targetDir + ".zip" + if err := zipDirectory(zipPath, targetDir); err != nil { + return fmt.Errorf("failed to create ZIP archive: %w", err) + } + + if err := os.RemoveAll(targetDir); err != nil { + return fmt.Errorf("failed to remove target directory after archive creation: %w", err) + } } - // Map GOARCH and GOOS to user-facing names. + return nil +} + +// userArchAndSystem maps GOARCH and GOOS to user-facing names. +func userArchAndSystem(target BuildTarget) (string, string) { arch := target.arch system := target.os @@ -79,48 +124,115 @@ func build(dir string, target BuildTarget, version string) error { arch = "aarch64" } - filename := fmt.Sprintf("%s-v%s-%s-%s%s", projectName, version, system, arch, ext) - outputPath := filepath.Join(dir, filename) + return arch, system +} + +func build(target BuildTarget, dir, pkg string) (string, error) { + fmt.Printf(" - %s\n", pkg) + + ext := "" + if target.os == "windows" { + ext = ".exe" + } + + outputPath := filepath.Join(dir, filepath.Base(pkg)+ext) cmd := exec.Command("go", "build", "-trimpath", "-o", outputPath, pkg) cmd.Env = append(os.Environ(), - "GOOS="+target.os, - "GOARCH="+target.arch, + fmt.Sprintf("GOOS=%s", target.os), + fmt.Sprintf("GOARCH=%s", target.arch), "CGO_ENABLED=0", ) if output, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("Build command failed: %v\nOutput:\n%s", err, output) + return "", fmt.Errorf("build command failed: %w (output: %q)", err, output) } - return generateChecksum(outputPath, version) + return outputPath, nil } -func generateChecksum(filePath, version string) error { +func zipDirectory(zipPath, dirPath string) error { + zipFile, err := os.Create(zipPath) + if err != nil { + return fmt.Errorf("failed to create ZIP file: %v", err) + } + defer zipFile.Close() + + zipWriter := zip.NewWriter(zipFile) + defer zipWriter.Close() + + err = filepath.Walk(dirPath, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + // Use a relative path in the ZIP archive. + relPath, err := filepath.Rel(dirPath, path) + if err != nil { + return fmt.Errorf("failed to get relative path: %v", err) + } + + // Create an entry with the original modification time. + header, err := zip.FileInfoHeader(info) + if err != nil { + return fmt.Errorf("failed to create ZIP header: %v", err) + } + + header.Name = filepath.Join(filepath.Base(dirPath), relPath) + zipEntry, err := zipWriter.CreateHeader(header) + if err != nil { + return fmt.Errorf("failed to create ZIP entry: %v", err) + } + + // Write the file contents. + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open file for zipping: %v", err) + } + defer file.Close() + + if _, err := io.Copy(zipEntry, file); err != nil { + return fmt.Errorf("failed to write zip entry: %v", err) + } + + return nil + }) + + return err +} + +func appendChecksum(checksumFilePath, filePath string) error { f, err := os.Open(filePath) if err != nil { - return fmt.Errorf("Failed to open file for checksumming: %v", err) + return fmt.Errorf("failed to open file for checksumming: %v", err) } defer f.Close() - h := sha512.New() + h := sha256.New() if _, err := io.Copy(h, f); err != nil { - return fmt.Errorf("Failed to calculate hash: %v", err) + return fmt.Errorf("failed to calculate hash: %v", err) } hash := hex.EncodeToString(h.Sum(nil)) - checksumLine := fmt.Sprintf("%s %s\n", hash, filepath.Base(filePath)) + relPath, err := filepath.Rel(filepath.Dir(checksumFilePath), filePath) + if err != nil { + return fmt.Errorf("failed to get relative path: %v", err) + } + checksumLine := fmt.Sprintf("%s %s\n", hash, relPath) - checksumFilePath := filepath.Join(filepath.Dir(filePath), checksumFilename) - f, err = os.OpenFile(checksumFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + f, err = os.OpenFile(checksumFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, filePerms) if err != nil { - return fmt.Errorf("Failed to open checksum file: %v", err) + return fmt.Errorf("failed to open checksum file: %w", err) } defer f.Close() - if _, err := f.WriteString(checksumLine); err != nil { - return fmt.Errorf("Failed to write checksum: %v", err) + if _, err := io.WriteString(f, checksumLine); err != nil { + return fmt.Errorf("failed to write checksum: %w", err) } return nil