Merge lp:~wallyworld/juju-core/marshal-image-metadata into lp:~go-bot/juju-core/trunk

Proposed by Ian Booth
Status: Merged
Approved by: Ian Booth
Approved revision: no longer in the source branch.
Merged at revision: 1993
Proposed branch: lp:~wallyworld/juju-core/marshal-image-metadata
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~wallyworld/juju-core/improve-image-metadata-command
Diff against target: 677 lines (+406/-137)
12 files modified
cmd/plugins/juju-metadata/imagemetadata.go (+6/-1)
cmd/plugins/juju-metadata/imagemetadata_test.go (+1/-1)
cmd/plugins/juju-metadata/validateimagemetadata_test.go (+6/-1)
environs/imagemetadata/generate.go (+37/-124)
environs/imagemetadata/generate_test.go (+40/-0)
environs/imagemetadata/marshal.go (+97/-0)
environs/imagemetadata/marshal_test.go (+127/-0)
environs/imagemetadata/simplestreams.go (+15/-6)
environs/imagemetadata/testing/testing.go (+70/-0)
environs/imagemetadata/validation_test.go (+4/-1)
environs/simplestreams/simplestreams.go (+2/-2)
environs/tools/marshal.go (+1/-1)
To merge this branch: bzr merge lp:~wallyworld/juju-core/marshal-image-metadata
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+190542@code.launchpad.net

Commit message

Write image metadata using json serialisation

This branch writes outs simplestreams image metadata using
json serialisation instead of a template based approach. It
is a pre-requisit to allow the next step of merging new image
metadata instead of overwriting. This will be used when setting
up private clouds.

https://codereview.appspot.com/14540055/

Description of the change

Write image metadata using json serialisation

This branch writes outs simplestreams image metadata using
json serialisation instead of a template based approach. It
is a pre-requisit to allow the next step of merging new image
metadata instead of overwriting. This will be used when setting
up private clouds.

https://codereview.appspot.com/14540055/

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

Reviewers: mp+190542_code.launchpad.net,

Message:
Please take a look.

Description:
Write image metadata using json serialisation

This branch writes outs simplestreams image metadata using
json serialisation instead of a template based approach. It
is a pre-requisit to allow the next step of merging new image
metadata instead of overwriting. This will be used when setting
up private clouds.

https://code.launchpad.net/~wallyworld/juju-core/marshal-image-metadata/+merge/190542

Requires:
https://code.launchpad.net/~wallyworld/juju-core/improve-image-metadata-command/+merge/190517

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/14540055/

Affected files (+398, -135 lines):
   A [revision details]
   M cmd/plugins/juju-metadata/imagemetadata.go
   M environs/imagemetadata/generate.go
   A environs/imagemetadata/generate_test.go
   A environs/imagemetadata/marshal.go
   A environs/imagemetadata/marshal_test.go
   M environs/imagemetadata/simplestreams.go
   A environs/imagemetadata/testing/testing.go
   M environs/imagemetadata/validation_test.go
   M environs/simplestreams/simplestreams.go
   M environs/tools/marshal.go

Revision history for this message
Ian Booth (wallyworld) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate.go
File environs/imagemetadata/generate.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate.go#newcode15
environs/imagemetadata/generate.go:15: metadataInfo, err :=
generateMetadata(series, im, cloudSpec)
This has me queezy.

Surely WriteMetadata would take the metadataInfo as an input parameter?

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate_test.go
File environs/imagemetadata/generate_test.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate_test.go#newcode17
environs/imagemetadata/generate_test.go:17: type generateSuite struct{}
testbase.LoggingSuite

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate_test.go#newcode34
environs/imagemetadata/generate_test.go:34: c.Assert(len(metadata),
gc.Equals, 1)
gc.HasLen

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/simplestreams.go
File environs/imagemetadata/simplestreams.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/simplestreams.go#newcode121
environs/imagemetadata/simplestreams.go:121: Release string
`json:"-"`
What does json:"-" do?

https://codereview.appspot.com/14540055/diff/4001/environs/tools/marshal.go
File environs/tools/marshal.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/tools/marshal.go#newcode69
environs/tools/marshal.go:69: itemsversion := updated.Format("20060102")
// YYYYMMDD
Some of these changes are the same as the branch I just reviewed.

https://codereview.appspot.com/14540055/

