launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/environs/imagemetadata/simplestreams.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The imagemetadata package supports locating, parsing, and filtering Ubuntu image metadata in simplestreams format. 5 // See http://launchpad.net/simplestreams and in particular the doc/README file in that project for more information 6 // about the file formats. 7 package imagemetadata 8 9 import ( 10 "fmt" 11 12 "launchpad.net/juju-core/environs/simplestreams" 13 ) 14 15 func init() { 16 simplestreams.RegisterStructTags(ImageMetadata{}) 17 } 18 19 const ( 20 ImageIds = "image-ids" 21 ) 22 23 // simplestreamsImagesPublicKey is the public key required to 24 // authenticate the simple streams data on http://cloud-images.ubuntu.com. 25 // Declared as a var so it can be overidden for testing. 26 // See http://bazaar.launchpad.net/~smoser/simplestreams/trunk/view/head:/examples/keys/cloud-images.pub 27 var simplestreamsImagesPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- 28 Version: GnuPG v1.4.12 (GNU/Linux) 29 30 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 78 =+MFT 79 -----END PGP PUBLIC KEY BLOCK----- 80 ` 81 82 const ( 83 // The location where Ubuntu cloud image metadata is published for 84 // public consumption. 85 UbuntuCloudImagesURL = "http://cloud-images.ubuntu.com" 86 // The path where released image metadata is found. 87 ReleasedImagesPath = "releases" 88 ) 89 90 // This needs to be a var so we can override it for testing and in bootstrap. 91 var DefaultBaseURL = UbuntuCloudImagesURL 92 93 // ImageConstraint defines criteria used to find an image metadata record. 94 type ImageConstraint struct { 95 simplestreams.LookupParams 96 } 97 98 func NewImageConstraint(params simplestreams.LookupParams) *ImageConstraint { 99 if len(params.Series) == 0 { 100 params.Series = simplestreams.SupportedSeries() 101 } 102 if len(params.Arches) == 0 { 103 params.Arches = []string{"amd64", "i386", "arm"} 104 } 105 return &ImageConstraint{LookupParams: params} 106 } 107 108 const ( 109 // Used to specify the released image metadata. 110 ReleasedStream = "released" 111 ) 112 113 // idStream returns the string to use in making a product id 114 // for the given product stream. 115 func idStream(stream string) string { 116 idstream := "" 117 if stream != "" && stream != ReleasedStream { 118 idstream = "." + stream 119 } 120 return idstream 121 } 122 123 // Generates a string array representing product ids formed similarly to an ISCSI qualified name (IQN). 124 func (ic *ImageConstraint) Ids() ([]string, error) { 125 stream := idStream(ic.Stream) 126 nrArches := len(ic.Arches) 127 nrSeries := len(ic.Series) 128 ids := make([]string, nrArches*nrSeries) 129 for i, arch := range ic.Arches { 130 for j, series := range ic.Series { 131 version, err := simplestreams.SeriesVersion(series) 132 if err != nil { 133 return nil, err 134 } 135 ids[j*nrArches+i] = fmt.Sprintf("com.ubuntu.cloud%s:server:%s:%s", stream, version, arch) 136 } 137 } 138 return ids, nil 139 } 140 141 // ImageMetadata holds information about a particular cloud image. 142 type ImageMetadata struct { 143 Id string `json:"id"` 144 Storage string `json:"root_store,omitempty"` 145 VType string `json:"virt,omitempty"` 146 Arch string `json:"arch,omitempty"` 147 Version string `json:"version,omitempty"` 148 RegionAlias string `json:"crsn,omitempty"` 149 RegionName string `json:"region,omitempty"` 150 Endpoint string `json:"endpoint,omitempty"` 151 Stream string `json:"-"` 152 } 153 154 func (im *ImageMetadata) String() string { 155 return fmt.Sprintf("%#v", im) 156 } 157 158 func (im *ImageMetadata) productId() string { 159 stream := idStream(im.Stream) 160 return fmt.Sprintf("com.ubuntu.cloud%s:server:%s:%s", stream, im.Version, im.Arch) 161 } 162 163 // Fetch returns a list of images for the specified cloud matching the constraint. 164 // The base URL locations are as specified - the first location which has a file is the one used. 165 // Signed data is preferred, but if there is no signed data available and onlySigned is false, 166 // then unsigned data is used. 167 func Fetch(sources []simplestreams.DataSource, indexPath string, cons *ImageConstraint, onlySigned bool) ([]*ImageMetadata, error) { 168 params := simplestreams.ValueParams{ 169 DataType: ImageIds, 170 FilterFunc: appendMatchingImages, 171 ValueTemplate: ImageMetadata{}, 172 PublicKey: simplestreamsImagesPublicKey, 173 } 174 items, err := simplestreams.GetMetadata(sources, indexPath, cons, onlySigned, params) 175 if err != nil { 176 return nil, err 177 } 178 metadata := make([]*ImageMetadata, len(items)) 179 for i, md := range items { 180 metadata[i] = md.(*ImageMetadata) 181 } 182 return metadata, nil 183 } 184 185 type imageKey struct { 186 vtype string 187 arch string 188 version string 189 region string 190 storage string 191 } 192 193 // appendMatchingImages updates matchingImages with image metadata records from images which belong to the 194 // specified region. If an image already exists in matchingImages, it is not overwritten. 195 func appendMatchingImages(source simplestreams.DataSource, matchingImages []interface{}, 196 images map[string]interface{}, cons simplestreams.LookupConstraint) []interface{} { 197 198 imagesMap := make(map[imageKey]*ImageMetadata, len(matchingImages)) 199 for _, val := range matchingImages { 200 im := val.(*ImageMetadata) 201 imagesMap[imageKey{im.VType, im.Arch, im.Version, im.RegionName, im.Storage}] = im 202 } 203 for _, val := range images { 204 im := val.(*ImageMetadata) 205 if cons != nil && cons.Params().Region != "" && cons.Params().Region != im.RegionName { 206 continue 207 } 208 if _, ok := imagesMap[imageKey{im.VType, im.Arch, im.Version, im.RegionName, im.Storage}]; !ok { 209 matchingImages = append(matchingImages, im) 210 } 211 } 212 return matchingImages 213 } 214 215 // GetLatestImageIdMetadata is provided so it can be call by tests outside the imagemetadata package. 216 func GetLatestImageIdMetadata(data []byte, source simplestreams.DataSource, cons *ImageConstraint) ([]*ImageMetadata, error) { 217 metadata, err := simplestreams.ParseCloudMetadata(data, "products:1.0", "<unknown>", ImageMetadata{}) 218 if err != nil { 219 return nil, err 220 } 221 items, err := simplestreams.GetLatestMetadata(metadata, cons, source, appendMatchingImages) 222 if err != nil { 223 return nil, err 224 } 225 result := make([]*ImageMetadata, len(items)) 226 for i, md := range items { 227 result[i] = md.(*ImageMetadata) 228 } 229 return result, nil 230 }