Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-05-26 10:12:37 -07:00
parent e6f988834d
commit e040851a4c
20 changed files with 219 additions and 1 deletions

View File

@@ -59,12 +59,14 @@ func extractTarGzFile(archivePath, wantPath, dest string) error {
if err != nil {
return fmt.Errorf("cannot open archive: %w", err)
}
defer func() { _ = f.Close() }()
gz, err := gzip.NewReader(f)
if err != nil {
return fmt.Errorf("cannot read gzip: %w", err)
}
defer func() { _ = gz.Close() }()
tr := tar.NewReader(gz)
@@ -82,7 +84,7 @@ func extractTarGzFile(archivePath, wantPath, dest string) error {
continue
}
if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
if hdr.Typeflag != tar.TypeReg {
return fmt.Errorf("update: %s is not a regular file", wantPath)
}
@@ -97,6 +99,7 @@ func extractZipFile(archivePath, wantPath, dest string) error {
if err != nil {
return fmt.Errorf("cannot open zip: %w", err)
}
defer func() { _ = r.Close() }()
for _, f := range r.File {

View File

@@ -54,6 +54,7 @@ func LayoutFor(goos, goarch string) (AssetLayout, error) {
binary := "probo-agent"
isZip := false
ext := "tar.gz"
if goos == "windows" {
binary += ".exe"
isZip = true

View File

@@ -26,6 +26,7 @@ func copyFile(src, dst string) error {
if err != nil {
return fmt.Errorf("cannot open %s: %w", src, err)
}
defer func() { _ = in.Close() }()
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
@@ -40,11 +41,14 @@ func copyFile(src, dst string) error {
if _, err := io.Copy(out, in); err != nil {
_ = out.Close()
_ = os.Remove(dst)
return fmt.Errorf("cannot copy to %s: %w", dst, err)
}
if err := out.Sync(); err != nil {
_ = out.Close()
_ = os.Remove(dst)
return fmt.Errorf("cannot fsync %s: %w", dst, err)
}

View File

@@ -46,6 +46,7 @@ func replaceBinary(dst, src string) error {
if err := copyFile(src, staging); err != nil {
return err
}
if err := os.Chmod(staging, 0o755); err != nil {
_ = os.Remove(staging)
return fmt.Errorf("cannot chmod staged binary: %w", err)

View File

@@ -135,6 +135,7 @@ func (j *jsonTimestamp) UnmarshalJSON(b []byte) error {
}
*j = jsonTimestamp(t)
return nil
}
@@ -191,6 +192,7 @@ func (u *Updater) CheckLatest(ctx context.Context) (*Release, error) {
current := normalizeSemver(u.CurrentVersion)
var best *Release
for i := range releases {
rel := &releases[i]
if rel.Draft || rel.Prerelease {
@@ -217,10 +219,12 @@ func (u *Updater) CheckLatest(ctx context.Context) (*Release, error) {
if !ok {
continue
}
checksumURL, ok := findAssetURL(rel.Assets, checksumFileName)
if !ok {
continue
}
bundleURL, ok := findAssetURL(rel.Assets, checksumBundleFileName)
if !ok {
// Releases without a Sigstore bundle predate the
@@ -281,6 +285,7 @@ func (u *Updater) Apply(ctx context.Context, rel *Release) error {
if err != nil {
return fmt.Errorf("cannot create update workdir: %w", err)
}
defer func() { _ = os.RemoveAll(workDir) }()
archivePath := filepath.Join(workDir, layout.ArchiveName)
@@ -353,6 +358,7 @@ func (u *Updater) resolveVerifier() (Verifier, error) {
}
u.Verifier = v
return v, nil
}
@@ -381,6 +387,7 @@ func (u *Updater) listReleases(ctx context.Context) ([]githubRelease, error) {
if err != nil {
return nil, fmt.Errorf("cannot build releases request: %w", err)
}
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("User-Agent", u.userAgent())
@@ -388,6 +395,7 @@ func (u *Updater) listReleases(ctx context.Context) ([]githubRelease, error) {
if err != nil {
return nil, fmt.Errorf("cannot fetch releases: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode >= 400 {
@@ -408,6 +416,7 @@ func (u *Updater) downloadFile(ctx context.Context, src, dst string) error {
if err != nil {
return fmt.Errorf("cannot build download request: %w", err)
}
req.Header.Set("Accept", "application/octet-stream")
req.Header.Set("User-Agent", u.userAgent())
@@ -415,6 +424,7 @@ func (u *Updater) downloadFile(ctx context.Context, src, dst string) error {
if err != nil {
return fmt.Errorf("cannot fetch %s: %w", src, err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode >= 400 {
@@ -423,6 +433,7 @@ func (u *Updater) downloadFile(ctx context.Context, src, dst string) error {
}
tmp := dst + ".part"
f, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
if err != nil {
return fmt.Errorf("cannot create %s: %w", tmp, err)
@@ -432,6 +443,7 @@ func (u *Updater) downloadFile(ctx context.Context, src, dst string) error {
_ = f.Close()
return fmt.Errorf("cannot stream %s: %w", src, err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("cannot close %s: %w", tmp, err)
}
@@ -440,6 +452,7 @@ func (u *Updater) downloadFile(ctx context.Context, src, dst string) error {
if err != nil {
return fmt.Errorf("cannot stat %s: %w", tmp, err)
}
if stat.Size() > defaultDownloadLimit {
_ = os.Remove(tmp)
return fmt.Errorf("download %s exceeds %d bytes", src, defaultDownloadLimit)
@@ -537,6 +550,7 @@ func verifyChecksum(archivePath, checksumPath, archiveName string) error {
if err != nil {
return fmt.Errorf("cannot open archive: %w", err)
}
defer func() { _ = f.Close() }()
h := sha256.New()
@@ -568,6 +582,7 @@ func readChecksum(path, archiveName string) (string, error) {
if line == "" {
continue
}
// `sha256sum` output is `<hex> <name>`; the GNU tool also
// supports a single-space separator and a leading `*` flag
// for binary mode. Handle both.

View File

@@ -163,6 +163,7 @@ func newFakeReleaseServer(t *testing.T, tag, version string, layout AssetLayout,
mux := http.NewServeMux()
mux.HandleFunc("/repos/getprobo/probo/releases", func(w http.ResponseWriter, r *http.Request) {
base := "http://" + r.Host
assets := []map[string]any{
{
"name": layout.ArchiveName,
@@ -179,6 +180,7 @@ func newFakeReleaseServer(t *testing.T, tag, version string, layout AssetLayout,
"browser_download_url": base + "/download/" + checksumBundleFileName,
})
}
body := []map[string]any{
{
"tag_name": frs.tag,
@@ -187,6 +189,7 @@ func newFakeReleaseServer(t *testing.T, tag, version string, layout AssetLayout,
"assets": assets,
},
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(body)
_ = version
@@ -206,6 +209,7 @@ func newFakeReleaseServer(t *testing.T, tag, version string, layout AssetLayout,
frs.server = httptest.NewServer(mux)
t.Cleanup(frs.server.Close)
return frs
}
@@ -213,9 +217,11 @@ func (f *fakeReleaseServer) URL() string { return f.server.URL }
func buildArchive(t *testing.T, layout AssetLayout, binary []byte) []byte {
t.Helper()
if layout.IsZip {
return buildZip(t, layout, binary)
}
return buildTarGz(t, layout, binary)
}
@@ -246,6 +252,7 @@ func buildTarGz(t *testing.T, layout AssetLayout, binary []byte) []byte {
data, err := os.ReadFile(out)
require.NoError(t, err)
return data
}
@@ -268,6 +275,7 @@ func buildZip(t *testing.T, layout AssetLayout, binary []byte) []byte {
data, err := os.ReadFile(out)
require.NoError(t, err)
return data
}

View File

@@ -106,12 +106,15 @@ func NewCosignVerifier(cfg CosignVerifierConfig) (*CosignVerifier, error) {
if cfg.Repo == "" {
return nil, fmt.Errorf("update: cosign verifier requires Repo")
}
if cfg.WorkflowPath == "" {
cfg.WorkflowPath = expectedWorkflowPath
}
if cfg.TagPrefix == "" {
cfg.TagPrefix = DefaultTagPrefix
}
if cfg.CacheDir == "" {
return nil, fmt.Errorf("update: cosign verifier requires CacheDir")
}
@@ -169,6 +172,7 @@ func (v *CosignVerifier) Verify(_ context.Context, artifactPath, bundlePath stri
if err != nil {
return fmt.Errorf("cannot open artifact for verification: %w", err)
}
defer func() { _ = artifact.Close() }()
policy := verify.NewPolicy(