Files
ReleaseKit/internal/delivery/assets.go

92 lines
3.1 KiB
Go

package delivery
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"strings"
internalbuild "gitea.maximumdirect.net/eric/ReleaseKit/internal/build"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gitea"
)
func declaredBuildAssets(result internalbuild.Result) ([]gitea.ExpectedAsset, error) {
if len(result.Targets) == 0 {
if len(result.Artifacts) != 0 {
return nil, fmt.Errorf("source-only build result contains artifacts")
}
return nil, nil
}
if len(result.Artifacts) != len(result.Targets) {
return nil, fmt.Errorf("build result must contain one artifact for each selected target")
}
wantByTarget := make(map[string]string, len(result.Targets))
for _, target := range result.Targets {
if target.OS == "" || target.Arch == "" || strings.ContainsAny(target.OS+target.Arch, "/\\\r\n") {
return nil, fmt.Errorf("build result contains an invalid target")
}
key := target.OS + "/" + target.Arch
if _, exists := wantByTarget[key]; exists {
return nil, fmt.Errorf("build result repeats target %q", key)
}
name := result.BinaryName + "-" + result.Version + "-" + target.OS + "-" + target.Arch
if target.OS == "windows" {
name += ".exe"
}
wantByTarget[key] = name
}
assets := make([]gitea.ExpectedAsset, 0, len(result.Artifacts)+1)
seenTargets := make(map[string]bool, len(result.Artifacts))
for _, artifact := range result.Artifacts {
key := artifact.Target.OS + "/" + artifact.Target.Arch
wantName, ok := wantByTarget[key]
if !ok || seenTargets[key] || artifact.Filename != wantName {
return nil, fmt.Errorf("build artifact %q does not match a selected target and its declared name", artifact.Filename)
}
seenTargets[key] = true
if filepath.Base(artifact.Path) != artifact.Filename {
return nil, fmt.Errorf("build artifact path %q does not end with filename %q", artifact.Path, artifact.Filename)
}
info, err := os.Stat(artifact.Path)
if err != nil {
return nil, fmt.Errorf("inspect build artifact %q: %w", artifact.Filename, err)
}
if !info.Mode().IsRegular() {
return nil, fmt.Errorf("build artifact %q is not a regular file", artifact.Filename)
}
file, err := os.Open(artifact.Path)
if err != nil {
return nil, fmt.Errorf("open build artifact %q: %w", artifact.Filename, err)
}
hash := sha256.New()
_, copyErr := io.Copy(hash, file)
closeErr := file.Close()
if copyErr != nil {
return nil, fmt.Errorf("hash build artifact %q: %w", artifact.Filename, copyErr)
}
if closeErr != nil {
return nil, fmt.Errorf("close build artifact %q: %w", artifact.Filename, closeErr)
}
if artifact.SHA256 != hex.EncodeToString(hash.Sum(nil)) {
return nil, fmt.Errorf("build artifact %q does not match its declared SHA-256 digest", artifact.Filename)
}
assets = append(assets, gitea.ExpectedAsset{
Filename: artifact.Filename,
SHA256: artifact.SHA256,
Size: info.Size(),
Path: artifact.Path,
})
}
manifest, err := gitea.BuildManifest(assets)
if err != nil {
return nil, fmt.Errorf("build checksum manifest: %w", err)
}
if manifest.Filename != "" {
assets = append(assets, manifest)
}
return assets, nil
}