diff --git a/discovery/discovery.go b/discovery/discovery.go index b2dc6595..17fe77ca 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -107,20 +107,26 @@ func extractACMeta(r io.Reader) []acMeta { } } -func renderTemplate(tpl string, kvs ...string) (string, bool) { +func renderTemplate(tpl string, kvs ...string) (string, int, bool) { + count := 0 for i := 0; i < len(kvs); i += 2 { k := kvs[i] v := kvs[i+1] + if k != "{name}" && strings.Contains(tpl, k) { + count++ + } tpl = strings.Replace(tpl, k, v, -1) } - return tpl, !templateExpression.MatchString(tpl) + return tpl, count, !templateExpression.MatchString(tpl) } func createTemplateVars(app App) []string { tplVars := []string{"{name}", app.Name.String()} - // If a label is called "name", it will be ignored as it appears after - // in the slice + // Ignore labels called "name" for n, v := range app.Labels { + if n == "name" { + continue + } tplVars = append(tplVars, fmt.Sprintf("{%s}", n), v) } return tplVars @@ -144,6 +150,7 @@ func doDiscover(pre string, hostHeaders map[string]http.Header, app App, insecur dd := &discoveryData{} + bestCount := 0 for _, m := range meta { if !strings.HasPrefix(app.Name.String(), m.prefix) { continue @@ -152,16 +159,22 @@ func doDiscover(pre string, hostHeaders map[string]http.Header, app App, insecur switch m.name { case "ac-discovery": // Ignore not handled variables as {ext} isn't already rendered. - uri, _ := renderTemplate(m.uri, tplVars...) - asc, ok := renderTemplate(uri, "{ext}", "aci.asc") + uri, count, _ := renderTemplate(m.uri, tplVars...) + asc, _, ok := renderTemplate(uri, "{ext}", "aci.asc") if !ok { continue } - aci, ok := renderTemplate(uri, "{ext}", "aci") + aci, _, ok := renderTemplate(uri, "{ext}", "aci") if !ok { continue } - dd.ACIEndpoints = append(dd.ACIEndpoints, ACIEndpoint{ACI: aci, ASC: asc}) + + if count > bestCount { + dd.ACIEndpoints = ACIEndpoints{ACIEndpoint{ACI: aci, ASC: asc}} + bestCount = count + } else if count == bestCount { + dd.ACIEndpoints = append(dd.ACIEndpoints, ACIEndpoint{ACI: aci, ASC: asc}) + } case "ac-discovery-pubkeys": dd.PublicKeys = append(dd.PublicKeys, m.uri) diff --git a/discovery/discovery_test.go b/discovery/discovery_test.go index bb2ca254..b55793c9 100644 --- a/discovery/discovery_test.go +++ b/discovery/discovery_test.go @@ -378,7 +378,8 @@ func TestDiscoverEndpoints(t *testing.T) { &mockHTTPDoer{ doer: fakeHTTPGet( []meta{ - {"/myapp", + { + "/myapp", "meta05.html", }, }, @@ -408,7 +409,8 @@ func TestDiscoverEndpoints(t *testing.T) { &mockHTTPDoer{ doer: fakeHTTPGet( []meta{ - {"/myapp", + { + "/myapp", "meta05.html", }, }, @@ -461,11 +463,13 @@ func TestDiscoverEndpoints(t *testing.T) { nil, }, // Test multiple ACIEndpoints. + // Should render the first two endpoint, since the others match fewer labels { &mockHTTPDoer{ doer: fakeHTTPGet( []meta{ - {"/myapp", + { + "/myapp", "meta06.html", }, }, @@ -488,12 +492,148 @@ func TestDiscoverEndpoints(t *testing.T) { ASC: "https://storage.example.com/example.com/myapp-1.0.0-linux-amd64.aci.asc", }, ACIEndpoint{ - ACI: "https://storage.example.com/example.com/myapp-1.0.0.aci", - ASC: "https://storage.example.com/example.com/myapp-1.0.0.aci.asc", + ACI: "https://mirror.storage.example.com/example.com/myapp-1.0.0-linux-amd64.aci", + ASC: "https://mirror.storage.example.com/example.com/myapp-1.0.0-linux-amd64.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + nil, + }, + // Test multiple ACIEndpoints. + // Should render endpoints 3 and 4, since 1 and 2 don't fully render and the others match fewer labels + // Example noarch versioned matching + { + &mockHTTPDoer{ + doer: fakeHTTPGet( + []meta{ + { + "/myapp", + "meta06.html", + }, + }, + nil, + ), + }, + true, + true, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + }, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp-1.0.0-noarch.aci", + ASC: "https://storage.example.com/example.com/myapp-1.0.0-noarch.aci.asc", + }, + ACIEndpoint{ + ACI: "https://mirror.storage.example.com/example.com/myapp-1.0.0-noarch.aci", + ASC: "https://mirror.storage.example.com/example.com/myapp-1.0.0-noarch.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + nil, + }, + // Test multiple ACIEndpoints. + // Should render endpoints 5 and 6, since 1, 2, 3, 4 don't fully render and the others match fewer labels + // Example latest matching + { + &mockHTTPDoer{ + doer: fakeHTTPGet( + []meta{ + { + "/myapp", + "meta06.html", + }, + }, + nil, + ), + }, + true, + true, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{ + "os": "linux", + "arch": "amd64", + }, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp-latest-linux-amd64.aci", + ASC: "https://storage.example.com/example.com/myapp-latest-linux-amd64.aci.asc", + }, + ACIEndpoint{ + ACI: "https://mirror.storage.example.com/example.com/myapp-latest-linux-amd64.aci", + ASC: "https://mirror.storage.example.com/example.com/myapp-latest-linux-amd64.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + nil, + }, + // Test multiple ACIEndpoints. + // Should render endpoints 7 and 8, since the others don't fully render. + // Example noarch latest matching + { + &mockHTTPDoer{ + doer: fakeHTTPGet( + []meta{ + { + "/myapp", + "meta06.html", + }, + }, + nil, + ), + }, + true, + true, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{}, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/example.com/myapp-latest-noarch.aci", + ASC: "https://storage.example.com/example.com/myapp-latest-noarch.aci.asc", }, ACIEndpoint{ - ACI: "hdfs://storage.example.com/example.com/myapp-1.0.0-linux-amd64.aci", - ASC: "hdfs://storage.example.com/example.com/myapp-1.0.0-linux-amd64.aci.asc", + ACI: "https://mirror.storage.example.com/example.com/myapp-latest-noarch.aci", + ASC: "https://mirror.storage.example.com/example.com/myapp-latest-noarch.aci.asc", + }, + }, + []string{"https://example.com/pubkeys.gpg"}, + nil, + }, + + // Test a discovery string that has an hardcoded app name instead of using the provided {name} + { + &mockHTTPDoer{ + doer: fakeHTTPGet( + []meta{ + { + "/myapp", + "meta07.html", + }, + }, + nil, + ), + }, + true, + true, + App{ + Name: "example.com/myapp", + Labels: map[types.ACIdentifier]string{ + "version": "1.0.0", + "os": "linux", + "arch": "amd64", + }, + }, + []ACIEndpoint{ + ACIEndpoint{ + ACI: "https://storage.example.com/myapp-1.0.0-linux-amd64.aci", + ASC: "https://storage.example.com/myapp-1.0.0-linux-amd64.aci.asc", }, }, []string{"https://example.com/pubkeys.gpg"}, @@ -505,7 +645,8 @@ func TestDiscoverEndpoints(t *testing.T) { &mockHTTPDoer{ doer: fakeHTTPGet( []meta{ - {"/myapp", + { + "/myapp", "meta01.html", }, }, diff --git a/discovery/testdata/meta06.html b/discovery/testdata/meta06.html index 0814f58b..487cd280 100644 --- a/discovery/testdata/meta06.html +++ b/discovery/testdata/meta06.html @@ -3,9 +3,19 @@