github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/simplestreams/simplestreams.go (about)

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