Merge lp:~wallyworld/juju-core/merge-new-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: 1994
Proposed branch: lp:~wallyworld/juju-core/merge-new-image-metadata
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 1144 lines (+501/-212)
11 files modified
cmd/plugins/juju-metadata/imagemetadata.go (+8/-6)
cmd/plugins/juju-metadata/imagemetadata_test.go (+18/-19)
cmd/plugins/juju-metadata/validateimagemetadata_test.go (+4/-6)
environs/imagemetadata/generate.go (+84/-30)
environs/imagemetadata/generate_test.go (+173/-7)
environs/imagemetadata/marshal.go (+16/-22)
environs/imagemetadata/marshal_test.go (+9/-6)
environs/imagemetadata/simplestreams.go (+28/-19)
environs/imagemetadata/simplestreams_test.go (+138/-84)
environs/imagemetadata/validation_test.go (+6/-4)
environs/simplestreams/simplestreams.go (+17/-9)
To merge this branch: bzr merge lp:~wallyworld/juju-core/merge-new-image-metadata
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+190898@code.launchpad.net

Commit message

Merge new image metadata with existing

When image id metadata is generated, any existing metadata is
now loaded and then the new metadata is merged in. This allows
the generate-image plugin to be run multiple times to build up
metadata for multiple images. Some fairly extensive refactoring
to how image metadata search constraints are set up was required.

https://codereview.appspot.com/14663043/

Description of the change

Merge new image metadata with existing

When image id metadata is generated, any existing metadata is
now loaded and then the new metadata is merged in. This allows
the generate-image plugin to be run multiple times to build up
metadata for multiple images. Some fairly extensive refactoring
to how image metadata search constraints are set up was required.

https://codereview.appspot.com/14663043/

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

Reviewers: mp+190898_code.launchpad.net,

Message:
Please take a look.

Description:
Merge new image metadata with existing

When image id metadata is generated, any existing metadata is
now loaded and then the new metadata is merged in. This allows
the generate-image plugin to be run multiple times to build up
metadata for multiple images. Some fairly extensive refactoring
to how image metadata search constraints are set up was required.

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

(do not edit description out of merge proposal)

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

Affected files (+843, -367 lines):
   A [revision details]
   M cmd/plugins/juju-metadata/imagemetadata.go
   M cmd/plugins/juju-metadata/imagemetadata_test.go
   M cmd/plugins/juju-metadata/validateimagemetadata_test.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
   M environs/imagemetadata/simplestreams_test.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 :
