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 }