Repository: tetratelabs/getenvoy
Pull request #25 (2019-07-26 12:42:29)

Move to new manifest api
Resolves https://github.com/tetratelabs/getenvoy/issues/10. Depends on https://github.com/tetratelabs/getenvoy-package/pull/26.

Signed-off-by: Liam White <liam@tetrate.io>


Patch:
diff --git a/go.mod b/go.mod
index 9a2a1ff..f0b96b6 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,16 @@ module github.com/tetratelabs/getenvoy
 go 1.12
 
 require (
+	github.com/dsnet/compress v0.0.1 // indirect
 	github.com/golang/protobuf v1.3.2
+	github.com/golang/snappy v0.0.1 // indirect
+	github.com/mholt/archiver v3.1.1+incompatible
+	github.com/mitchellh/go-homedir v1.1.0
+	github.com/nwaples/rardecode v1.0.0 // indirect
+	github.com/pierrec/lz4 v2.0.5+incompatible // indirect
+	github.com/schollz/progressbar/v2 v2.13.2
 	github.com/spf13/cobra v0.0.5
-	github.com/stretchr/testify v1.2.2
-	github.com/tetratelabs/getenvoy-package v0.0.0-20190718134531-9487f25b3273
+	github.com/stretchr/testify v1.3.0
+	github.com/tetratelabs/getenvoy-package v0.0.0-20190730071641-da31aed4333e
+	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
 )
diff --git a/go.sum b/go.sum
index edf6076..969214b 100644
--- a/go.sum
+++ b/go.sum
@@ -4,27 +4,45 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
+github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
+github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
+github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
+github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
+github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
+github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/schollz/progressbar/v2 v2.13.2 h1:3L9bP5KQOGEnFP8P5V8dz+U0yo5I29iY5Oa9s9EAwn0=
+github.com/schollz/progressbar/v2 v2.13.2/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8=
 github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
