Merge lp:~wallyworld/juju-core/improve-image-metadata-command into lp:~go-bot/juju-core/trunk
- improve-image-metadata-command
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Ian Booth |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1987 |
Proposed branch: | lp:~wallyworld/juju-core/improve-image-metadata-command |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
750 lines (+262/-148) 5 files modified
cmd/plugins/juju-metadata/imagemetadata.go (+99/-25) cmd/plugins/juju-metadata/imagemetadata_test.go (+127/-66) cmd/plugins/juju-metadata/validateimagemetadata_test.go (+22/-18) environs/imagemetadata/generate.go (+9/-25) environs/imagemetadata/validation_test.go (+5/-14) |
To merge this branch: | bzr merge lp:~wallyworld/juju-core/improve-image-metadata-command |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+190517@code.launchpad.net |
Commit message
Improve the image metadata generation command
juju metadata generate-image was originally written
as a developer tool to aid simplestreams development.
It now is useful for setting up private clouds so has
been given a facelift to make it more user friendly.
The key feature is it will now use the current environment
to pick up most of what it needs, and the user just has to
supply the image id and architecture (if something different
to amd64 is required).
Description of the change
Improve the image metadata generation command
juju metadata generate-image was originally written
as a developer tool to aid simplestreams development.
It now is useful for setting up private clouds so has
been given a facelift to make it more user friendly.
The key feature is it will now use the current environment
to pick up most of what it needs, and the user just has to
supply the image id and architecture (if something different
to amd64 is required).
Ian Booth (wallyworld) wrote : | # |
Andrew Wilkins (axwalk) wrote : | # |
https:/
File cmd/plugins/
https:/
cmd/plugins/
fmt.Errorf(
environ.Name())
What if the user has specified -s, -r, and -e on the command line? Is
this
really an error then?
Just a thought: perhaps ImageMetadataCo
If environ doesn't implement HasRegion, fall back to
ImageMetadataCo
https:/
File cmd/plugins/
https:/
cmd/plugins/
Move me please
https:/
cmd/plugins/
testbase.
You can just use s.PatchEnvironment here, which wraps
testbase.
Ian Booth (wallyworld) wrote : | # |
Please take a look.
https:/
File cmd/plugins/
https:/
cmd/plugins/
fmt.Errorf(
environ.Name())
On 2013/10/15 07:08:34, axw wrote:
> What if the user has specified -s, -r, and -e on the command line? Is
this
> really an error then?
Hmmm. I thought yes when I wrote this. But I changed it so that it works
more as expected.
> Just a thought: perhaps ImageMetadataCo
HasRegion. If
> environ doesn't implement HasRegion, fall back to
ImageMetadataCo
See my changes - I think it's ok as is.
https:/
File cmd/plugins/
https:/
cmd/plugins/
On 2013/10/15 07:08:34, axw wrote:
> Move me please
Done.
https:/
cmd/plugins/
testbase.
On 2013/10/15 07:08:34, axw wrote:
> You can just use s.PatchEnvironment here, which wraps
testbase.
> & AddCleanup.
Done.
Andrew Wilkins (axwalk) wrote : | # |
On 2013/10/16 02:57:49, wallyworld wrote:
> Please take a look.
https:/
> File cmd/plugins/
https:/
> cmd/plugins/
fmt.Errorf(
> cannot provide region and endpoint", environ.Name())
> On 2013/10/15 07:08:34, axw wrote:
> > What if the user has specified -s, -r, and -e on the command line?
Is this
> > really an error then?
> >
> Hmmm. I thought yes when I wrote this. But I changed it so that it
works more as
> expected.
> > Just a thought: perhaps ImageMetadataCo
HasRegion. If
> > environ doesn't implement HasRegion, fall back to
ImageMetadataCo
> See my changes - I think it's ok as is.
https:/
> File cmd/plugins/
https:/
> cmd/plugins/
> On 2013/10/15 07:08:34, axw wrote:
> > Move me please
> Done.
https:/
> cmd/plugins/
> testbase.
> On 2013/10/15 07:08:34, axw wrote:
> > You can just use s.PatchEnvironment here, which wraps
> testbase.
> > & AddCleanup.
> Done.
Thanks, LGTM
Preview Diff
1 | === modified file 'cmd/plugins/juju-metadata/imagemetadata.go' |
2 | --- cmd/plugins/juju-metadata/imagemetadata.go 2013-08-07 04:37:43 +0000 |
3 | +++ cmd/plugins/juju-metadata/imagemetadata.go 2013-10-16 02:57:31 +0000 |
4 | @@ -5,51 +5,102 @@ |
5 | |
6 | import ( |
7 | "fmt" |
8 | - "strings" |
9 | + "os" |
10 | + "path/filepath" |
11 | |
12 | "launchpad.net/gnuflag" |
13 | - "launchpad.net/goose/identity" |
14 | |
15 | "launchpad.net/juju-core/cmd" |
16 | + "launchpad.net/juju-core/environs" |
17 | "launchpad.net/juju-core/environs/config" |
18 | + "launchpad.net/juju-core/environs/configstore" |
19 | "launchpad.net/juju-core/environs/imagemetadata" |
20 | "launchpad.net/juju-core/environs/simplestreams" |
21 | ) |
22 | |
23 | -// ImageMetadataCommand is used to write out a boilerplate environments.yaml file. |
24 | +// ImageMetadataCommand is used to write out simplestreams image metadata information. |
25 | type ImageMetadataCommand struct { |
26 | - cmd.CommandBase |
27 | - Name string |
28 | - Series string |
29 | - Arch string |
30 | - ImageId string |
31 | - Region string |
32 | - Endpoint string |
33 | + cmd.EnvCommandBase |
34 | + Dir string |
35 | + Series string |
36 | + Arch string |
37 | + ImageId string |
38 | + Region string |
39 | + Endpoint string |
40 | + privateStorage string |
41 | } |
42 | |
43 | +var imageMetadataDoc = ` |
44 | +generate-image creates simplestreams image metadata for the specified cloud. |
45 | + |
46 | +The cloud specification comes from the current Juju environment, as specified in |
47 | +the usual way from either ~/.juju/environments.yaml, the -e option, or JUJU_ENV. |
48 | + |
49 | +Using command arguments, it is possible to override cloud attributes region, endpoint, and series. |
50 | +By default, "amd64" is used for the architecture but this may also be changed. |
51 | +` |
52 | + |
53 | func (c *ImageMetadataCommand) Info() *cmd.Info { |
54 | return &cmd.Info{ |
55 | Name: "generate-image", |
56 | Purpose: "generate simplestreams image metadata", |
57 | + Doc: imageMetadataDoc, |
58 | } |
59 | } |
60 | |
61 | func (c *ImageMetadataCommand) SetFlags(f *gnuflag.FlagSet) { |
62 | - f.StringVar(&c.Series, "s", "precise", "the charm series") |
63 | + c.EnvCommandBase.SetFlags(f) |
64 | + f.StringVar(&c.Series, "s", "", "the charm series") |
65 | f.StringVar(&c.Arch, "a", "amd64", "the image achitecture") |
66 | - f.StringVar(&c.Name, "n", "", "the cloud name, as a prefix for the generated file names") |
67 | + f.StringVar(&c.Dir, "d", "", "the destination directory in which to place the metadata files") |
68 | f.StringVar(&c.ImageId, "i", "", "the image id") |
69 | f.StringVar(&c.Region, "r", "", "the region") |
70 | f.StringVar(&c.Endpoint, "u", "", "the cloud endpoint (for Openstack, this is the Identity Service endpoint)") |
71 | } |
72 | |
73 | func (c *ImageMetadataCommand) Init(args []string) error { |
74 | - cred := identity.CredentialsFromEnv() |
75 | - if c.Region == "" { |
76 | - c.Region = cred.Region |
77 | - } |
78 | - if c.Endpoint == "" { |
79 | - c.Endpoint = cred.URL |
80 | + c.privateStorage = "<private storage name>" |
81 | + var environ environs.Environ |
82 | + if store, err := configstore.Default(); err == nil { |
83 | + if environ, err = environs.PrepareFromName(c.EnvName, store); err == nil { |
84 | + logger.Infof("creating image metadata for environment %q", environ.Name()) |
85 | + // If the user has not specified region and endpoint, try and get it from the environment. |
86 | + if c.Region == "" || c.Endpoint == "" { |
87 | + var cloudSpec simplestreams.CloudSpec |
88 | + if inst, ok := environ.(simplestreams.HasRegion); ok { |
89 | + if cloudSpec, err = inst.Region(); err != nil { |
90 | + return err |
91 | + } |
92 | + } else { |
93 | + return fmt.Errorf("environment %q cannot provide region and endpoint", environ.Name()) |
94 | + } |
95 | + // If only one of region or endpoint is provided, that is a problem. |
96 | + if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") { |
97 | + return fmt.Errorf("cannot generate metadata without a complete cloud configuration") |
98 | + } |
99 | + if c.Region == "" { |
100 | + c.Region = cloudSpec.Region |
101 | + } |
102 | + if c.Endpoint == "" { |
103 | + c.Endpoint = cloudSpec.Endpoint |
104 | + } |
105 | + } |
106 | + cfg := environ.Config() |
107 | + if c.Series == "" { |
108 | + c.Series = cfg.DefaultSeries() |
109 | + } |
110 | + if v, ok := cfg.AllAttrs()["control-bucket"]; ok { |
111 | + c.privateStorage = v.(string) |
112 | + } |
113 | + } else { |
114 | + logger.Warningf("environment %q could not be opened: %v", c.EnvName, err) |
115 | + } |
116 | + } |
117 | + if environ == nil { |
118 | + logger.Infof("no environment found, creating image metadata using user supplied data") |
119 | + } |
120 | + if c.Series == "" { |
121 | + c.Series = config.DefaultSeries |
122 | } |
123 | if c.ImageId == "" { |
124 | return fmt.Errorf("image id must be specified") |
125 | @@ -60,10 +111,36 @@ |
126 | if c.Endpoint == "" { |
127 | return fmt.Errorf("cloud endpoint URL must be specified") |
128 | } |
129 | + if c.Dir == "" { |
130 | + logger.Infof("no destination directory specified, using current directory") |
131 | + var err error |
132 | + if c.Dir, err = os.Getwd(); err != nil { |
133 | + return err |
134 | + } |
135 | + } |
136 | |
137 | return cmd.CheckEmpty(args) |
138 | } |
139 | |
140 | +var helpDoc = ` |
141 | +image metadata files have been written to: |
142 | +%q. |
143 | +For Juju to use this metadata, the files need to be put into the |
144 | +image metadata search path. There are 2 options: |
145 | + |
146 | +1. Use tools-url in $JUJU_HOME/environments.yaml |
147 | +Configure a http server to serve the contents of |
148 | +%q |
149 | +and set the value of tools-url accordingly. |
150 | + |
151 | +2. Upload the contents of |
152 | +%q |
153 | +to your cloud's private storage (for ec2 and openstack). |
154 | +eg for openstack |
155 | +"cd %s; swift upload %s streams/v1/*" |
156 | + |
157 | +` |
158 | + |
159 | func (c *ImageMetadataCommand) Run(context *cmd.Context) error { |
160 | out := context.Stdout |
161 | |
162 | @@ -75,14 +152,11 @@ |
163 | Region: c.Region, |
164 | Endpoint: c.Endpoint, |
165 | } |
166 | - files, err := imagemetadata.Boilerplate(c.Name, c.Series, &im, &cloudSpec) |
167 | + _, err := imagemetadata.GenerateMetadata(c.Series, &im, &cloudSpec, c.Dir) |
168 | if err != nil { |
169 | - return fmt.Errorf("boilerplate image metadata files could not be created: %v", err) |
170 | + return fmt.Errorf("image metadata files could not be created: %v", err) |
171 | } |
172 | - fmt.Fprintf( |
173 | - out, |
174 | - "Boilerplate image metadata files %q have been written to %s.\n", strings.Join(files, ", "), config.JujuHome()) |
175 | - fmt.Fprintf(out, `Copy the files to the path "streams/v1" in your cloud's public bucket.`) |
176 | - fmt.Fprintln(out, "") |
177 | + dest := filepath.Join(c.Dir, "streams", "v1") |
178 | + fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, c.Dir, c.Dir, c.Dir, c.privateStorage)) |
179 | return nil |
180 | } |
181 | |
182 | === modified file 'cmd/plugins/juju-metadata/imagemetadata_test.go' |
183 | --- cmd/plugins/juju-metadata/imagemetadata_test.go 2013-07-30 23:18:16 +0000 |
184 | +++ cmd/plugins/juju-metadata/imagemetadata_test.go 2013-10-16 02:57:31 +0000 |
185 | @@ -9,26 +9,45 @@ |
186 | "fmt" |
187 | "io/ioutil" |
188 | "os" |
189 | + "path/filepath" |
190 | "strings" |
191 | |
192 | gc "launchpad.net/gocheck" |
193 | |
194 | "launchpad.net/juju-core/cmd" |
195 | "launchpad.net/juju-core/testing" |
196 | + jc "launchpad.net/juju-core/testing/checkers" |
197 | + "launchpad.net/juju-core/testing/testbase" |
198 | ) |
199 | |
200 | type ImageMetadataSuite struct { |
201 | + testbase.LoggingSuite |
202 | environ []string |
203 | + home *testing.FakeHome |
204 | + dir string |
205 | } |
206 | |
207 | var _ = gc.Suite(&ImageMetadataSuite{}) |
208 | |
209 | func (s *ImageMetadataSuite) SetUpSuite(c *gc.C) { |
210 | + s.LoggingSuite.SetUpSuite(c) |
211 | s.environ = os.Environ() |
212 | } |
213 | |
214 | func (s *ImageMetadataSuite) SetUpTest(c *gc.C) { |
215 | + s.LoggingSuite.SetUpTest(c) |
216 | os.Clearenv() |
217 | + s.dir = c.MkDir() |
218 | + // Create a fake certificate so azure test environment can be opened. |
219 | + certfile, err := ioutil.TempFile(s.dir, "") |
220 | + c.Assert(err, gc.IsNil) |
221 | + filename := certfile.Name() |
222 | + err = ioutil.WriteFile(filename, []byte("test certificate"), 0644) |
223 | + c.Assert(err, gc.IsNil) |
224 | + envConfig := strings.Replace(metadataTestEnvConfig, "/home/me/azure.pem", filename, -1) |
225 | + s.home = testing.MakeFakeHome(c, envConfig) |
226 | + s.PatchEnvironment("AWS_ACCESS_KEY_ID", "access") |
227 | + s.PatchEnvironment("AWS_SECRET_ACCESS_KEY", "secret") |
228 | } |
229 | |
230 | func (s *ImageMetadataSuite) TearDownTest(c *gc.C) { |
231 | @@ -36,6 +55,8 @@ |
232 | kv := strings.SplitN(envstring, "=", 2) |
233 | os.Setenv(kv[0], kv[1]) |
234 | } |
235 | + s.home.Restore() |
236 | + s.LoggingSuite.TearDownTest(c) |
237 | } |
238 | |
239 | var seriesVersions map[string]string = map[string]string{ |
240 | @@ -43,31 +64,46 @@ |
241 | "raring": "13.04", |
242 | } |
243 | |
244 | -func (*ImageMetadataSuite) assertCommandOutput(c *gc.C, errOut, series, arch, indexFileName, imageFileName string) { |
245 | +type expectedMetadata struct { |
246 | + series string |
247 | + arch string |
248 | + region string |
249 | + endpoint string |
250 | +} |
251 | + |
252 | +func (s *ImageMetadataSuite) assertCommandOutput(c *gc.C, expected expectedMetadata, errOut, indexFileName, imageFileName string) { |
253 | + if expected.region == "" { |
254 | + expected.region = "region" |
255 | + } |
256 | + if expected.endpoint == "" { |
257 | + expected.endpoint = "endpoint" |
258 | + } |
259 | strippedOut := strings.Replace(errOut, "\n", "", -1) |
260 | - c.Check(strippedOut, gc.Matches, `Boilerplate image metadata files.*have been written.*Copy the files.*`) |
261 | - indexpath := testing.HomePath(".juju", indexFileName) |
262 | + c.Check(strippedOut, gc.Matches, `image metadata files have been written to.*`) |
263 | + indexpath := filepath.Join(s.dir, "streams", "v1", indexFileName) |
264 | data, err := ioutil.ReadFile(indexpath) |
265 | c.Assert(err, gc.IsNil) |
266 | + content := string(data) |
267 | var indices interface{} |
268 | err = json.Unmarshal(data, &indices) |
269 | c.Assert(err, gc.IsNil) |
270 | c.Assert(indices.(map[string]interface{})["format"], gc.Equals, "index:1.0") |
271 | - prodId := fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", seriesVersions[series], arch) |
272 | - c.Assert(strings.Contains(string(data), prodId), gc.Equals, true) |
273 | - c.Assert(strings.Contains(string(data), `"region": "region"`), gc.Equals, true) |
274 | - c.Assert(strings.Contains(string(data), `"endpoint": "endpoint"`), gc.Equals, true) |
275 | - c.Assert(strings.Contains(string(data), fmt.Sprintf(`"path": "streams/v1/%s"`, imageFileName)), gc.Equals, true) |
276 | + prodId := fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", seriesVersions[expected.series], expected.arch) |
277 | + c.Assert(strings.Contains(content, prodId), jc.IsTrue) |
278 | + c.Assert(strings.Contains(content, fmt.Sprintf(`"region": %q`, expected.region)), jc.IsTrue) |
279 | + c.Assert(strings.Contains(content, fmt.Sprintf(`"endpoint": %q`, expected.endpoint)), jc.IsTrue) |
280 | + c.Assert(strings.Contains(content, fmt.Sprintf(`"path": "streams/v1/%s"`, imageFileName)), jc.IsTrue) |
281 | |
282 | - imagepath := testing.HomePath(".juju", imageFileName) |
283 | + imagepath := filepath.Join(s.dir, "streams", "v1", imageFileName) |
284 | data, err = ioutil.ReadFile(imagepath) |
285 | c.Assert(err, gc.IsNil) |
286 | + content = string(data) |
287 | var images interface{} |
288 | err = json.Unmarshal(data, &images) |
289 | c.Assert(err, gc.IsNil) |
290 | c.Assert(images.(map[string]interface{})["format"], gc.Equals, "products:1.0") |
291 | - c.Assert(strings.Contains(string(data), prodId), gc.Equals, true) |
292 | - c.Assert(strings.Contains(string(data), `"id": "1234"`), gc.Equals, true) |
293 | + c.Assert(strings.Contains(content, prodId), gc.Equals, true) |
294 | + c.Assert(strings.Contains(content, `"id": "1234"`), gc.Equals, true) |
295 | } |
296 | |
297 | const ( |
298 | @@ -76,71 +112,92 @@ |
299 | ) |
300 | |
301 | func (s *ImageMetadataSuite) TestImageMetadataFilesNoEnv(c *gc.C) { |
302 | - defer testing.MakeEmptyFakeHome(c).Restore() |
303 | - |
304 | - ctx := testing.Context(c) |
305 | - code := cmd.Main( |
306 | - &ImageMetadataCommand{}, ctx, []string{"-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint", "-s", "raring"}) |
307 | - c.Assert(code, gc.Equals, 0) |
308 | - errOut := ctx.Stdout.(*bytes.Buffer).String() |
309 | - s.assertCommandOutput(c, errOut, "raring", "arch", defaultIndexFileName, defaultImageFileName) |
310 | -} |
311 | - |
312 | -func (s *ImageMetadataSuite) TestImageMetadataFilesWithName(c *gc.C) { |
313 | - defer testing.MakeEmptyFakeHome(c).Restore() |
314 | - |
315 | - ctx := testing.Context(c) |
316 | - code := cmd.Main( |
317 | - &ImageMetadataCommand{}, ctx, []string{"-n", "foo", "-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint", "-s", "raring"}) |
318 | - c.Assert(code, gc.Equals, 0) |
319 | - errOut := ctx.Stdout.(*bytes.Buffer).String() |
320 | - s.assertCommandOutput(c, errOut, "raring", "arch", "foo-"+defaultIndexFileName, "foo-"+defaultImageFileName) |
321 | + ctx := testing.Context(c) |
322 | + code := cmd.Main( |
323 | + &ImageMetadataCommand{}, ctx, []string{ |
324 | + "-d", s.dir, "-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint", "-s", "raring"}) |
325 | + c.Assert(code, gc.Equals, 0) |
326 | + errOut := ctx.Stdout.(*bytes.Buffer).String() |
327 | + expected := expectedMetadata{ |
328 | + series: "raring", |
329 | + arch: "arch", |
330 | + } |
331 | + s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName) |
332 | } |
333 | |
334 | func (s *ImageMetadataSuite) TestImageMetadataFilesDefaultArch(c *gc.C) { |
335 | - defer testing.MakeEmptyFakeHome(c).Restore() |
336 | - |
337 | ctx := testing.Context(c) |
338 | code := cmd.Main( |
339 | - &ImageMetadataCommand{}, ctx, []string{"-i", "1234", "-r", "region", "-u", "endpoint", "-s", "raring"}) |
340 | + &ImageMetadataCommand{}, ctx, []string{ |
341 | + "-d", s.dir, "-i", "1234", "-r", "region", "-u", "endpoint", "-s", "raring"}) |
342 | c.Assert(code, gc.Equals, 0) |
343 | errOut := ctx.Stdout.(*bytes.Buffer).String() |
344 | - s.assertCommandOutput(c, errOut, "raring", "amd64", defaultIndexFileName, defaultImageFileName) |
345 | + expected := expectedMetadata{ |
346 | + series: "raring", |
347 | + arch: "amd64", |
348 | + } |
349 | + s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName) |
350 | } |
351 | |
352 | func (s *ImageMetadataSuite) TestImageMetadataFilesDefaultSeries(c *gc.C) { |
353 | - defer testing.MakeEmptyFakeHome(c).Restore() |
354 | - |
355 | - ctx := testing.Context(c) |
356 | - code := cmd.Main( |
357 | - &ImageMetadataCommand{}, ctx, []string{"-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint"}) |
358 | - c.Assert(code, gc.Equals, 0) |
359 | - errOut := ctx.Stdout.(*bytes.Buffer).String() |
360 | - s.assertCommandOutput(c, errOut, "precise", "arch", defaultIndexFileName, defaultImageFileName) |
361 | -} |
362 | - |
363 | -func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvRegion(c *gc.C) { |
364 | - defer testing.MakeEmptyFakeHome(c).Restore() |
365 | - |
366 | - os.Setenv("OS_REGION_NAME", "region") |
367 | - ctx := testing.Context(c) |
368 | - code := cmd.Main( |
369 | - &ImageMetadataCommand{}, ctx, []string{"-i", "1234", "-u", "endpoint"}) |
370 | - c.Assert(code, gc.Equals, 0) |
371 | - errOut := ctx.Stdout.(*bytes.Buffer).String() |
372 | - s.assertCommandOutput(c, errOut, "precise", "amd64", defaultIndexFileName, defaultImageFileName) |
373 | -} |
374 | - |
375 | -func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvEndpoint(c *gc.C) { |
376 | - defer testing.MakeEmptyFakeHome(c).Restore() |
377 | - |
378 | - os.Setenv("OS_AUTH_URL", "endpoint") |
379 | - ctx := testing.Context(c) |
380 | - code := cmd.Main( |
381 | - &ImageMetadataCommand{}, ctx, []string{"-i", "1234", "-r", "region"}) |
382 | - c.Assert(code, gc.Equals, 0) |
383 | - errOut := ctx.Stdout.(*bytes.Buffer).String() |
384 | - s.assertCommandOutput(c, errOut, "precise", "amd64", defaultIndexFileName, defaultImageFileName) |
385 | + ctx := testing.Context(c) |
386 | + code := cmd.Main( |
387 | + &ImageMetadataCommand{}, ctx, []string{ |
388 | + "-d", s.dir, "-i", "1234", "-r", "region", "-a", "arch", "-u", "endpoint"}) |
389 | + c.Assert(code, gc.Equals, 0) |
390 | + errOut := ctx.Stdout.(*bytes.Buffer).String() |
391 | + expected := expectedMetadata{ |
392 | + series: "precise", |
393 | + arch: "arch", |
394 | + } |
395 | + s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName) |
396 | +} |
397 | + |
398 | +func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnv(c *gc.C) { |
399 | + ctx := testing.Context(c) |
400 | + code := cmd.Main( |
401 | + &ImageMetadataCommand{}, ctx, []string{"-d", s.dir, "-e", "ec2", "-i", "1234"}) |
402 | + c.Assert(code, gc.Equals, 0) |
403 | + errOut := ctx.Stdout.(*bytes.Buffer).String() |
404 | + expected := expectedMetadata{ |
405 | + series: "precise", |
406 | + arch: "amd64", |
407 | + region: "us-east-1", |
408 | + endpoint: "https://ec2.us-east-1.amazonaws.com", |
409 | + } |
410 | + s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName) |
411 | +} |
412 | + |
413 | +func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvWithRegionOverride(c *gc.C) { |
414 | + ctx := testing.Context(c) |
415 | + code := cmd.Main( |
416 | + &ImageMetadataCommand{}, ctx, []string{ |
417 | + "-d", s.dir, "-e", "ec2", "-r", "us-west-1", "-u", "https://ec2.us-west-1.amazonaws.com", "-i", "1234"}) |
418 | + c.Assert(code, gc.Equals, 0) |
419 | + errOut := ctx.Stdout.(*bytes.Buffer).String() |
420 | + expected := expectedMetadata{ |
421 | + series: "precise", |
422 | + arch: "amd64", |
423 | + region: "us-west-1", |
424 | + endpoint: "https://ec2.us-west-1.amazonaws.com", |
425 | + } |
426 | + s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName) |
427 | +} |
428 | + |
429 | +func (s *ImageMetadataSuite) TestImageMetadataFilesUsingEnvWithNoHasRegion(c *gc.C) { |
430 | + ctx := testing.Context(c) |
431 | + code := cmd.Main( |
432 | + &ImageMetadataCommand{}, ctx, []string{ |
433 | + "-d", s.dir, "-e", "azure", "-r", "region", "-u", "endpoint", "-i", "1234"}) |
434 | + c.Assert(code, gc.Equals, 0) |
435 | + errOut := ctx.Stdout.(*bytes.Buffer).String() |
436 | + expected := expectedMetadata{ |
437 | + series: "raring", |
438 | + arch: "amd64", |
439 | + region: "region", |
440 | + endpoint: "endpoint", |
441 | + } |
442 | + s.assertCommandOutput(c, expected, errOut, defaultIndexFileName, defaultImageFileName) |
443 | } |
444 | |
445 | type errTestParams struct { |
446 | @@ -160,6 +217,10 @@ |
447 | // Missing endpoint |
448 | args: []string{"-i", "1234", "-u", "endpoint", "-a", "arch", "-s", "precise"}, |
449 | }, |
450 | + { |
451 | + // Missing endpoint/region for environment with no HasRegion interface |
452 | + args: []string{"-i", "1234", "-e", "azure"}, |
453 | + }, |
454 | } |
455 | |
456 | func (s *ImageMetadataSuite) TestImageMetadataBadArgs(c *gc.C) { |
457 | |
458 | === modified file 'cmd/plugins/juju-metadata/validateimagemetadata_test.go' |
459 | --- cmd/plugins/juju-metadata/validateimagemetadata_test.go 2013-10-10 11:40:54 +0000 |
460 | +++ cmd/plugins/juju-metadata/validateimagemetadata_test.go 2013-10-16 02:57:31 +0000 |
461 | @@ -11,7 +11,6 @@ |
462 | gc "launchpad.net/gocheck" |
463 | |
464 | "launchpad.net/juju-core/cmd" |
465 | - "launchpad.net/juju-core/environs/config" |
466 | "launchpad.net/juju-core/environs/imagemetadata" |
467 | "launchpad.net/juju-core/environs/simplestreams" |
468 | coretesting "launchpad.net/juju-core/testing" |
469 | @@ -20,7 +19,8 @@ |
470 | |
471 | type ValidateImageMetadataSuite struct { |
472 | testbase.LoggingSuite |
473 | - home *coretesting.FakeHome |
474 | + home *coretesting.FakeHome |
475 | + metadataDir string |
476 | } |
477 | |
478 | var _ = gc.Suite(&ValidateImageMetadataSuite{}) |
479 | @@ -73,7 +73,7 @@ |
480 | Region: region, |
481 | Endpoint: endpoint, |
482 | } |
483 | - _, err := imagemetadata.MakeBoilerplate("", series, &im, &cloudSpec, false) |
484 | + _, err := imagemetadata.GenerateMetadata(series, &im, &cloudSpec, s.metadataDir) |
485 | if err != nil { |
486 | return err |
487 | } |
488 | @@ -84,13 +84,21 @@ |
489 | environments: |
490 | ec2: |
491 | type: ec2 |
492 | - control-bucket: foo |
493 | default-series: precise |
494 | region: us-east-1 |
495 | + |
496 | + azure: |
497 | + type: azure |
498 | + default-series: raring |
499 | + location: US West |
500 | + management-subscription-id: foo |
501 | + storage-account-name: bar |
502 | + management-certificate-path: /home/me/azure.pem |
503 | ` |
504 | |
505 | func (s *ValidateImageMetadataSuite) SetUpTest(c *gc.C) { |
506 | s.LoggingSuite.SetUpTest(c) |
507 | + s.metadataDir = c.MkDir() |
508 | s.home = coretesting.MakeFakeHome(c, metadataTestEnvConfig) |
509 | restore := testbase.PatchEnvironment("AWS_ACCESS_KEY_ID", "access") |
510 | s.AddCleanup(func(*gc.C) { restore() }) |
511 | @@ -115,9 +123,8 @@ |
512 | func (s *ValidateImageMetadataSuite) TestEc2LocalMetadataUsingEnvironment(c *gc.C) { |
513 | s.setupEc2LocalMetadata(c, "us-east-1") |
514 | ctx := coretesting.Context(c) |
515 | - metadataDir := config.JujuHomePath("") |
516 | code := cmd.Main( |
517 | - &ValidateImageMetadataCommand{}, ctx, []string{"-e", "ec2", "-d", metadataDir}, |
518 | + &ValidateImageMetadataCommand{}, ctx, []string{"-e", "ec2", "-d", s.metadataDir}, |
519 | ) |
520 | c.Assert(code, gc.Equals, 0) |
521 | errOut := ctx.Stdout.(*bytes.Buffer).String() |
522 | @@ -128,11 +135,12 @@ |
523 | func (s *ValidateImageMetadataSuite) TestEc2LocalMetadataUsingIncompleteEnvironment(c *gc.C) { |
524 | testbase.PatchEnvironment("AWS_ACCESS_KEY_ID", "") |
525 | testbase.PatchEnvironment("AWS_SECRET_ACCESS_KEY", "") |
526 | + testbase.PatchEnvironment("EC2_ACCESS_KEY", "") |
527 | + testbase.PatchEnvironment("EC2_SECRET_KEY", "") |
528 | s.setupEc2LocalMetadata(c, "us-east-1") |
529 | ctx := coretesting.Context(c) |
530 | - metadataDir := config.JujuHomePath("") |
531 | code := cmd.Main( |
532 | - &ValidateImageMetadataCommand{}, ctx, []string{"-e", "ec2", "-d", metadataDir}, |
533 | + &ValidateImageMetadataCommand{}, ctx, []string{"-e", "ec2", "-d", s.metadataDir}, |
534 | ) |
535 | c.Assert(code, gc.Equals, 1) |
536 | errOut := ctx.Stderr.(*bytes.Buffer).String() |
537 | @@ -143,11 +151,10 @@ |
538 | func (s *ValidateImageMetadataSuite) TestEc2LocalMetadataWithManualParams(c *gc.C) { |
539 | s.setupEc2LocalMetadata(c, "us-west-1") |
540 | ctx := coretesting.Context(c) |
541 | - metadataDir := config.JujuHomePath("") |
542 | code := cmd.Main( |
543 | &ValidateImageMetadataCommand{}, ctx, []string{ |
544 | "-p", "ec2", "-s", "precise", "-r", "us-west-1", |
545 | - "-u", "https://ec2.us-west-1.amazonaws.com", "-d", metadataDir}, |
546 | + "-u", "https://ec2.us-west-1.amazonaws.com", "-d", s.metadataDir}, |
547 | ) |
548 | c.Assert(code, gc.Equals, 0) |
549 | errOut := ctx.Stdout.(*bytes.Buffer).String() |
550 | @@ -158,17 +165,16 @@ |
551 | func (s *ValidateImageMetadataSuite) TestEc2LocalMetadataNoMatch(c *gc.C) { |
552 | s.setupEc2LocalMetadata(c, "us-east-1") |
553 | ctx := coretesting.Context(c) |
554 | - metadataDir := config.JujuHomePath("") |
555 | code := cmd.Main( |
556 | &ValidateImageMetadataCommand{}, ctx, []string{ |
557 | "-p", "ec2", "-s", "raring", "-r", "us-west-1", |
558 | - "-u", "https://ec2.us-west-1.amazonaws.com", "-d", metadataDir}, |
559 | + "-u", "https://ec2.us-west-1.amazonaws.com", "-d", s.metadataDir}, |
560 | ) |
561 | c.Assert(code, gc.Equals, 1) |
562 | code = cmd.Main( |
563 | &ValidateImageMetadataCommand{}, ctx, []string{ |
564 | "-p", "ec2", "-s", "precise", "-r", "region", |
565 | - "-u", "https://ec2.region.amazonaws.com", "-d", metadataDir}, |
566 | + "-u", "https://ec2.region.amazonaws.com", "-d", s.metadataDir}, |
567 | ) |
568 | c.Assert(code, gc.Equals, 1) |
569 | } |
570 | @@ -176,11 +182,10 @@ |
571 | func (s *ValidateImageMetadataSuite) TestOpenstackLocalMetadataWithManualParams(c *gc.C) { |
572 | s.makeLocalMetadata(c, "1234", "region-2", "raring", "some-auth-url") |
573 | ctx := coretesting.Context(c) |
574 | - metadataDir := config.JujuHomePath("") |
575 | code := cmd.Main( |
576 | &ValidateImageMetadataCommand{}, ctx, []string{ |
577 | "-p", "openstack", "-s", "raring", "-r", "region-2", |
578 | - "-u", "some-auth-url", "-d", metadataDir}, |
579 | + "-u", "some-auth-url", "-d", s.metadataDir}, |
580 | ) |
581 | c.Assert(code, gc.Equals, 0) |
582 | errOut := ctx.Stdout.(*bytes.Buffer).String() |
583 | @@ -191,17 +196,16 @@ |
584 | func (s *ValidateImageMetadataSuite) TestOpenstackLocalMetadataNoMatch(c *gc.C) { |
585 | s.makeLocalMetadata(c, "1234", "region-2", "raring", "some-auth-url") |
586 | ctx := coretesting.Context(c) |
587 | - metadataDir := config.JujuHomePath("") |
588 | code := cmd.Main( |
589 | &ValidateImageMetadataCommand{}, ctx, []string{ |
590 | "-p", "openstack", "-s", "precise", "-r", "region-2", |
591 | - "-u", "some-auth-url", "-d", metadataDir}, |
592 | + "-u", "some-auth-url", "-d", s.metadataDir}, |
593 | ) |
594 | c.Assert(code, gc.Equals, 1) |
595 | code = cmd.Main( |
596 | &ValidateImageMetadataCommand{}, ctx, []string{ |
597 | "-p", "openstack", "-s", "raring", "-r", "region-3", |
598 | - "-u", "some-auth-url", "-d", metadataDir}, |
599 | + "-u", "some-auth-url", "-d", s.metadataDir}, |
600 | ) |
601 | c.Assert(code, gc.Equals, 1) |
602 | } |
603 | |
604 | === renamed file 'environs/imagemetadata/boilerplate.go' => 'environs/imagemetadata/generate.go' |
605 | --- environs/imagemetadata/boilerplate.go 2013-08-07 04:34:02 +0000 |
606 | +++ environs/imagemetadata/generate.go 2013-10-16 02:57:31 +0000 |
607 | @@ -12,38 +12,25 @@ |
608 | "text/template" |
609 | "time" |
610 | |
611 | - "launchpad.net/juju-core/environs/config" |
612 | "launchpad.net/juju-core/environs/simplestreams" |
613 | ) |
614 | |
615 | const ( |
616 | defaultIndexFileName = "index.json" |
617 | defaultImageFileName = "imagemetadata.json" |
618 | - streamsDir = "streams/v1" |
619 | ) |
620 | |
621 | -// Boilerplate generates some basic simplestreams metadata using the specified cloud and image details. |
622 | -// If name is non-empty, it will be used as a prefix for the names of the generated index and image files. |
623 | -func Boilerplate(name, series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec) ([]string, error) { |
624 | - return MakeBoilerplate(name, series, im, cloudSpec, true) |
625 | -} |
626 | - |
627 | -// MakeBoilerplate exists so it can be called by tests. See Boilerplate above. It provides an option to retain |
628 | -// the streams directories when writing the generated metadata files. |
629 | -func MakeBoilerplate(name, series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec, flattenPath bool) ([]string, error) { |
630 | +// GenerateMetadata generates some basic simplestreams metadata using the specified cloud and image details. |
631 | +func GenerateMetadata(series string, im *ImageMetadata, cloudSpec *simplestreams.CloudSpec, dest string) ([]string, error) { |
632 | indexFileName := defaultIndexFileName |
633 | imageFileName := defaultImageFileName |
634 | - if name != "" { |
635 | - indexFileName = fmt.Sprintf("%s-%s", name, indexFileName) |
636 | - imageFileName = fmt.Sprintf("%s-%s", name, imageFileName) |
637 | - } |
638 | now := time.Now() |
639 | imparams := imageMetadataParams{ |
640 | Id: im.Id, |
641 | Arch: im.Arch, |
642 | Region: cloudSpec.Region, |
643 | URL: cloudSpec.Endpoint, |
644 | - Path: streamsDir, |
645 | + Path: "streams/v1", |
646 | ImageFileName: imageFileName, |
647 | Updated: now.Format(time.RFC1123Z), |
648 | VersionKey: now.Format("20060102"), |
649 | @@ -55,14 +42,12 @@ |
650 | return nil, fmt.Errorf("invalid series %q", series) |
651 | } |
652 | |
653 | - if !flattenPath { |
654 | - streamsPath := config.JujuHomePath(streamsDir) |
655 | - if err = os.MkdirAll(streamsPath, 0755); err != nil { |
656 | - return nil, err |
657 | - } |
658 | - indexFileName = filepath.Join(streamsDir, indexFileName) |
659 | - imageFileName = filepath.Join(streamsDir, imageFileName) |
660 | + streamsPath := filepath.Join(dest, "streams", "v1") |
661 | + if err = os.MkdirAll(streamsPath, 0755); err != nil { |
662 | + return nil, err |
663 | } |
664 | + indexFileName = filepath.Join(streamsPath, indexFileName) |
665 | + imageFileName = filepath.Join(streamsPath, imageFileName) |
666 | err = writeJsonFile(imparams, indexFileName, indexBoilerplate) |
667 | if err != nil { |
668 | return nil, err |
669 | @@ -94,8 +79,7 @@ |
670 | panic(fmt.Errorf("cannot generate %s metdata: %v", filename, err)) |
671 | } |
672 | data := metadata.Bytes() |
673 | - path := config.JujuHomePath(filename) |
674 | - if err := ioutil.WriteFile(path, data, 0666); err != nil { |
675 | + if err := ioutil.WriteFile(filename, data, 0666); err != nil { |
676 | return err |
677 | } |
678 | return nil |
679 | |
680 | === modified file 'environs/imagemetadata/validation_test.go' |
681 | --- environs/imagemetadata/validation_test.go 2013-09-24 08:01:52 +0000 |
682 | +++ environs/imagemetadata/validation_test.go 2013-10-16 02:57:31 +0000 |
683 | @@ -6,16 +6,14 @@ |
684 | import ( |
685 | gc "launchpad.net/gocheck" |
686 | |
687 | - "launchpad.net/juju-core/environs/config" |
688 | "launchpad.net/juju-core/environs/imagemetadata" |
689 | "launchpad.net/juju-core/environs/simplestreams" |
690 | - coretesting "launchpad.net/juju-core/testing" |
691 | "launchpad.net/juju-core/testing/testbase" |
692 | ) |
693 | |
694 | type ValidateSuite struct { |
695 | testbase.LoggingSuite |
696 | - home *coretesting.FakeHome |
697 | + metadataDir string |
698 | } |
699 | |
700 | var _ = gc.Suite(&ValidateSuite{}) |
701 | @@ -29,7 +27,7 @@ |
702 | Region: region, |
703 | Endpoint: endpoint, |
704 | } |
705 | - _, err := imagemetadata.MakeBoilerplate("", series, &im, &cloudSpec, false) |
706 | + _, err := imagemetadata.GenerateMetadata(series, &im, &cloudSpec, s.metadataDir) |
707 | if err != nil { |
708 | return err |
709 | } |
710 | @@ -38,23 +36,17 @@ |
711 | |
712 | func (s *ValidateSuite) SetUpTest(c *gc.C) { |
713 | s.LoggingSuite.SetUpTest(c) |
714 | - s.home = coretesting.MakeEmptyFakeHome(c) |
715 | -} |
716 | - |
717 | -func (s *ValidateSuite) TearDownTest(c *gc.C) { |
718 | - s.home.Restore() |
719 | - s.LoggingSuite.TearDownTest(c) |
720 | + s.metadataDir = c.MkDir() |
721 | } |
722 | |
723 | func (s *ValidateSuite) TestMatch(c *gc.C) { |
724 | s.makeLocalMetadata(c, "1234", "region-2", "raring", "some-auth-url") |
725 | - metadataDir := config.JujuHomePath("") |
726 | params := &simplestreams.MetadataLookupParams{ |
727 | Region: "region-2", |
728 | Series: "raring", |
729 | Architectures: []string{"amd64"}, |
730 | Endpoint: "some-auth-url", |
731 | - Sources: []simplestreams.DataSource{simplestreams.NewURLDataSource("file://"+metadataDir, simplestreams.VerifySSLHostnames)}, |
732 | + Sources: []simplestreams.DataSource{simplestreams.NewURLDataSource("file://"+s.metadataDir, simplestreams.VerifySSLHostnames)}, |
733 | } |
734 | imageIds, err := imagemetadata.ValidateImageMetadata(params) |
735 | c.Assert(err, gc.IsNil) |
736 | @@ -63,13 +55,12 @@ |
737 | |
738 | func (s *ValidateSuite) TestNoMatch(c *gc.C) { |
739 | s.makeLocalMetadata(c, "1234", "region-2", "raring", "some-auth-url") |
740 | - metadataDir := config.JujuHomePath("") |
741 | params := &simplestreams.MetadataLookupParams{ |
742 | Region: "region-2", |
743 | Series: "precise", |
744 | Architectures: []string{"amd64"}, |
745 | Endpoint: "some-auth-url", |
746 | - Sources: []simplestreams.DataSource{simplestreams.NewURLDataSource("file://"+metadataDir, simplestreams.VerifySSLHostnames)}, |
747 | + Sources: []simplestreams.DataSource{simplestreams.NewURLDataSource("file://"+s.metadataDir, simplestreams.VerifySSLHostnames)}, |
748 | } |
749 | _, err := imagemetadata.ValidateImageMetadata(params) |
750 | c.Assert(err, gc.Not(gc.IsNil)) |
Reviewers: mp+190517_ code.launchpad. net,
Message:
Please take a look.
Description:
Improve the image metadata generation command
juju metadata generate-image was originally written
as a developer tool to aid simplestreams development.
It now is useful for setting up private clouds so has
been given a facelift to make it more user friendly.
The key feature is it will now use the current environment
to pick up most of what it needs, and the user just has to
supply the image id and architecture (if something different
to amd64 is required).
https:/ /code.launchpad .net/~wallyworl d/juju- core/improve- image-metadata- command/ +merge/ 190517
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/14502059/
Affected files (+202, -133 lines): juju-metadata/ imagemetadata. go juju-metadata/ imagemetadata_ test.go juju-metadata/ validateimageme tadata_ test.go imagemetadata/ generate. go
A [revision details]
M cmd/plugins/
M cmd/plugins/
M cmd/plugins/
M environs/