From dd5dfe16f8eee04ab0f2c5171797031a72451fc0 Mon Sep 17 00:00:00 2001
From: Stephen Lowrie <stephen.lowrie@gmail.com>
Date: Tue, 25 Jun 2019 15:59:42 -0500
Subject: [PATCH] cmd/plume/fcos: modify build metadata structure

Per discussions in the [fedora-coreos-tracker](https://github.com/coreos/fedora-coreos-tracker/issues/98#issuecomment-505104633), modify the build metadata structure to better support multi-arch.
---
 cmd/plume/fcos.go    |  4 ---
 cmd/plume/release.go | 78 +++++++++++++++++++++++++++++++++++++++++---
 cmd/plume/types.go   | 19 +++++++++--
 3 files changed, 89 insertions(+), 12 deletions(-)

diff --git a/cmd/plume/fcos.go b/cmd/plume/fcos.go
index 379887a0d..8d25678c1 100644
--- a/cmd/plume/fcos.go
+++ b/cmd/plume/fcos.go
@@ -31,7 +31,6 @@ var (
 
 func AddFcosSpecFlags(flags *pflag.FlagSet) {
 	flags.StringVar(&specPolicy, "policy", "public-read", "Canned ACL policy")
-	flags.StringVar(&specCommitId, "commit-id", "", "OSTree Commit ID")
 }
 
 func FcosValidateArguments() {
@@ -41,9 +40,6 @@ func FcosValidateArguments() {
 	if specChannel == "" {
 		plog.Fatal("--channel is required")
 	}
-	if specCommitId == "" {
-		plog.Fatal("--commit-id is required")
-	}
 }
 
 func FcosChannelSpec() fcosChannelSpec {
diff --git a/cmd/plume/release.go b/cmd/plume/release.go
index 845d45101..bec20f31f 100644
--- a/cmd/plume/release.go
+++ b/cmd/plume/release.go
@@ -518,25 +518,62 @@ func modifyReleaseMetadataIndex(spec *fcosChannelSpec, commitId string) {
 		plog.Fatalf("unmarshaling release metadata json: %v", err)
 	}
 
-	url, err := url.Parse(fmt.Sprintf("https://%s.s3.amazonaws.com/prod/streams/%s/builds/%s/release.json", spec.Bucket, specChannel, specVersion))
+	releasePath := filepath.Join("prod", "streams", specChannel, "builds", specVersion, "release.json")
+	url, err := url.Parse(fmt.Sprintf("https://%s.s3.amazonaws.com/%s", spec.Bucket, releasePath))
 	if err != nil {
 		plog.Fatalf("creating metadata url: %v", err)
 	}
 
+	releaseFile, err := api.DownloadFile(spec.Bucket, releasePath)
+	if err != nil {
+		plog.Fatalf("downloading release metadata: %v", err)
+	}
+	defer releaseFile.Close()
+
+	releaseData, err := ioutil.ReadAll(releaseFile)
+	if err != nil {
+		plog.Fatalf("reading release metadata: %v", err)
+	}
+
+	var im IndividualReleaseMetadata
+	err = json.Unmarshal(releaseData, &im)
+	if err != nil {
+		plog.Fatalf("unmarshaling release metadata: %v", err)
+	}
+
+	var commits []Commit
+	for arch, vals := range im.Architectures {
+		commits = append(commits, Commit{
+			Architecture: arch,
+			Checksum:     vals.Commit,
+		})
+	}
+
 	newRel := BuildMetadata{
-		CommitHash: specCommitId,
+		CommitHash: commits,
 		Version:    specVersion,
 		Endpoint:   url.String(),
 	}
 
 	for i, rel := range m.Releases {
-		if rel == newRel {
+		if compareStaticReleaseInfo(rel, newRel) {
 			if i != (len(m.Releases) - 1) {
 				plog.Fatalf("build is already present and is not the latest release")
 			}
 
-			// the build is already the latest release, exit
-			return
+			comp := compareCommits(rel.CommitHash, newRel.CommitHash)
+			if comp == 0 {
+				// the build is already the latest release, exit
+				return
+			} else if comp == -1 {
+				// the build is present and contains a subset of the new release data,
+				// pop the old entry and add the new version
+				m.Releases = m.Releases[:len(m.Releases)-1]
+				break
+			} else {
+				// the commit hash of the new build is not a superset of the current release
+				plog.Fatalf("build is present but commit hashes are not a superset of latest release")
+			}
 		}
 	}
 
@@ -555,3 +592,34 @@ func modifyReleaseMetadataIndex(spec *fcosChannelSpec, commitId string) {
 		plog.Fatalf("uploading release metadata json: %v", err)
 	}
 }
+
+func compareStaticReleaseInfo(a, b BuildMetadata) bool {
+	if a.Version != b.Version || a.Endpoint != b.Endpoint {
+		return false
+	}
+	return true
+}
+
+// returns -1 if a is a subset of b, 0 if equal, 1 if a is not a subset of b
+func compareCommits(a, b []Commit) int {
+	if len(a) > len(b) {
+		return 1
+	}
+	sameLength := len(a) == len(b)
+	for _, aHash := range a {
+		found := false
+		for _, bHash := range b {
+			if aHash.Architecture == bHash.Architecture && aHash.Checksum == bHash.Checksum {
+				found = true
+				break
+			}
+		}
+		if !found {
+			return 1
+		}
+	}
+	if sameLength {
+		return 0
+	}
+	return -1
+}
diff --git a/cmd/plume/types.go b/cmd/plume/types.go
index 54ac4f3aa..356b7a057 100644
--- a/cmd/plume/types.go
+++ b/cmd/plume/types.go
@@ -92,11 +92,24 @@ type ReleaseMetadata struct {
 }
 
 type BuildMetadata struct {
-	CommitHash string `json:"commit"`
-	Version    string `json:"version"`
-	Endpoint   string `json:"endpoint"`
+	CommitHash []Commit `json:"commits"`
+	Version    string   `json:"version"`
+	Endpoint   string   `json:"metadata"`
 }
 
 type Metadata struct {
 	LastModified string `json:"last-modified"`
 }
+
+type IndividualReleaseMetadata struct {
+	Architectures map[string]Architecture `json:"architectures"`
+}
+
+type Architecture struct {
+	Commit string `json:"commit"`
+}
+
+type Commit struct {
+	Architecture string `json:"architecture"`
+	Checksum     string `json:"checksum"`
+}