@@ -36,11 +54,18 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/tetratelabs/getenvoy-package v0.0.0-20190718134531-9487f25b3273 h1:Iy7CXZY65z7KY0z6LXh2V2urACTo8EDBbKqXf8O+mRU=
-github.com/tetratelabs/getenvoy-package v0.0.0-20190718134531-9487f25b3273/go.mod h1:IrGJ2YZz3+qia1dEU9GFg3QieuMUUwiJf5tjG8uhogg=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/tetratelabs/getenvoy-package v0.0.0-20190730071641-da31aed4333e h1:P03rR9jXeD2ilkYVsRlDz/EyS2rTg5Nko8T/LzPri5k=
+github.com/tetratelabs/getenvoy-package v0.0.0-20190730071641-da31aed4333e/go.mod h1:IrGJ2YZz3+qia1dEU9GFg3QieuMUUwiJf5tjG8uhogg=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
+github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
+github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
+github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/pkg/binary/getenvoy/fetch.go b/pkg/binary/getenvoy/fetch.go
new file mode 100644
index 0000000..171f55f
--- /dev/null
+++ b/pkg/binary/getenvoy/fetch.go
@@ -0,0 +1,110 @@
+// Copyright 2019 Tetrate
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package getenvoy
+
+import (
+	"errors"
+	"net/http"
+	"path/filepath"
+
+	"os"
+
+	"fmt"
+
+	"io/ioutil"
+
+	"io"
+
+	"github.com/mholt/archiver"
+	"github.com/schollz/progressbar/v2"
+	"github.com/tetratelabs/getenvoy/pkg/manifest"
+)
+
+// Fetch downloads an Envoy binary from the passed location
+func (r *Runtime) Fetch(key *manifest.Key, binaryLocation string) error {
+	dst := filepath.Join(r.local, key.Flavor, key.Version, key.Platform)
+	if err := os.MkdirAll(dst, 0750); err != nil {
+		return fmt.Errorf("unable to create directory %q: %v", dst, err)
+	}
+	return fetchEnvoy(dst, binaryLocation)
+}
+
+func fetchEnvoy(dst, src string) error {
+	tmpDir, err := ioutil.TempDir("", "getenvoy-")
+	if err != nil {
+		return err
+	}
+	defer func() { _ = os.RemoveAll(tmpDir) }()
+
+	tarball, err := doDownload(tmpDir, src)
+	if err != nil {
+		return fmt.Errorf("unable to fetch envoy from %q: %v", src, err)
+	}
+	if err := extractEnvoy(dst, tarball); err != nil {
+		return fmt.Errorf("unable to extract envoy to %q: %v", dst, err)
+	}
+	return nil
+}
+
+func doDownload(dst, src string) (string, error) {
+	// #nosec -> src destination can be anywhere by design
+	resp, err := http.Get(src)
+	if err != nil {
+		return "", err
+	}
+	defer func() { _ = resp.Body.Close() }()
+
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("received %v status code from %q", resp.StatusCode, src)
+	}
+
+	tarball := filepath.Join(dst, "envoy.tar.gz")
+	f, err := os.OpenFile(tarball, os.O_CREATE|os.O_WRONLY, 0600)
+	if err != nil {
+		return "", err
+	}
+	defer func() { _ = f.Close() }()
+
+	bar := progressbar.NewOptions64(resp.ContentLength, progressbar.OptionSetDescription("[Fetching Envoy]"))
+	out := io.MultiWriter(f, bar)
+	_, err = io.Copy(out, resp.Body)
+	return tarball, err
+}
+
+func extractEnvoy(dst, tarball string) error {
+	// #nosec -> envoy binary needs to be executable
+	envoy, err := os.OpenFile(filepath.Join(dst, "envoy"), os.O_CREATE|os.O_WRONLY, 0755)
+	if err != nil {
+		return err
+	}
+	defer func() { _ = envoy.Close() }()
+	// Walk the tarball until we find file named envoy, then copy to our destination
+	found := false
+	if err := archiver.Walk(tarball, func(f archiver.File) error {
+		if f.Name() == "envoy" && !f.IsDir() {
+			found = true
+			if _, err := io.Copy(envoy, f); err != nil {
+				return err
+			}
+		}
+		return nil
+	}); err != nil {
+		return err
+	}
+	if !found {
+		return errors.New("unable to find Envoy binary in downloaded tarball")
+	}
+	return nil
+}
diff --git a/pkg/binary/run.go b/pkg/binary/getenvoy/run.go
similarity index 93%
rename from pkg/binary/run.go
rename to pkg/binary/getenvoy/run.go
index 643d950..46899a7 100644
--- a/pkg/binary/run.go
+++ b/pkg/binary/getenvoy/run.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package binary
+package getenvoy
 
 import (
 	"fmt"
@@ -22,7 +22,7 @@ import (
 )
 
 // Run execs the file at the path with the args passed
-func Run(path string, args []string) error {
+func (r *Runtime) Run(path string, args []string) error {
 	if _, err := os.Stat(path); err != nil {
 		return fmt.Errorf("unable to stat %q: %v", path, err)
 	}
diff --git a/pkg/binary/getenvoy/runtime.go b/pkg/binary/getenvoy/runtime.go
new file mode 100644
index 0000000..4152c07
--- /dev/null
+++ b/pkg/binary/getenvoy/runtime.go
@@ -0,0 +1,35 @@
+// Copyright 2019 Tetrate
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package getenvoy
+
+import (
+	"path/filepath"
+
+	"github.com/mitchellh/go-homedir"
+	"github.com/tetratelabs/getenvoy/pkg/binary"
+)
+
+// New creates a new GetEnvoy binary.Runtime with the local file storage set to the home directory
+func New() (binary.Runtime, error) {
+	usrDir, err := homedir.Dir()
+	return &Runtime{
+		local: filepath.Join(usrDir, ".getenvoy", "builds"),
+	}, err
+}
+
+// Runtime implements the GetEnvoy binary.Runtime
+type Runtime struct {
+	local string
+}
diff --git a/pkg/binary/runtime.go b/pkg/binary/runtime.go
new file mode 100644
index 0000000..077c3fb
--- /dev/null
+++ b/pkg/binary/runtime.go
@@ -0,0 +1,33 @@
+// Copyright 2019 Tetrate
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binary
+
+import "github.com/tetratelabs/getenvoy/pkg/manifest"
+
+// Runner wraps the Envoy Run interface
+type Runner interface {
+	Run(build string, args []string) error
+}
+
+// Fetcher wraps the Envoy Fetch interface
+type Fetcher interface {
+	Fetch(key *manifest.Key, binaryLocation string) error
+}
+
+// Runtime wraps the Run and Fetch interfaces
+type Runtime interface {
+	Runner
+	Fetcher
+}
diff --git a/pkg/cmd/fetch.go b/pkg/cmd/fetch.go
new file mode 100644
index 0000000..2988f6f
--- /dev/null
+++ b/pkg/cmd/fetch.go
@@ -0,0 +1,60 @@
+// Copyright 2019 Tetrate
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+	"errors"
+
+	"github.com/spf13/cobra"
+	"github.com/tetratelabs/getenvoy/pkg/binary/getenvoy"
+	"github.com/tetratelabs/getenvoy/pkg/manifest"
+)
+
+// NewFetchCmd create a command responsible for retrieving Envoy binaries
+func NewFetchCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "fetch [binary]",
+		Short: "Retreives the passed Envoy binary.",
+		Long: `
+Retreives the passed Envoy binary. 
+Location can be a full or partial manifest reference, use getenvoy list for a complete list of available builds.`,
+		Example: `# Fetch using a partial manifest reference to retrieve a build for your operating system.
+getenvoy fetch standard:1.11.1
+		
+# Fetch using a full manifest reference to retrieve a specific build. 
+getenvoy fetch standard:1.11.1/debian`,
+		Args: func(cmd *cobra.Command, args []string) error {
+			if len(args) == 0 {
+				return errors.New("missing binary parameter")
+			}
+			return nil
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			key, err := manifest.NewKey(args[0])
+			if err != nil {
+				return err
+			}
+			location, err := manifest.Locate(key, manifestURL)
+			if err != nil {
+				return err
+			}
+			runtime, err := getenvoy.New()
+			if err != nil {
+				return err
+			}
+			return runtime.Fetch(key, location)
+		},
+	}
+}
diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go
index ab71852..cb3edff 100644
--- a/pkg/cmd/list.go
+++ b/pkg/cmd/list.go
@@ -21,20 +21,13 @@ import (
 	"github.com/tetratelabs/getenvoy/pkg/manifest"
 )
 
