Skip to content

Commit 9438e6f

Browse files
committed
fix(wfctl): preserve registry download checksums
1 parent 14140b6 commit 9438e6f

2 files changed

Lines changed: 105 additions & 8 deletions

File tree

cmd/wfctl/plugin_registry_sync.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,11 @@ func releaseExists(ghRepo, tag string) bool {
270270
}
271271

272272
type releaseAsset struct {
273-
Name string `json:"name"`
274-
OS string `json:"os"`
275-
Arch string `json:"arch"`
276-
URL string `json:"url"`
273+
Name string `json:"name"`
274+
OS string `json:"os"`
275+
Arch string `json:"arch"`
276+
URL string `json:"url"`
277+
SHA256 string `json:"sha256,omitempty"`
277278
}
278279

279280
// releaseDownloads returns the platform release-asset list for a tag, in the
@@ -294,17 +295,49 @@ func releaseDownloads(ghRepo, tag string) ([]releaseAsset, error) {
294295
if err := json.Unmarshal(out, &resp); err != nil {
295296
return nil, err
296297
}
298+
checksums, _ := releaseChecksums(ghRepo, tag)
297299
var assets []releaseAsset
298300
for _, a := range resp.Assets {
299301
goos, goarch, ok := releaseAssetPlatform(a.Name)
300302
if !ok {
301303
continue
302304
}
303-
assets = append(assets, releaseAsset{Name: a.Name, OS: goos, Arch: goarch, URL: a.URL})
305+
assets = append(assets, releaseAsset{
306+
Name: a.Name,
307+
OS: goos,
308+
Arch: goarch,
309+
URL: a.URL,
310+
SHA256: checksums[a.Name],
311+
})
304312
}
305313
return assets, nil
306314
}
307315