Revision history for this message
Ian Booth (wallyworld) wrote :

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate.go
File environs/imagemetadata/generate.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate.go#newcode15
environs/imagemetadata/generate.go:15: metadataInfo, err :=
generateMetadata(series, im, cloudSpec)
On 2013/10/16 01:26:11, thumper wrote:
> This has me queezy.

> Surely WriteMetadata would take the metadataInfo as an input
parameter?

The sematics of the write function is to take a ImageMetadata struct and
write out the simplestreams metadata. In order to write the metadata, it
needs to be generated from the struct. The method could be called
WriteAndGenerate I guess. But the generation and writing to file is an
atomic operation.

A subsequent branch renames the method anyway and refactors the content
to align with what Andrew did for tools metadata.

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate_test.go
File environs/imagemetadata/generate_test.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate_test.go#newcode17
environs/imagemetadata/generate_test.go:17: type generateSuite struct{}
On 2013/10/16 01:26:11, thumper wrote:
> testbase.LoggingSuite

Will fix

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/generate_test.go#newcode34
environs/imagemetadata/generate_test.go:34: c.Assert(len(metadata),
gc.Equals, 1)
On 2013/10/16 01:26:11, thumper wrote:
> gc.HasLen

Will fix

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/simplestreams.go
File environs/imagemetadata/simplestreams.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/imagemetadata/simplestreams.go#newcode121
environs/imagemetadata/simplestreams.go:121: Release string
`json:"-"`
On 2013/10/16 01:26:11, thumper wrote:
> What does json:"-" do?

Tells json not to marshall that attribute. It's there to provide info
for other mthods in the struct to do their thing, but we don't want it
serialised to json.

https://codereview.appspot.com/14540055/diff/4001/environs/tools/marshal.go
File environs/tools/marshal.go (right):

https://codereview.appspot.com/14540055/diff/4001/environs/tools/marshal.go#newcode69
environs/tools/marshal.go:69: itemsversion := updated.Format("20060102")
// YYYYMMDD
On 2013/10/16 01:26:11, thumper wrote:
> Some of these changes are the same as the branch I just reviewed.

I fixed this in a downstream branch - I didn't pump the changes back up
the pipe sorry.

https://codereview.appspot.com/14540055/

