github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/environs/imagedownloads/simplestreams.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package imagedownloads 5 6 import ( 7 "fmt" 8 "net/url" 9 "sort" 10 11 "github.com/juju/errors" 12 "github.com/juju/os/series" 13 "github.com/juju/utils" 14 "github.com/juju/utils/arch" 15 16 "github.com/juju/juju/environs/imagemetadata" 17 "github.com/juju/juju/environs/simplestreams" 18 ) 19 20 func init() { 21 simplestreams.RegisterStructTags(Metadata{}) 22 } 23 24 const ( 25 // DataType is the simplestreams datatype. 26 DataType = "image-downloads" 27 ) 28 29 // DefaultSource creates a new signed simplestreams datasource for use with the 30 // image-downloads datatype. 31 func DefaultSource() simplestreams.DataSource { 32 ubuntuImagesURL := imagemetadata.UbuntuCloudImagesURL + "/" + imagemetadata.ReleasedImagesPath 33 return newDataSourceFunc(ubuntuImagesURL)() 34 } 35 36 // NewDataSource returns a new simplestreams.DataSource from the provided 37 // baseURL. baseURL MUST include the image stream. 38 func NewDataSource(baseURL string) simplestreams.DataSource { 39 return newDataSourceFunc(baseURL)() 40 } 41 42 // NewDataSource returns a datasourceFunc from the baseURL provided. 43 func newDataSourceFunc(baseURL string) func() simplestreams.DataSource { 44 return func() simplestreams.DataSource { 45 return simplestreams.NewURLSignedDataSource( 46 "ubuntu cloud images", 47 baseURL, 48 imagemetadata.SimplestreamsImagesPublicKey, 49 utils.VerifySSLHostnames, 50 simplestreams.DEFAULT_CLOUD_DATA, 51 true) 52 } 53 } 54 55 // Metadata models the information about a particular cloud image download 56 // product. 57 type Metadata struct { 58 Arch string `json:"arch,omitempty"` 59 // For testing. 60 // TODO(ro) 2016-12-07 BaseURL was jammed on to allow for testing in 61 // juju/container/kvm/sync_internal_test. Refactor to pass it in rather 62 // than setting it on an otherwise needlessly exported member. 63 BaseURL string `json:"-"` 64 Release string `json:"release,omitempty"` 65 Version string `json:"version,omitempty"` 66 FType string `json:"ftype,omitempty"` 67 SHA256 string `json:"sha256,omitempty"` 68 Path string `json:"path,omitempty"` 69 Size int64 `json:"size,omitempty"` 70 } 71 72 // DownloadURL returns the URL representing the image. 73 func (m *Metadata) DownloadURL() (*url.URL, error) { 74 if m.BaseURL == "" { 75 m.BaseURL = imagemetadata.UbuntuCloudImagesURL 76 } 77 u, err := url.Parse(m.BaseURL + "/" + m.Path) 78 if err != nil { 79 return nil, errors.Annotate(err, "failed to create url") 80 } 81 return u, nil 82 } 83 84 // Fetch gets product results as Metadata from the provided datasources, given 85 // some constraints and an optional filter function. 86 func Fetch( 87 src []simplestreams.DataSource, 88 cons *imagemetadata.ImageConstraint, 89 ff simplestreams.AppendMatchingFunc) ([]*Metadata, *simplestreams.ResolveInfo, error) { 90 if ff == nil { 91 ff = Filter("") 92 } 93 params := simplestreams.GetMetadataParams{ 94 StreamsVersion: imagemetadata.StreamsVersionV1, 95 LookupConstraint: cons, 96 ValueParams: simplestreams.ValueParams{ 97 DataType: DataType, 98 FilterFunc: ff, 99 ValueTemplate: Metadata{}, 100 }, 101 } 102 items, resolveInfo, err := simplestreams.GetMetadata(src, params) 103 if err != nil { 104 return nil, resolveInfo, err 105 } 106 md := make([]*Metadata, len(items)) 107 for i, im := range items { 108 md[i] = im.(*Metadata) 109 } 110 111 Sort(md) 112 113 return md, resolveInfo, nil 114 } 115 116 func validateArgs(arch, release, ftype string) error { 117 bad := map[string]string{} 118 119 if !validArches[arch] { 120 bad[arch] = fmt.Sprintf("arch=%q", arch) 121 } 122 123 validSeries := false 124 for _, supported := range series.SupportedSeries() { 125 if release == supported { 126 validSeries = true 127 break 128 } 129 } 130 if !validSeries { 131 bad[release] = fmt.Sprintf("series=%q", release) 132 } 133 134 if !validFTypes[ftype] { 135 bad[ftype] = fmt.Sprintf("ftype=%q", ftype) 136 } 137 138 if len(bad) > 0 { 139 errMsg := "invalid parameters supplied" 140 for _, k := range []string{arch, release, ftype} { 141 if v, ok := bad[k]; ok { 142 errMsg += fmt.Sprintf(" %s", v) 143 } 144 } 145 return errors.New(errMsg) 146 } 147 return nil 148 } 149 150 // One gets Metadata for one content download item: 151 // The most recent of: 152 // - architecture 153 // - OS release 154 // - Simplestreams stream 155 // - File image type. 156 // src exists to pass in a data source for testing. 157 func One(arch, release, stream, ftype string, src func() simplestreams.DataSource) (*Metadata, error) { 158 if err := validateArgs(arch, release, ftype); err != nil { 159 return nil, errors.Trace(err) 160 } 161 if src == nil { 162 src = DefaultSource 163 } 164 ds := []simplestreams.DataSource{src()} 165 limit := imagemetadata.NewImageConstraint( 166 simplestreams.LookupParams{ 167 Arches: []string{arch}, 168 Series: []string{release}, 169 Stream: stream, 170 }, 171 ) 172 173 md, _, err := Fetch(ds, limit, Filter(ftype)) 174 if err != nil { 175 // It doesn't appear that arch is vetted anywhere else so we can wind 176 // up with empty results if someone requests any arch with valid series 177 // and ftype.. 178 return nil, errors.Trace(err) 179 } 180 if len(md) < 1 { 181 return nil, errors.Errorf("no results for %q, %q, %q, %q", arch, release, stream, ftype) 182 } 183 if len(md) > 1 { 184 // Should not be possible. 185 return nil, errors.Errorf("got %d results expected 1 for %q, %q, %q, %q", len(md), arch, release, stream, ftype) 186 } 187 return md[0], nil 188 } 189 190 // validFTypes is a simple map of file types that we can glean from looking at 191 // simple streams. 192 var validFTypes = map[string]bool{ 193 "disk1.img": true, 194 "lxd.tar.xz": true, 195 "manifest": true, 196 "ova": true, 197 "root.tar.gz": true, 198 "root.tar.xz": true, 199 "tar.gz": true, 200 "uefi1.img": true, 201 } 202 203 // validArches are the arches we support running kvm containers on. 204 var validArches = map[string]bool{ 205 arch.AMD64: true, 206 arch.ARM64: true, 207 arch.PPC64EL: true, 208 } 209 210 // Filter collects only matching products. Series and Arch are filtered by 211 // imagemetadata.ImageConstraints. So this really only let's us filter on a 212 // file type. 213 func Filter(ftype string) simplestreams.AppendMatchingFunc { 214 return func(source simplestreams.DataSource, matchingImages []interface{}, 215 images map[string]interface{}, cons simplestreams.LookupConstraint) ([]interface{}, error) { 216 217 imagesMap := make(map[imageKey]*Metadata, len(matchingImages)) 218 for _, val := range matchingImages { 219 im := val.(*Metadata) 220 imagesMap[imageKey{im.Arch, im.FType, im.Release, im.Version}] = im 221 } 222 for _, val := range images { 223 im := val.(*Metadata) 224 if ftype != "" { 225 if im.FType != ftype { 226 continue 227 } 228 } 229 if _, ok := imagesMap[imageKey{im.Arch, im.FType, im.Release, im.Version}]; !ok { 230 matchingImages = append(matchingImages, im) 231 } 232 } 233 return matchingImages, nil 234 } 235 } 236 237 // imageKey is the key used while filtering products. 238 type imageKey struct { 239 arch string 240 ftype string 241 release string 242 version string 243 } 244 245 // Sort sorts a slice of ImageMetadata in ascending order of their id 246 // in order to ensure the results of Fetch are ordered deterministically. 247 func Sort(metadata []*Metadata) { 248 sort.Sort(byRelease(metadata)) 249 } 250 251 type byRelease []*Metadata 252 253 func (b byRelease) Len() int { return len(b) } 254 func (b byRelease) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 255 func (b byRelease) Less(i, j int) bool { return b[i].Release < b[j].Release }