316+
func releaseChecksums(ghRepo, tag string) (map[string]string, error) {
317+
cmd := exec.Command("gh", "release", "download", tag, "--repo", ghRepo, "--pattern", "checksums.txt", "--output", "-") // #nosec G204 -- ghRepo+tag from trusted manifest
318+
out, err := cmd.Output()
319+
if err != nil {
320+
return nil, err
321+
}
322+
return parseReleaseChecksums(string(out)), nil
323+
}
324+
325+
func parseReleaseChecksums(text string) map[string]string {
326+
checksums := make(map[string]string)
327+
for _, line := range strings.Split(text, "\n") {
328+
fields := strings.Fields(line)
329+
if len(fields) < 2 {
330+
continue
331+
}
332+
sha, err := NormalizeSHA256Hex(fields[0])
333+
if err != nil {
334+
continue
335+
}
336+
checksums[filepath.Base(fields[len(fields)-1])] = sha
337+
}
338+
return checksums
339+
}
340+
308341
func releaseAssetPlatform(assetName string) (string, string, bool) {
309342
nameNoExt := strings.TrimSuffix(assetName, ".tar.gz")
310343
nameNoExt = strings.TrimSuffix(nameNoExt, ".tgz")
@@ -511,18 +544,22 @@ func versionGT(newVer, oldVer string) bool {
511544
}
512545

513546
func applyFix(manifestPath string, raw map[string]any, ghRepo, targetTag, targetVersion string) error {
514-
downloads, _ := releaseDownloads(ghRepo, targetTag)
547+
downloads, _ := registrySyncReleaseDownloads(ghRepo, targetTag)
515548
if len(downloads) == 0 {
516549
raw["version"] = targetVersion
517550
} else {
518551
raw["version"] = targetVersion
519552
dlAny := make([]any, 0, len(downloads))
520553
for _, dl := range downloads {
521-
dlAny = append(dlAny, map[string]any{
554+
entry := map[string]any{
522555
"os": dl.OS,
523556
"arch": dl.Arch,
524557
"url": dl.URL,
525-
})
558+
}
559+
if dl.SHA256 != "" {
560+
entry["sha256"] = dl.SHA256
561+
}
562+
dlAny = append(dlAny, entry)
526563
}
527564
raw["downloads"] = dlAny
528565
}

cmd/wfctl/plugin_registry_sync_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,24 @@ func TestPluginRegistrySync_ReleaseAssetPlatform(t *testing.T) {
145145
}
146146
}
147147

148+
func TestPluginRegistrySync_ParseReleaseChecksums(t *testing.T) {
149+
got := parseReleaseChecksums(`
150+
44A1F367B554555A872EC274D9B50D71372D40FA66704C3F806F1CAFAF14412C workflow-plugin-aws-darwin-amd64.tar.gz
151+
not-a-sha workflow-plugin-aws-linux-amd64.tar.gz
152+
1f1043c2addbc1a668873d12b1696c03ab428c32df034425fc072d2235af664b ./dist/workflow-plugin-aws-darwin-arm64.tar.gz
153+
`)
154+
155+
if got["workflow-plugin-aws-darwin-amd64.tar.gz"] != "44a1f367b554555a872ec274d9b50d71372d40fa66704c3f806f1cafaf14412c" {
156+
t.Fatalf("darwin amd64 checksum = %q", got["workflow-plugin-aws-darwin-amd64.tar.gz"])
157+
}
158+
if got["workflow-plugin-aws-darwin-arm64.tar.gz"] != "1f1043c2addbc1a668873d12b1696c03ab428c32df034425fc072d2235af664b" {
159+
t.Fatalf("darwin arm64 checksum = %q", got["workflow-plugin-aws-darwin-arm64.tar.gz"])
160+
}
161+
if _, ok := got["workflow-plugin-aws-linux-amd64.tar.gz"]; ok {
162+
t.Fatal("invalid checksum should be ignored")
163+
}
164+
}
165+
148166
func TestPluginRegistrySync_MetadataSyncProjectsIaCServices(t *testing.T) {
149167
raw := map[string]any{
150168
"capabilities": map[string]any{
@@ -498,6 +516,48 @@ func TestPluginRegistrySync_AssetBinaryName(t *testing.T) {
498516
}
499517
}
500518

519+
func TestPluginRegistrySync_ApplyFixIncludesDownloadChecksums(t *testing.T) {
520+
restoreRegistrySyncTestHooks(t)
521+
522+
registrySyncReleaseDownloads = func(ghRepo, tag string) ([]releaseAsset, error) {
523+
if ghRepo != "owner/repo" || tag != "v1.2.3" {
524+
t.Fatalf("releaseDownloads args = %q %q, want owner/repo v1.2.3", ghRepo, tag)
525+
}
526+
return []releaseAsset{{
527+
Name: "workflow-plugin-foo-linux-amd64.tar.gz",
528+
OS: "linux",
529+
Arch: "amd64",
530+
URL: "https://github.com/owner/repo/releases/download/v1.2.3/workflow-plugin-foo-linux-amd64.tar.gz",
531+
SHA256: strings.Repeat("a", 64),
532+
}}, nil
533+
}
534+
535+
manifest := filepath.Join(t.TempDir(), "manifest.json")
536+
raw := map[string]any{
537+
"name": "workflow-plugin-foo",
538+
"type": "external",
539+
"version": "1.0.0",
540+
}
541+
542+
if err := applyFix(manifest, raw, "owner/repo", "v1.2.3", "1.2.3"); err != nil {
543+
t.Fatalf("applyFix returned error: %v", err)
544+
}
545+
546+
var got map[string]any
547+
data, err := os.ReadFile(manifest)
548+
if err != nil {
549+
t.Fatal(err)
550+
}
551+
if err := json.Unmarshal(data, &got); err != nil {
552+
t.Fatal(err)
553+
}
554+
downloads := got["downloads"].([]any)
555+
first := downloads[0].(map[string]any)
556+
if first["sha256"] != strings.Repeat("a", 64) {
557+
t.Fatalf("sha256 = %q", first["sha256"])
558+
}
559+
}
560+
501561
func TestPluginRegistrySync_VerifyCapabilitiesDownloadsExtractsAndSkipsName(t *testing.T) {
502562
restoreRegistrySyncTestHooks(t)
503563

0 commit comments

Comments
 (0)