-// ListCmd lists available Envoy binaries
-var (
-	manifestURL string
-	ListCmd     = &cobra.Command{
+// NewListCmd returns command that lists available Envoy binaries
+func NewListCmd() *cobra.Command {
+	return &cobra.Command{
 		Use:   "list",
 		Short: "Lists available Envoys from GetEnvoy.",
 		RunE: func(cmd *cobra.Command, _ []string) error {
 			return manifest.Print(os.Stdout, manifestURL)
 		},
 	}
-)
-
-func init() {
-	ListCmd.Flags().StringVarP(&manifestURL, "url", "u",
-		"https://bintray.com/tetrate/getenvoy/download_file?file_path=manifest.json", "overrides the manifest URL")
-	rootCmd.AddCommand(ListCmd)
 }
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
index fccfcd8..ff433d4 100644
--- a/pkg/cmd/root.go
+++ b/pkg/cmd/root.go
@@ -21,11 +21,26 @@ import (
 // NewRoot create a new root command and sets the cliVersion to the passed variable
 // TODO: Add version support on the command
 func NewRoot() *cobra.Command {
+	runCmd := NewRunCmd()
+	rootCmd.AddCommand(runCmd)
+
+	listCmd := NewListCmd()
+	rootCmd.AddCommand(listCmd)
+
+	fetchCmd := NewFetchCmd()
+	rootCmd.AddCommand(fetchCmd)
+
+	rootCmd.PersistentFlags().StringVarP(&manifestURL, "url", "u",
+		"https://tetrate.bintray.com/getenvoy/manifest.json", "sets the manifest URL")
 	return rootCmd
 }
 
-var rootCmd = &cobra.Command{
-	Use:   "getenvoy",
-	Short: "getenvoy",
-	Long:  "getenvoy",
-}
+var (
+	rootCmd = &cobra.Command{
+		Use:   "getenvoy",
+		Short: "getenvoy",
+		Long:  "getenvoy",
+	}
+
+	manifestURL string
+)
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index ffe0513..4b11e8e 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -16,20 +16,20 @@ package cmd
 
 import (
 	"errors"
-	"regexp"
 
 	"github.com/spf13/cobra"
-	"github.com/tetratelabs/getenvoy/pkg/binary"
+	"github.com/tetratelabs/getenvoy/pkg/binary/getenvoy"
 )
 
-// RunCmd starts the Envoy process
-var RunCmd = &cobra.Command{
-	Use:   "run [binary] -- <envoy-args>",
-	Short: "Starts an Envoy process using the binary passed.",
-	Long: `
+// NewRunCmd create a command responsible for starting an Envoy process
+func NewRunCmd() *cobra.Command {
+	return &cobra.Command{
+		Use:   "run [binary] -- <envoy-args>",
+		Short: "Starts an Envoy process using the binary passed.",
+		Long: `
 Starts an Envoy process using the binary passed. 
 Location can be a manifest reference or local file.`,
-	Example: `# Run using a manifest reference. Reference format is <flavor>:<version>.
+		Example: `# Run using a manifest reference. Reference format is <flavor>:<version>.
 getenvoy run standard:1.10.1 -- --config-path ./bootstrap.yaml
 
 # Run using a local file.
@@ -38,23 +38,18 @@ getenvoy run ./envoy -- --config-path ./bootstrap.yaml
 # List available Envoy flags
 getenvoy run standard:1.10.1 -- --help
 `,
-	Args: func(cmd *cobra.Command, args []string) error {
-		if len(args) == 0 {
-			return errors.New("missing binary parameter")
-		}
-		return nil
-	},
-	RunE: func(cmd *cobra.Command, args []string) error {
-		if match, _ := regexp.MatchString("^.*:.*$", args[0]); match {
-			// TODO: handle manifest reference.
-			// Download Envoy to ~/.getenvoy/builds/.... if not already there.
-			// Set args[0] to newly downloaded envoy.
+		Args: func(cmd *cobra.Command, args []string) error {
+			if len(args) == 0 {
+				return errors.New("missing binary parameter")
+			}
 			return nil
-		}
-		return binary.Run(args[0], args[1:])
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(RunCmd)
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			runtime, err := getenvoy.New()
+			if err != nil {
+				return err
+			}
+			return runtime.Run(args[0], args[1:])
+		},
+	}
 }
diff --git a/pkg/manifest/locate.go b/pkg/manifest/locate.go
index f42ffd6..1d1e122 100644
--- a/pkg/manifest/locate.go
+++ b/pkg/manifest/locate.go
@@ -18,30 +18,38 @@ import (
 	"errors"
 	"fmt"
 	"net/url"
+	"regexp"
 	"strings"
-
-	"github.com/tetratelabs/getenvoy-package/api"
 )
 
-// Key is the primary key used to locate Envoy builds in the manifest
-type Key struct {
-	Flavor                 string
-	Version                string
-	OperatingSystem        string
-	OperatingSystemVersion string
+// NewKey creates a manifest key based on the reference it is given
+func NewKey(reference string) (*Key, error) {
+	r := regexp.MustCompile(`^(.+):(.+)/(.+)$`)
+	matches := r.FindStringSubmatch(reference)
+	if len(matches) != 4 {
+		return nil, fmt.Errorf("reference %v is not of valid format <flavor>:<version>/<platform>", reference)
+	}
+
+	return &Key{strings.ToLower(matches[1]), strings.ToLower(matches[2]), platformToEnum(matches[3])}, nil
+}
+
+func platformToEnum(s string) string {
+	s = strings.ToUpper(s)
+	s = strings.ReplaceAll(s, "-", "_")
+	return s
 }
 
-func (k *Key) normalize() {
-	k.Flavor = strings.ToLower(k.Flavor)
-	k.Version = strings.ToLower(k.Version)
-	k.OperatingSystem = strings.ToLower(k.OperatingSystem)
-	k.OperatingSystemVersion = strings.ToLower(k.OperatingSystemVersion)
+// Key is the primary key used to locate Envoy builds in the manifest
+type Key struct {
+	Flavor   string
+	Version  string
+	Platform string
 }
 
 // Locate returns the location of the binary for the passed parameters from the passed manifest
 // The build version is searched for as a prefix of the OperatingSystemVersion.
 // If the OperatingSystemVersion is empty it returns the first build listed for that operating system
-func Locate(key Key, manifestLocation string) (string, error) {
+func Locate(key *Key, manifestLocation string) (string, error) {
 	if _, err := url.Parse(manifestLocation); err != nil {
 		return "", errors.New("only URL manifest locations are supported")
 	}
@@ -50,33 +58,13 @@ func Locate(key Key, manifestLocation string) (string, error) {
 		return "", err
 	}
 
-	key.normalize()
 	// This is pretty horrible... Not sure there is a nicer way though.
 	if manifest.Flavors[key.Flavor] != nil && manifest.Flavors[key.Flavor].Versions[key.Version] != nil {
-		for _, os := range manifest.Flavors[key.Flavor].Versions[key.Version].OperatingSystems {
-			if strings.EqualFold(os.Name.String(), key.OperatingSystem) {
-				if build, found := locateBuildForVersion(key.OperatingSystemVersion, os.Builds); found {
-					return build, nil
-				}
+		for _, build := range manifest.Flavors[key.Flavor].Versions[key.Version].Builds {
+			if strings.EqualFold(build.Platform.String(), key.Platform) {
+				return build.DownloadLocationUrl, nil
 			}
 		}
 	}
 	return "", fmt.Errorf("unable to find matching build for %v", key)
 }
-
-func locateBuildForVersion(want string, builds []*api.Build) (string, bool) {
-	if len(builds) == 0 {
-		return "", false
-	}
-	if want == "" {
-		return builds[0].DownloadLocationUrl, true
-	}
-	for _, build := range builds {
-		for _, osVersion := range build.OperatingSystemVersions {
-			if strings.HasPrefix(want, strings.ToLower(osVersion)) {
-				return build.DownloadLocationUrl, true
-			}
-		}
-	}
-	return "", false
-}
diff --git a/pkg/manifest/print.go b/pkg/manifest/print.go
index e42cf72..e20c1fd 100644
--- a/pkg/manifest/print.go
+++ b/pkg/manifest/print.go
@@ -20,11 +20,12 @@ import (
 	"io"
 	"net/http"
 	"sort"
-	"strings"
 	"text/tabwriter"
 
 	"net/url"
 
+	"strings"
+
 	"github.com/golang/protobuf/jsonpb"
 	"github.com/tetratelabs/getenvoy-package/api"
 )
@@ -39,24 +40,25 @@ func Print(writer io.Writer, manifestLocation string) error {
 		return err
 	}
 	w := new(tabwriter.Writer).Init(writer, 0, 8, 5, ' ', 0)
-	fmt.Fprintln(w, "FLAVOR\tVERSION\tAVAILABLE ON")
+	fmt.Fprintln(w, "REFERENCE\tFLAVOR\tVERSION")
 
 	for _, flavor := range deterministicFlavors(manifest.Flavors) {
 		for _, version := range deterministicVersions(flavor.Versions) {
-			osList := []string{}
-			for os := range version.GetOperatingSystems() {
-				osList = append(osList, os)
+			for _, build := range deterministicBuilds(version.Builds) {
+				ref := fmt.Sprintf("%v:%v/%v", flavor.Name, version.Name, platformFromEnum(build.Platform.String()))
+				fmt.Fprintf(w, "%v\t%v\t%v\n", ref, flavor.Name, version.Name)
 			}
-			sort.Slice(osList, func(i, j int) bool {
-				return strings.ToUpper(osList[i]) < strings.ToUpper(osList[j])
-			})
-			prettyOS := strings.Trim(fmt.Sprintf("%v", osList), "[]")
-			fmt.Fprintf(w, "%v\t%v\t%v\n", flavor.Name, version.Name, prettyOS)
 		}
 	}
 	return w.Flush()
 }
 
+func platformFromEnum(s string) string {
+	s = strings.ToLower(s)
+	s = strings.ReplaceAll(s, "_", "-")
+	return s
+}
+
 func fetch(manifestURL string) (*api.Manifest, error) {
 	// #nosec => This is by design, users can call out to wherever they like!
 	resp, err := http.Get(manifestURL)
@@ -97,3 +99,15 @@ func deterministicVersions(versions map[string]*api.Version) []*api.Version {
 	})
 	return versionList
 }
+
+func deterministicBuilds(builds map[string]*api.Build) []*api.Build {
+	buildList := []*api.Build{}
+	for _, flavor := range builds {
+		buildList = append(buildList, flavor)
+	}
+	sort.Slice(buildList, func(i, j int) bool {
+		// Note: build is reverse alphabetical so that Linux versions are before Darwin
+		return buildList[i].String() > buildList[j].String()
+	})
+	return buildList
+}


Test patch:
diff --git a/pkg/binary/getenvoy/fetch_test.go b/pkg/binary/getenvoy/fetch_test.go
new file mode 100644
index 0000000..d98fead
--- /dev/null
+++ b/pkg/binary/getenvoy/fetch_test.go
@@ -0,0 +1,101 @@
+// Copyright 2019 Tetrate
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package getenvoy
+
+import (
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"path/filepath"
+	"testing"
+
+	"os"
+
+	"github.com/mholt/archiver"
+	"github.com/stretchr/testify/assert"
+	"github.com/tetratelabs/getenvoy/pkg/manifest"
+)
+
+func TestRuntime_Fetch(t *testing.T) {
+	defaultDarwinKey := &manifest.Key{Flavor: "standard", Version: "1.11.0", Platform: "darwin"}
+	tests := []struct {
+		name             string
+		key              *manifest.Key
+		tarballStructure string
+		envoyLocation    string
+		responseStatus   int
+		wantErr          bool
+	}{
+		{
+			name:             "Downloads and untars envoy to local/key",
+			key:              defaultDarwinKey,
+			tarballStructure: "golden",
+			responseStatus:   http.StatusOK,
+			envoyLocation:    "standard/1.11.0/darwin/envoy",
+		},
+		{
+			name:             "Handles directories called Envoy",
+			key:              defaultDarwinKey,
+			tarballStructure: "envoydirectory",
+			responseStatus:   http.StatusOK,
+			envoyLocation:    "standard/1.11.0/darwin/envoy",
+		},
+		{
+			name:             "errors if it can't find an envoy binary in tarball",
+			key:              defaultDarwinKey,
+			tarballStructure: "noenvoy",
+			responseStatus:   http.StatusOK,
+			wantErr:          true,
+		},
+		{
+			name:             "errors if it gets !200 from download",
+			key:              defaultDarwinKey,
+			tarballStructure: "noenvoy",
+			responseStatus:   http.StatusTeapot,
+			wantErr:          true,
+		},
+	}
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			tmpDir, _ := ioutil.TempDir("", "getenvoy-test-")
+			defer os.RemoveAll(tmpDir)
+			mock := mockServer(tc.responseStatus, tc.tarballStructure, tmpDir)
+
+			r := &Runtime{local: tmpDir}
+			err := r.Fetch(tc.key, mock.URL)
+			if tc.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.Nil(t, err)
+				f, _ := os.Open(filepath.Join(tmpDir, tc.envoyLocation))
+				bytes, _ := ioutil.ReadAll(f)
+				assert.Contains(t, string(bytes), "some complied c++")
+			}
+		})
+	}
+}
+
+func mockServer(responseStatusCode int, tarballStructure string, tmpDir string) *httptest.Server {
+	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(responseStatusCode)
+		if responseStatusCode == http.StatusOK {
+			tarball := filepath.Join(tmpDir, tarballStructure+".tar.gz")
+			archiver.Archive([]string{filepath.Join("testdata", tarballStructure)}, tarball)
+			bytes, _ := ioutil.ReadFile(tarball)
+			w.Write(bytes)
+		}
+	}))
+}
diff --git a/pkg/binary/getenvoy/testdata/envoydirectory/bin/envoy/envoy b/pkg/binary/getenvoy/testdata/envoydirectory/bin/envoy/envoy
new file mode 100644
index 0000000..820cfc4
--- /dev/null
+++ b/pkg/binary/getenvoy/testdata/envoydirectory/bin/envoy/envoy
@@ -0,0 +1,1 @@
+some complied c++
diff --git a/pkg/binary/getenvoy/testdata/golden/bin/envoy b/pkg/binary/getenvoy/testdata/golden/bin/envoy
new file mode 100644
index 0000000..820cfc4
--- /dev/null
+++ b/pkg/binary/getenvoy/testdata/golden/bin/envoy
@@ -0,0 +1,1 @@
+some complied c++
diff --git a/pkg/manifest/locate_test.go b/pkg/manifest/locate_test.go
index d4ec1c4..a5bc6cb 100644
--- a/pkg/manifest/locate_test.go
+++ b/pkg/manifest/locate_test.go
@@ -24,40 +24,30 @@ import (
 func TestLocate(t *testing.T) {
 	tests := []struct {
 		name             string
-		key              Key
+		reference        string
 		locationOverride string
 		want             string
 		wantErr          bool
 	}{
 		{
-			name: "Ubuntu bionic standard 1.11.0 matches",
-			key:  Key{"standard", "1.11.0", "Ubuntu", "bionic"},
-			want: "standard:1.11.0/debian",
+			name:      "standard 1.11.0 linux-glibc matches",
+			reference: "standard:1.11.0/linux-glibc",
+			want:      "standard:1.11.0/linux-glibc",
 		},
 		{
-			name: "Ubuntu xenial standard-fips1402 1.10.0 matches",
-			key:  Key{"standard-fips1402", "1.10.0", "Ubuntu", "xenial"},
-			want: "standard-fips1402:1.10.0/debian",
+			name:      "standard-fips1402:1.10.0/linux-glibc matches",
+			reference: "standard-fips1402:1.10.0/linux-glibc",
+			want:      "standard-fips1402:1.10.0/linux-glibc",
 		},
 		{
-			name: "CentOS 7.1 standard nightly matches to CentOS 7",
-			key:  Key{"standard", "nightly", "centos", "7.1"},
-			want: "standard:nightly/centos",
+			name:      "sTanDard:nIgHTLY/LiNuX-gLiBc matches",
+			reference: "sTanDard:nIgHTLY/LiNuX-gLiBc",
+			want:      "standard:nightly/linux-glibc",
 		},
 		{
-			name: "cEnTOS 7 sTanDard nIgHTLY matches",
-			key:  Key{"sTanDard", "nIgHTLY", "cEnTOS", "7"},
-			want: "standard:nightly/centos",
-		},
-		{
-			name: "MacOS standard 1.11.0 with no OS version returns the only macos build",
-			key:  Key{Flavor: "standard", Version: "1.11.0", OperatingSystem: "macos"},
-			want: "standard:1.11.0/macos",
-		},
-		{
-			name:    "Error if not found",
-			key:     Key{"notaFlavor", "1.11.0", "Ubuntu", "bionic"},
-			wantErr: true,
+			name:      "Error if not found",
+			reference: "notaFlavor:1.11.0/notaPlatform",
+			wantErr:   true,
 		},
 		{
 			name:             "Error on non-url manifest locations",
@@ -74,7 +64,8 @@ func TestLocate(t *testing.T) {
 			if tc.locationOverride != "" {
 				location = tc.locationOverride
 			}
-			if got, err := Locate(tc.key, location); tc.wantErr {
+			key, _ := NewKey(tc.reference)
+			if got, err := Locate(key, location); tc.wantErr {
 				assert.Error(t, err)
 				assert.Equal(t, "", got)
 			} else {
@@ -84,3 +75,32 @@ func TestLocate(t *testing.T) {
 		})
 	}
 }
+
+func TestNewKey(t *testing.T) {
+	tests := []struct {
+		reference string
+		want      *Key
+		wantErr   bool
+	}{
+		{"flavor:version/platform", &Key{Flavor: "flavor", Version: "version", Platform: "PLATFORM"}, false},
+		{"flavor:version/platform-glibc", &Key{Flavor: "flavor", Version: "version", Platform: "PLATFORM_GLIBC"}, false},
+		{"fLaVoR:VeRsIoN/pLaTfOrM", &Key{Flavor: "flavor", Version: "version", Platform: "PLATFORM"}, false},
+		{"flavor:version/", nil, true},
+		{"flavor:version", nil, true},
+		{"flavor:", nil, true},
+		{"flavor", nil, true},
+	}
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.reference, func(t *testing.T) {
+			got, err := NewKey(tc.reference)
+			if tc.wantErr {
+				assert.Error(t, err)
+				assert.Nil(t, got)
+			} else {
+				assert.Nil(t, err)
+				assert.Equal(t, tc.want, got)
+			}
+		})
+	}
+}
diff --git a/pkg/manifest/print_test.go b/pkg/manifest/print_test.go
index 982d594..57bc1da 100644
--- a/pkg/manifest/print_test.go
+++ b/pkg/manifest/print_test.go
@@ -128,47 +128,23 @@ func goodManifest() *api.Manifest {
 				Versions: map[string]*api.Version{
 					"1.11.0": {
 						Name: "1.11.0",
-						OperatingSystems: map[string]*api.OperatingSystem{
-							"Ubuntu": {
-								Name: api.OperatingSystemName_UBUNTU,
-								Builds: []*api.Build{
-									{
-										OperatingSystemVersions: []string{"xenial", "bionic"},
-										DownloadLocationUrl:     "standard:1.11.0/debian",
-									},
-								},
+						Builds: map[string]*api.Build{
+							api.Build_LINUX_GLIBC.String(): {
+								Platform:            api.Build_LINUX_GLIBC,
+								DownloadLocationUrl: "standard:1.11.0/linux-glibc",
 							},
-							"macOS": {
-								Name: api.OperatingSystemName_MACOS,
-								Builds: []*api.Build{
-									{
-										OperatingSystemVersions: []string{"10.14"},
-										DownloadLocationUrl:     "standard:1.11.0/macos",
-									},
-								},
-							},
-							"CentOS": {
-								Name: api.OperatingSystemName_CENTOS,
-								Builds: []*api.Build{
-									{
-										OperatingSystemVersions: []string{"7"},
-										DownloadLocationUrl:     "standard:1.11.0/centos",
-									},
-								},
+							api.Build_DARWIN.String(): {
+								Platform:            api.Build_DARWIN,
+								DownloadLocationUrl: "standard:1.11.0/darwin",
 							},
 						},
 					},
 					"nightly": {
 						Name: "nightly",
-						OperatingSystems: map[string]*api.OperatingSystem{
-							"CentOS": {
-								Name: api.OperatingSystemName_CENTOS,
-								Builds: []*api.Build{
-									{
-										OperatingSystemVersions: []string{"7"},
-										DownloadLocationUrl:     "standard:nightly/centos",
-									},
-								},
+						Builds: map[string]*api.Build{
+							api.Build_LINUX_GLIBC.String(): {
+								Platform:            api.Build_LINUX_GLIBC,
+								DownloadLocationUrl: "standard:nightly/linux-glibc",
 							},
 						},
 					},
@@ -177,28 +153,14 @@ func goodManifest() *api.Manifest {
 			"standard-fips1402": {
 				Name:          "standard-fips1402",
 				FilterProfile: "standard",
-				Compliances:   []api.Compliance{api.Compliance_FIPS_1402},
+				Compliances:   []api.Compliance{api.Compliance_FIPS1402},
 				Versions: map[string]*api.Version{
 					"1.10.0": {
 						Name: "1.10.0",
-						OperatingSystems: map[string]*api.OperatingSystem{
-							"Ubuntu": {
-								Name: api.OperatingSystemName_UBUNTU,
-								Builds: []*api.Build{
-									{
-										OperatingSystemVersions: []string{"xenial"},
-										DownloadLocationUrl:     "standard-fips1402:1.10.0/debian",
-									},
-								},
-							},
-							"CentOS": {
-								Name: api.OperatingSystemName_CENTOS,
-								Builds: []*api.Build{
-									{
-										OperatingSystemVersions: []string{"7"},
-										DownloadLocationUrl:     "standard-fips1402:1.10.0/centos",
-									},
-								},
+						Builds: map[string]*api.Build{
+							api.Build_LINUX_GLIBC.String(): {
+								Platform:            api.Build_LINUX_GLIBC,
+								DownloadLocationUrl: "standard-fips1402:1.10.0/linux-glibc",
 							},
 						},
 					},
diff --git a/pkg/manifest/testdata/list.golden b/pkg/manifest/testdata/list.golden
index cb0c5a6..e863ef6 100644
--- a/pkg/manifest/testdata/list.golden
+++ b/pkg/manifest/testdata/list.golden
@@ -1,4 +1,5 @@
-FLAVOR                VERSION     AVAILABLE ON
-standard              nightly     CentOS
-standard              1.11.0      CentOS macOS Ubuntu
-standard-fips1402     1.10.0      CentOS Ubuntu
+REFERENCE                                FLAVOR                VERSION
+standard:nightly/linux-glibc             standard              nightly
+standard:1.11.0/linux-glibc              standard              1.11.0
+standard:1.11.0/darwin                   standard              1.11.0
+standard-fips1402:1.10.0/linux-glibc     standard-fips1402     1.10.0
diff --git a/pkg/manifest/testdata/manifest.golden b/pkg/manifest/testdata/manifest.golden
index 2fb8b8f..7ef98db 100644
--- a/pkg/manifest/testdata/manifest.golden
+++ b/pkg/manifest/testdata/manifest.golden
@@ -7,46 +7,23 @@
       "versions": {
         "nightly": {
           "name": "nightly",
-          "operatingSystems": {
-            "CentOS": {
-              "name": "CENTOS",
-              "builds": [
-                {
-                  "operatingSystemVersions": ["7"],
-                  "downloadLocationUrl": "standard:nightly/centos"
-                }
-              ]
+          "builds": {
+            "LINUX_GLIBC": {
+              "downloadLocationUrl": "standard:nightly/linux-glibc",
+              "platform": "LINUX_GLIBC"
             }
           }
         },
         "1.11.0": {
           "name": "1.11.0",
-          "operatingSystems": {
-            "CentOS": {
-              "name": "CENTOS",
-              "builds": [
-                {
-                  "operatingSystemVersions": ["7"],
-                  "downloadLocationUrl": "standard:1.11.0/centos"
-                }
-              ]
+          "builds": {
+            "LINUX_GLIBC": {
+              "downloadLocationUrl": "standard:1.11.0/linux-glibc",
+              "platform": "LINUX_GLIBC"
             },
-            "macOS": {
-              "name": "MACOS",
-              "builds": [
-                {
-                  "operatingSystemVersions": ["10.14"],
-                  "downloadLocationUrl": "standard:1.11.0/macos"
-                }
-              ]
-            },
-            "Ubuntu": {
-              "builds": [
-                {
-                  "operatingSystemVersions": ["xenial", "bionic"],
-                  "downloadLocationUrl": "standard:1.11.0/debian"
-                }
-              ]
+            "DARWIN": {
+              "downloadLocationUrl": "standard:1.11.0/darwin",
+              "platform": "DARWIN"
             }
           }
         }
@@ -55,27 +32,14 @@
     "standard-fips1402": {
       "name": "standard-fips1402",
       "filterProfile": "standard",
-      "compliances": ["FIPS_1402"],
+      "compliances": ["FIPS1402"],
       "versions": {
         "1.10.0": {
           "name": "1.10.0",
-          "operatingSystems": {
-            "CentOS": {
-              "name": "CENTOS",
-              "builds": [
-                {
-                  "operatingSystemVersions": ["7"],
-                  "downloadLocationUrl": "standard-fips1402:1.10.0/centos"
-                }
-              ]
-            },
-            "Ubuntu": {
-              "builds": [
-                {
-                  "operatingSystemVersions": ["xenial"],
-                  "downloadLocationUrl": "standard-fips1402:1.10.0/debian"
-                }
-              ]
+          "builds": {
+            "LINUX_GLIBC": {
+              "downloadLocationUrl": "standard-fips1402:1.10.0/linux-glibc",
+              "platform": "LINUX_GLIBC"
             }
           }
         }