blob: 088e086bdc81c781262178e21f19898968ae0665 [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
72 for _, value := range resp.Versions.Values {
Ash Wilsona0c4c842014-09-09 11:30:58 -040073 href := ""
74 for _, link := range value.Links {
75 if link.Rel == "self" {
76 href = link.Href
77 }
78 }
Ash Wilsonc4360202014-08-29 14:14:24 -040079
Ash Wilsona0c4c842014-09-09 11:30:58 -040080 if matching, ok := byID[value.ID]; ok {
81 // Prefer a version that exactly matches the provided endpoint.
82 if href == identityEndpoint {
83 if href == "" {
84 return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, normalized)
Ash Wilsonc4360202014-08-29 14:14:24 -040085 }
Ash Wilsona0c4c842014-09-09 11:30:58 -040086 return matching, href, nil
87 }
Ash Wilsonc4360202014-08-29 14:14:24 -040088
Ash Wilsona0c4c842014-09-09 11:30:58 -040089 // Otherwise, find the highest-priority version with a whitelisted status.
90 if goodStatus[strings.ToLower(value.Status)] {
91 if highest == nil || matching.Priority > highest.Priority {
92 highest = matching
93 endpoint = href
Ash Wilsonc4360202014-08-29 14:14:24 -040094 }
95 }
96 }
97 }
98
Ash Wilsona0c4c842014-09-09 11:30:58 -040099 if highest == nil {
100 return nil, "", fmt.Errorf("No supported version available from endpoint %s", normalized)
101 }
102 if endpoint == "" {
103 return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", highest.ID, normalized)
Ash Wilsonc4360202014-08-29 14:14:24 -0400104 }
105
106 return highest, endpoint, nil
107}