launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/environs/simplestreams/simplestreams.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The simplestreams package supports locating, parsing, and filtering 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  //
     8  // Users of this package provide an empty struct and a matching function to be able to query and return a list
     9  // of typed values for a given criteria.
    10  package simplestreams
    11  
    12  import (
    13  	"bufio"
    14  	"encoding/json"
    15  	"fmt"
    16  	"io"
    17  	"io/ioutil"
    18  	"net/http"
    19  	"os"
    20  	"path"
    21  	"reflect"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/loggo/loggo"
    27  
    28  	"launchpad.net/juju-core/errors"
    29  )
    30  
    31  var logger = loggo.GetLogger("juju.environs.simplestreams")
    32  
    33  // CloudSpec uniquely defines a specific cloud deployment.
    34  type CloudSpec struct {
    35  	Region   string `json:"region"`
    36  	Endpoint string `json:"endpoint"`
    37  }
    38  
    39  // equals returns true if spec == other, allowing for endpoints
    40  // with or without a trailing "/".
    41  func (spec *CloudSpec) equals(other *CloudSpec) bool {
    42  	if spec.Region != other.Region {
    43  		return false
    44  	}
    45  	specEndpoint := spec.Endpoint
    46  	if !strings.HasSuffix(specEndpoint, "/") {
    47  		specEndpoint += "/"
    48  	}
    49  	otherEndpoint := other.Endpoint
    50  	if !strings.HasSuffix(otherEndpoint, "/") {
    51  		otherEndpoint += "/"
    52  	}
    53  	return specEndpoint == otherEndpoint
    54  }
    55  
    56  // EmptyCloudSpec is used when we want all records regardless of cloud to be loaded.
    57  var EmptyCloudSpec = CloudSpec{}
    58  
    59  // HasRegion is implemented by instances which can provide a region to which they belong.
    60  // A region is defined by region name and endpoint.
    61  type HasRegion interface {
    62  	// Region returns the necessary attributes to uniquely identify this cloud instance.
    63  	// Currently these attributes are "region" and "endpoint" values.
    64  	Region() (CloudSpec, error)
    65  }
    66  
    67  type LookupConstraint interface {
    68  	// Generates a string array representing product ids formed similarly to an ISCSI qualified name (IQN).
    69  	Ids() ([]string, error)
    70  	// Returns the constraint parameters.
    71  	Params() LookupParams
    72  }
    73  
    74  // LookupParams defines criteria used to find a metadata record.
    75  // Derived structs implement the Ids() method.
    76  type LookupParams struct {
    77  	CloudSpec
    78  	Series []string
    79  	Arches []string
    80  	// Stream can be "" or "released" for the default "released" stream,
    81  	// or "daily" for daily images, or any other stream that the available
    82  	// simplestreams metadata supports.
    83  	Stream string
    84  }
    85  
    86  func (p LookupParams) Params() LookupParams {
    87  	return p
    88  }
    89  
    90  // seriesVersions provides a mapping between Ubuntu series names and version numbers.
    91  // The values here are current as of the time of writing. On Ubuntu systems, we update
    92  // these values from /usr/share/distro-info/ubuntu.csv to ensure we have the latest values.
    93  // On non-Ubuntu systems, these values provide a nice fallback option.
    94  // Exported so tests can change the values to ensure the distro-info lookup works.
    95  var seriesVersions = map[string]string{
    96  	"precise": "12.04",
    97  	"quantal": "12.10",
    98  	"raring":  "13.04",
    99  	"saucy":   "13.10",
   100  	"trusty":  "14.04",
   101  }
   102  
   103  var (
   104  	seriesVersionsMutex   sync.Mutex
   105  	updatedseriesVersions bool
   106  )
   107  
   108  // SeriesVersion returns the version number for the specified Ubuntu series.
   109  func SeriesVersion(series string) (string, error) {
   110  	if series == "" {
   111  		panic("cannot pass empty series to SeriesVersion()")
   112  	}
   113  	seriesVersionsMutex.Lock()
   114  	defer seriesVersionsMutex.Unlock()
   115  	if vers, ok := seriesVersions[series]; ok {
   116  		return vers, nil
   117  	}
   118  	updateSeriesVersions()
   119  	if vers, ok := seriesVersions[series]; ok {
   120  		return vers, nil
   121  	}
   122  	return "", fmt.Errorf("invalid series %q", series)
   123  }
   124  
   125  // Supported series returns the Ubuntu series for which we expect to find metadata.
   126  func SupportedSeries() []string {
   127  	seriesVersionsMutex.Lock()
   128  	defer seriesVersionsMutex.Unlock()
   129  	updateSeriesVersions()
   130  	series := []string{}
   131  	for s, _ := range seriesVersions {
   132  		series = append(series, s)
   133  	}
   134  	return series
   135  }
   136  
   137  func updateSeriesVersions() {
   138  	if !updatedseriesVersions {
   139  		err := updateDistroInfo()
   140  		if err != nil {
   141  			logger.Warningf("failed to update distro info: %v", err)
   142  		}
   143  		updatedseriesVersions = true
   144  	}
   145  }
   146  
   147  // updateDistroInfo updates seriesVersions from /usr/share/distro-info/ubuntu.csv if possible..
   148  func updateDistroInfo() error {
   149  	// We need to find the series version eg 12.04 from the series eg precise. Use the information found in
   150  	// /usr/share/distro-info/ubuntu.csv provided by distro-info-data package.
   151  	f, err := os.Open("/usr/share/distro-info/ubuntu.csv")
   152  	if err != nil {
   153  		// On non-Ubuntu systems this file won't exist but that's expected.
   154  		return nil
   155  	}
   156  	defer f.Close()
   157  	bufRdr := bufio.NewReader(f)
   158  	// Only find info for precise or later.
   159  	preciseOrLaterFound := false
   160  	for {
   161  		line, err := bufRdr.ReadString('\n')
   162  		if err == io.EOF {
   163  			break
   164  		}
   165  		if err != nil {
   166  			return fmt.Errorf("reading distro info file file: %v", err)
   167  		}
   168  		// lines are of the form: "12.04 LTS,Precise Pangolin,precise,2011-10-13,2012-04-26,2017-04-26"
   169  		parts := strings.Split(line, ",")
   170  		// Ignore any malformed lines.
   171  		if len(parts) < 3 {
   172  			continue
   173  		}
   174  		series := parts[2]
   175  		if series == "precise" {
   176  			preciseOrLaterFound = true
   177  		}
   178  		if series != "precise" && !preciseOrLaterFound {
   179  			continue
   180  		}
   181  		// the numeric version may contain a LTS moniker so strip that out.
   182  		seriesInfo := strings.Split(parts[0], " ")
   183  		seriesVersions[series] = seriesInfo[0]
   184  	}
   185  	return nil
   186  }
   187  
   188  // The following structs define the data model used in the JSON metadata files.
   189  // Not every model attribute is defined here, only the ones we care about.
   190  // See the doc/README file in lp:simplestreams for more information.
   191  
   192  // Metadata attribute values may point to a map of attribute values (aka aliases) and these attributes
   193  // are used to override/augment the existing attributes.
   194  type attributeValues map[string]string
   195  type aliasesByAttribute map[string]attributeValues
   196  
   197  type CloudMetadata struct {
   198  	Products  map[string]MetadataCatalog    `json:"products"`
   199  	Aliases   map[string]aliasesByAttribute `json:"_aliases,omitempty"`
   200  	Updated   string                        `json:"updated"`
   201  	Format    string                        `json:"format"`
   202  	ContentId string                        `json:"content_id"`
   203  }
   204  
   205  type MetadataCatalog struct {
   206  	Series     string `json:"release,omitempty"`
   207  	Version    string `json:"version,omitempty"`
   208  	Arch       string `json:"arch,omitempty"`
   209  	RegionName string `json:"region,omitempty"`
   210  	Endpoint   string `json:"endpoint,omitempty"`
   211  
   212  	// Items is a mapping from version to an ItemCollection,
   213  	// where the version is the date the items were produced,
   214  	// in the format YYYYMMDD.
   215  	Items map[string]*ItemCollection `json:"versions"`
   216  }
   217  
   218  type ItemCollection struct {
   219  	rawItems   map[string]*json.RawMessage
   220  	Items      map[string]interface{} `json:"items"`
   221  	Arch       string                 `json:"arch,omitempty"`
   222  	Series     string                 `json:"release,omitempty"`
   223  	Version    string                 `json:"version,omitempty"`
   224  	RegionName string                 `json:"region,omitempty"`
   225  	Endpoint   string                 `json:"endpoint,omitempty"`
   226  }
   227  
   228  // These structs define the model used for metadata indices.
   229  
   230  type Indices struct {
   231  	Indexes map[string]*IndexMetadata `json:"index"`
   232  	Updated string                    `json:"updated"`
   233  	Format  string                    `json:"format"`
   234  }
   235  
   236  // Exported for testing.
   237  type IndexReference struct {
   238  	Indices
   239  	MirroredProductsPath string
   240  	Source               DataSource
   241  	valueParams          ValueParams
   242  }
   243  
   244  type IndexMetadata struct {
   245  	Updated          string      `json:"updated"`
   246  	Format           string      `json:"format"`
   247  	DataType         string      `json:"datatype"`
   248  	CloudName        string      `json:"cloudname,omitempty"`
   249  	Clouds           []CloudSpec `json:"clouds,omitempty"`
   250  	ProductsFilePath string      `json:"path"`
   251  	ProductIds       []string    `json:"products"`
   252  }
   253  
   254  // These structs define the model used to describe download mirrors.
   255  
   256  type MirrorRefs struct {
   257  	Mirrors map[string][]MirrorReference `json:"mirrors"`
   258  }
   259  
   260  type MirrorReference struct {
   261  	Updated  string      `json:"updated"`
   262  	Format   string      `json:"format"`
   263  	DataType string      `json:"datatype"`
   264  	Path     string      `json:"path"`
   265  	Clouds   []CloudSpec `json:"clouds"`
   266  }
   267  
   268  type MirrorMetadata struct {
   269  	Updated string                  `json:"updated"`
   270  	Format  string                  `json:"format"`
   271  	Mirrors map[string][]MirrorInfo `json:"mirrors"`
   272  }
   273  
   274  type MirrorInfo struct {
   275  	Clouds    []CloudSpec `json:"clouds"`
   276  	MirrorURL string      `json:"mirror"`
   277  	Path      string      `json:"path"`
   278  }
   279  
   280  type MirrorInfoSlice []MirrorInfo
   281  type MirrorRefSlice []MirrorReference
   282  
   283  // filter returns those entries from an MirrorInfo array for which the given
   284  // match function returns true. It preserves order.
   285  func (entries MirrorInfoSlice) filter(match func(*MirrorInfo) bool) MirrorInfoSlice {
   286  	result := MirrorInfoSlice{}
   287  	for _, mirrorInfo := range entries {
   288  		if match(&mirrorInfo) {
   289  			result = append(result, mirrorInfo)
   290  		}
   291  	}
   292  	return result
   293  }
   294  
   295  // filter returns those entries from an MirrorInfo array for which the given
   296  // match function returns true. It preserves order.
   297  func (entries MirrorRefSlice) filter(match func(*MirrorReference) bool) MirrorRefSlice {
   298  	result := MirrorRefSlice{}
   299  	for _, mirrorRef := range entries {
   300  		if match(&mirrorRef) {
   301  			result = append(result, mirrorRef)
   302  		}
   303  	}
   304  	return result
   305  }
   306  
   307  // extractCatalogsForProducts gives you just those catalogs from a
   308  // cloudImageMetadata that are for the given product IDs.  They are kept in
   309  // the order of the parameter.
   310  func (metadata *CloudMetadata) extractCatalogsForProducts(productIds []string) []MetadataCatalog {
   311  	result := []MetadataCatalog{}
   312  	for _, id := range productIds {
   313  		if catalog, ok := metadata.Products[id]; ok {
   314  			result = append(result, catalog)
   315  		}
   316  	}
   317  	return result
   318  }
   319  
   320  // extractIndexes returns just the array of indexes, in arbitrary order.
   321  func (ind *Indices) extractIndexes() IndexMetadataSlice {
   322  	result := make(IndexMetadataSlice, 0, len(ind.Indexes))
   323  	for _, metadata := range ind.Indexes {
   324  		result = append(result, metadata)
   325  	}
   326  	return result
   327  }
   328  
   329  func (metadata *IndexMetadata) String() string {
   330  	return fmt.Sprintf("%v", *metadata)
   331  }
   332  
   333  // hasCloud tells you whether an IndexMetadata has the given cloud in its
   334  // Clouds list. If IndexMetadata has no clouds defined, then hasCloud
   335  // returns true regardless so that the corresponding product records
   336  // are searched.
   337  func (metadata *IndexMetadata) hasCloud(cloud CloudSpec) bool {
   338  	for _, metadataCloud := range metadata.Clouds {
   339  		if metadataCloud.equals(&cloud) {
   340  			return true
   341  		}
   342  	}
   343  	return len(metadata.Clouds) == 0
   344  }
   345  
   346  // hasProduct tells you whether an IndexMetadata provides any of the given
   347  // product IDs.
   348  func (metadata *IndexMetadata) hasProduct(prodIds []string) bool {
   349  	for _, pid := range metadata.ProductIds {
   350  		if containsString(prodIds, pid) {
   351  			return true
   352  		}
   353  	}
   354  	return false
   355  }
   356  
   357  type IndexMetadataSlice []*IndexMetadata
   358  
   359  // filter returns those entries from an IndexMetadata array for which the given
   360  // match function returns true.  It preserves order.
   361  func (entries IndexMetadataSlice) filter(match func(*IndexMetadata) bool) IndexMetadataSlice {
   362  	result := IndexMetadataSlice{}
   363  	for _, metadata := range entries {
   364  		if match(metadata) {
   365  			result = append(result, metadata)
   366  		}
   367  	}
   368  	return result
   369  }
   370  
   371  func init() {
   372  	RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
   373  }
   374  
   375  // RegisterProtocol registers a new protocol with the simplestreams http client.
   376  // Exported for testing.
   377  func RegisterProtocol(scheme string, rt http.RoundTripper) {
   378  	http.DefaultTransport.(*http.Transport).RegisterProtocol(scheme, rt)
   379  }
   380  
   381  // noMatchingProductsError is used to indicate that metadata files have been located,
   382  // but there is no metadata satisfying a product criteria.
   383  // It is used to distinguish from the situation where the metadata files could not be found.
   384  type noMatchingProductsError struct {
   385  	msg string
   386  }
   387  
   388  func (e *noMatchingProductsError) Error() string {
   389  	return e.msg
   390  }
   391  
   392  func newNoMatchingProductsError(message string, args ...interface{}) error {
   393  	return &noMatchingProductsError{fmt.Sprintf(message, args...)}
   394  }
   395  
   396  const (
   397  	StreamsDir       = "streams/v1"
   398  	UnsignedIndex    = "streams/v1/index.json"
   399  	DefaultIndexPath = "streams/v1/index"
   400  	UnsignedMirror   = "streams/v1/mirrors.json"
   401  	mirrorsPath      = "streams/v1/mirrors"
   402  	signedSuffix     = ".sjson"
   403  	UnsignedSuffix   = ".json"
   404  )
   405  
   406  type appendMatchingFunc func(DataSource, []interface{}, map[string]interface{}, LookupConstraint) []interface{}
   407  
   408  // ValueParams contains the information required to pull out from the metadata structs of a particular type.
   409  type ValueParams struct {
   410  	// The simplestreams data type key.
   411  	DataType string
   412  	// The key to use when looking for content mirrors.
   413  	MirrorContentId string
   414  	// A function used to filter and return records of a given type.
   415  	FilterFunc appendMatchingFunc
   416  	// An struct representing the type of records to return.
   417  	ValueTemplate interface{}
   418  	// For signed metadata, the public key used to validate the signature.
   419  	PublicKey string
   420  }
   421  
   422  // GetMetadata returns metadata records matching the specified constraint,looking in each source for signed metadata.
   423  // If onlySigned is false and no signed metadata is found in a source, the source is used to look for unsigned metadata.
   424  // Each source is tried in turn until at least one signed (or unsigned) match is found.
   425  func GetMetadata(sources []DataSource, baseIndexPath string, cons LookupConstraint, onlySigned bool, params ValueParams) (items []interface{}, err error) {
   426  	for _, source := range sources {
   427  		items, err = getMaybeSignedMetadata(source, baseIndexPath, cons, true, params)
   428  		// If no items are found using signed metadata, check unsigned.
   429  		if err != nil && len(items) == 0 && !onlySigned {
   430  			items, err = getMaybeSignedMetadata(source, baseIndexPath, cons, false, params)
   431  		}
   432  		if err == nil {
   433  			break
   434  		}
   435  	}
   436  	if _, ok := err.(*noMatchingProductsError); ok {
   437  		// no matching products is an internal error only
   438  		err = nil
   439  	}
   440  	return items, err
   441  }
   442  
   443  // getMaybeSignedMetadata returns metadata records matching the specified constraint.
   444  func getMaybeSignedMetadata(source DataSource, baseIndexPath string, cons LookupConstraint, signed bool, params ValueParams) ([]interface{}, error) {
   445  	indexPath := baseIndexPath + UnsignedSuffix
   446  	if signed {
   447  		indexPath = baseIndexPath + signedSuffix
   448  	}
   449  	var items []interface{}
   450  	indexURL, err := source.URL(indexPath)
   451  	if err != nil {
   452  		// Some providers return an error if asked for the URL of a non-existent file.
   453  		// So the best we can do is use the relative path for the URL when logging messages.
   454  		indexURL = indexPath
   455  	}
   456  	indexRef, err := GetIndexWithFormat(source, indexPath, "index:1.0", signed, cons.Params().CloudSpec, params)
   457  	if err != nil {
   458  		if errors.IsNotFoundError(err) || errors.IsUnauthorizedError(err) {
   459  			logger.Debugf("cannot load index %q: %v", indexURL, err)
   460  		}
   461  		return nil, err
   462  	}
   463  	logger.Debugf("read metadata index at %q", indexURL)
   464  	items, err = indexRef.getLatestMetadataWithFormat(cons, "products:1.0", signed)
   465  	if err != nil {
   466  		if errors.IsNotFoundError(err) {
   467  			logger.Debugf("skipping index because of error getting latest metadata %q: %v", indexURL, err)
   468  			return nil, err
   469  		}
   470  		if _, ok := err.(*noMatchingProductsError); ok {
   471  			logger.Debugf("%v", err)
   472  		}
   473  	}
   474  	return items, err
   475  }
   476  
   477  // fetchData gets all the data from the given source located at the specified path.
   478  // It returns the data found and the full URL used.
   479  func fetchData(source DataSource, path string, requireSigned bool, publicKey string) (data []byte, dataURL string, err error) {
   480  	rc, dataURL, err := source.Fetch(path)
   481  	if err != nil {
   482  		logger.Debugf("fetchData failed for %q: %v", dataURL, err)
   483  		return nil, dataURL, errors.NotFoundf("invalid URL %q", dataURL)
   484  	}
   485  	defer rc.Close()
   486  	if requireSigned {
   487  		data, err = DecodeCheckSignature(rc, publicKey)
   488  	} else {
   489  		data, err = ioutil.ReadAll(rc)
   490  	}
   491  	if err != nil {
   492  		return nil, dataURL, fmt.Errorf("cannot read URL data, %v", err)
   493  	}
   494  	return data, dataURL, nil
   495  }
   496  
   497  // GetIndexWithFormat returns a simplestreams index of the specified format.
   498  // Exported for testing.
   499  func GetIndexWithFormat(source DataSource, indexPath, indexFormat string, requireSigned bool,
   500  	cloudSpec CloudSpec, params ValueParams) (*IndexReference, error) {
   501  
   502  	data, url, err := fetchData(source, indexPath, requireSigned, params.PublicKey)
   503  	if err != nil {
   504  		if errors.IsNotFoundError(err) || errors.IsUnauthorizedError(err) {
   505  			return nil, err
   506  		}
   507  		return nil, fmt.Errorf("cannot read index data, %v", err)
   508  	}
   509  	var indices Indices
   510  	err = json.Unmarshal(data, &indices)
   511  	if err != nil {
   512  		return nil, fmt.Errorf("cannot unmarshal JSON index metadata at URL %q: %v", url, err)
   513  	}
   514  	if indices.Format != indexFormat {
   515  		return nil, fmt.Errorf(
   516  			"unexpected index file format %q, expected %q at URL %q", indices.Format, indexFormat, url)
   517  	}
   518  
   519  	mirrors, url, err := getMirrorRefs(source, mirrorsPath, requireSigned, params)
   520  	if err != nil && !errors.IsNotFoundError(err) && !errors.IsUnauthorizedError(err) {
   521  		return nil, fmt.Errorf("cannot load mirror metadata at URL %q: %v", url, err)
   522  	}
   523  
   524  	indexRef := &IndexReference{
   525  		Source:      source,
   526  		Indices:     indices,
   527  		valueParams: params,
   528  	}
   529  
   530  	// Apply any mirror information to the source.
   531  	if params.MirrorContentId != "" {
   532  		mirrorInfo, err := getMirror(
   533  			source, mirrors, params.DataType, params.MirrorContentId, cloudSpec, requireSigned, params.PublicKey)
   534  		if err == nil {
   535  			logger.Debugf("using mirrored products path: %s", path.Join(mirrorInfo.MirrorURL, mirrorInfo.Path))
   536  			indexRef.Source = NewURLDataSource(mirrorInfo.MirrorURL, VerifySSLHostnames)
   537  			indexRef.MirroredProductsPath = mirrorInfo.Path
   538  		} else {
   539  			logger.Debugf("no mirror information available for %s: %v", cloudSpec, err)
   540  		}
   541  	}
   542  
   543  	return indexRef, nil
   544  }
   545  
   546  // getMirrorRefs parses and returns a simplestreams mirror reference.
   547  func getMirrorRefs(source DataSource, baseMirrorsPath string, requireSigned bool,
   548  	params ValueParams) (MirrorRefs, string, error) {
   549  
   550  	mirrorsPath := baseMirrorsPath + UnsignedSuffix
   551  	if requireSigned {
   552  		mirrorsPath = baseMirrorsPath + signedSuffix
   553  	}
   554  	var mirrors MirrorRefs
   555  	data, url, err := fetchData(source, mirrorsPath, requireSigned, params.PublicKey)
   556  	if err != nil {
   557  		if errors.IsNotFoundError(err) || errors.IsUnauthorizedError(err) {
   558  			logger.Debugf("no mirror index file found")
   559  			return mirrors, url, err
   560  		}
   561  		return mirrors, url, fmt.Errorf("cannot read mirrors data, %v", err)
   562  	}
   563  	err = json.Unmarshal(data, &mirrors)
   564  	if err != nil {
   565  		return mirrors, url, fmt.Errorf("cannot unmarshal JSON mirror metadata at URL %q: %v", url, err)
   566  	}
   567  	return mirrors, url, err
   568  }
   569  
   570  // getMirror returns a mirror info struct matching the specified content and cloud.
   571  func getMirror(source DataSource, mirrors MirrorRefs, datatype, contentId string, cloudSpec CloudSpec,
   572  	requireSigned bool, publicKey string) (*MirrorInfo, error) {
   573  
   574  	mirrorRef, err := mirrors.getMirrorReference(datatype, contentId, cloudSpec)
   575  	if err != nil {
   576  		return nil, err
   577  	}
   578  	mirrorInfo, err := mirrorRef.getMirrorInfo(source, contentId, cloudSpec, "mirrors:1.0", requireSigned, publicKey)
   579  	if err != nil {
   580  		return nil, err
   581  	}
   582  	if mirrorInfo == nil {
   583  		return nil, errors.NotFoundf("mirror metadata for %q and cloud %v", contentId, cloudSpec)
   584  	}
   585  	return mirrorInfo, nil
   586  }
   587  
   588  // GetProductsPath returns the path to the metadata file containing products for the specified constraint.
   589  // Exported for testing.
   590  func (indexRef *IndexReference) GetProductsPath(cons LookupConstraint) (string, error) {
   591  	if indexRef.MirroredProductsPath != "" {
   592  		return indexRef.MirroredProductsPath, nil
   593  	}
   594  	prodIds, err := cons.Ids()
   595  	if err != nil {
   596  		return "", err
   597  	}
   598  	candidates := indexRef.extractIndexes()
   599  	// Restrict to image-ids entries.
   600  	dataTypeMatches := func(metadata *IndexMetadata) bool {
   601  		return metadata.DataType == indexRef.valueParams.DataType
   602  	}
   603  	candidates = candidates.filter(dataTypeMatches)
   604  	if len(candidates) == 0 {
   605  		return "", errors.NotFoundf("index file missing %q data", indexRef.valueParams.DataType)
   606  	}
   607  	// Restrict by cloud spec, if required.
   608  	if cons.Params().CloudSpec != EmptyCloudSpec {
   609  		hasRightCloud := func(metadata *IndexMetadata) bool {
   610  			return metadata.hasCloud(cons.Params().CloudSpec)
   611  		}
   612  		candidates = candidates.filter(hasRightCloud)
   613  		if len(candidates) == 0 {
   614  			return "", errors.NotFoundf("index file has no data for cloud %v", cons.Params().CloudSpec)
   615  		}
   616  	}
   617  	// Restrict by product IDs.
   618  	hasProduct := func(metadata *IndexMetadata) bool {
   619  		return metadata.hasProduct(prodIds)
   620  	}
   621  	candidates = candidates.filter(hasProduct)
   622  	if len(candidates) == 0 {
   623  		return "", newNoMatchingProductsError("index file has no data for product name(s) %q", prodIds)
   624  	}
   625  
   626  	logger.Debugf("candidate matches for products %q are %v", prodIds, candidates)
   627  
   628  	// Pick arbitrary match.
   629  	return candidates[0].ProductsFilePath, nil
   630  }
   631  
   632  // extractMirrorRefs returns just the array of MirrorRef structs for the contentId, in arbitrary order.
   633  func (mirrorRefs *MirrorRefs) extractMirrorRefs(contentId string) MirrorRefSlice {
   634  	for id, refs := range mirrorRefs.Mirrors {
   635  		if id == contentId {
   636  			return refs
   637  		}
   638  	}
   639  	return nil
   640  }
   641  
   642  // hasCloud tells you whether a MirrorReference has the given cloud in its
   643  // Clouds list.
   644  func (mirrorRef *MirrorReference) hasCloud(cloud CloudSpec) bool {
   645  	for _, refCloud := range mirrorRef.Clouds {
   646  		if refCloud.equals(&cloud) {
   647  			return true
   648  		}
   649  	}
   650  	return false
   651  }
   652  
   653  // GetMirrorReference returns the reference to the metadata file containing mirrors for the specified content and cloud.
   654  func (mirrorRefs *MirrorRefs) getMirrorReference(datatype, contentId string, cloud CloudSpec) (*MirrorReference, error) {
   655  	candidates := mirrorRefs.extractMirrorRefs(contentId)
   656  	if len(candidates) == 0 {
   657  		return nil, errors.NotFoundf("mirror data for %q", contentId)
   658  	}
   659  	// Restrict by cloud spec and datatype.
   660  	hasRightCloud := func(mirrorRef *MirrorReference) bool {
   661  		return mirrorRef.hasCloud(cloud) && mirrorRef.DataType == datatype
   662  	}
   663  	matchingCandidates := candidates.filter(hasRightCloud)
   664  	if len(matchingCandidates) == 0 {
   665  		// No cloud specific mirrors found so look for a non cloud specific mirror.
   666  		for _, candidate := range candidates {
   667  			if len(candidate.Clouds) == 0 {
   668  				logger.Debugf("using default candidate for content id %q are %v", contentId, candidate)
   669  				return &candidate, nil
   670  			}
   671  		}
   672  		return nil, errors.NotFoundf("index file with cloud %v", cloud)
   673  	}
   674  
   675  	logger.Debugf("candidate matches for content id %q are %v", contentId, candidates)
   676  
   677  	// Pick arbitrary match.
   678  	return &matchingCandidates[0], nil
   679  }
   680  
   681  // getMirrorInfo returns mirror information from the mirror file at the given path for the specified content and cloud.
   682  func (mirrorRef *MirrorReference) getMirrorInfo(source DataSource, contentId string, cloud CloudSpec, format string,
   683  	requireSigned bool, publicKey string) (*MirrorInfo, error) {
   684  
   685  	metadata, err := GetMirrorMetadataWithFormat(source, mirrorRef.Path, format, requireSigned, publicKey)
   686  	if err != nil {
   687  		return nil, err
   688  	}
   689  	mirrorInfo, err := metadata.getMirrorInfo(contentId, cloud)
   690  	if err != nil {
   691  		return nil, err
   692  	}
   693  	return mirrorInfo, nil
   694  }
   695  
   696  // GetMirrorMetadataWithFormat returns simplestreams mirror data of the specified format.
   697  // Exported for testing.
   698  func GetMirrorMetadataWithFormat(source DataSource, mirrorPath, format string,
   699  	requireSigned bool, publicKey string) (*MirrorMetadata, error) {
   700  
   701  	data, url, err := fetchData(source, mirrorPath, requireSigned, publicKey)
   702  	if err != nil {
   703  		if errors.IsNotFoundError(err) || errors.IsUnauthorizedError(err) {
   704  			return nil, err
   705  		}
   706  		return nil, fmt.Errorf("cannot read mirror data, %v", err)
   707  	}
   708  	var mirrors MirrorMetadata
   709  	err = json.Unmarshal(data, &mirrors)
   710  	if err != nil {
   711  		return nil, fmt.Errorf("cannot unmarshal JSON mirror metadata at URL %q: %v", url, err)
   712  	}
   713  	if mirrors.Format != format {
   714  		return nil, fmt.Errorf("unexpected mirror file format %q, expected %q at URL %q", mirrors.Format, format, url)
   715  	}
   716  	return &mirrors, nil
   717  }
   718  
   719  // hasCloud tells you whether an MirrorInfo has the given cloud in its
   720  // Clouds list.
   721  func (mirrorInfo *MirrorInfo) hasCloud(cloud CloudSpec) bool {
   722  	for _, metadataCloud := range mirrorInfo.Clouds {
   723  		if metadataCloud.equals(&cloud) {
   724  			return true
   725  		}
   726  	}
   727  	return false
   728  }
   729  
   730  // getMirrorInfo returns the mirror metadata for the specified content and cloud.
   731  func (mirrorMetadata *MirrorMetadata) getMirrorInfo(contentId string, cloud CloudSpec) (*MirrorInfo, error) {
   732  	var candidates MirrorInfoSlice
   733  	for id, m := range mirrorMetadata.Mirrors {
   734  		if id == contentId {
   735  			candidates = m
   736  			break
   737  		}
   738  	}
   739  	if len(candidates) == 0 {
   740  		return nil, errors.NotFoundf("mirror info for %q", contentId)
   741  	}
   742  
   743  	// Restrict by cloud spec.
   744  	hasRightCloud := func(mirrorInfo *MirrorInfo) bool {
   745  		return mirrorInfo.hasCloud(cloud)
   746  	}
   747  	candidates = candidates.filter(hasRightCloud)
   748  	if len(candidates) == 0 {
   749  		return nil, errors.NotFoundf("mirror info with cloud %v", cloud)
   750  	}
   751  
   752  	// Pick arbitrary match.
   753  	return &candidates[0], nil
   754  }
   755  
   756  // utility function to see if element exists in values slice.
   757  func containsString(values []string, element string) bool {
   758  	for _, value := range values {
   759  		if value == element {
   760  			return true
   761  		}
   762  	}
   763  	return false
   764  }
   765  
   766  // To keep the metadata concise, attributes on the metadata struct which have the same value for each
   767  // item may be moved up to a higher level in the tree. denormaliseMetadata descends the tree
   768  // and fills in any missing attributes with values from a higher level.
   769  func (metadata *CloudMetadata) denormaliseMetadata() {
   770  	for _, metadataCatalog := range metadata.Products {
   771  		for _, ItemCollection := range metadataCatalog.Items {
   772  			for _, item := range ItemCollection.Items {
   773  				coll := *ItemCollection
   774  				inherit(&coll, metadataCatalog)
   775  				inherit(item, &coll)
   776  			}
   777  		}
   778  	}
   779  }
   780  
   781  // inherit sets any blank fields in dst to their equivalent values in fields in src that have matching json tags.
   782  // The dst parameter must be a pointer to a struct.
   783  func inherit(dst, src interface{}) {
   784  	for tag := range tags(dst) {
   785  		setFieldByTag(dst, tag, fieldByTag(src, tag), false)
   786  	}
   787  }
   788  
   789  // processAliases looks through the struct fields to see if
   790  // any aliases apply, and sets attributes appropriately if so.
   791  func (metadata *CloudMetadata) processAliases(item interface{}) {
   792  	for tag := range tags(item) {
   793  		aliases, ok := metadata.Aliases[tag]
   794  		if !ok {
   795  			continue
   796  		}
   797  		// We have found a set of aliases for one of the fields in the metadata struct.
   798  		// Now check to see if the field matches one of the defined aliases.
   799  		fields, ok := aliases[fieldByTag(item, tag)]
   800  		if !ok {
   801  			continue
   802  		}
   803  		// The alias matches - set all the aliased fields in the struct.
   804  		for attr, val := range fields {
   805  			setFieldByTag(item, attr, val, true)
   806  		}
   807  	}
   808  }
   809  
   810  // Apply any attribute aliases to the metadata records.
   811  func (metadata *CloudMetadata) applyAliases() {
   812  	for _, metadataCatalog := range metadata.Products {
   813  		for _, ItemCollection := range metadataCatalog.Items {
   814  			for _, item := range ItemCollection.Items {
   815  				metadata.processAliases(item)
   816  			}
   817  		}
   818  	}
   819  }
   820  
   821  // construct iterates over the metadata records and replaces the generic maps of values
   822  // with structs of the required type.
   823  func (metadata *CloudMetadata) construct(valueType reflect.Type) error {
   824  	for _, metadataCatalog := range metadata.Products {
   825  		for _, ItemCollection := range metadataCatalog.Items {
   826  			if err := ItemCollection.construct(valueType); err != nil {
   827  				return err
   828  			}
   829  		}
   830  	}
   831  	return nil
   832  }
   833  
   834  type structTags map[reflect.Type]map[string]int
   835  
   836  var tagsForType structTags = make(structTags)
   837  
   838  // RegisterStructTags ensures the json tags for the given structs are able to be used
   839  // when parsing the simplestreams metadata.
   840  func RegisterStructTags(vals ...interface{}) {
   841  	tags := mkTags(vals...)
   842  	for k, v := range tags {
   843  		tagsForType[k] = v
   844  	}
   845  }
   846  
   847  func init() {
   848  	RegisterStructTags(MetadataCatalog{}, ItemCollection{})
   849  }
   850  
   851  func mkTags(vals ...interface{}) map[reflect.Type]map[string]int {
   852  	typeMap := make(map[reflect.Type]map[string]int)
   853  	for _, v := range vals {
   854  		t := reflect.TypeOf(v)
   855  		typeMap[t] = jsonTags(t)
   856  	}
   857  	return typeMap
   858  }
   859  
   860  // jsonTags returns a map from json tag to the field index for the string fields in the given type.
   861  func jsonTags(t reflect.Type) map[string]int {
   862  	if t.Kind() != reflect.Struct {
   863  		panic(fmt.Errorf("cannot get json tags on type %s", t))
   864  	}
   865  	tags := make(map[string]int)
   866  	for i := 0; i < t.NumField(); i++ {
   867  		f := t.Field(i)
   868  		if f.Type != reflect.TypeOf("") {
   869  			continue
   870  		}
   871  		if tag := f.Tag.Get("json"); tag != "" {
   872  			if i := strings.Index(tag, ","); i >= 0 {
   873  				tag = tag[0:i]
   874  			}
   875  			if tag == "-" {
   876  				continue
   877  			}
   878  			if tag != "" {
   879  				f.Name = tag
   880  			}
   881  		}
   882  		tags[f.Name] = i
   883  	}
   884  	return tags
   885  }
   886  
   887  // tags returns the field offsets for the JSON tags defined by the given value, which must be
   888  // a struct or a pointer to a struct.
   889  func tags(x interface{}) map[string]int {
   890  	t := reflect.TypeOf(x)
   891  	if t.Kind() == reflect.Ptr {
   892  		t = t.Elem()
   893  	}
   894  	if t.Kind() != reflect.Struct {
   895  		panic(fmt.Errorf("expected struct, not %s", t))
   896  	}
   897  
   898  	if tagm := tagsForType[t]; tagm != nil {
   899  		return tagm
   900  	}
   901  	panic(fmt.Errorf("%s not found in type table", t))
   902  }
   903  
   904  // fieldByTag returns the value for the field in x with the given JSON tag, or "" if there is no such field.
   905  func fieldByTag(x interface{}, tag string) string {
   906  	tagm := tags(x)
   907  	v := reflect.ValueOf(x)
   908  	if v.Kind() == reflect.Ptr {
   909  		v = v.Elem()
   910  	}
   911  	if i, ok := tagm[tag]; ok {
   912  		return v.Field(i).Interface().(string)
   913  	}
   914  	return ""
   915  }
   916  
   917  // setFieldByTag sets the value for the field in x with the given JSON tag to val.
   918  // The override parameter specifies whether the value will be set even if the original value is non-empty.
   919  func setFieldByTag(x interface{}, tag, val string, override bool) {
   920  	i, ok := tags(x)[tag]
   921  	if !ok {
   922  		return
   923  	}
   924  	v := reflect.ValueOf(x).Elem()
   925  	f := v.Field(i)
   926  	if override || f.Interface().(string) == "" {
   927  		f.Set(reflect.ValueOf(val))
   928  	}
   929  }
   930  
   931  // GetCloudMetadataWithFormat loads the entire cloud metadata encoded using the specified format.
   932  // Exported for testing.
   933  func (indexRef *IndexReference) GetCloudMetadataWithFormat(cons LookupConstraint, format string, requireSigned bool) (*CloudMetadata, error) {
   934  	productFilesPath, err := indexRef.GetProductsPath(cons)
   935  	if err != nil {
   936  		return nil, err
   937  	}
   938  	logger.Debugf("finding products at path %q", productFilesPath)
   939  	data, url, err := fetchData(indexRef.Source, productFilesPath, requireSigned, indexRef.valueParams.PublicKey)
   940  	if err != nil {
   941  		return nil, fmt.Errorf("cannot read product data, %v", err)
   942  	}
   943  	return ParseCloudMetadata(data, format, url, indexRef.valueParams.ValueTemplate)
   944  }
   945  
   946  // ParseCloudMetadata parses the given bytes into simplestreams metadata.
   947  func ParseCloudMetadata(data []byte, format, url string, valueTemplate interface{}) (*CloudMetadata, error) {
   948  	var metadata CloudMetadata
   949  	err := json.Unmarshal(data, &metadata)
   950  	if err != nil {
   951  		return nil, fmt.Errorf("cannot unmarshal JSON metadata at URL %q: %v", url, err)
   952  	}
   953  	if metadata.Format != format {
   954  		return nil, fmt.Errorf("unexpected index file format %q, expected %q at URL %q", metadata.Format, format, url)
   955  	}
   956  	if valueTemplate != nil {
   957  		err = metadata.construct(reflect.TypeOf(valueTemplate))
   958  	}
   959  	if err != nil {
   960  		return nil, fmt.Errorf("cannot unmarshal JSON metadata at URL %q: %v", url, err)
   961  	}
   962  	metadata.applyAliases()
   963  	metadata.denormaliseMetadata()
   964  	return &metadata, nil
   965  }
   966  
   967  // getLatestMetadataWithFormat loads the metadata for the given cloud and orders the resulting structs
   968  // starting with the most recent, and returns items which match the product criteria, choosing from the
   969  // latest versions first.
   970  func (indexRef *IndexReference) getLatestMetadataWithFormat(cons LookupConstraint, format string, requireSigned bool) ([]interface{}, error) {
   971  	metadata, err := indexRef.GetCloudMetadataWithFormat(cons, format, requireSigned)
   972  	if err != nil {
   973  		return nil, err
   974  	}
   975  	logger.Debugf("metadata: %v", metadata)
   976  	matches, err := GetLatestMetadata(metadata, cons, indexRef.Source, indexRef.valueParams.FilterFunc)
   977  	if err != nil {
   978  		return nil, err
   979  	}
   980  	if len(matches) == 0 {
   981  		return nil, newNoMatchingProductsError("index has no matching records")
   982  	}
   983  	return matches, nil
   984  }
   985  
   986  // GetLatestMetadata extracts and returns the metadata records matching the given criteria.
   987  func GetLatestMetadata(metadata *CloudMetadata, cons LookupConstraint, source DataSource, filterFunc appendMatchingFunc) ([]interface{}, error) {
   988  	prodIds, err := cons.Ids()
   989  	if err != nil {
   990  		return nil, err
   991  	}
   992  
   993  	catalogs := metadata.extractCatalogsForProducts(prodIds)
   994  	if len(catalogs) == 0 {
   995  		availableProducts := make([]string, 0, len(metadata.Products))
   996  		for product := range metadata.Products {
   997  			availableProducts = append(availableProducts, product)
   998  		}
   999  		return nil, newNoMatchingProductsError(
  1000  			"index has no records for product ids %v; it does have product ids %v", prodIds, availableProducts)
  1001  	}
  1002  
  1003  	var matchingItems []interface{}
  1004  	for _, catalog := range catalogs {
  1005  		var bv byVersionDesc = make(byVersionDesc, len(catalog.Items))
  1006  		i := 0
  1007  		for vers, itemColl := range catalog.Items {
  1008  			bv[i] = collectionVersion{vers, itemColl}
  1009  			i++
  1010  		}
  1011  		sort.Sort(bv)
  1012  		for _, itemCollVersion := range bv {
  1013  			matchingItems = filterFunc(source, matchingItems, itemCollVersion.ItemCollection.Items, cons)
  1014  		}
  1015  	}
  1016  	return matchingItems, nil
  1017  }
  1018  
  1019  type collectionVersion struct {
  1020  	version        string
  1021  	ItemCollection *ItemCollection
  1022  }
  1023  
  1024  // byVersionDesc is used to sort a slice of collections in descending order of their
  1025  // version in YYYYMMDD.
  1026  type byVersionDesc []collectionVersion
  1027  
  1028  func (bv byVersionDesc) Len() int { return len(bv) }
  1029  func (bv byVersionDesc) Swap(i, j int) {
  1030  	bv[i], bv[j] = bv[j], bv[i]
  1031  }
  1032  func (bv byVersionDesc) Less(i, j int) bool {
  1033  	return bv[i].version > bv[j].version
  1034  }