github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/simplestreams/datasource.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package simplestreams 5 6 import ( 7 "fmt" 8 "io" 9 "net/http" 10 "strings" 11 12 "github.com/juju/errors" 13 "github.com/juju/utils" 14 ) 15 16 // A DataSource retrieves simplestreams metadata. 17 type DataSource interface { 18 // Description describes the origin of this datasource. 19 // eg agent-metadata-url, cloud storage, keystone catalog etc. 20 Description() string 21 22 // Fetch loads the data at the specified relative path. It returns a reader from which 23 // the data can be retrieved as well as the full URL of the file. The full URL is typically 24 // used in log messages to help diagnose issues accessing the data. 25 Fetch(path string) (io.ReadCloser, string, error) 26 27 // URL returns the full URL of the path, as applicable to this datasource. 28 // This method is used primarily for logging purposes. 29 URL(path string) (string, error) 30 31 // PublicSigningKey returns the public key used to validate signed metadata. 32 PublicSigningKey() string 33 34 // SetAllowRetry sets the flag which determines if the datasource will retry fetching the metadata 35 // if it is not immediately available. 36 SetAllowRetry(allow bool) 37 // Priority is an importance factor for Data Source. Higher number means higher priority. 38 // This is will allow to sort data sources in order of importance. 39 Priority() int 40 // RequireSigned indicates whether this data source requires signed data. 41 RequireSigned() bool 42 } 43 44 const ( 45 // These values used as priority factors for sorting data source data. 46 47 // EXISTING_CLOUD_DATA is the lowest in priority. 48 // It is mostly used in merge functions 49 // where existing data does not need to be ranked. 50 EXISTING_CLOUD_DATA = 0 51 52 // DEFAULT_CLOUD_DATA is used for common cloud data that 53 // is shared an is publically available. 54 DEFAULT_CLOUD_DATA = 10 55 56 // SPECIFIC_CLOUD_DATA is used to rank cloud specific data 57 // above commonly available. 58 // For e.g., openstack's "keystone catalogue". 59 SPECIFIC_CLOUD_DATA = 20 60 61 // CUSTOM_CLOUD_DATA is the highest available ranking and 62 // is given to custom data. 63 CUSTOM_CLOUD_DATA = 50 64 ) 65 66 // A urlDataSource retrieves data from an HTTP URL. 67 type urlDataSource struct { 68 description string 69 baseURL string 70 hostnameVerification utils.SSLHostnameVerification 71 publicSigningKey string 72 priority int 73 requireSigned bool 74 } 75 76 // NewURLDataSource returns a new datasource reading from the specified baseURL. 77 func NewURLDataSource(description, baseURL string, hostnameVerification utils.SSLHostnameVerification, priority int, requireSigned bool) DataSource { 78 return &urlDataSource{ 79 description: description, 80 baseURL: baseURL, 81 hostnameVerification: hostnameVerification, 82 priority: priority, 83 requireSigned: requireSigned, 84 } 85 } 86 87 // NewURLSignedDataSource returns a new datasource for signed metadata reading from the specified baseURL. 88 func NewURLSignedDataSource(description, baseURL, publicKey string, hostnameVerification utils.SSLHostnameVerification, priority int, requireSigned bool) DataSource { 89 return &urlDataSource{ 90 description: description, 91 baseURL: baseURL, 92 publicSigningKey: publicKey, 93 hostnameVerification: hostnameVerification, 94 priority: priority, 95 requireSigned: requireSigned, 96 } 97 } 98 99 // Description is defined in simplestreams.DataSource. 100 func (u *urlDataSource) Description() string { 101 return u.description 102 } 103 104 func (u *urlDataSource) GoString() string { 105 return fmt.Sprintf("%v: urlDataSource(%q)", u.description, u.baseURL) 106 } 107 108 // urlJoin returns baseURL + relpath making sure to have a '/' inbetween them 109 // This doesn't try to do anything fancy with URL query or parameter bits 110 // It also doesn't use path.Join because that normalizes slashes, and you need 111 // to keep both slashes in 'http://'. 112 func urlJoin(baseURL, relpath string) string { 113 if strings.HasSuffix(baseURL, "/") { 114 return baseURL + relpath 115 } 116 return baseURL + "/" + relpath 117 } 118 119 // Fetch is defined in simplestreams.DataSource. 120 func (h *urlDataSource) Fetch(path string) (io.ReadCloser, string, error) { 121 dataURL := urlJoin(h.baseURL, path) 122 client := utils.GetHTTPClient(h.hostnameVerification) 123 // dataURL can be http:// or file:// 124 // MakeFileURL will only modify the URL if it's a file URL 125 dataURL = utils.MakeFileURL(dataURL) 126 resp, err := client.Get(dataURL) 127 if err != nil { 128 logger.Tracef("Got error requesting %q: %v", dataURL, err) 129 return nil, dataURL, errors.NotFoundf("invalid URL %q", dataURL) 130 } 131 if resp.StatusCode != http.StatusOK { 132 resp.Body.Close() 133 switch resp.StatusCode { 134 case http.StatusNotFound: 135 return nil, dataURL, errors.NotFoundf("cannot find URL %q", dataURL) 136 case http.StatusUnauthorized: 137 return nil, dataURL, errors.Unauthorizedf("unauthorised access to URL %q", dataURL) 138 } 139 return nil, dataURL, fmt.Errorf("cannot access URL %q, %q", dataURL, resp.Status) 140 } 141 return resp.Body, dataURL, nil 142 } 143 144 // URL is defined in simplestreams.DataSource. 145 func (h *urlDataSource) URL(path string) (string, error) { 146 return utils.MakeFileURL(urlJoin(h.baseURL, path)), nil 147 } 148 149 // PublicSigningKey is defined in simplestreams.DataSource. 150 func (u *urlDataSource) PublicSigningKey() string { 151 return u.publicSigningKey 152 } 153 154 // SetAllowRetry is defined in simplestreams.DataSource. 155 func (h *urlDataSource) SetAllowRetry(allow bool) { 156 // This is a NOOP for url datasources. 157 } 158 159 // Priority is defined in simplestreams.DataSource. 160 func (h *urlDataSource) Priority() int { 161 return h.priority 162 } 163 164 // RequireSigned is defined in simplestreams.DataSource. 165 func (h *urlDataSource) RequireSigned() bool { 166 return h.requireSigned 167 }