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