Revision history for this message
Ian Booth (wallyworld) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/plugins/juju-metadata/imagemetadata.go'
2--- cmd/plugins/juju-metadata/imagemetadata.go 2013-10-16 02:56:51 +0000
3+++ cmd/plugins/juju-metadata/imagemetadata.go 2013-10-16 04:12:27 +0000
4@@ -14,6 +14,7 @@
5 "launchpad.net/juju-core/environs"
6 "launchpad.net/juju-core/environs/config"
7 "launchpad.net/juju-core/environs/configstore"
8+ "launchpad.net/juju-core/environs/filestorage"
9 "launchpad.net/juju-core/environs/imagemetadata"
10 "launchpad.net/juju-core/environs/simplestreams"
11 )
12@@ -152,7 +153,11 @@
13 Region: c.Region,
14 Endpoint: c.Endpoint,
15 }
16- _, err := imagemetadata.GenerateMetadata(c.Series, &im, &cloudSpec, c.Dir)
17+ targetStorage, err := filestorage.NewFileStorageWriter(c.Dir, filestorage.UseDefaultTmpDir)
18+ if err != nil {
19+ return err
20+ }
21+ err = imagemetadata.WriteMetadata(c.Series, &im, &cloudSpec, targetStorage)
22 if err != nil {
23 return fmt.Errorf("image metadata files could not be created: %v", err)
24 }
25
26=== modified file 'cmd/plugins/juju-metadata/imagemetadata_test.go'
27--- cmd/plugins/juju-metadata/imagemetadata_test.go 2013-10-16 02:56:51 +0000
28+++ cmd/plugins/juju-metadata/imagemetadata_test.go 2013-10-16 04:12:27 +0000
29@@ -108,7 +108,7 @@
30
31 const (
32 defaultIndexFileName = "index.json"
33- defaultImageFileName = "imagemetadata.json"
34+ defaultImageFileName = "com.ubuntu.cloud:released:imagemetadata.json"
35 )
36
37 func (s *ImageMetadataSuite) TestImageMetadataFilesNoEnv(c *gc.C) {
38
39=== modified file 'cmd/plugins/juju-metadata/validateimagemetadata_test.go'
40--- cmd/plugins/juju-metadata/validateimagemetadata_test.go 2013-10-16 03:38:32 +0000
41+++ cmd/plugins/juju-metadata/validateimagemetadata_test.go 2013-10-16 04:12:27 +0000
42@@ -11,6 +11,7 @@
43 gc "launchpad.net/gocheck"
44
45 "launchpad.net/juju-core/cmd"
46+ "launchpad.net/juju-core/environs/filestorage"
47 "launchpad.net/juju-core/environs/imagemetadata"
48 "launchpad.net/juju-core/environs/simplestreams"
49 coretesting "launchpad.net/juju-core/testing"
50@@ -73,7 +74,11 @@
51 Region: region,
52 Endpoint: endpoint,
53 }
54- _, err := imagemetadata.GenerateMetadata(series, &im, &cloudSpec, s.metadataDir)
55+ targetStorage, err := filestorage.NewFileStorageWriter(s.metadataDir, filestorage.UseDefaultTmpDir)
56+ if err != nil {
57+ return err
58+ }
59+ err = imagemetadata.WriteMetadata(series, &im, &cloudSpec, targetStorage)
60 if err != nil {
61 return err
62 }
63
64=== modified file 'environs/imagemetadata/generate.go'
65--- environs/imagemetadata/generate.go 2013-10-11 01:20:34 +0000
66+++ environs/imagemetadata/generate.go 2013-10-16 04:12:27 +0000
67@@ -5,135 +5,48 @@
68
69 import (
70 "bytes"
71- "fmt"
72- "io/ioutil"
73- "os"
74- "path/filepath"
75- "text/template"
76 "time"
77
78 "launchpad.net/juju-core/environs/simplestreams"
79-)
80-
81-const (
82- defaultIndexFileName = "index.json"
83- defaultImageFileName = "imagemetadata.json"
84-)
85-
86-// GenerateMetadata generates some basic simplestreams metadata using the specified cloud and image details.
87-func GenerateMetadata(series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec, dest string) ([]string, error) {
88- indexFileName := defaultIndexFileName
89- imageFileName := defaultImageFileName
90- now := time.Now()
91- imparams := imageMetadataParams{
92- Id: im.Id,
93- Arch: im.Arch,
94- Region: cloudSpec.Region,
95- URL: cloudSpec.Endpoint,
96- Path: "streams/v1",
97- ImageFileName: imageFileName,
98- Updated: now.Format(time.RFC1123Z),
99- VersionKey: now.Format("20060102"),
100- }
101-
102- var err error
103- imparams.Version, err = simplestreams.SeriesVersion(series)
104- if err != nil {
105- return nil, fmt.Errorf("invalid series %q", series)
106- }
107-
108- streamsPath := filepath.Join(dest, "streams", "v1")
109- if err = os.MkdirAll(streamsPath, 0755); err != nil {
110- return nil, err
111- }
112- indexFileName = filepath.Join(streamsPath, indexFileName)
113- imageFileName = filepath.Join(streamsPath, imageFileName)
114- err = writeJsonFile(imparams, indexFileName, indexBoilerplate)
115- if err != nil {
116- return nil, err
117- }
118- err = writeJsonFile(imparams, imageFileName, productBoilerplate)
119- if err != nil {
120- return nil, err
121- }
122- return []string{indexFileName, imageFileName}, nil
123-}
124-
125-type imageMetadataParams struct {
126- Region string
127- URL string
128- Updated string
129- Arch string
130- Path string
131- Series string
132- Version string
133- VersionKey string
134- Id string
135- ImageFileName string
136-}
137-
138-func writeJsonFile(imparams imageMetadataParams, filename, boilerplate string) error {
139- t := template.Must(template.New("").Parse(boilerplate))
140- var metadata bytes.Buffer
141- if err := t.Execute(&metadata, imparams); err != nil {
142- panic(fmt.Errorf("cannot generate %s metdata: %v", filename, err))
143- }
144- data := metadata.Bytes()
145- if err := ioutil.WriteFile(filename, data, 0666); err != nil {
146+ "launchpad.net/juju-core/environs/storage"
147+)
148+
149+func WriteMetadata(series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec, metadataStore storage.Storage) error {
150+ metadataInfo, err := generateMetadata(series, im, cloudSpec)
151+ if err != nil {
152 return err
153 }
154+ for _, md := range metadataInfo {
155+ err = metadataStore.Put(md.Path, bytes.NewReader(md.Data), int64(len(md.Data)))
156+ if err != nil {
157+ return err
158+ }
159+ }
160 return nil
161 }
162
163-var indexBoilerplate = `
164-{
165- "index": {
166- "com.ubuntu.cloud:custom": {
167- "updated": "{{.Updated}}",
168- "clouds": [
169- {
170- "region": "{{.Region}}",
171- "endpoint": "{{.URL}}"
172- }
173- ],
174- "cloudname": "custom",
175- "datatype": "image-ids",
176- "format": "products:1.0",
177- "products": [
178- "com.ubuntu.cloud:server:{{.Version}}:{{.Arch}}"
179- ],
180- "path": "{{.Path}}/{{.ImageFileName}}"
181- }
182- },
183- "updated": "{{.Updated}}",
184- "format": "index:1.0"
185-}
186-`
187-
188-var productBoilerplate = `
189-{
190- "content_id": "com.ubuntu.cloud:custom",
191- "format": "products:1.0",
192- "updated": "{{.Updated}}",
193- "datatype": "image-ids",
194- "products": {
195- "com.ubuntu.cloud:server:{{.Version}}:{{.Arch}}": {
196- "release": "{{.Series}}",
197- "version": "{{.Version}}",
198- "arch": "{{.Arch}}",
199- "versions": {
200- "{{.VersionKey}}": {
201- "items": {
202- "{{.Id}}": {
203- "region": "{{.Region}}",
204- "id": "{{.Id}}"
205- }
206- },
207- "pubname": "ubuntu-{{.Series}}-{{.Version}}-{{.Arch}}-server-{{.VersionKey}}",
208- "label": "custom"
209- }
210- }
211- }
212- }
213-}
214-`
215+type MetadataFile struct {
216+ Path string
217+ Data []byte
218+}
219+
220+// generateMetadata generates some basic simplestreams metadata using the specified cloud and image details.
221+func generateMetadata(series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec) ([]MetadataFile, error) {
222+ metadata := &ImageMetadata{
223+ Id: im.Id,
224+ Arch: im.Arch,
225+ Release: series,
226+ RegionName: cloudSpec.Region,
227+ Endpoint: cloudSpec.Endpoint,
228+ }
229+
230+ index, products, err := MarshalImageMetadataJSON([]*ImageMetadata{metadata}, cloudSpec, time.Now())
231+ if err != nil {
232+ return nil, err
233+ }
234+ objects := []MetadataFile{
235+ {simplestreams.UnsignedIndex, index},
236+ {ProductMetadataPath, products},
237+ }
238+ return objects, nil
239+}
240
241=== added file 'environs/imagemetadata/generate_test.go'
242--- environs/imagemetadata/generate_test.go 1970-01-01 00:00:00 +0000
243+++ environs/imagemetadata/generate_test.go 2013-10-16 04:12:27 +0000
244@@ -0,0 +1,40 @@
245+// Copyright 2013 Canonical Ltd.
246+// Licensed under the AGPLv3, see LICENCE file for details.
247+
248+package imagemetadata_test
249+
250+import (
251+ gc "launchpad.net/gocheck"
252+
253+ "launchpad.net/juju-core/environs/filestorage"
254+ "launchpad.net/juju-core/environs/imagemetadata"
255+ "launchpad.net/juju-core/environs/imagemetadata/testing"
256+ "launchpad.net/juju-core/environs/simplestreams"
257+ "launchpad.net/juju-core/testing/testbase"
258+)
259+
260+var _ = gc.Suite(&generateSuite{})
261+
262+type generateSuite struct {
263+ testbase.LoggingSuite
264+}
265+
266+func (s *generateSuite) TestWriteMetadata(c *gc.C) {
267+ im := &imagemetadata.ImageMetadata{
268+ Id: "1234",
269+ Arch: "amd64",
270+ }
271+ cloudSpec := &simplestreams.CloudSpec{
272+ Region: "region",
273+ Endpoint: "endpoint",
274+ }
275+ dir := c.MkDir()
276+ targetStorage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
277+ c.Assert(err, gc.IsNil)
278+ err = imagemetadata.WriteMetadata("raring", im, cloudSpec, targetStorage)
279+ c.Assert(err, gc.IsNil)
280+ metadata := testing.ParseMetadata(c, dir)
281+ c.Assert(metadata, gc.HasLen, 1)
282+ im.RegionName = cloudSpec.Region
283+ c.Assert(im, gc.DeepEquals, metadata[0])
284+}
285
286=== added file 'environs/imagemetadata/marshal.go'
287--- environs/imagemetadata/marshal.go 1970-01-01 00:00:00 +0000
288+++ environs/imagemetadata/marshal.go 2013-10-16 04:12:27 +0000
289@@ -0,0 +1,97 @@
290+// Copyright 2013 Canonical Ltd.
291+// Licensed under the AGPLv3, see LICENCE file for details.
292+
293+package imagemetadata
294+
295+import (
296+ "encoding/json"
297+ "time"
298+
299+ "launchpad.net/juju-core/environs/simplestreams"
300+ "launchpad.net/juju-core/utils/set"
301+)
302+
303+const (
304+ ProductMetadataPath = "streams/v1/com.ubuntu.cloud:released:imagemetadata.json"
305+)
306+
307+// MarshalImageMetadataJSON marshals image metadata to index and products JSON.
308+//
309+// updated is the time at which the JSON file was updated.
310+func MarshalImageMetadataJSON(metadata []*ImageMetadata, cloudSpec *simplestreams.CloudSpec, updated time.Time) (index, products []byte, err error) {
311+ if index, err = MarshalImageMetadataIndexJSON(metadata, cloudSpec, updated); err != nil {
312+ return nil, nil, err
313+ }
314+ if products, err = MarshalImageMetadataProductsJSON(metadata, updated); err != nil {
315+ return nil, nil, err
316+ }
317+ return index, products, err
318+}
319+
320+// MarshalImageMetadataIndexJSON marshals image metadata to index JSON.
321+//
322+// updated is the time at which the JSON file was updated.
323+func MarshalImageMetadataIndexJSON(metadata []*ImageMetadata, cloudSpec *simplestreams.CloudSpec, updated time.Time) (out []byte, err error) {
324+ productIds := make([]string, len(metadata))
325+ for i, t := range metadata {
326+ productIds[i], err = t.productId()
327+ if err != nil {
328+ return nil, err
329+ }
330+ }
331+ var indices simplestreams.Indices
332+ indices.Updated = updated.Format(time.RFC1123Z)
333+ indices.Format = "index:1.0"
334+ indices.Indexes = map[string]*simplestreams.IndexMetadata{
335+ "com.ubuntu.cloud:custom": &simplestreams.IndexMetadata{
336+ CloudName: "custom",
337+ Updated: indices.Updated,
338+ Format: "products:1.0",
339+ DataType: "image-ids",
340+ ProductsFilePath: ProductMetadataPath,
341+ ProductIds: set.NewStrings(productIds...).SortedValues(),
342+ Clouds: []simplestreams.CloudSpec{*cloudSpec},
343+ },
344+ }
345+ return json.MarshalIndent(&indices, "", " ")
346+}
347+
348+// MarshalImageMetadataProductsJSON marshals image metadata to products JSON.
349+//
350+// updated is the time at which the JSON file was updated.
351+func MarshalImageMetadataProductsJSON(metadata []*ImageMetadata, updated time.Time) (out []byte, err error) {
352+ var cloud simplestreams.CloudMetadata
353+ cloud.Updated = updated.Format(time.RFC1123Z)
354+ cloud.Format = "products:1.0"
355+ cloud.Products = make(map[string]simplestreams.MetadataCatalog)
356+ itemsversion := updated.Format("20060201") // YYYYMMDD
357+ for _, t := range metadata {
358+ id, err := t.productId()
359+ if err != nil {
360+ return nil, err
361+ }
362+ version, err := simplestreams.SeriesVersion(t.Release)
363+ if err != nil {
364+ return nil, err
365+ }
366+ toWrite := &ImageMetadata{
367+ Id: t.Id,
368+ }
369+ if catalog, ok := cloud.Products[id]; ok {
370+ catalog.Items[itemsversion].Items[t.Id] = t
371+ } else {
372+ catalog = simplestreams.MetadataCatalog{
373+ Arch: t.Arch,
374+ RegionName: t.RegionName,
375+ Version: version,
376+ Items: map[string]*simplestreams.ItemCollection{
377+ itemsversion: &simplestreams.ItemCollection{
378+ Items: map[string]interface{}{t.Id: toWrite},
379+ },
380+ },
381+ }
382+ cloud.Products[id] = catalog
383+ }
384+ }
385+ return json.MarshalIndent(&cloud, "", " ")
386+}
387
388=== added file 'environs/imagemetadata/marshal_test.go'
389--- environs/imagemetadata/marshal_test.go 1970-01-01 00:00:00 +0000
390+++ environs/imagemetadata/marshal_test.go 2013-10-16 04:12:27 +0000
391@@ -0,0 +1,127 @@
392+// Copyright 2013 Canonical Ltd.
393+// Licensed under the AGPLv3, see LICENCE file for details.
394+
395+package imagemetadata_test
396+
397+import (
398+ "time"
399+
400+ gc "launchpad.net/gocheck"
401+
402+ "launchpad.net/juju-core/environs/imagemetadata"
403+ "launchpad.net/juju-core/environs/simplestreams"
404+)
405+
406+var _ = gc.Suite(&marshalSuite{})
407+
408+type marshalSuite struct{}
409+
410+var expectedIndex = `{
411+ "index": {
412+ "com.ubuntu.cloud:custom": {
413+ "updated": "Thu, 01 Jan 1970 00:00:00 +0000",
414+ "format": "products:1.0",
415+ "datatype": "image-ids",
416+ "cloudname": "custom",
417+ "clouds": [
418+ {
419+ "region": "region",
420+ "endpoint": "endpoint"
421+ }
422+ ],
423+ "path": "streams/v1/com.ubuntu.cloud:released:imagemetadata.json",
424+ "products": [
425+ "com.ubuntu.cloud:server:12.04:amd64",
426+ "com.ubuntu.cloud:server:12.04:arm",
427+ "com.ubuntu.cloud:server:13.10:arm"
428+ ]
429+ }
430+ },
431+ "updated": "Thu, 01 Jan 1970 00:00:00 +0000",
432+ "format": "index:1.0"
433+}`
434+
435+var expectedProducts = `{
436+ "products": {
437+ "com.ubuntu.cloud:server:12.04:amd64": {
438+ "version": "12.04",
439+ "arch": "amd64",
440+ "versions": {
441+ "19700101": {
442+ "items": {
443+ "abcd": {
444+ "id": "abcd"
445+ }
446+ }
447+ }
448+ }
449+ },
450+ "com.ubuntu.cloud:server:12.04:arm": {
451+ "version": "12.04",
452+ "arch": "arm",
453+ "versions": {
454+ "19700101": {
455+ "items": {
456+ "5678": {
457+ "id": "5678"
458+ }
459+ }
460+ }
461+ }
462+ },
463+ "com.ubuntu.cloud:server:13.10:arm": {
464+ "version": "13.10",
465+ "arch": "arm",
466+ "versions": {
467+ "19700101": {
468+ "items": {
469+ "1234": {
470+ "id": "1234"
471+ }
472+ }
473+ }
474+ }
475+ }
476+ },
477+ "updated": "Thu, 01 Jan 1970 00:00:00 +0000",
478+ "format": "products:1.0"
479+}`
480+
481+var imageMetadataForTesting = []*imagemetadata.ImageMetadata{
482+ &imagemetadata.ImageMetadata{
483+ Id: "1234",
484+ Release: "saucy",
485+ Arch: "arm",
486+ },
487+ &imagemetadata.ImageMetadata{
488+ Id: "5678",
489+ Release: "precise",
490+ Arch: "arm",
491+ },
492+ &imagemetadata.ImageMetadata{
493+ Id: "abcd",
494+ Release: "precise",
495+ Arch: "amd64",
496+ },
497+}
498+
499+func (s *marshalSuite) TestMarshalIndex(c *gc.C) {
500+ cloudSpec := &simplestreams.CloudSpec{Region: "region", Endpoint: "endpoint"}
501+ index, err := imagemetadata.MarshalImageMetadataIndexJSON(imageMetadataForTesting, cloudSpec, time.Unix(0, 0).UTC())
502+ c.Assert(err, gc.IsNil)
503+ c.Assert(string(index), gc.Equals, expectedIndex)
504+}
505+
506+func (s *marshalSuite) TestMarshalProducts(c *gc.C) {
507+ products, err := imagemetadata.MarshalImageMetadataProductsJSON(imageMetadataForTesting, time.Unix(0, 0).UTC())
508+ c.Assert(err, gc.IsNil)
509+ c.Assert(string(products), gc.Equals, expectedProducts)
510+}
511+
512+func (s *marshalSuite) TestMarshal(c *gc.C) {
513+ cloudSpec := &simplestreams.CloudSpec{Region: "region", Endpoint: "endpoint"}
514+ index, products, err := imagemetadata.MarshalImageMetadataJSON(imageMetadataForTesting, cloudSpec, time.Unix(0, 0).UTC())
515+ c.Assert(err, gc.IsNil)
516+ c.Assert(string(index), gc.Equals, expectedIndex)
517+ c.Assert(string(products), gc.Equals, expectedProducts)
518+}
519
520=== modified file 'environs/imagemetadata/simplestreams.go'
521--- environs/imagemetadata/simplestreams.go 2013-10-10 11:40:54 +0000
522+++ environs/imagemetadata/simplestreams.go 2013-10-16 04:12:27 +0000
523@@ -115,12 +115,21 @@
524 // ImageMetadata holds information about a particular cloud image.
525 type ImageMetadata struct {
526 Id string `json:"id"`
527- Storage string `json:"root_store"`
528- VType string `json:"virt"`
529- Arch string `json:"arch"`
530- RegionAlias string `json:"crsn"`
531- RegionName string `json:"region"`
532- Endpoint string `json:"endpoint"`
533+ Storage string `json:"root_store,omitempty"`
534+ VType string `json:"virt,omitempty"`
535+ Arch string `json:"arch,omitempty"`
536+ Release string `json:"-"`
537+ RegionAlias string `json:"crsn,omitempty"`
538+ RegionName string `json:"region,omitempty"`
539+ Endpoint string `json:"endpoint,omitempty"`
540+}
541+
542+func (t *ImageMetadata) productId() (string, error) {
543+ seriesVersion, err := simplestreams.SeriesVersion(t.Release)
544+ if err != nil {
545+ return "", err
546+ }
547+ return fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", seriesVersion, t.Arch), nil
548 }
549
550 // Fetch returns a list of images for the specified cloud matching the constraint.
551
552=== added directory 'environs/imagemetadata/testing'
553=== added file 'environs/imagemetadata/testing/testing.go'
554--- environs/imagemetadata/testing/testing.go 1970-01-01 00:00:00 +0000
555+++ environs/imagemetadata/testing/testing.go 2013-10-16 04:12:27 +0000
556@@ -0,0 +1,70 @@
557+// Copyright 2013 Canonical Ltd.
558+// Licensed under the AGPLv3, see LICENCE file for details.
559+
560+package testing
561+
562+import (
563+ "fmt"
564+ "io/ioutil"
565+ "path/filepath"
566+ "sort"
567+
568+ gc "launchpad.net/gocheck"
569+
570+ "launchpad.net/juju-core/environs/imagemetadata"
571+ "launchpad.net/juju-core/environs/simplestreams"
572+ "launchpad.net/juju-core/utils/set"
573+)
574+
575+// ParseMetadata loads ImageMetadata from the specified directory.
576+func ParseMetadata(c *gc.C, metadataDir string) []*imagemetadata.ImageMetadata {
577+ params := simplestreams.ValueParams{
578+ DataType: "image-ids",
579+ ValueTemplate: imagemetadata.ImageMetadata{},
580+ }
581+
582+ source := simplestreams.NewURLDataSource("file://"+metadataDir, simplestreams.VerifySSLHostnames)
583+
584+ const requireSigned = false
585+ indexPath := simplestreams.UnsignedIndex
586+ indexRef, err := simplestreams.GetIndexWithFormat(
587+ source, indexPath, "index:1.0", requireSigned, simplestreams.CloudSpec{}, params)
588+ c.Assert(err, gc.IsNil)
589+ c.Assert(indexRef.Indexes, gc.HasLen, 1)
590+
591+ imageIndexMetadata := indexRef.Indexes["com.ubuntu.cloud:custom"]
592+ c.Assert(imageIndexMetadata, gc.NotNil)
593+
594+ data, err := ioutil.ReadFile(filepath.Join(metadataDir, imageIndexMetadata.ProductsFilePath))
595+ c.Assert(err, gc.IsNil)
596+
597+ url, err := source.URL(imageIndexMetadata.ProductsFilePath)
598+ c.Assert(err, gc.IsNil)
599+ cloudMetadata, err := simplestreams.ParseCloudMetadata(data, "products:1.0", url, imagemetadata.ImageMetadata{})
600+ c.Assert(err, gc.IsNil)
601+
602+ imageMetadataMap := make(map[string]*imagemetadata.ImageMetadata)
603+ var expectedProductIds set.Strings
604+ var imageVersions set.Strings
605+ for _, mc := range cloudMetadata.Products {
606+ for _, items := range mc.Items {
607+ for key, item := range items.Items {
608+ imageMetadata := item.(*imagemetadata.ImageMetadata)
609+ imageMetadataMap[key] = imageMetadata
610+ imageVersions.Add(key)
611+ productId := fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", mc.Version, imageMetadata.Arch)
612+ expectedProductIds.Add(productId)
613+ }
614+ }
615+ }
616+
617+ // Make sure index's product IDs are all represented in the products metadata.
618+ sort.Strings(imageIndexMetadata.ProductIds)
619+ c.Assert(imageIndexMetadata.ProductIds, gc.DeepEquals, expectedProductIds.SortedValues())
620+
621+ imageMetadata := make([]*imagemetadata.ImageMetadata, len(imageMetadataMap))
622+ for i, key := range imageVersions.SortedValues() {
623+ imageMetadata[i] = imageMetadataMap[key]
624+ }
625+ return imageMetadata
626+}
627
628=== modified file 'environs/imagemetadata/validation_test.go'
629--- environs/imagemetadata/validation_test.go 2013-10-11 04:09:04 +0000
630+++ environs/imagemetadata/validation_test.go 2013-10-16 04:12:27 +0000
631@@ -6,6 +6,7 @@
632 import (
633 gc "launchpad.net/gocheck"
634
635+ "launchpad.net/juju-core/environs/filestorage"
636 "launchpad.net/juju-core/environs/imagemetadata"
637 "launchpad.net/juju-core/environs/simplestreams"
638 "launchpad.net/juju-core/testing/testbase"
639@@ -27,7 +28,9 @@
640 Region: region,
641 Endpoint: endpoint,
642 }
643- _, err := imagemetadata.GenerateMetadata(series, &im, &cloudSpec, s.metadataDir)
644+ targetStorage, err := filestorage.NewFileStorageWriter(s.metadataDir, filestorage.UseDefaultTmpDir)
645+ c.Assert(err, gc.IsNil)
646+ err = imagemetadata.WriteMetadata(series, &im, &cloudSpec, targetStorage)
647 if err != nil {
648 return err
649 }
650
651=== modified file 'environs/simplestreams/simplestreams.go'
652--- environs/simplestreams/simplestreams.go 2013-10-10 11:40:54 +0000
653+++ environs/simplestreams/simplestreams.go 2013-10-16 04:12:27 +0000
654@@ -32,8 +32,8 @@
655
656 // CloudSpec uniquely defines a specific cloud deployment.
657 type CloudSpec struct {
658- Region string
659- Endpoint string
660+ Region string `json:"region"`
661+ Endpoint string `json:"endpoint"`
662 }
663
664 // HasRegion is implemented by instances which can provide a region to which they belong.
665
666=== modified file 'environs/tools/marshal.go'
667--- environs/tools/marshal.go 2013-09-02 23:29:30 +0000
668+++ environs/tools/marshal.go 2013-10-16 04:12:27 +0000
669@@ -66,7 +66,7 @@
670 cloud.Updated = updated.Format(time.RFC1123Z)
671 cloud.Format = "products:1.0"
672 cloud.Products = make(map[string]simplestreams.MetadataCatalog)
673- itemsversion := updated.Format("20060201") // YYYYMMDD
674+ itemsversion := updated.Format("20060102") // YYYYMMDD
675 for _, t := range metadata {
676 id, err := t.productId()
677 if err != nil {

Subscribers

People subscribed via source and target branches

to status/vote changes: