Repository: tetratelabs/getenvoy
Pull request #22 (2019-07-22 14:15:27)

Add binary locater to manifest API
Signed-off-by: Liam White <liam@tetrate.io>


Patch:
diff --git a/pkg/manifest/locate.go b/pkg/manifest/locate.go
new file mode 100644
index 0000000..f42ffd6
--- /dev/null
+++ b/pkg/manifest/locate.go
@@ -0,0 +1,82 @@
+// 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 manifest
+
+import (
+	"errors"
+	"fmt"
+	"net/url"
+	"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
+}
+
+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)
+}
+
+// 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) {
+	if _, err := url.Parse(manifestLocation); err != nil {
+		return "", errors.New("only URL manifest locations are supported")
+	}
+	manifest, err := fetch(manifestLocation)
+	if err != nil {
+		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
+				}
+			}
+		}
+	}
+	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
+}


Test patch:
diff --git a/pkg/manifest/locate_test.go b/pkg/manifest/locate_test.go
new file mode 100644
index 0000000..d4ec1c4
--- /dev/null
+++ b/pkg/manifest/locate_test.go
@@ -0,0 +1,86 @@
+// 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 manifest
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLocate(t *testing.T) {
+	tests := []struct {
+		name             string
+		key              Key
+		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: "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: "CentOS 7.1 standard nightly matches to CentOS 7",
+			key:  Key{"standard", "nightly", "centos", "7.1"},
+			want: "standard:nightly/centos",
+		},
+		{
+			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 on non-url manifest locations",
+			locationOverride: "not-a-url",
+			wantErr:          true,
+		},
+	}
+	for _, tt := range tests {
+		tc := tt
+		t.Run(tc.name, func(t *testing.T) {
+			mock := mockServer(http.StatusOK, "manifest.golden")
+			defer mock.Close()
+			location := mock.URL
+			if tc.locationOverride != "" {
+				location = tc.locationOverride
+			}
+			if got, err := Locate(tc.key, location); tc.wantErr {
+				assert.Error(t, err)
+				assert.Equal(t, "", got)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tc.want, got)
+			}
+		})
+	}
+}
diff --git a/pkg/manifest/print_test.go b/pkg/manifest/print_test.go
index 792bb3a..982d594 100644
--- a/pkg/manifest/print_test.go
+++ b/pkg/manifest/print_test.go
@@ -133,8 +133,8 @@ func goodManifest() *api.Manifest {
 								Name: api.OperatingSystemName_UBUNTU,
 								Builds: []*api.Build{
 									{
-										OperatingSystemVersions: []string{"16.04", "18.04"},
-										DownloadLocationUrl:     "http://example.com",
+										OperatingSystemVersions: []string{"xenial", "bionic"},
+										DownloadLocationUrl:     "standard:1.11.0/debian",
 									},
 								},
 							},
@@ -143,7 +143,7 @@ func goodManifest() *api.Manifest {
 								Builds: []*api.Build{
 									{
 										OperatingSystemVersions: []string{"10.14"},
-										DownloadLocationUrl:     "http://example.com",
+										DownloadLocationUrl:     "standard:1.11.0/macos",
 									},
 								},
 							},
@@ -152,7 +152,7 @@ func goodManifest() *api.Manifest {
 								Builds: []*api.Build{
 									{
 										OperatingSystemVersions: []string{"7"},
-										DownloadLocationUrl:     "http://example.com",
+										DownloadLocationUrl:     "standard:1.11.0/centos",
 									},
 								},
 							},
@@ -166,7 +166,7 @@ func goodManifest() *api.Manifest {
 								Builds: []*api.Build{
 									{
 										OperatingSystemVersions: []string{"7"},
-										DownloadLocationUrl:     "http://example.com",
+										DownloadLocationUrl:     "standard:nightly/centos",
 									},
 								},
 							},
@@ -186,8 +186,8 @@ func goodManifest() *api.Manifest {
 								Name: api.OperatingSystemName_UBUNTU,
 								Builds: []*api.Build{
 									{
-										OperatingSystemVersions: []string{"16.04"},
-										DownloadLocationUrl:     "http://example.com",
+										OperatingSystemVersions: []string{"xenial"},
+										DownloadLocationUrl:     "standard-fips1402:1.10.0/debian",
 									},
 								},
 							},
@@ -196,7 +196,7 @@ func goodManifest() *api.Manifest {
 								Builds: []*api.Build{
 									{
 										OperatingSystemVersions: []string{"7"},
-										DownloadLocationUrl:     "http://example.com",
+										DownloadLocationUrl:     "standard-fips1402:1.10.0/centos",
 									},
 								},
 							},
diff --git a/pkg/manifest/testdata/manifest.golden b/pkg/manifest/testdata/manifest.golden
index 4cd637a..2fb8b8f 100644
--- a/pkg/manifest/testdata/manifest.golden
+++ b/pkg/manifest/testdata/manifest.golden
@@ -13,7 +13,7 @@
               "builds": [
                 {
                   "operatingSystemVersions": ["7"],
-                  "downloadLocationUrl": "http://example.com"
+                  "downloadLocationUrl": "standard:nightly/centos"
                 }
               ]
             }
@@ -27,7 +27,7 @@
               "builds": [
                 {
                   "operatingSystemVersions": ["7"],
-                  "downloadLocationUrl": "http://example.com"
+                  "downloadLocationUrl": "standard:1.11.0/centos"
                 }
               ]
             },
@@ -36,15 +36,15 @@
               "builds": [
                 {
                   "operatingSystemVersions": ["10.14"],
-                  "downloadLocationUrl": "http://example.com"
+                  "downloadLocationUrl": "standard:1.11.0/macos"
                 }
               ]
             },
             "Ubuntu": {
               "builds": [
                 {
-                  "operatingSystemVersions": ["16.04", "18.04"],
-                  "downloadLocationUrl": "http://example.com"
+                  "operatingSystemVersions": ["xenial", "bionic"],
+                  "downloadLocationUrl": "standard:1.11.0/debian"
                 }
               ]
             }
@@ -65,15 +65,15 @@
               "builds": [
                 {
                   "operatingSystemVersions": ["7"],
-                  "downloadLocationUrl": "http://example.com"
+                  "downloadLocationUrl": "standard-fips1402:1.10.0/centos"
                 }
               ]
             },
             "Ubuntu": {
               "builds": [
                 {
-                  "operatingSystemVersions": ["16.04"],
-                  "downloadLocationUrl": "http://example.com"
+                  "operatingSystemVersions": ["xenial"],
+                  "downloadLocationUrl": "standard-fips1402:1.10.0/debian"
                 }
               ]
             }