Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(159)

Side by Side Diff: environs/tools/simplestreams.go

Issue 14527043: Fix tools metadata generation for null provider
Patch Set: Fix tools metadata generation for null provider Created 11 years, 6 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « environs/testing/tools.go ('k') | environs/tools/simplestreams_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 Canonical Ltd. 1 // Copyright 2013 Canonical Ltd.
2 // Licensed under the AGPLv3, see LICENCE file for details. 2 // Licensed under the AGPLv3, see LICENCE file for details.
3 3
4 // The tools package supports locating, parsing, and filtering Ubuntu tools meta data in simplestreams format. 4 // The tools package supports locating, parsing, and filtering Ubuntu tools meta data in simplestreams format.
5 // See http://launchpad.net/simplestreams and in particular the doc/README file in that project for more information 5 // See http://launchpad.net/simplestreams and in particular the doc/README file in that project for more information
6 // about the file formats. 6 // about the file formats.
7 package tools 7 package tools
8 8
9 import ( 9 import (
10 "bytes" 10 "bytes"
11 "crypto/sha256" 11 "crypto/sha256"
12 "fmt" 12 "fmt"
13 "hash" 13 "hash"
14 "io" 14 "io"
15 "net/http"
16 "strings" 15 "strings"
17 "time" 16 "time"
18 17
19 "launchpad.net/juju-core/environs/simplestreams" 18 "launchpad.net/juju-core/environs/simplestreams"
20 "launchpad.net/juju-core/environs/storage" 19 "launchpad.net/juju-core/environs/storage"
21 "launchpad.net/juju-core/errors" 20 "launchpad.net/juju-core/errors"
22 coretools "launchpad.net/juju-core/tools" 21 coretools "launchpad.net/juju-core/tools"
23 "launchpad.net/juju-core/utils/set" 22 "launchpad.net/juju-core/utils/set"
24 "launchpad.net/juju-core/version" 23 "launchpad.net/juju-core/version"
25 ) 24 )
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 return allIds, nil 73 return allIds, nil
75 } 74 }
76 75
77 // ToolsMetadata holds information about a particular tools tarball. 76 // ToolsMetadata holds information about a particular tools tarball.
78 type ToolsMetadata struct { 77 type ToolsMetadata struct {
79 Release string `json:"release"` 78 Release string `json:"release"`
80 Version string `json:"version"` 79 Version string `json:"version"`
81 Arch string `json:"arch"` 80 Arch string `json:"arch"`
82 Size int64 `json:"size"` 81 Size int64 `json:"size"`
83 Path string `json:"path"` 82 Path string `json:"path"`
84 » FullPath string `json:"-,omitempty"` 83 » FullPath string `json:"-"`
85 FileType string `json:"ftype"` 84 FileType string `json:"ftype"`
86 SHA256 string `json:"sha256"` 85 SHA256 string `json:"sha256"`
87 } 86 }
88 87
89 func (t *ToolsMetadata) String() string { 88 func (t *ToolsMetadata) String() string {
90 return fmt.Sprintf("%+v", *t) 89 return fmt.Sprintf("%+v", *t)
91 } 90 }
92 91
92 // binary returns the tools metadata's binary version,
93 // which may be used for map lookup.
94 func (t *ToolsMetadata) binary() version.Binary {
95 return version.Binary{
96 Number: version.MustParse(t.Version),
97 Series: t.Release,
98 Arch: t.Arch,
99 }
100 }
101
93 func (t *ToolsMetadata) productId() (string, error) { 102 func (t *ToolsMetadata) productId() (string, error) {
94 seriesVersion, err := simplestreams.SeriesVersion(t.Release) 103 seriesVersion, err := simplestreams.SeriesVersion(t.Release)
95 if err != nil { 104 if err != nil {
96 return "", err 105 return "", err
97 } 106 }
98 return fmt.Sprintf("com.ubuntu.juju:%s:%s", seriesVersion, t.Arch), nil 107 return fmt.Sprintf("com.ubuntu.juju:%s:%s", seriesVersion, t.Arch), nil
99 } 108 }
100 109
101 func excludeDefaultSource(sources []simplestreams.DataSource) []simplestreams.Da taSource { 110 func excludeDefaultSource(sources []simplestreams.DataSource) []simplestreams.Da taSource {
102 var result []simplestreams.DataSource 111 var result []simplestreams.DataSource
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
134 metadata[i] = md.(*ToolsMetadata) 143 metadata[i] = md.(*ToolsMetadata)
135 } 144 }
136 return metadata, nil 145 return metadata, nil
137 } 146 }
138 147
139 // appendMatchingTools updates matchingTools with tools metadata records from to ols which belong to the 148 // appendMatchingTools updates matchingTools with tools metadata records from to ols which belong to the
140 // specified series. If a tools record already exists in matchingTools, it is no t overwritten. 149 // specified series. If a tools record already exists in matchingTools, it is no t overwritten.
141 func appendMatchingTools(source simplestreams.DataSource, matchingTools []interf ace{}, 150 func appendMatchingTools(source simplestreams.DataSource, matchingTools []interf ace{},
142 tools map[string]interface{}, cons simplestreams.LookupConstraint) []int erface{} { 151 tools map[string]interface{}, cons simplestreams.LookupConstraint) []int erface{} {
143 152
144 » toolsMap := make(map[string]*ToolsMetadata, len(matchingTools)) 153 » toolsMap := make(map[version.Binary]*ToolsMetadata, len(matchingTools))
145 for _, val := range matchingTools { 154 for _, val := range matchingTools {
146 tm := val.(*ToolsMetadata) 155 tm := val.(*ToolsMetadata)
147 » » toolsMap[fmt.Sprintf("%s-%s-%s", tm.Release, tm.Version, tm.Arch )] = tm 156 » » toolsMap[tm.binary()] = tm
148 } 157 }
149 for _, val := range tools { 158 for _, val := range tools {
150 tm := val.(*ToolsMetadata) 159 tm := val.(*ToolsMetadata)
151 if !set.NewStrings(cons.Params().Series...).Contains(tm.Release) { 160 if !set.NewStrings(cons.Params().Series...).Contains(tm.Release) {
152 continue 161 continue
153 } 162 }
154 if toolsConstraint, ok := cons.(*ToolsConstraint); ok { 163 if toolsConstraint, ok := cons.(*ToolsConstraint); ok {
155 tmNumber := version.MustParse(tm.Version) 164 tmNumber := version.MustParse(tm.Version)
156 if toolsConstraint.Version == version.Zero { 165 if toolsConstraint.Version == version.Zero {
157 if toolsConstraint.Released && tmNumber.IsDev() { 166 if toolsConstraint.Released && tmNumber.IsDev() {
158 continue 167 continue
159 } 168 }
160 if toolsConstraint.MajorVersion >= 0 && toolsCon straint.MajorVersion != tmNumber.Major { 169 if toolsConstraint.MajorVersion >= 0 && toolsCon straint.MajorVersion != tmNumber.Major {
161 continue 170 continue
162 } 171 }
163 if toolsConstraint.MinorVersion >= 0 && toolsCon straint.MinorVersion != tmNumber.Minor { 172 if toolsConstraint.MinorVersion >= 0 && toolsCon straint.MinorVersion != tmNumber.Minor {
164 continue 173 continue
165 } 174 }
166 } else { 175 } else {
167 if toolsConstraint.Version != tmNumber { 176 if toolsConstraint.Version != tmNumber {
168 continue 177 continue
169 } 178 }
170 } 179 }
171 } 180 }
172 » » if _, ok := toolsMap[fmt.Sprintf("%s-%s-%s", tm.Release, tm.Vers ion, tm.Arch)]; !ok { 181 » » if _, ok := toolsMap[tm.binary()]; !ok {
173 tm.FullPath, _ = source.URL(tm.Path) 182 tm.FullPath, _ = source.URL(tm.Path)
174 matchingTools = append(matchingTools, tm) 183 matchingTools = append(matchingTools, tm)
175 } 184 }
176 } 185 }
177 return matchingTools 186 return matchingTools
178 } 187 }
179 188
180 type MetadataFile struct { 189 type MetadataFile struct {
181 Path string 190 Path string
182 Data []byte 191 Data []byte
183 } 192 }
184 193
185 func WriteMetadata(toolsList coretools.List, fetch bool, metadataStore storage.S torage) error { 194 // MetadataFromTools returns a tools metadata list derived from the
186 » // Read any existing metadata so we can merge the new tools metadata wit h what's there. 195 // given tools list. The size and sha256 will not be computed if
187 » // The metadata from toolsList is already present, the existing data is overwritten. 196 // missing.
188 » dataSource := storage.NewStorageSimpleStreamsDataSource(metadataStore, " tools") 197 func MetadataFromTools(toolsList coretools.List) []*ToolsMetadata {
198 » metadata := make([]*ToolsMetadata, len(toolsList))
199 » for i, t := range toolsList {
200 » » path := fmt.Sprintf("releases/juju-%s-%s-%s.tgz", t.Version.Numb er, t.Version.Series, t.Version.Arch)
201 » » metadata[i] = &ToolsMetadata{
202 » » » Release: t.Version.Series,
203 » » » Version: t.Version.Number.String(),
204 » » » Arch: t.Version.Arch,
205 » » » FullPath: t.URL,
206 » » » Path: path,
207 » » » FileType: "tar.gz",
208 » » » Size: t.Size,
209 » » » SHA256: t.SHA256,
210 » » }
211 » }
212 » return metadata
213 }
214
215 // ResolveMetadata resolves incomplete metadata
216 // by fetching the tools from storage and computing
217 // the size and hash locally.
218 func ResolveMetadata(stor storage.StorageReader, metadata []*ToolsMetadata) erro r {
219 » for _, md := range metadata {
220 » » if md.Size != 0 {
221 » » » continue
222 » » }
223 » » binary := md.binary()
224 » » logger.Infof("Fetching tools to generate hash: %v", binary)
225 » » var sha256hash hash.Hash
226 » » size, sha256hash, err := fetchToolsHash(stor, binary)
227 » » if err != nil {
228 » » » return err
229 » » }
230 » » md.Size = size
231 » » md.SHA256 = fmt.Sprintf("%x", sha256hash.Sum(nil))
232 » }
233 » return nil
234 }
235
236 // MergeMetadata merges the given tools metadata.
237 // If metadata for the same tools version exists in both lists,
238 // an entry with non-empty size/SHA256 takes precedence; if
239 // the two entries have different sizes/hashes, then an error is
240 // returned.
241 func MergeMetadata(tmlist1, tmlist2 []*ToolsMetadata) ([]*ToolsMetadata, error) {
242 » merged := make(map[version.Binary]*ToolsMetadata)
243 » for _, tm := range tmlist1 {
244 » » merged[tm.binary()] = tm
245 » }
246 » for _, tm := range tmlist2 {
247 » » binary := tm.binary()
248 » » if existing, ok := merged[binary]; ok {
249 » » » if tm.Size != 0 {
250 » » » » if existing.Size == 0 {
251 » » » » » merged[binary] = tm
252 » » » » } else if existing.Size != tm.Size || existing.S HA256 != tm.SHA256 {
253 » » » » » return nil, fmt.Errorf(
254 » » » » » » "metadata mismatch for %s: sizes =(%v,%v) sha256=(%v,%v)",
255 » » » » » » binary.String(),
256 » » » » » » existing.Size, tm.Size,
257 » » » » » » existing.SHA256, tm.SHA256,
258 » » » » » )
259 » » » » }
260 » » » }
261 » » } else {
262 » » » merged[binary] = tm
263 » » }
264 » }
265 » list := make([]*ToolsMetadata, 0, len(merged))
266 » for _, metadata := range merged {
267 » » list = append(list, metadata)
268 » }
269 » return list, nil
270 }
271
272 // ReadMetadata returns the tools metadata from the given storage.
273 func ReadMetadata(store storage.StorageReader) ([]*ToolsMetadata, error) {
274 » dataSource := storage.NewStorageSimpleStreamsDataSource(store, "tools")
189 toolsConstraint, err := makeToolsConstraint(simplestreams.CloudSpec{}, - 1, -1, coretools.Filter{}) 275 toolsConstraint, err := makeToolsConstraint(simplestreams.CloudSpec{}, - 1, -1, coretools.Filter{})
190 if err != nil { 276 if err != nil {
277 return nil, err
278 }
279 metadata, err := Fetch([]simplestreams.DataSource{dataSource}, simplestr eams.DefaultIndexPath, toolsConstraint, false)
280 if err != nil && !errors.IsNotFoundError(err) {
281 return nil, err
282 }
283 return metadata, nil
284 }
285
286 // WriteMetadata writes the given tools metadata to the given storage.
287 func WriteMetadata(stor storage.Storage, metadata []*ToolsMetadata) error {
288 index, products, err := MarshalToolsMetadataJSON(metadata, time.Now())
289 if err != nil {
191 return err 290 return err
192 } 291 }
193 » existingMetadata, err := Fetch([]simplestreams.DataSource{dataSource}, s implestreams.DefaultIndexPath, toolsConstraint, false) 292 » metadataInfo := []MetadataFile{
194 » if err != nil && !errors.IsNotFoundError(err) { 293 » » {simplestreams.UnsignedIndex, index},
195 » » return err 294 » » {ProductMetadataPath, products},
196 » }
197 » newToolsVersions := make(map[string]bool)
198 » for _, tool := range toolsList {
199 » » newToolsVersions[tool.Version.String()] = true
200 » }
201 » // Merge in existing records.
202 » for _, toolsMetadata := range existingMetadata {
203 » » vers := version.Binary{version.MustParse(toolsMetadata.Version), toolsMetadata.Release, toolsMetadata.Arch}
204 » » if _, ok := newToolsVersions[vers.String()]; ok {
205 » » » continue
206 » » }
207 » » tool := &coretools.Tools{
208 » » » Version: vers,
209 » » » SHA256: toolsMetadata.SHA256,
210 » » » Size: toolsMetadata.Size,
211 » » }
212 » » toolsList = append(toolsList, tool)
213 » }
214 » metadataInfo, err := generateMetadata(toolsList, fetch)
215 » if err != nil {
216 » » return err
217 } 295 }
218 for _, md := range metadataInfo { 296 for _, md := range metadataInfo {
219 logger.Infof("Writing %s", "tools/"+md.Path) 297 logger.Infof("Writing %s", "tools/"+md.Path)
220 » » err = metadataStore.Put("tools/"+md.Path, bytes.NewReader(md.Dat a), int64(len(md.Data))) 298 » » err = stor.Put("tools/"+md.Path, bytes.NewReader(md.Data), int64 (len(md.Data)))
221 if err != nil { 299 if err != nil {
222 return err 300 return err
223 } 301 }
224 } 302 }
225 return nil 303 return nil
226 } 304 }
227 305
228 func generateMetadata(toolsList coretools.List, fetch bool) ([]MetadataFile, err or) { 306 // MergeAndWriteMetadata reads the existing metadata from storage (if any),
229 » metadata := make([]*ToolsMetadata, len(toolsList)) 307 // and merges it with metadata generated from the given tools list. The
230 » for i, t := range toolsList { 308 // resulting metadata is written to storage.
231 » » var size int64 309 func MergeAndWriteMetadata(stor storage.Storage, tools coretools.List) error {
232 » » var sha256hex string 310 » existing, err := ReadMetadata(stor)
233 » » var err error 311 » if err != nil {
234 » » if fetch && t.Size == 0 { 312 » » return err
235 » » » logger.Infof("Fetching tools to generate hash: %v", t.UR L)
236 » » » var sha256hash hash.Hash
237 » » » size, sha256hash, err = fetchToolsHash(t.URL)
238 » » » if err != nil {
239 » » » » return nil, err
240 » » » }
241 » » » sha256hex = fmt.Sprintf("%x", sha256hash.Sum(nil))
242 » » } else {
243 » » » size = t.Size
244 » » » sha256hex = t.SHA256
245 » » }
246
247 » » path := fmt.Sprintf("releases/juju-%s-%s-%s.tgz", t.Version.Numb er, t.Version.Series, t.Version.Arch)
248 » » metadata[i] = &ToolsMetadata{
249 » » » Release: t.Version.Series,
250 » » » Version: t.Version.Number.String(),
251 » » » Arch: t.Version.Arch,
252 » » » Path: path,
253 » » » FileType: "tar.gz",
254 » » » Size: size,
255 » » » SHA256: sha256hex,
256 » » }
257 } 313 }
258 314 » metadata := MetadataFromTools(tools)
259 » index, products, err := MarshalToolsMetadataJSON(metadata, time.Now()) 315 » if metadata, err = MergeMetadata(metadata, existing); err != nil {
260 » if err != nil { 316 » » return err
261 » » return nil, err
262 } 317 }
263 » objects := []MetadataFile{ 318 » return WriteMetadata(stor, metadata)
264 » » {simplestreams.UnsignedIndex, index},
265 » » {ProductMetadataPath, products},
266 » }
267 » return objects, nil
268 } 319 }
269 320
270 // fetchToolsHash fetches the file at the specified URL, 321 // fetchToolsHash fetches the tools from storage and calculates
271 // and calculates its size in bytes and computes a SHA256 322 // its size in bytes and computes a SHA256 hash of its contents.
272 // hash of its contents. 323 func fetchToolsHash(stor storage.StorageReader, ver version.Binary) (size int64, sha256hash hash.Hash, err error) {
273 func fetchToolsHash(url string) (size int64, sha256hash hash.Hash, err error) { 324 » r, err := storage.Get(stor, StorageName(ver))
274 » resp, err := http.Get(url)
275 if err != nil { 325 if err != nil {
276 return 0, nil, err 326 return 0, nil, err
277 } 327 }
328 defer r.Close()
278 sha256hash = sha256.New() 329 sha256hash = sha256.New()
279 » size, err = io.Copy(sha256hash, resp.Body) 330 » size, err = io.Copy(sha256hash, r)
280 » resp.Body.Close()
281 return size, sha256hash, err 331 return size, sha256hash, err
282 } 332 }
OLDNEW
« no previous file with comments | « environs/testing/tools.go ('k') | environs/tools/simplestreams_test.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b