blob: 402a870df9b6bd147565eaff3cac7551b489f39a [file] [log] [blame]
Ash Wilsonc4360202014-08-29 14:14:24 -04001package utils
2
3import (
4 "fmt"
Ash Wilsona0c4c842014-09-09 11:30:58 -04005 "net/url"
Ash Wilsonaef9dc52014-08-29 14:21:26 -04006 "strings"
Ash Wilsonc4360202014-08-29 14:14:24 -04007
8 "github.com/racker/perigee"
9)
10
11// Version is a supported API version, corresponding to a vN package within the appropriate service.
12type Version struct {
13 ID string
14 Priority int
15}
16
Ash Wilsonaef9dc52014-08-29 14:21:26 -040017var goodStatus = map[string]bool{
18 "current": true,
19 "supported": true,
20 "stable": true,
21}
22
Ash Wilsonc4360202014-08-29 14:14:24 -040023// ChooseVersion queries the base endpoint of a API to choose the most recent non-experimental alternative from a service's
24// published versions.
25// It returns the highest-Priority Version among the alternatives that are provided, as well as its corresponding endpoint.
Ash Wilsona0c4c842014-09-09 11:30:58 -040026func ChooseVersion(identityEndpoint string, recognized []*Version) (*Version, string, error) {
Ash Wilsonc4360202014-08-29 14:14:24 -040027 type linkResp struct {
28 Href string `json:"href"`
29 Rel string `json:"rel"`
30 }
31
32 type valueResp struct {
33 ID string `json:"id"`
34 Status string `json:"status"`
35 Links []linkResp `json:"links"`
36 }
37
38 type versionsResp struct {
39 Values []valueResp `json:"values"`
40 }
41
42 type response struct {
43 Versions versionsResp `json:"versions"`
44 }
45
Ash Wilsona0c4c842014-09-09 11:30:58 -040046 // Normalize the identity endpoint that's provided by trimming any path, query or fragment from the URL.
47 u, err := url.Parse(identityEndpoint)
48 if err != nil {
49 return nil, "", err
50 }
51 u.Path, u.RawQuery, u.Fragment = "", "", ""
52 normalized := u.String()
53
Ash Wilsonc4360202014-08-29 14:14:24 -040054 var resp response
Ash Wilsona0c4c842014-09-09 11:30:58 -040055 _, err = perigee.Request("GET", normalized, perigee.Options{
Ash Wilsonc4360202014-08-29 14:14:24 -040056 Results: &resp,
Ash Wilson72f7dfb2014-09-02 14:06:00 -040057 OkCodes: []int{200, 300},
Ash Wilsonc4360202014-08-29 14:14:24 -040058 })
59
60 if err != nil {
61 return nil, "", err
62 }
63
64 byID := make(map[string]*Version)
65 for _, version := range recognized {
66 byID[version.ID] = version
67 }
68
69 var highest *Version
70 var endpoint string
71
Ash Wilsonccfccce2014-09-09 11:43:04 -040072 normalize := func(endpoint string) string {
73 if !strings.HasSuffix(endpoint, "/") {
74 return endpoint + "/"
75 }
76 return endpoint
77 }
Ash Wilsone7da01c2014-09-09 12:31:06 -040078 normalizedGiven := normalize(identityEndpoint)
Ash Wilsonccfccce2014-09-09 11:43:04 -040079
Ash Wilsonc4360202014-08-29 14:14:24 -040080 for _, value := range resp.Versions.Values {
Ash Wilsona0c4c842014-09-09 11:30:58 -040081 href := ""
82 for _, link := range value.Links {
83 if link.Rel == "self" {
Ash Wilsone7da01c2014-09-09 12:31:06 -040084 href = normalize(link.Href)
Ash Wilsona0c4c842014-09-09 11:30:58 -040085 }
86 }
Ash Wilsonc4360202014-08-29 14:14:24 -040087
Ash Wilsona0c4c842014-09-09 11:30:58 -040088 if matching, ok := byID[value.ID]; ok {
89 // Prefer a version that exactly matches the provided endpoint.
Ash Wilsone7da01c2014-09-09 12:31:06 -040090 if href == normalizedGiven {
Ash Wilsona0c4c842014-09-09 11:30:58 -040091 if href == "" {
92 return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, normalized)
Ash Wilsonc4360202014-08-29 14:14:24 -040093 }
Ash Wilsona0c4c842014-09-09 11:30:58 -040094 return matching, href, nil
95 }
Ash Wilsonc4360202014-08-29 14:14:24 -040096
Ash Wilsona0c4c842014-09-09 11:30:58 -040097 // Otherwise, find the highest-priority version with a whitelisted status.
98 if goodStatus[strings.ToLower(value.Status)] {
99 if highest == nil || matching.Priority > highest.Priority {
100 highest = matching
101 endpoint = href
Ash Wilsonc4360202014-08-29 14:14:24 -0400102 }
103 }
104 }
105 }
106
Ash Wilsona0c4c842014-09-09 11:30:58 -0400107 if highest == nil {
108 return nil, "", fmt.Errorf("No supported version available from endpoint %s", normalized)
109 }
110 if endpoint == "" {
111 return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, normalized)
Ash Wilsonc4360202014-08-29 14:14:24 -0400112 }
113
114 return highest, endpoint, nil
115}