Download full text (5.6 KiB)

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata.go
File cmd/plugins/juju-metadata/imagemetadata.go (right):

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata.go#newcode64
cmd/plugins/juju-metadata/imagemetadata.go:64: if store, err :=
configstore.Default(); err == nil {
What should we be doing if there is no configstore? Is it an error?

It seems that either we allow to use to say "don't use an environment"
or we should fail if we can't open the environment defined by the
command.

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata.go#newcode131
cmd/plugins/juju-metadata/imagemetadata.go:131: %q
why are the directories quoted?

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go
File cmd/plugins/juju-metadata/imagemetadata_test.go (right):

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode39
cmd/plugins/juju-metadata/imagemetadata_test.go:39: os.Clearenv()
Why clear the entire environment?

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode42
cmd/plugins/juju-metadata/imagemetadata_test.go:42: restore :=
testbase.PatchEnvironment("AWS_ACCESS_KEY_ID", "access")
just use:

s.PatchEnvironment("AWS_ACCESS_KEY_ID", "access")

This adds the restore cleanup for you.

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode49
cmd/plugins/juju-metadata/imagemetadata_test.go:49: for _, envstring :=
range s.environ {
I'd be tempted to make this a function by itself, and call "addCleanup"
just after the os.Clearenv().

Adding the home.Restore() as a cleanup also means you don't need a
TestTearDown.

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode88
cmd/plugins/juju-metadata/imagemetadata_test.go:88:
c.Assert(strings.Contains(content, fmt.Sprintf(`"region": %q`,
expected.region)), jc.IsTrue)
Do you realise that we have a checker for Contains?

c.Assert(content, jc.Contains, fmt.Sprintf(`"region": %q`,
expected.region))

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode129
cmd/plugins/juju-metadata/imagemetadata_test.go:129: errOut :=
ctx.Stdout.(*bytes.Buffer).String()
there is also testing.Stdout(ctx) which does the horrible cast for you.

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/validateimagemetadata_test.go
File cmd/plugins/juju-metadata/validateimagemetadata_test.go (right):

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/validateimagemetadata_test.go#newcode101
cmd/plugins/juju-metadata/validateimagemetadata_test.go:101:
s.AddCleanup(func(*gc.C) { restore() })
s.PatchEnvironment("AWS_ACCESS_KEY_ID", "access")

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/validateimagemetadata_test.go#newcode133
cmd/plugins/juju-metadata/validateimagemetadata_test.go:133:
testbase.PatchEnvironment("AWS_ACCESS_KEY_ID", "")
s.PatchEnvironment...

Read more...

Revision history for this message
Ian Booth (wallyworld) wrote :
Download full text (6.7 KiB)

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata.go
File cmd/plugins/juju-metadata/imagemetadata.go (right):

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata.go#newcode64
cmd/plugins/juju-metadata/imagemetadata.go:64: if store, err :=
configstore.Default(); err == nil {
On 2013/10/16 00:47:59, thumper wrote:
> What should we be doing if there is no configstore? Is it an error?

No. It just means that the user wants to run the command without an
environment. It is not an error as far as this command is concerned.

> It seems that either we allow to use to say "don't use an environment"
or we
> should fail if we can't open the environment defined by the command.

I'd rather keep the semantics simple for the user. A written, the
command works quite nicely without an environment, so long as all the
parameters are specified. But it does complain if an env name is
specified and the env cannot be opened.

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata.go#newcode131
cmd/plugins/juju-metadata/imagemetadata.go:131: %q
On 2013/10/16 00:47:59, thumper wrote:
> why are the directories quoted?

Seemed like a good idea. I can unquote.

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go
File cmd/plugins/juju-metadata/imagemetadata_test.go (right):

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode39
cmd/plugins/juju-metadata/imagemetadata_test.go:39: os.Clearenv()
On 2013/10/16 00:47:59, thumper wrote:
> Why clear the entire environment?

We don't need *any* env vars set for the test and there's so many
combinations of AWS and EC2 env vars it's just easier.

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode42
cmd/plugins/juju-metadata/imagemetadata_test.go:42: restore :=
testbase.PatchEnvironment("AWS_ACCESS_KEY_ID", "access")
On 2013/10/16 00:47:59, thumper wrote:
> just use:

> s.PatchEnvironment("AWS_ACCESS_KEY_ID", "access")

> This adds the restore cleanup for you.

will do

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode88
cmd/plugins/juju-metadata/imagemetadata_test.go:88:
c.Assert(strings.Contains(content, fmt.Sprintf(`"region": %q`,
expected.region)), jc.IsTrue)
On 2013/10/16 00:47:59, thumper wrote:
> Do you realise that we have a checker for Contains?

> c.Assert(content, jc.Contains, fmt.Sprintf(`"region": %q`,
expected.region))

I do now

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/imagemetadata_test.go#newcode129
cmd/plugins/juju-metadata/imagemetadata_test.go:129: errOut :=
ctx.Stdout.(*bytes.Buffer).String()
On 2013/10/16 00:47:59, thumper wrote:
> there is also testing.Stdout(ctx) which does the horrible cast for
you.

\o/

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata/validateimagemetadata_test.go
File cmd/plugins/juju-metadata/validateimagemetadata_test.go (right):

https://codereview.appspot.com/14663043/diff/4001/cmd/plugins/juju-metadata...

Read more...

Revision history for this message
Ian Booth (wallyworld) wrote :
Revision history for this message
Ian Booth (wallyworld) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :
Revision history for this message
Go Bot (go-bot) wrote :
Download full text (6.9 KiB)

The attempt to merge lp:~wallyworld/juju-core/merge-new-image-metadata into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.264s
ok launchpad.net/juju-core/agent/tools 0.259s
ok launchpad.net/juju-core/bzr 7.130s
ok launchpad.net/juju-core/cert 2.848s
ok launchpad.net/juju-core/charm 0.613s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.031s
ok launchpad.net/juju-core/cmd 0.228s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 171.617s
ok launchpad.net/juju-core/cmd/jujud 48.162s
FAIL launchpad.net/juju-core/cmd/plugins/juju-metadata [build failed]
ok launchpad.net/juju-core/constraints 0.043s
ok launchpad.net/juju-core/container/lxc 0.348s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.296s
ok launchpad.net/juju-core/environs 3.600s
ok launchpad.net/juju-core/environs/bootstrap 5.091s
ok launchpad.net/juju-core/environs/cloudinit 0.500s
ok launchpad.net/juju-core/environs/config 0.788s
ok launchpad.net/juju-core/environs/configstore 0.045s
ok launchpad.net/juju-core/environs/filestorage 0.032s
ok launchpad.net/juju-core/environs/httpstorage 0.880s
ok launchpad.net/juju-core/environs/imagemetadata 0.532s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.052s
ok launchpad.net/juju-core/environs/jujutest 0.259s
ok launchpad.net/juju-core/environs/manual 4.176s
ok launchpad.net/juju-core/environs/simplestreams 0.385s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.125s
ok launchpad.net/juju-core/environs/storage 1.118s
ok launchpad.net/juju-core/environs/sync 28.576s
ok launchpad.net/juju-core/environs/testing 0.200s
ok launchpad.net/juju-core/environs/tools 7.098s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.016s
ok launchpad.net/juju-core/instance 0.023s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 17.758s
ok launchpad.net/juju-core/juju/osenv 0.018s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.016s
ok launchpad.net/juju-core/log/syslog 0.020s
ok launchpad.net/juju-core/names 0.023s
? launchpad.net/juju-core/provider [no test files]
? launchpad.net/juju-core/provider/all [no test files]
ok launchpad.net/juju-core/provider/azure 6.727s
ok launchpad.net/juju-core/provider/common 0.263s
ok launchpad.net/juju-core/provider/dummy 22.424s
ok launchpad.net/juju-core/provider/ec2 5.660s
ok launchpad.net/juju-core/provider/ec2/httpstorage 0.200s
ok launchpad.net/juju-core/provider/local 2.251s
ok launchpad.net/juju-core/provider/maas 10.070s
ok launchpad.net/juju-core/provider/null 1.205s
ok launchpad.net/juju-core/provider/openstack 13.886s
ok launchpad.net/juju-core/rpc 0.078s
ok launchpad.net/juju...

Read more...

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (19.2 KiB)

The attempt to merge lp:~wallyworld/juju-core/merge-new-image-metadata into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 0.914s
ok launchpad.net/juju-core/agent/tools 0.255s
ok launchpad.net/juju-core/bzr 7.287s
ok launchpad.net/juju-core/cert 3.762s
ok launchpad.net/juju-core/charm 0.576s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.027s
ok launchpad.net/juju-core/cmd 0.275s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 173.522s
ok launchpad.net/juju-core/cmd/jujud 48.185s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 2.549s
ok launchpad.net/juju-core/constraints 0.032s
ok launchpad.net/juju-core/container/lxc 0.324s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.305s
ok launchpad.net/juju-core/environs 3.236s
ok launchpad.net/juju-core/environs/bootstrap 5.048s
ok launchpad.net/juju-core/environs/cloudinit 0.521s
ok launchpad.net/juju-core/environs/config 0.858s
ok launchpad.net/juju-core/environs/configstore 0.041s
ok launchpad.net/juju-core/environs/filestorage 0.040s
ok launchpad.net/juju-core/environs/httpstorage 0.913s
ok launchpad.net/juju-core/environs/imagemetadata 0.491s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.055s
ok launchpad.net/juju-core/environs/jujutest 0.253s
ok launchpad.net/juju-core/environs/manual 4.230s
ok launchpad.net/juju-core/environs/simplestreams 0.338s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.218s
ok launchpad.net/juju-core/environs/storage 1.177s
ok launchpad.net/juju-core/environs/sync 29.731s
ok launchpad.net/juju-core/environs/testing 0.224s
ok launchpad.net/juju-core/environs/tools 7.134s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.017s
ok launchpad.net/juju-core/instance 0.024s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 17.834s
ok launchpad.net/juju-core/juju/osenv 0.028s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.016s
ok launchpad.net/juju-core/log/syslog 0.031s
ok launchpad.net/juju-core/names 0.025s
? launchpad.net/juju-core/provider [no test files]
? launchpad.net/juju-core/provider/all [no test files]
ok launchpad.net/juju-core/provider/azure 6.501s
ok launchpad.net/juju-core/provider/common 0.327s
ok launchpad.net/juju-core/provider/dummy 21.903s
ok launchpad.net/juju-core/provider/ec2 5.529s
ok launchpad.net/juju-core/provider/ec2/httpstorage 0.224s
ok launchpad.net/juju-core/provider/local 2.309s
ok launchpad.net/juju-core/provider/maas 10.245s
ok launchpad.net/juju-core/provider/null 1.246s
ok launchpad.net/juju-core/provider/openstack 13.733s
ok launchpad.net/juju-core/rpc 0.087s
ok launchpad.net/juju-core/rp...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/plugins/juju-metadata/imagemetadata.go'
--- cmd/plugins/juju-metadata/imagemetadata.go 2013-10-16 03:31:36 +0000
+++ cmd/plugins/juju-metadata/imagemetadata.go 2013-10-17 05:10:43 +0000
@@ -17,6 +17,7 @@
17 "launchpad.net/juju-core/environs/filestorage"17 "launchpad.net/juju-core/environs/filestorage"
18 "launchpad.net/juju-core/environs/imagemetadata"18 "launchpad.net/juju-core/environs/imagemetadata"
19 "launchpad.net/juju-core/environs/simplestreams"19 "launchpad.net/juju-core/environs/simplestreams"
20 "launchpad.net/juju-core/utils"
20)21)
2122
22// ImageMetadataCommand is used to write out simplestreams image metadata information.23// ImageMetadataCommand is used to write out simplestreams image metadata information.
@@ -125,17 +126,17 @@
125126
126var helpDoc = `127var helpDoc = `
127image metadata files have been written to:128image metadata files have been written to:
128%q.129%s.
129For Juju to use this metadata, the files need to be put into the130For Juju to use this metadata, the files need to be put into the
130image metadata search path. There are 2 options:131image metadata search path. There are 2 options:
131132
1321. Use tools-url in $JUJU_HOME/environments.yaml1331. Use tools-url in $JUJU_HOME/environments.yaml
133Configure a http server to serve the contents of134Configure a http server to serve the contents of
134%q135%s
135and set the value of tools-url accordingly.136and set the value of tools-url accordingly.
136137
1372. Upload the contents of1382. Upload the contents of
138%q139%s
139to your cloud's private storage (for ec2 and openstack).140to your cloud's private storage (for ec2 and openstack).
140eg for openstack141eg for openstack
141"cd %s; swift upload %s streams/v1/*"142"cd %s; swift upload %s streams/v1/*"
@@ -145,7 +146,7 @@
145func (c *ImageMetadataCommand) Run(context *cmd.Context) error {146func (c *ImageMetadataCommand) Run(context *cmd.Context) error {
146 out := context.Stdout147 out := context.Stdout
147148
148 im := imagemetadata.ImageMetadata{149 im := &imagemetadata.ImageMetadata{
149 Id: c.ImageId,150 Id: c.ImageId,
150 Arch: c.Arch,151 Arch: c.Arch,
151 }152 }
@@ -157,11 +158,12 @@
157 if err != nil {158 if err != nil {
158 return err159 return err
159 }160 }
160 err = imagemetadata.WriteMetadata(c.Series, &im, &cloudSpec, targetStorage)161 err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage)
161 if err != nil {162 if err != nil {
162 return fmt.Errorf("image metadata files could not be created: %v", err)163 return fmt.Errorf("image metadata files could not be created: %v", err)
163 }164 }
164 dest := filepath.Join(c.Dir, "streams", "v1")165 dest := filepath.Join(c.Dir, "streams", "v1")
165 fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, c.Dir, c.Dir, c.Dir, c.privateStorage))166 dir := utils.NormalizePath(c.Dir)
167 fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir, dir, c.privateStorage))
166 return nil168 return nil
167}169}
168170
=== modified file 'cmd/plugins/juju-metadata/imagemetadata_test.go'
--- cmd/plugins/juju-metadata/imagemetadata_test.go 2013-10-16 03:31:36 +0000
+++ cmd/plugins/juju-metadata/imagemetadata_test.go 2013-10-17 05:10:43 +0000
@@ -4,7 +4,6 @@
4package main4package main
55
6import (6import (
7 "bytes"
8 "encoding/json"7 "encoding/json"
9 "fmt"8 "fmt"
10 "io/ioutil"9 "io/ioutil"
@@ -89,10 +88,10 @@
89 c.Assert(err, gc.IsNil)88 c.Assert(err, gc.IsNil)
90 c.Assert(indices.(map[string]interface{})["format"], gc.Equals, "index:1.0")89 c.Assert(indices.(map[string]interface{})["format"], gc.Equals, "index:1.0")
91 prodId := fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", seriesVersions[expected.series], expected.arch)90 prodId := fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", seriesVersions[expected.series], expected.arch)
92 c.Assert(strings.Contains(content, prodId), jc.IsTrue)91 c.Assert(content, jc.Contains, prodId)
93 c.Assert(strings.Contains(content, fmt.Sprintf(`"region": %q`, expected.region)), jc.IsTrue)92 c.Assert(content, jc.Contains, fmt.Sprintf(`"region": %q`, expected.region))
94 c.Assert(strings.Contains(content, fmt.Sprintf(`"endpoint": %q`, expected.endpoint)), jc.IsTrue)93 c.Assert(content, jc.Contains, fmt.Sprintf(`"endpoint": %q`, expected.endpoint))
95 c.Assert(strings.Contains(content, fmt.Sprintf(`"path": "streams/v1/%s"`, imageFileName)), jc.IsTrue)94 c.Assert(content, jc.Contains, fmt.Sprintf(`"path": "streams/v1/%s"`, imageFileName))
9695
97 imagepath := filepath.Join(s.dir, "streams", "v1", imageFileName)96 imagepath := filepath.Join(s.dir, "streams", "v1", imageFileName)
98 data, err = ioutil.ReadFile(imagepath)97 data, err = ioutil.ReadFile(imagepath)
@@ -102,8 +101,8 @@
102 err = json.Unmarshal(data, &images)101 err = json.Unmarshal(data, &images)
103 c.Assert(err, gc.IsNil)102 c.Assert(err, gc.IsNil)
104 c.Assert(images.(map[string]interface{})["format"], gc.Equals, "products:1.0")103 c.Assert(images.(map[string]interface{})["format"], gc.Equals, "products:1.0")
105 c.Assert(strings.Contains(content, prodId), gc.Equals, true)104 c.Assert(content, jc.Contains, prodId)
106 c.Assert(strings.Contains(content, `"id": "1234"`), gc.Equals, true)105 c.Assert(content, jc.Contains, `"id": "1234"`)
107}106}
108107
109const (108const (
@@ -117,12 +116,12 @@
117 &ImageMetadataCommand{}, ctx, []string{116 &ImageMetadataCommand{}, ctx, []string{
118 "-d", s.dir, "-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint", "-s", "raring"})117 "-d", s.dir, "-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint", "-s", "raring"})
119 c.Assert(code, gc.Equals, 0)118 c.Assert(code, gc.Equals, 0)
120 errOut := ctx.Stdout.(*bytes.Buffer).String()119 out := testing.Stdout(ctx)
121 expected := expectedMetadata{120 expected := expectedMetadata{
122 series: "raring",121 series: "raring",
123 arch: "arch",122 arch: "arch",
124 }123 }
125 s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName)124 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
126}125}
127126
128func (s *ImageMetadataSuite) TestImageMetadataFilesDefaultArch(c *gc.C) {127func (s *ImageMetadataSuite) TestImageMetadataFilesDefaultArch(c *gc.C) {
@@ -131,12 +130,12 @@
131 &ImageMetadataCommand{}, ctx, []string{130 &ImageMetadataCommand{}, ctx, []string{
132 "-d", s.dir, "-i", "1234", "-r", "region", "-u", "endpoint", "-s", "raring"})131 "-d", s.dir, "-i", "1234", "-r", "region", "-u", "endpoint", "-s", "raring"})
133 c.Assert(code, gc.Equals, 0)132 c.Assert(code, gc.Equals, 0)
134 errOut := ctx.Stdout.(*bytes.Buffer).String()133 out := testing.Stdout(ctx)
135 expected := expectedMetadata{134 expected := expectedMetadata{
136 series: "raring",135 series: "raring",
137 arch: "amd64",136 arch: "amd64",
138 }137 }
139 s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName)138 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
140}139}
141140
142func (s *ImageMetadataSuite) TestImageMetadataFilesDefaultSeries(c *gc.C) {141func (s *ImageMetadataSuite) TestImageMetadataFilesDefaultSeries(c *gc.C) {
@@ -145,12 +144,12 @@
145 &ImageMetadataCommand{}, ctx, []string{144 &ImageMetadataCommand{}, ctx, []string{
146 "-d", s.dir, "-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint"})145 "-d", s.dir, "-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint"})
147 c.Assert(code, gc.Equals, 0)146 c.Assert(code, gc.Equals, 0)
148 errOut := ctx.Stdout.(*bytes.Buffer).String()147 out := testing.Stdout(ctx)
149 expected := expectedMetadata{148 expected := expectedMetadata{
150 series: "precise",149 series: "precise",
151 arch: "arch",150 arch: "arch",
152 }151 }
153 s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName)152 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
154}153}
155154
156func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnv(c *gc.C) {155func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnv(c *gc.C) {
@@ -158,14 +157,14 @@
158 code := cmd.Main(157 code := cmd.Main(
159 &ImageMetadataCommand{}, ctx, []string{"-d", s.dir, "-e", "ec2", "-i", "1234"})158 &ImageMetadataCommand{}, ctx, []string{"-d", s.dir, "-e", "ec2", "-i", "1234"})
160 c.Assert(code, gc.Equals, 0)159 c.Assert(code, gc.Equals, 0)
161 errOut := ctx.Stdout.(*bytes.Buffer).String()160 out := testing.Stdout(ctx)
162 expected := expectedMetadata{161 expected := expectedMetadata{
163 series: "precise",162 series: "precise",
164 arch: "amd64",163 arch: "amd64",
165 region: "us-east-1",164 region: "us-east-1",
166 endpoint: "https://ec2.us-east-1.amazonaws.com",165 endpoint: "https://ec2.us-east-1.amazonaws.com",
167 }166 }
168 s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName)167 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
169}168}
170169
171func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvWithRegionOverride(c *gc.C) {170func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvWithRegionOverride(c *gc.C) {
@@ -174,14 +173,14 @@
174 &ImageMetadataCommand{}, ctx, []string{173 &ImageMetadataCommand{}, ctx, []string{
175 "-d", s.dir, "-e", "ec2", "-r", "us-west-1", "-u", "https://ec2.us-west-1.amazonaws.com", "-i", "1234"})174 "-d", s.dir, "-e", "ec2", "-r", "us-west-1", "-u", "https://ec2.us-west-1.amazonaws.com", "-i", "1234"})
176 c.Assert(code, gc.Equals, 0)175 c.Assert(code, gc.Equals, 0)
177 errOut := ctx.Stdout.(*bytes.Buffer).String()176 out := testing.Stdout(ctx)
178 expected := expectedMetadata{177 expected := expectedMetadata{
179 series: "precise",178 series: "precise",
180 arch: "amd64",179 arch: "amd64",
181 region: "us-west-1",180 region: "us-west-1",
182 endpoint: "https://ec2.us-west-1.amazonaws.com",181 endpoint: "https://ec2.us-west-1.amazonaws.com",
183 }182 }
184 s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName)183 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
185}184}
186185
187func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvWithNoHasRegion(c *gc.C) {186func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvWithNoHasRegion(c *gc.C) {
@@ -190,14 +189,14 @@
190 &ImageMetadataCommand{}, ctx, []string{189 &ImageMetadataCommand{}, ctx, []string{
191 "-d", s.dir, "-e", "azure", "-r", "region", "-u", "endpoint", "-i", "1234"})190 "-d", s.dir, "-e", "azure", "-r", "region", "-u", "endpoint", "-i", "1234"})
192 c.Assert(code, gc.Equals, 0)191 c.Assert(code, gc.Equals, 0)
193 errOut := ctx.Stdout.(*bytes.Buffer).String()192 out := testing.Stdout(ctx)
194 expected := expectedMetadata{193 expected := expectedMetadata{
195 series: "raring",194 series: "raring",
196 arch: "amd64",195 arch: "amd64",
197 region: "region",196 region: "region",
198 endpoint: "endpoint",197 endpoint: "endpoint",
199 }198 }
200 s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName)199 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
201}200}
202201
203type errTestParams struct {202type errTestParams struct {
204203
=== modified file 'cmd/plugins/juju-metadata/validateimagemetadata_test.go'
--- cmd/plugins/juju-metadata/validateimagemetadata_test.go 2013-10-17 04:53:59 +0000
+++ cmd/plugins/juju-metadata/validateimagemetadata_test.go 2013-10-17 05:10:43 +0000
@@ -66,7 +66,7 @@
66}66}
6767
68func (s *ValidateImageMetadataSuite) makeLocalMetadata(c *gc.C, id, region, series, endpoint string) error {68func (s *ValidateImageMetadataSuite) makeLocalMetadata(c *gc.C, id, region, series, endpoint string) error {
69 im := imagemetadata.ImageMetadata{69 im := &imagemetadata.ImageMetadata{
70 Id: id,70 Id: id,
71 Arch: "amd64",71 Arch: "amd64",
72 }72 }
@@ -78,7 +78,7 @@
78 if err != nil {78 if err != nil {
79 return err79 return err
80 }80 }
81 err = imagemetadata.WriteMetadata(series, &im, &cloudSpec, targetStorage)81 err = imagemetadata.MergeAndWriteMetadata(series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage)
82 if err != nil {82 if err != nil {
83 return err83 return err
84 }84 }
@@ -105,10 +105,8 @@
105 s.LoggingSuite.SetUpTest(c)105 s.LoggingSuite.SetUpTest(c)
106 s.metadataDir = c.MkDir()106 s.metadataDir = c.MkDir()
107 s.home = coretesting.MakeFakeHome(c, metadataTestEnvConfig)107 s.home = coretesting.MakeFakeHome(c, metadataTestEnvConfig)
108 restore := testbase.PatchEnvironment("AWS_ACCESS_KEY_ID", "access")108 s.PatchEnvironment("AWS_ACCESS_KEY_ID", "access")
109 s.AddCleanup(func(*gc.C) { restore() })109 s.PatchEnvironment("AWS_SECRET_ACCESS_KEY", "secret")
110 restore = testbase.PatchEnvironment("AWS_SECRET_ACCESS_KEY", "secret")
111 s.AddCleanup(func(*gc.C) { restore() })
112}110}
113111
114func (s *ValidateImageMetadataSuite) TearDownTest(c *gc.C) {112func (s *ValidateImageMetadataSuite) TearDownTest(c *gc.C) {
115113
=== modified file 'environs/imagemetadata/generate.go'
--- environs/imagemetadata/generate.go 2013-10-11 06:44:35 +0000
+++ environs/imagemetadata/generate.go 2013-10-17 05:10:43 +0000
@@ -5,16 +5,96 @@
55
6import (6import (
7 "bytes"7 "bytes"
8 "fmt"
8 "time"9 "time"
910
10 "launchpad.net/juju-core/environs/simplestreams"11 "launchpad.net/juju-core/environs/simplestreams"
11 "launchpad.net/juju-core/environs/storage"12 "launchpad.net/juju-core/environs/storage"
13 "launchpad.net/juju-core/errors"
12)14)
1315
14func WriteMetadata(series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec, metadataStore storage.Storage) error {16// MergeAndWriteMetadata reads the existing metadata from storage (if any),
15 metadataInfo, err := generateMetadata(series, im, cloudSpec)17// and merges it with supplied metadata, writing the resulting metadata is written to storage.
16 if err != nil {18func MergeAndWriteMetadata(series string, metadata []*ImageMetadata, cloudSpec *simplestreams.CloudSpec,
17 return err19 metadataStore storage.Storage) error {
20
21 existingMetadata, err := readMetadata(metadataStore)
22 if err != nil {
23 return err
24 }
25 seriesVersion, err := simplestreams.SeriesVersion(series)
26 if err != nil {
27 return err
28 }
29 toWrite, allCloudSpec := mergeMetadata(seriesVersion, cloudSpec, metadata, existingMetadata)
30 return writeMetadata(toWrite, allCloudSpec, metadataStore)
31}
32
33// readMetadata reads the image metadata from metadataStore.
34func readMetadata(metadataStore storage.Storage) ([]*ImageMetadata, error) {
35 // Read any existing metadata so we can merge the new tools metadata with what's there.
36 dataSource := storage.NewStorageSimpleStreamsDataSource(metadataStore, "")
37 imageConstraint := NewImageConstraint(simplestreams.LookupParams{})
38 existingMetadata, err := Fetch(
39 []simplestreams.DataSource{dataSource}, simplestreams.DefaultIndexPath, imageConstraint, false)
40 if err != nil && !errors.IsNotFoundError(err) {
41 return nil, err
42 }
43 return existingMetadata, nil
44}
45
46func mapKey(im *ImageMetadata) string {
47 return fmt.Sprintf("%s-%s", im.productId(), im.RegionName)
48}
49
50// mergeMetadata merges the newMetadata into existingMetadata, overwriting existing matching image records.
51func mergeMetadata(seriesVersion string, cloudSpec *simplestreams.CloudSpec, newMetadata,
52 existingMetadata []*ImageMetadata) ([]*ImageMetadata, []simplestreams.CloudSpec) {
53
54 var toWrite = make([]*ImageMetadata, len(newMetadata))
55 imageIds := make(map[string]bool)
56 for i, im := range newMetadata {
57 newRecord := *im
58 newRecord.Version = seriesVersion
59 newRecord.RegionName = cloudSpec.Region
60 newRecord.Endpoint = cloudSpec.Endpoint
61 toWrite[i] = &newRecord
62 imageIds[mapKey(&newRecord)] = true
63 }
64 regions := make(map[string]bool)
65 var allCloudSpecs = []simplestreams.CloudSpec{*cloudSpec}
66 for _, im := range existingMetadata {
67 if _, ok := imageIds[mapKey(im)]; ok {
68 continue
69 }
70 toWrite = append(toWrite, im)
71 if _, ok := regions[im.RegionName]; ok {
72 continue
73 }
74 regions[im.RegionName] = true
75 existingCloudSpec := simplestreams.CloudSpec{im.RegionName, im.Endpoint}
76 allCloudSpecs = append(allCloudSpecs, existingCloudSpec)
77 }
78 return toWrite, allCloudSpecs
79}
80
81type MetadataFile struct {
82 Path string
83 Data []byte
84}
85
86// writeMetadata generates some basic simplestreams metadata using the specified cloud and image details and writes
87// it to the supplied store.
88func writeMetadata(metadata []*ImageMetadata, cloudSpec []simplestreams.CloudSpec,
89 metadataStore storage.Storage) error {
90
91 index, products, err := MarshalImageMetadataJSON(metadata, cloudSpec, time.Now())
92 if err != nil {
93 return err
94 }
95 metadataInfo := []MetadataFile{
96 {simplestreams.UnsignedIndex, index},
97 {ProductMetadataPath, products},
18 }98 }
19 for _, md := range metadataInfo {99 for _, md := range metadataInfo {
20 err = metadataStore.Put(md.Path, bytes.NewReader(md.Data), int64(len(md.Data)))100 err = metadataStore.Put(md.Path, bytes.NewReader(md.Data), int64(len(md.Data)))
@@ -24,29 +104,3 @@
24 }104 }
25 return nil105 return nil
26}106}
27
28type MetadataFile struct {
29 Path string
30 Data []byte
31}
32
33// generateMetadata generates some basic simplestreams metadata using the specified cloud and image details.
34func generateMetadata(series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec) ([]MetadataFile, error) {
35 metadata := &ImageMetadata{
36 Id: im.Id,
37 Arch: im.Arch,
38 Release: series,
39 RegionName: cloudSpec.Region,
40 Endpoint: cloudSpec.Endpoint,
41 }
42
43 index, products, err := MarshalImageMetadataJSON([]*ImageMetadata{metadata}, cloudSpec, time.Now())
44 if err != nil {
45 return nil, err
46 }
47 objects := []MetadataFile{
48 {simplestreams.UnsignedIndex, index},
49 {ProductMetadataPath, products},
50 }
51 return objects, nil
52}
53107
=== modified file 'environs/imagemetadata/generate_test.go'
--- environs/imagemetadata/generate_test.go 2013-10-16 04:11:38 +0000
+++ environs/imagemetadata/generate_test.go 2013-10-17 05:10:43 +0000
@@ -4,12 +4,15 @@
4package imagemetadata_test4package imagemetadata_test
55
6import (6import (
7 "sort"
8
7 gc "launchpad.net/gocheck"9 gc "launchpad.net/gocheck"
810
9 "launchpad.net/juju-core/environs/filestorage"11 "launchpad.net/juju-core/environs/filestorage"
10 "launchpad.net/juju-core/environs/imagemetadata"12 "launchpad.net/juju-core/environs/imagemetadata"
11 "launchpad.net/juju-core/environs/imagemetadata/testing"13 "launchpad.net/juju-core/environs/imagemetadata/testing"
12 "launchpad.net/juju-core/environs/simplestreams"14 "launchpad.net/juju-core/environs/simplestreams"
15 "launchpad.net/juju-core/environs/storage"
13 "launchpad.net/juju-core/testing/testbase"16 "launchpad.net/juju-core/testing/testbase"
14)17)
1518
@@ -19,10 +22,27 @@
19 testbase.LoggingSuite22 testbase.LoggingSuite
20}23}
2124
25func assertFetch(c *gc.C, stor storage.Storage, series, arch, region, endpoint, id string) {
26 cons := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
27 CloudSpec: simplestreams.CloudSpec{region, endpoint},
28 Series: []string{series},
29 Arches: []string{arch},
30 })
31 dataSource := storage.NewStorageSimpleStreamsDataSource(stor, "")
32 metadata, err := imagemetadata.Fetch(
33 []simplestreams.DataSource{dataSource}, simplestreams.DefaultIndexPath, cons, false)
34 c.Assert(err, gc.IsNil)
35 c.Assert(metadata, gc.HasLen, 1)
36 c.Assert(metadata[0].Id, gc.Equals, id)
37}
38
22func (s *generateSuite) TestWriteMetadata(c *gc.C) {39func (s *generateSuite) TestWriteMetadata(c *gc.C) {
23 im := &imagemetadata.ImageMetadata{40 im := []*imagemetadata.ImageMetadata{
24 Id: "1234",41 {
25 Arch: "amd64",42 Id: "1234",
43 Arch: "amd64",
44 Version: "13.04",
45 },
26 }46 }
27 cloudSpec := &simplestreams.CloudSpec{47 cloudSpec := &simplestreams.CloudSpec{
28 Region: "region",48 Region: "region",
@@ -31,10 +51,156 @@
31 dir := c.MkDir()51 dir := c.MkDir()
32 targetStorage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)52 targetStorage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
33 c.Assert(err, gc.IsNil)53 c.Assert(err, gc.IsNil)
34 err = imagemetadata.WriteMetadata("raring", im, cloudSpec, targetStorage)54 err = imagemetadata.MergeAndWriteMetadata("raring", im, cloudSpec, targetStorage)
35 c.Assert(err, gc.IsNil)55 c.Assert(err, gc.IsNil)
36 metadata := testing.ParseMetadata(c, dir)56 metadata := testing.ParseMetadata(c, dir)
37 c.Assert(metadata, gc.HasLen, 1)57 c.Assert(metadata, gc.HasLen, 1)
38 im.RegionName = cloudSpec.Region58 im[0].RegionName = cloudSpec.Region
39 c.Assert(im, gc.DeepEquals, metadata[0])59 im[0].Endpoint = cloudSpec.Endpoint
40}60 c.Assert(im[0], gc.DeepEquals, metadata[0])
61 assertFetch(c, targetStorage, "raring", "amd64", "region", "endpoint", "1234")
62}
63
64func (s *generateSuite) TestWriteMetadataMergeOverwriteSameArch(c *gc.C) {
65 existingImageMetadata := []*imagemetadata.ImageMetadata{
66 {
67 Id: "1234",
68 Arch: "amd64",
69 Version: "13.04",
70 },
71 }
72 cloudSpec := &simplestreams.CloudSpec{
73 Region: "region",
74 Endpoint: "endpoint",
75 }
76 dir := c.MkDir()
77 targetStorage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
78 c.Assert(err, gc.IsNil)
79 err = imagemetadata.MergeAndWriteMetadata("raring", existingImageMetadata, cloudSpec, targetStorage)
80 c.Assert(err, gc.IsNil)
81 newImageMetadata := []*imagemetadata.ImageMetadata{
82 {
83 Id: "abcd",
84 Arch: "amd64",
85 Version: "13.04",
86 },
87 {
88 Id: "xyz",
89 Arch: "arm",
90 Version: "13.04",
91 },
92 }
93 err = imagemetadata.MergeAndWriteMetadata("raring", newImageMetadata, cloudSpec, targetStorage)
94 c.Assert(err, gc.IsNil)
95 metadata := testing.ParseMetadata(c, dir)
96 c.Assert(metadata, gc.HasLen, 2)
97 for _, im := range newImageMetadata {
98 im.RegionName = cloudSpec.Region
99 im.Endpoint = cloudSpec.Endpoint
100 }
101 c.Assert(metadata, gc.DeepEquals, newImageMetadata)
102 assertFetch(c, targetStorage, "raring", "amd64", "region", "endpoint", "abcd")
103 assertFetch(c, targetStorage, "raring", "arm", "region", "endpoint", "xyz")
104}
105
106func (s *generateSuite) TestWriteMetadataMergeDifferentSeries(c *gc.C) {
107 existingImageMetadata := []*imagemetadata.ImageMetadata{
108 {
109 Id: "1234",
110 Arch: "amd64",
111 Version: "13.04",
112 },
113 }
114 cloudSpec := &simplestreams.CloudSpec{
115 Region: "region",
116 Endpoint: "endpoint",
117 }
118 dir := c.MkDir()
119 targetStorage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
120 c.Assert(err, gc.IsNil)
121 err = imagemetadata.MergeAndWriteMetadata("raring", existingImageMetadata, cloudSpec, targetStorage)
122 c.Assert(err, gc.IsNil)
123 newImageMetadata := []*imagemetadata.ImageMetadata{
124 {
125 Id: "abcd",
126 Arch: "amd64",
127 Version: "12.04",
128 },
129 {
130 Id: "xyz",
131 Arch: "arm",
132 Version: "12.04",
133 },
134 }
135 err = imagemetadata.MergeAndWriteMetadata("precise", newImageMetadata, cloudSpec, targetStorage)
136 c.Assert(err, gc.IsNil)
137 metadata := testing.ParseMetadata(c, dir)
138 c.Assert(metadata, gc.HasLen, 3)
139 newImageMetadata = append(newImageMetadata, existingImageMetadata[0])
140 for _, im := range newImageMetadata {
141 im.RegionName = cloudSpec.Region
142 im.Endpoint = cloudSpec.Endpoint
143 }
144 sort.Sort(byId(metadata))
145 sort.Sort(byId(newImageMetadata))
146 c.Assert(metadata, gc.DeepEquals, newImageMetadata)
147 assertFetch(c, targetStorage, "raring", "amd64", "region", "endpoint", "1234")
148 assertFetch(c, targetStorage, "precise", "amd64", "region", "endpoint", "abcd")
149}
150
151func (s *generateSuite) TestWriteMetadataMergeDifferentRegion(c *gc.C) {
152 existingImageMetadata := []*imagemetadata.ImageMetadata{
153 {
154 Id: "1234",
155 Arch: "amd64",
156 Version: "13.04",
157 },
158 }
159 cloudSpec := &simplestreams.CloudSpec{
160 Region: "region",
161 Endpoint: "endpoint",
162 }
163 dir := c.MkDir()
164 targetStorage, err := filestorage.NewFileStorageWriter(dir, filestorage.UseDefaultTmpDir)
165 c.Assert(err, gc.IsNil)
166 err = imagemetadata.MergeAndWriteMetadata("raring", existingImageMetadata, cloudSpec, targetStorage)
167 c.Assert(err, gc.IsNil)
168 newImageMetadata := []*imagemetadata.ImageMetadata{
169 {
170 Id: "abcd",
171 Arch: "amd64",
172 Version: "13.04",
173 },
174 {
175 Id: "xyz",
176 Arch: "arm",
177 Version: "13.04",
178 },
179 }
180 cloudSpec = &simplestreams.CloudSpec{
181 Region: "region2",
182 Endpoint: "endpoint2",
183 }
184 err = imagemetadata.MergeAndWriteMetadata("raring", newImageMetadata, cloudSpec, targetStorage)
185 c.Assert(err, gc.IsNil)
186 metadata := testing.ParseMetadata(c, dir)
187 c.Assert(metadata, gc.HasLen, 3)
188 for _, im := range newImageMetadata {
189 im.RegionName = "region2"
190 im.Endpoint = "endpoint2"
191 }
192 existingImageMetadata[0].RegionName = "region"
193 existingImageMetadata[0].Endpoint = "endpoint"
194 newImageMetadata = append(newImageMetadata, existingImageMetadata[0])
195 sort.Sort(byId(metadata))
196 sort.Sort(byId(newImageMetadata))
197 c.Assert(metadata, gc.DeepEquals, newImageMetadata)
198 assertFetch(c, targetStorage, "raring", "amd64", "region", "endpoint", "1234")
199 assertFetch(c, targetStorage, "raring", "amd64", "region2", "endpoint2", "abcd")
200}
201
202type byId []*imagemetadata.ImageMetadata
203
204func (b byId) Len() int { return len(b) }
205func (b byId) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
206func (b byId) Less(i, j int) bool { return b[i].Id < b[j].Id }
41207
=== modified file 'environs/imagemetadata/marshal.go'
--- environs/imagemetadata/marshal.go 2013-10-16 04:11:38 +0000
+++ environs/imagemetadata/marshal.go 2013-10-17 05:10:43 +0000
@@ -18,7 +18,9 @@
18// MarshalImageMetadataJSON marshals image metadata to index and products JSON.18// MarshalImageMetadataJSON marshals image metadata to index and products JSON.
19//19//
20// updated is the time at which the JSON file was updated.20// updated is the time at which the JSON file was updated.
21func MarshalImageMetadataJSON(metadata []*ImageMetadata, cloudSpec *simplestreams.CloudSpec, updated time.Time) (index, products []byte, err error) {21func MarshalImageMetadataJSON(metadata []*ImageMetadata, cloudSpec []simplestreams.CloudSpec,
22 updated time.Time) (index, products []byte, err error) {
23
22 if index, err = MarshalImageMetadataIndexJSON(metadata, cloudSpec, updated); err != nil {24 if index, err = MarshalImageMetadataIndexJSON(metadata, cloudSpec, updated); err != nil {
23 return nil, nil, err25 return nil, nil, err
24 }26 }
@@ -31,13 +33,12 @@
31// MarshalImageMetadataIndexJSON marshals image metadata to index JSON.33// MarshalImageMetadataIndexJSON marshals image metadata to index JSON.
32//34//
33// updated is the time at which the JSON file was updated.35// updated is the time at which the JSON file was updated.
34func MarshalImageMetadataIndexJSON(metadata []*ImageMetadata, cloudSpec *simplestreams.CloudSpec, updated time.Time) (out []byte, err error) {36func MarshalImageMetadataIndexJSON(metadata []*ImageMetadata, cloudSpec []simplestreams.CloudSpec,
37 updated time.Time) (out []byte, err error) {
38
35 productIds := make([]string, len(metadata))39 productIds := make([]string, len(metadata))
36 for i, t := range metadata {40 for i, t := range metadata {
37 productIds[i], err = t.productId()41 productIds[i] = t.productId()
38 if err != nil {
39 return nil, err
40 }
41 }42 }
42 var indices simplestreams.Indices43 var indices simplestreams.Indices
43 indices.Updated = updated.Format(time.RFC1123Z)44 indices.Updated = updated.Format(time.RFC1123Z)
@@ -50,7 +51,7 @@
50 DataType: "image-ids",51 DataType: "image-ids",
51 ProductsFilePath: ProductMetadataPath,52 ProductsFilePath: ProductMetadataPath,
52 ProductIds: set.NewStrings(productIds...).SortedValues(),53 ProductIds: set.NewStrings(productIds...).SortedValues(),
53 Clouds: []simplestreams.CloudSpec{*cloudSpec},54 Clouds: cloudSpec,
54 },55 },
55 }56 }
56 return json.MarshalIndent(&indices, "", " ")57 return json.MarshalIndent(&indices, "", " ")
@@ -66,31 +67,24 @@
66 cloud.Products = make(map[string]simplestreams.MetadataCatalog)67 cloud.Products = make(map[string]simplestreams.MetadataCatalog)
67 itemsversion := updated.Format("20060201") // YYYYMMDD68 itemsversion := updated.Format("20060201") // YYYYMMDD
68 for _, t := range metadata {69 for _, t := range metadata {
69 id, err := t.productId()
70 if err != nil {
71 return nil, err
72 }
73 version, err := simplestreams.SeriesVersion(t.Release)
74 if err != nil {
75 return nil, err
76 }
77 toWrite := &ImageMetadata{70 toWrite := &ImageMetadata{
78 Id: t.Id,71 Id: t.Id,
72 RegionName: t.RegionName,
73 Endpoint: t.Endpoint,
79 }74 }
80 if catalog, ok := cloud.Products[id]; ok {75 if catalog, ok := cloud.Products[t.productId()]; ok {
81 catalog.Items[itemsversion].Items[t.Id] = t76 catalog.Items[itemsversion].Items[t.Id] = toWrite
82 } else {77 } else {
83 catalog = simplestreams.MetadataCatalog{78 catalog = simplestreams.MetadataCatalog{
84 Arch: t.Arch,79 Arch: t.Arch,
85 RegionName: t.RegionName,80 Version: t.Version,
86 Version: version,
87 Items: map[string]*simplestreams.ItemCollection{81 Items: map[string]*simplestreams.ItemCollection{
88 itemsversion: &simplestreams.ItemCollection{82 itemsversion: &simplestreams.ItemCollection{
89 Items: map[string]interface{}{t.Id: toWrite},83 Items: map[string]interface{}{t.Id: toWrite},
90 },84 },
91 },85 },
92 }86 }
93 cloud.Products[id] = catalog87 cloud.Products[t.productId()] = catalog
94 }88 }
95 }89 }
96 return json.MarshalIndent(&cloud, "", " ")90 return json.MarshalIndent(&cloud, "", " ")
9791
=== modified file 'environs/imagemetadata/marshal_test.go'
--- environs/imagemetadata/marshal_test.go 2013-10-11 06:44:35 +0000
+++ environs/imagemetadata/marshal_test.go 2013-10-17 05:10:43 +0000
@@ -10,11 +10,14 @@
1010
11 "launchpad.net/juju-core/environs/imagemetadata"11 "launchpad.net/juju-core/environs/imagemetadata"
12 "launchpad.net/juju-core/environs/simplestreams"12 "launchpad.net/juju-core/environs/simplestreams"
13 "launchpad.net/juju-core/testing/testbase"
13)14)
1415
15var _ = gc.Suite(&marshalSuite{})16var _ = gc.Suite(&marshalSuite{})
1617
17type marshalSuite struct{}18type marshalSuite struct {
19 testbase.LoggingSuite
20}
1821
19var expectedIndex = `{22var expectedIndex = `{
20 "index": {23 "index": {
@@ -90,23 +93,23 @@
90var imageMetadataForTesting = []*imagemetadata.ImageMetadata{93var imageMetadataForTesting = []*imagemetadata.ImageMetadata{
91 &imagemetadata.ImageMetadata{94 &imagemetadata.ImageMetadata{
92 Id: "1234",95 Id: "1234",
93 Release: "saucy",96 Version: "13.10",
94 Arch: "arm",97 Arch: "arm",
95 },98 },
96 &imagemetadata.ImageMetadata{99 &imagemetadata.ImageMetadata{
97 Id: "5678",100 Id: "5678",
98 Release: "precise",101 Version: "12.04",
99 Arch: "arm",102 Arch: "arm",
100 },103 },
101 &imagemetadata.ImageMetadata{104 &imagemetadata.ImageMetadata{
102 Id: "abcd",105 Id: "abcd",
103 Release: "precise",106 Version: "12.04",
104 Arch: "amd64",107 Arch: "amd64",
105 },108 },
106}109}
107110
108func (s *marshalSuite) TestMarshalIndex(c *gc.C) {111func (s *marshalSuite) TestMarshalIndex(c *gc.C) {
109 cloudSpec := &simplestreams.CloudSpec{Region: "region", Endpoint: "endpoint"}112 cloudSpec := []simplestreams.CloudSpec{{Region: "region", Endpoint: "endpoint"}}
110 index, err := imagemetadata.MarshalImageMetadataIndexJSON(imageMetadataForTesting, cloudSpec, time.Unix(0, 0).UTC())113 index, err := imagemetadata.MarshalImageMetadataIndexJSON(imageMetadataForTesting, cloudSpec, time.Unix(0, 0).UTC())
111 c.Assert(err, gc.IsNil)114 c.Assert(err, gc.IsNil)
112 c.Assert(string(index), gc.Equals, expectedIndex)115 c.Assert(string(index), gc.Equals, expectedIndex)
@@ -119,7 +122,7 @@
119}122}
120123
121func (s *marshalSuite) TestMarshal(c *gc.C) {124func (s *marshalSuite) TestMarshal(c *gc.C) {
122 cloudSpec := &simplestreams.CloudSpec{Region: "region", Endpoint: "endpoint"}125 cloudSpec := []simplestreams.CloudSpec{{Region: "region", Endpoint: "endpoint"}}
123 index, products, err := imagemetadata.MarshalImageMetadataJSON(imageMetadataForTesting, cloudSpec, time.Unix(0, 0).UTC())126 index, products, err := imagemetadata.MarshalImageMetadataJSON(imageMetadataForTesting, cloudSpec, time.Unix(0, 0).UTC())
124 c.Assert(err, gc.IsNil)127 c.Assert(err, gc.IsNil)
125 c.Assert(string(index), gc.Equals, expectedIndex)128 c.Assert(string(index), gc.Equals, expectedIndex)
126129
=== modified file 'environs/imagemetadata/simplestreams.go'
--- environs/imagemetadata/simplestreams.go 2013-10-17 04:53:59 +0000
+++ environs/imagemetadata/simplestreams.go 2013-10-17 05:10:43 +0000
@@ -88,9 +88,11 @@
88}88}
8989
90func NewImageConstraint(params simplestreams.LookupParams) *ImageConstraint {90func NewImageConstraint(params simplestreams.LookupParams) *ImageConstraint {
91 if len(params.Series) != 1 {91 if len(params.Series) == 0 {
92 // This can only happen as a result of a coding error.92 params.Series = simplestreams.SupportedSeries()
93 panic(fmt.Sprintf("image constraint requires a single series, got %v", params.Series))93 }
94 if len(params.Arches) == 0 {
95 params.Arches = []string{"amd64", "i386", "arm"}
94 }96 }
95 return &ImageConstraint{LookupParams: params}97 return &ImageConstraint{LookupParams: params}
96}98}
@@ -101,13 +103,18 @@
101 if stream != "" {103 if stream != "" {
102 stream = "." + stream104 stream = "." + stream
103 }105 }
104 version, err := simplestreams.SeriesVersion(ic.Series[0])106
105 if err != nil {107 nrArches := len(ic.Arches)
106 return nil, err108 nrSeries := len(ic.Series)
107 }109 ids := make([]string, nrArches*nrSeries)
108 ids := make([]string, len(ic.Arches))
109 for i, arch := range ic.Arches {110 for i, arch := range ic.Arches {
110 ids[i] = fmt.Sprintf("com.ubuntu.cloud%s:server:%s:%s", stream, version, arch)111 for j, series := range ic.Series {
112 version, err := simplestreams.SeriesVersion(series)
113 if err != nil {
114 return nil, err
115 }
116 ids[j*nrArches+i] = fmt.Sprintf("com.ubuntu.cloud%s:server:%s:%s", stream, version, arch)
117 }
111 }118 }
112 return ids, nil119 return ids, nil
113}120}
@@ -118,18 +125,18 @@
118 Storage string `json:"root_store,omitempty"`125 Storage string `json:"root_store,omitempty"`
119 VType string `json:"virt,omitempty"`126 VType string `json:"virt,omitempty"`
120 Arch string `json:"arch,omitempty"`127 Arch string `json:"arch,omitempty"`
121 Release string `json:"-"`128 Version string `json:"version,omitempty"`
122 RegionAlias string `json:"crsn,omitempty"`129 RegionAlias string `json:"crsn,omitempty"`
123 RegionName string `json:"region,omitempty"`130 RegionName string `json:"region,omitempty"`
124 Endpoint string `json:"endpoint,omitempty"`131 Endpoint string `json:"endpoint,omitempty"`
125}132}
126133
127func (t *ImageMetadata) productId() (string, error) {134func (im *ImageMetadata) String() string {
128 seriesVersion, err := simplestreams.SeriesVersion(t.Release)135 return fmt.Sprintf("%#v", im)
129 if err != nil {136}
130 return "", err137
131 }138func (im *ImageMetadata) productId() string {
132 return fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", seriesVersion, t.Arch), nil139 return fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", im.Version, im.Arch)
133}140}
134141
135// Fetch returns a list of images for the specified cloud matching the constraint.142// Fetch returns a list of images for the specified cloud matching the constraint.
@@ -157,6 +164,8 @@
157type imageKey struct {164type imageKey struct {
158 vtype string165 vtype string
159 arch string166 arch string
167 version string
168 region string
160 storage string169 storage string
161}170}
162171
@@ -168,14 +177,14 @@
168 imagesMap := make(map[imageKey]*ImageMetadata, len(matchingImages))177 imagesMap := make(map[imageKey]*ImageMetadata, len(matchingImages))
169 for _, val := range matchingImages {178 for _, val := range matchingImages {
170 im := val.(*ImageMetadata)179 im := val.(*ImageMetadata)
171 imagesMap[imageKey{im.VType, im.Arch, im.Storage}] = im180 imagesMap[imageKey{im.VType, im.Arch, im.Version, im.RegionName, im.Storage}] = im
172 }181 }
173 for _, val := range images {182 for _, val := range images {
174 im := val.(*ImageMetadata)183 im := val.(*ImageMetadata)
175 if cons.Params().Region != im.RegionName {184 if cons != nil && cons.Params().Region != "" && cons.Params().Region != im.RegionName {
176 continue185 continue
177 }186 }
178 if _, ok := imagesMap[imageKey{im.VType, im.Arch, im.Storage}]; !ok {187 if _, ok := imagesMap[imageKey{im.VType, im.Arch, im.Version, im.RegionName, im.Storage}]; !ok {
179 matchingImages = append(matchingImages, im)188 matchingImages = append(matchingImages, im)
180 }189 }
181 }190 }
182191
=== modified file 'environs/imagemetadata/simplestreams_test.go'
--- environs/imagemetadata/simplestreams_test.go 2013-10-10 11:40:54 +0000
+++ environs/imagemetadata/simplestreams_test.go 2013-10-17 05:10:43 +0000
@@ -107,91 +107,138 @@
107}107}
108108
109var fetchTests = []struct {109var fetchTests = []struct {
110 region string110 region string
111 series string111 version string
112 arches []string112 arches []string
113 images []*imagemetadata.ImageMetadata113 images []*imagemetadata.ImageMetadata
114}{114}{
115 {115 {
116 region: "us-east-1",116 region: "us-east-1",
117 series: "precise",117 version: "12.04",
118 arches: []string{"amd64", "arm"},118 arches: []string{"amd64", "arm"},
119 images: []*imagemetadata.ImageMetadata{119 images: []*imagemetadata.ImageMetadata{
120 {120 {
121 Id: "ami-442ea674",121 Id: "ami-442ea674",
122 VType: "hvm",122 VType: "hvm",
123 Arch: "amd64",123 Arch: "amd64",
124 RegionName: "us-east-1",124 RegionName: "us-east-1",
125 Endpoint: "https://ec2.us-east-1.amazonaws.com",125 Endpoint: "https://ec2.us-east-1.amazonaws.com",
126 Storage: "ebs",126 Storage: "ebs",
127 },127 },
128 {128 {
129 Id: "ami-442ea684",129 Id: "ami-442ea684",
130 VType: "pv",130 VType: "pv",
131 Arch: "amd64",131 Arch: "amd64",
132 RegionName: "us-east-1",132 RegionName: "us-east-1",
133 Endpoint: "https://ec2.us-east-1.amazonaws.com",133 Endpoint: "https://ec2.us-east-1.amazonaws.com",
134 Storage: "instance",134 Storage: "instance",
135 },135 },
136 {136 {
137 Id: "ami-442ea699",137 Id: "ami-442ea699",
138 VType: "pv",138 VType: "pv",
139 Arch: "arm",139 Arch: "arm",
140 RegionName: "us-east-1",140 RegionName: "us-east-1",
141 Endpoint: "https://ec2.us-east-1.amazonaws.com",141 Endpoint: "https://ec2.us-east-1.amazonaws.com",
142 Storage: "ebs",142 Storage: "ebs",
143 },143 },
144 },144 },
145 },145 },
146 {146 {
147 region: "us-east-1",147 region: "us-east-1",
148 series: "precise",148 version: "12.04",
149 arches: []string{"amd64"},149 arches: []string{"amd64"},
150 images: []*imagemetadata.ImageMetadata{150 images: []*imagemetadata.ImageMetadata{
151 {151 {
152 Id: "ami-442ea674",152 Id: "ami-442ea674",
153 VType: "hvm",153 VType: "hvm",
154 Arch: "amd64",154 Arch: "amd64",
155 RegionName: "us-east-1",155 RegionName: "us-east-1",
156 Endpoint: "https://ec2.us-east-1.amazonaws.com",156 Endpoint: "https://ec2.us-east-1.amazonaws.com",
157 Storage: "ebs",157 Storage: "ebs",
158 },158 },
159 {159 {
160 Id: "ami-442ea684",160 Id: "ami-442ea684",
161 VType: "pv",161 VType: "pv",
162 Arch: "amd64",162 Arch: "amd64",
163 RegionName: "us-east-1",163 RegionName: "us-east-1",
164 Endpoint: "https://ec2.us-east-1.amazonaws.com",164 Endpoint: "https://ec2.us-east-1.amazonaws.com",
165 Storage: "instance",165 Storage: "instance",
166 },166 },
167 },167 },
168 },168 },
169 {169 {
170 region: "us-east-1",170 region: "us-east-1",
171 series: "precise",171 version: "12.04",
172 arches: []string{"arm"},172 arches: []string{"arm"},
173 images: []*imagemetadata.ImageMetadata{173 images: []*imagemetadata.ImageMetadata{
174 {174 {
175 Id: "ami-442ea699",175 Id: "ami-442ea699",
176 VType: "pv",176 VType: "pv",
177 Arch: "arm",177 Arch: "arm",
178 RegionName: "us-east-1",178 RegionName: "us-east-1",
179 Endpoint: "https://ec2.us-east-1.amazonaws.com",179 Endpoint: "https://ec2.us-east-1.amazonaws.com",
180 Storage: "ebs",180 Storage: "ebs",
181 },181 },
182 },182 },
183 },183 },
184 {184 {
185 region: "us-east-1",185 region: "us-east-1",
186 series: "precise",186 version: "12.04",
187 arches: []string{"amd64"},187 arches: []string{"amd64"},
188 images: []*imagemetadata.ImageMetadata{188 images: []*imagemetadata.ImageMetadata{
189 {189 {
190 Id: "ami-442ea674",190 Id: "ami-442ea674",
191 VType: "hvm",191 VType: "hvm",
192 Arch: "amd64",192 Arch: "amd64",
193 RegionName: "us-east-1",193 RegionName: "us-east-1",
194 Endpoint: "https://ec2.us-east-1.amazonaws.com",194 Endpoint: "https://ec2.us-east-1.amazonaws.com",
195 Storage: "ebs",
196 },
197 {
198 Id: "ami-442ea684",
199 VType: "pv",
200 Arch: "amd64",
201 RegionName: "us-east-1",
202 Endpoint: "https://ec2.us-east-1.amazonaws.com",
203 Storage: "instance",
204 },
205 },
206 },
207 {
208 version: "12.04",
209 arches: []string{"amd64"},
210 images: []*imagemetadata.ImageMetadata{
211 {
212 Id: "ami-26745463",
213 VType: "pv",
214 Arch: "amd64",
215 RegionName: "au-east-2",
216 Endpoint: "https://somewhere-else",
217 Storage: "ebs",
218 },
219 {
220 Id: "ami-442ea674",
221 VType: "hvm",
222 Arch: "amd64",
223 RegionName: "us-east-1",
224 Endpoint: "https://ec2.us-east-1.amazonaws.com",
225 Storage: "ebs",
226 },
227 {
228 Id: "ami-442ea675",
229 VType: "hvm",
230 Arch: "amd64",
231 RegionAlias: "uswest3",
232 RegionName: "us-west-3",
233 Endpoint: "https://ec2.us-west-3.amazonaws.com",
234 Storage: "ebs",
235 },
236 {
237 Id: "ami-26745464",
238 VType: "pv",
239 Arch: "amd64",
240 RegionName: "au-east-1",
241 Endpoint: "https://somewhere",
195 Storage: "ebs",242 Storage: "ebs",
196 },243 },
197 {244 {
@@ -209,8 +256,12 @@
209func (s *simplestreamsSuite) TestFetch(c *gc.C) {256func (s *simplestreamsSuite) TestFetch(c *gc.C) {
210 for i, t := range fetchTests {257 for i, t := range fetchTests {
211 c.Logf("test %d", i)258 c.Logf("test %d", i)
259 cloudSpec := simplestreams.CloudSpec{t.region, "https://ec2.us-east-1.amazonaws.com"}
260 if t.region == "" {
261 cloudSpec = simplestreams.EmptyCloudSpec
262 }
212 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{263 imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
213 CloudSpec: simplestreams.CloudSpec{t.region, "https://ec2.us-east-1.amazonaws.com"},264 CloudSpec: cloudSpec,
214 Series: []string{"precise"},265 Series: []string{"precise"},
215 Arches: t.arches,266 Arches: t.arches,
216 })267 })
@@ -218,6 +269,9 @@
218 if !c.Check(err, gc.IsNil) {269 if !c.Check(err, gc.IsNil) {
219 continue270 continue
220 }271 }
272 for _, testImage := range t.images {
273 testImage.Version = t.version
274 }
221 c.Check(images, gc.DeepEquals, t.images)275 c.Check(images, gc.DeepEquals, t.images)
222 }276 }
223}277}
224278
=== modified file 'environs/imagemetadata/validation_test.go'
--- environs/imagemetadata/validation_test.go 2013-10-11 06:44:35 +0000
+++ environs/imagemetadata/validation_test.go 2013-10-17 05:10:43 +0000
@@ -20,9 +20,11 @@
20var _ = gc.Suite(&ValidateSuite{})20var _ = gc.Suite(&ValidateSuite{})
2121
22func (s *ValidateSuite) makeLocalMetadata(c *gc.C, id, region, series, endpoint string) error {22func (s *ValidateSuite) makeLocalMetadata(c *gc.C, id, region, series, endpoint string) error {
23 im := imagemetadata.ImageMetadata{23 metadata := []*imagemetadata.ImageMetadata{
24 Id: id,24 {
25 Arch: "amd64",25 Id: id,
26 Arch: "amd64",
27 },
26 }28 }
27 cloudSpec := simplestreams.CloudSpec{29 cloudSpec := simplestreams.CloudSpec{
28 Region: region,30 Region: region,
@@ -30,7 +32,7 @@
30 }32 }
31 targetStorage, err := filestorage.NewFileStorageWriter(s.metadataDir, filestorage.UseDefaultTmpDir)33 targetStorage, err := filestorage.NewFileStorageWriter(s.metadataDir, filestorage.UseDefaultTmpDir)
32 c.Assert(err, gc.IsNil)34 c.Assert(err, gc.IsNil)
33 err = imagemetadata.WriteMetadata(series, &im, &cloudSpec, targetStorage)35 err = imagemetadata.MergeAndWriteMetadata(series, metadata, &cloudSpec, targetStorage)
34 if err != nil {36 if err != nil {
35 return err37 return err
36 }38 }
3739
=== modified file 'environs/simplestreams/simplestreams.go'
--- environs/simplestreams/simplestreams.go 2013-10-17 04:53:59 +0000
+++ environs/simplestreams/simplestreams.go 2013-10-17 05:10:43 +0000
@@ -36,6 +36,9 @@
36 Endpoint string `json:"endpoint"`36 Endpoint string `json:"endpoint"`
37}37}
3838
39// EmptyCloudSpec is used when we want all records regardless of cloud to be loaded.
40var EmptyCloudSpec = CloudSpec{}
41
39// HasRegion is implemented by instances which can provide a region to which they belong.42// HasRegion is implemented by instances which can provide a region to which they belong.
40// A region is defined by region name and endpoint.43// A region is defined by region name and endpoint.
41type HasRegion interface {44type HasRegion interface {
@@ -86,6 +89,9 @@
8689
87// SeriesVersion returns the version number for the specified Ubuntu series.90// SeriesVersion returns the version number for the specified Ubuntu series.
88func SeriesVersion(series string) (string, error) {91func SeriesVersion(series string) (string, error) {
92 if series == "" {
93 panic("cannot pass empty series to SeriesVersion()")
94 }
89 seriesVersionsMutex.Lock()95 seriesVersionsMutex.Lock()
90 defer seriesVersionsMutex.Unlock()96 defer seriesVersionsMutex.Unlock()
91 if vers, ok := seriesVersions[series]; ok {97 if vers, ok := seriesVersions[series]; ok {
@@ -556,13 +562,15 @@
556 if len(candidates) == 0 {562 if len(candidates) == 0 {
557 return "", errors.NotFoundf("index file missing %q data", indexRef.valueParams.DataType)563 return "", errors.NotFoundf("index file missing %q data", indexRef.valueParams.DataType)
558 }564 }
559 // Restrict by cloud spec.565 // Restrict by cloud spec, if required.
560 hasRightCloud := func(metadata *IndexMetadata) bool {566 if cons.Params().CloudSpec != EmptyCloudSpec {
561 return metadata.hasCloud(cons.Params().CloudSpec)567 hasRightCloud := func(metadata *IndexMetadata) bool {
562 }568 return metadata.hasCloud(cons.Params().CloudSpec)
563 candidates = candidates.filter(hasRightCloud)569 }
564 if len(candidates) == 0 {570 candidates = candidates.filter(hasRightCloud)
565 return "", errors.NotFoundf("index file has no data for cloud %v", cons.Params().CloudSpec)571 if len(candidates) == 0 {
572 return "", errors.NotFoundf("index file has no data for cloud %v", cons.Params().CloudSpec)
573 }
566 }574 }
567 // Restrict by product IDs.575 // Restrict by product IDs.
568 hasProduct := func(metadata *IndexMetadata) bool {576 hasProduct := func(metadata *IndexMetadata) bool {
@@ -880,8 +888,8 @@
880888
881// GetCloudMetadataWithFormat loads the entire cloud metadata encoded using the specified format.889// GetCloudMetadataWithFormat loads the entire cloud metadata encoded using the specified format.
882// Exported for testing.890// Exported for testing.
883func (indexRef *IndexReference) GetCloudMetadataWithFormat(ic LookupConstraint, format string, requireSigned bool) (*CloudMetadata, error) {891func (indexRef *IndexReference) GetCloudMetadataWithFormat(cons LookupConstraint, format string, requireSigned bool) (*CloudMetadata, error) {
884 productFilesPath, err := indexRef.GetProductsPath(ic)892 productFilesPath, err := indexRef.GetProductsPath(cons)
885 if err != nil {893 if err != nil {
886 return nil, err894 return nil, err
887 }895 }

Subscribers

People subscribed via source and target branches

to status/vote changes: