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  mQINBFCMc9EBEADDKn9mOi9VZhW+0cxmu3aFZWMg0p7NEKuIokkEdd6P+BRITccO
    31  ddDLaBuuamMbt/V1vrxWC5J+UXe33TwgO6KGfH+ECnXD5gYdEOyjVKkUyIzYV5RV
    32  U5BMrxTukHuh+PkcMVUy5vossCk9MivtCRIqM6eRqfeXv6IBV9MFkAbG3x96ZNI/
    33  TqaWTlaHGszz2Axf9JccHCNfb3muLI2uVnUaojtDiZPm9SHTn6O0p7Tz7M7+P8qy
    34  vc6bdn5FYAk+Wbo+zejYVBG/HLLE4+fNZPESGVCWZtbZODBPxppTnNVm3E84CTFt
    35  pmWFBvBE/q2G9e8s5/mP2ATrzLdUKMxr3vcbNX+NY1Uyvn0Z02PjbxThiz1G+4qh
    36  6Ct7gprtwXPOB/bCITZL9YLrchwXiNgLLKcGF0XjlpD1hfELGi0aPZaHFLAa6qq8
    37  Ro9WSJljY/Z0g3woj6sXpM9TdWe/zaWhxBGmteJl33WBV7a1GucN0zF1dHIvev4F
    38  krp13Uej3bMWLKUWCmZ01OHStLASshTqVxIBj2rgsxIcqH66DKTSdZWyBQtgm/kC
    39  qBvuoQLFfUgIlGZihTQ96YZXqn+VfBiFbpnh1vLt24CfnVdKmzibp48KkhfqduDE
    40  Xxx/f/uZENH7t8xCuNd3p+u1zemGNnxuO8jxS6Ico3bvnJaG4DAl48vaBQARAQAB
    41  tG9VYnVudHUgQ2xvdWQgSW1hZ2UgQnVpbGRlciAoQ2Fub25pY2FsIEludGVybmFs
    42  IENsb3VkIEltYWdlIEJ1aWxkZXIpIDx1YnVudHUtY2xvdWRidWlsZGVyLW5vcmVw
    43  bHlAY2Fub25pY2FsLmNvbT6JAjgEEwECACIFAlCMc9ECGwMGCwkIBwMCBhUIAgkK
    44  CwQWAgMBAh4BAheAAAoJEH/z9AhHbPEAvRIQAMLE4ZMYiLvwSoWPAicM+3FInaqP
    45  2rf1ZEf1k6175/G2n8cG3vK0nIFQE9Cus+ty2LrTggm79onV2KBGGScKe3ga+meO
    46  txj601Wd7zde10IWUa1wlTxPXBxLo6tpF4s4aw6xWOf4OFqYfPU4esKblFYn1eMK
    47  Dd53s3/123u8BZqzFC8WSMokY6WgBa+hvr5J3qaNT95UXo1tkMf65ZXievcQJ+Hr
    48  bp1m5pslHgd5PqzlultNWePwzqmHXXf14zI1QKtbc4UjXPQ+a59ulZLVdcpvmbjx
    49  HdZfK0NJpQX+j5PU6bMuQ3QTMscuvrH4W41/zcZPFaPkdJE5+VcYDL17DBFVzknJ
    50  eC1uzNHxRqSMRQy9fzOuZ72ARojvL3+cyPR1qrqSCceX1/Kp838P2/CbeNvJxadt
    51  liwI6rzUgK7mq1Bw5LTyBo3mLwzRJ0+eJHevNpxl6VoFyuoA3rCeoyE4on3oah1G
    52  iAJt576xXMDoa1Gdj3YtnZItEaX3jb9ZB3iz9WkzZWlZsssdyZMNmpYV30Ayj3CE
    53  KyurYF9lzIQWyYsNPBoXORNh73jkHJmL6g1sdMaxAZeQqKqznXbuhBbt8lkbEHMJ
    54  Stxc2IGZaNpQ+/3LCwbwCphVnSMq+xl3iLg6c0s4uRn6FGX+8aknmc/fepvRe+ba
    55  ntqvgz+SMPKrjeevuQINBFCMc9EBEADKGFPKBL7/pMSTKf5YH1zhFH2lr7tf5hbz
    56  ztsx6j3y+nODiaQumdG+TPMbrFlgRlJ6Ah1FTuJZqdPYObGSQ7qd/VvvYZGnDYJv
    57  Z1kPkNDmCJrWJs+6PwNARvyLw2bMtjCIOAq/k8wByKkMzegobJgWsbr2Jb5fT4cv
    58  FxYpm3l0QxQSw49rriO5HmwyiyG1ncvaFUcpxXJY8A2s7qX1jmjsqDY1fWsv5PaN
    59  ue0Fr3VXfOi9p+0CfaPY0Pl4GHzat/D+wLwnOhnjl3hFtfbhY5bPl5+cD51SbOnh
    60  2nFv+bUK5HxiZlz0bw8hTUBN3oSbAC+2zViRD/9GaBYY1QjimOuAfpO1GZmqohVI
    61  msZKxHNIIsk5H98mN2+LB3vH+B6zrSMDm3d2Hi7ZA8wH26mLIKLbVkh7hr8RGQjf
    62  UZRxeQEf+f8F3KVoSqmfXGJfBMUtGQMTkaIeEFpMobVeHZZ3wk+Wj3dCMZ6bbt2i
    63  QBaoa7SU5ZmRShJkPJzCG3SkqN+g9ZcbFMQsybl+wLN7UnZ2MbSk7JEy6SLsyuVi
    64  7EjLmqHmG2gkybisnTu3wjJezpG12oz//cuylOzjuPWUWowVQQiLs3oANzYdZ0Hp
    65  SuNjjtEILSRnN5FAeogs0AKH6sy3kKjxtlj764CIgn1hNidSr2Hyb4xbJ/1GE3Rk
    66  sjJi6uYIJwARAQABiQIfBBgBAgAJBQJQjHPRAhsMAAoJEH/z9AhHbPEA6IsP/3jJ
    67  DaowJcKOBhU2TXZglHM+ZRMauHRZavo+xAKmqgQc/izgtyMxsLwJQ+wcTEQT5uqE
    68  4DoWH2T7DGiHZd/89Qe6HuRExR4p7lQwUop7kdoabqm1wQfcqr+77Znp1+KkRDyS
    69  lWfbsh9ARU6krQGryODEOpXJdqdzTgYhdbVRxq6dUopz1Gf+XDreFgnqJ+okGve2
    70  fJGERKYynUmHxkFZJPWZg5ifeGVt+YY6vuOCg489dzx/CmULpjZeiOQmWyqUzqy2
    71  QJ70/sC8BJYCjsESId9yPmgdDoMFd+gf3jhjpuZ0JHTeUUw+ncf+1kRf7LAALPJp
    72  2PTSo7VXUwoEXDyUTM+dI02dIMcjTcY4yxvnpxRFFOtklvXt8Pwa9x/aCmJb9f0E
    73  5FO0nj7l9pRd2g7UCJWETFRfSW52iktvdtDrBCft9OytmTl492wAmgbbGeoRq3ze
    74  QtzkRx9cPiyNQokjXXF+SQcq586oEd8K/JUSFPdvth3IoKlfnXSQnt/hRKv71kbZ
    75  IXmR3B/q5x2Msr+NfUxyXfUnYOZ5KertdprUfbZjudjmQ78LOvqPF8TdtHg3gD2H
    76  +G2z+IoH7qsOsc7FaJsIIa4+dljwV3QZTE7JFmsas90bRcMuM4D37p3snOpHAHY3
    77  p7vH1ewg+vd9ySST0+OkWXYpbMOIARfBKyrGM3nu
    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  }