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