github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/environs/imagemetadata/generate.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package imagemetadata
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"path"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils/series"
    14  
    15  	"github.com/juju/juju/environs/simplestreams"
    16  	"github.com/juju/juju/environs/storage"
    17  )
    18  
    19  // IndexStoragePath returns the storage path for the image metadata index file.
    20  func IndexStoragePath() string {
    21  	return path.Join(storage.BaseImagesPath, simplestreams.UnsignedIndex(currentStreamsVersion, IndexFileVersion))
    22  }
    23  
    24  // ProductMetadataStoragePath returns the storage path for the image metadata products file.
    25  func ProductMetadataStoragePath() string {
    26  	return path.Join(storage.BaseImagesPath, ProductMetadataPath)
    27  }
    28  
    29  // MergeAndWriteMetadata reads the existing metadata from storage (if any),
    30  // and merges it with supplied metadata, writing the resulting metadata is written to storage.
    31  func MergeAndWriteMetadata(ser string, metadata []*ImageMetadata, cloudSpec *simplestreams.CloudSpec,
    32  	metadataStore storage.Storage) error {
    33  
    34  	existingMetadata, err := readMetadata(metadataStore)
    35  	if err != nil {
    36  		return err
    37  	}
    38  	seriesVersion, err := series.SeriesVersion(ser)
    39  	if err != nil {
    40  		return err
    41  	}
    42  	toWrite, allCloudSpec := mergeMetadata(seriesVersion, cloudSpec, metadata, existingMetadata)
    43  	return writeMetadata(toWrite, allCloudSpec, metadataStore)
    44  }
    45  
    46  // readMetadata reads the image metadata from metadataStore.
    47  func readMetadata(metadataStore storage.Storage) ([]*ImageMetadata, error) {
    48  	// Read any existing metadata so we can merge the new tools metadata with what's there.
    49  	dataSource := storage.NewStorageSimpleStreamsDataSource("existing metadata", metadataStore, storage.BaseImagesPath, simplestreams.EXISTING_CLOUD_DATA, false)
    50  	imageConstraint := NewImageConstraint(simplestreams.LookupParams{})
    51  	existingMetadata, _, err := Fetch([]simplestreams.DataSource{dataSource}, imageConstraint)
    52  	if err != nil && !errors.IsNotFound(err) {
    53  		return nil, err
    54  	}
    55  	return existingMetadata, nil
    56  }
    57  
    58  func mapKey(im *ImageMetadata) string {
    59  	return fmt.Sprintf("%s-%s", im.productId(), im.RegionName)
    60  }
    61  
    62  // mergeMetadata merges the newMetadata into existingMetadata, overwriting existing matching image records.
    63  func mergeMetadata(seriesVersion string, cloudSpec *simplestreams.CloudSpec, newMetadata,
    64  	existingMetadata []*ImageMetadata) ([]*ImageMetadata, []simplestreams.CloudSpec) {
    65  
    66  	var toWrite = make([]*ImageMetadata, len(newMetadata))
    67  	imageIds := make(map[string]bool)
    68  	for i, im := range newMetadata {
    69  		newRecord := *im
    70  		newRecord.Version = seriesVersion
    71  		newRecord.RegionName = cloudSpec.Region
    72  		newRecord.Endpoint = cloudSpec.Endpoint
    73  		toWrite[i] = &newRecord
    74  		imageIds[mapKey(&newRecord)] = true
    75  	}
    76  	regions := make(map[string]bool)
    77  	var allCloudSpecs = []simplestreams.CloudSpec{*cloudSpec}
    78  	for _, im := range existingMetadata {
    79  		if _, ok := imageIds[mapKey(im)]; ok {
    80  			continue
    81  		}
    82  		toWrite = append(toWrite, im)
    83  		if _, ok := regions[im.RegionName]; ok {
    84  			continue
    85  		}
    86  		regions[im.RegionName] = true
    87  		existingCloudSpec := simplestreams.CloudSpec{
    88  			Region:   im.RegionName,
    89  			Endpoint: im.Endpoint,
    90  		}
    91  		allCloudSpecs = append(allCloudSpecs, existingCloudSpec)
    92  	}
    93  	return toWrite, allCloudSpecs
    94  }
    95  
    96  type MetadataFile struct {
    97  	Path string
    98  	Data []byte
    99  }
   100  
   101  // writeMetadata generates some basic simplestreams metadata using the specified cloud and image details and writes
   102  // it to the supplied store.
   103  func writeMetadata(metadata []*ImageMetadata, cloudSpec []simplestreams.CloudSpec,
   104  	metadataStore storage.Storage) error {
   105  
   106  	index, products, err := MarshalImageMetadataJSON(metadata, cloudSpec, time.Now())
   107  	if err != nil {
   108  		return err
   109  	}
   110  	metadataInfo := []MetadataFile{
   111  		{IndexStoragePath(), index},
   112  		{ProductMetadataStoragePath(), products},
   113  	}
   114  	for _, md := range metadataInfo {
   115  		err = metadataStore.Put(md.Path, bytes.NewReader(md.Data), int64(len(md.Data)))
   116  		if err != nil {
   117  			return err
   118  		}
   119  	}
   120  	return nil
   121  }