Merge lp:~axwalk/juju-core/lp1238934-manual-provision-aptrepos into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 2019
Proposed branch: lp:~axwalk/juju-core/lp1238934-manual-provision-aptrepos
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 337 lines (+223/-31)
5 files modified
cloudinit/cloudinit.go (+3/-2)
cloudinit/options.go (+40/-10)
environs/cloudinit/cloudinit.go (+6/-2)
environs/manual/agent.go (+74/-17)
environs/manual/agent_test.go (+100/-0)
To merge this branch: bzr merge lp:~axwalk/juju-core/lp1238934-manual-provision-aptrepos
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+191561@code.launchpad.net

Commit message

environs/manual: process apt sources, bootcmds

This change takes care of processing apt repository
sources and bootcmds from the cloud-init config
and adding appropriate commands into the manual
provisioning script.

In the future, we should work towards using
cloud-init directly on the machine, as discussed
in lp:1238934. This task should be handled in lp:1215777

Fixes #1238934

https://codereview.appspot.com/14775044/

Description of the change

environs/manual: process apt sources, bootcmds

This change takes care of processing apt repository
sources and bootcmds from the cloud-init config
and adding appropriate commands into the manual
provisioning script.

In the future, we should work towards using
cloud-init directly on the machine, as discussed
in lp:1238934. This task should be handled in lp:1215777

Fixes #1238934

https://codereview.appspot.com/14775044/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Reviewers: mp+191561_code.launchpad.net,

Message:
Please take a look.

Description:
environs/manual: process apt sources, bootcmds

This change takes care of processing apt repository
sources and bootcmds from the cloud-init config
and adding appropriate commands into the manual
provisioning script.

The only slightly complicated part to this is
handling of keyids; if a keyid is specified, then
we must fetch the key from keyserver.ubuntu.com
(or some other keyserver, if specified), and then
add it to the apt keyring.

In the future, we should work towards using
cloud-init directly on the machine, as discussed
in lp:1238934. This task should be handled in lp:1215777

Fixes #1238934

https://code.launchpad.net/~axwalk/juju-core/lp1238934-manual-provision-aptrepos/+merge/191561

(do not edit description out of merge proposal)

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

Affected files (+262, -33 lines):
   A [revision details]
   M cloudinit/cloudinit.go
   M cloudinit/options.go
   M environs/cloudinit/cloudinit.go
   M environs/manual/agent.go
   A environs/manual/agent_test.go

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Tim Penhey (thumper) wrote :
Revision history for this message
Go Bot (go-bot) wrote :
Download full text (108.5 KiB)

The attempt to merge lp:~axwalk/juju-core/lp1238934-manual-provision-aptrepos into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 0.710s
ok launchpad.net/juju-core/agent/tools 0.293s
ok launchpad.net/juju-core/bzr 6.901s
ok launchpad.net/juju-core/cert 3.245s
ok launchpad.net/juju-core/charm 0.537s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.029s
ok launchpad.net/juju-core/cmd 0.236s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]

----------------------------------------------------------------------
PANIC: addmachine.go:0: AddMachineSuite.SetUpTest

[LOG] 2.12880 DEBUG juju.environs.configstore Making /tmp/gocheck-7504504064263669287/5/home/ubuntu/.juju/environments
[LOG] 2.21823 DEBUG juju.environs.tools reading v1.* tools
[LOG] 2.21827 INFO juju environs/testing: uploading FAKE tools 1.17.0-precise-amd64
[LOG] 2.21955 DEBUG juju.environs.tools no architecture specified when finding tools, looking for any
[LOG] 2.21957 DEBUG juju.environs.tools no series specified when finding tools, looking for any
[LOG] 2.21963 DEBUG juju.environs.simplestreams fetchData failed for "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.sjson": file "tools/streams/v1/index.sjson" not found not found
[LOG] 2.21965 DEBUG juju.environs.simplestreams cannot load index "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.sjson": invalid URL "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.sjson" not found
[LOG] 2.21970 DEBUG juju.environs.simplestreams fetchData failed for "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.json": file "tools/streams/v1/index.json" not found not found
[LOG] 2.21972 DEBUG juju.environs.simplestreams cannot load index "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.json": invalid URL "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.json" not found
[LOG] 2.22000 INFO juju.environs.tools Writing tools/streams/v1/index.json
[LOG] 2.22015 INFO juju.environs.tools Writing tools/streams/v1/com.ubuntu.juju:released:tools.json
[LOG] 2.22020 INFO juju.environs.boostrap bootstrapping environment "dummyenv"
[LOG] 2.22023 INFO juju.environs.tools reading tools with major.minor version 1.17
[LOG] 2.22025 INFO juju.environs.tools filtering tools by version: 1.17.0
[LOG] 2.22026 INFO juju.environs.tools filtering tools by series: precise
[LOG] 2.22029 DEBUG juju.environs.tools no architecture specified when finding tools, looking for any
[LOG] 2.22034 DEBUG juju.environs.simplestreams fetchData failed for "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.sjson": file "tools/streams/v1/index.sjson" not found not found
[LOG] 2.22036 DEBUG juju.environs.simplestreams cannot load index "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.sjson": invalid URL "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/index.sjson" not found
[LOG] 2.22046 DEBUG juju.environs.simplestreams fetchData failed for "http://127.0.0.1:58320/dummyenv/private/tools/streams/v1/mirr...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/cloudinit.go'
2--- cloudinit/cloudinit.go 2013-10-23 23:51:56 +0000
3+++ cloudinit/cloudinit.go 2013-11-01 04:21:16 +0000
4@@ -37,8 +37,9 @@
5 }
6 }
7
8-// source is a Source and a Key
9-type source struct {
10+// AptSource is an apt(8) source, comprising a source location,
11+// with an optional Key.
12+type AptSource struct {
13 Source string `yaml:"source"`
14 Key string `yaml:"key,omitempty"`
15 }
16
17=== modified file 'cloudinit/options.go'
18--- cloudinit/options.go 2013-10-23 22:17:14 +0000
19+++ cloudinit/options.go 2013-11-01 04:21:16 +0000
20@@ -37,6 +37,13 @@
21 cfg.set("apt_update", yes, yes)
22 }
23
24+// AptUpdate returns the value set by SetAptUpdate, or
25+// false if no call to SetAptUpdate has been made.
26+func (cfg *Config) AptUpdate() bool {
27+ update, _ := cfg.attrs["apt_update"].(bool)
28+ return update
29+}
30+
31 // SetAptProxy sets the URL to be used as the apt
32 // proxy.
33 func (cfg *Config) SetAptProxy(url string) {
34@@ -60,14 +67,20 @@
35 // AddAptSource adds an apt source. The key holds the
36 // public key of the source, in the form expected by apt-key(8).
37 func (cfg *Config) AddAptSource(name, key string) {
38- src, _ := cfg.attrs["apt_sources"].([]*source)
39+ src, _ := cfg.attrs["apt_sources"].([]*AptSource)
40 cfg.attrs["apt_sources"] = append(src,
41- &source{
42+ &AptSource{
43 Source: name,
44 Key: key,
45 })
46 }
47
48+// AptSources returns the apt sources added with AddAptSource.
49+func (cfg *Config) AptSources() []*AptSource {
50+ srcs, _ := cfg.attrs["apt_sources"].([]*AptSource)
51+ return srcs
52+}
53+
54 // SetDebconfSelections provides preseeded debconf answers
55 // for the boot process. The given answers will be used as input
56 // to debconf-set-selections(1).
57@@ -98,14 +111,11 @@
58 return cmds
59 }
60
61-// RunCmds returns a list of commands that will be
62-// run at first boot.
63-//
64-// Each element in the resultant slice is either a
65-// string or []string, corresponding to how the command
66-// was added.
67-func (cfg *Config) RunCmds() []interface{} {
68- cmds := cfg.getCmds("runcmd")
69+// getCmdStrings returns a slice of interface{}, where
70+// each interface's dynamic value is either a string
71+// or slice of strings.
72+func (cfg *Config) getCmdStrings(kind string) []interface{} {
73+ cmds := cfg.getCmds(kind)
74 result := make([]interface{}, len(cmds))
75 for i, cmd := range cmds {
76 if cmd.args != nil {
77@@ -117,6 +127,26 @@
78 return result
79 }
80
81+// BootCmds returns a list of commands added with
82+// AddBootCmd*.
83+//
84+// Each element in the resultant slice is either a
85+// string or []string, corresponding to how the command
86+// was added.
87+func (cfg *Config) BootCmds() []interface{} {
88+ return cfg.getCmdStrings("bootcmd")
89+}
90+
91+// RunCmds returns a list of commands that will be
92+// run at first boot.
93+//
94+// Each element in the resultant slice is either a
95+// string or []string, corresponding to how the command
96+// was added.
97+func (cfg *Config) RunCmds() []interface{} {
98+ return cfg.getCmdStrings("runcmd")
99+}
100+
101 // AddRunCmd adds a command to be executed
102 // at first boot. The command will be run
103 // by the shell with any metacharacters retaining
104
105=== modified file 'environs/cloudinit/cloudinit.go'
106--- environs/cloudinit/cloudinit.go 2013-10-23 22:17:14 +0000
107+++ environs/cloudinit/cloudinit.go 2013-11-01 04:21:16 +0000
108@@ -197,8 +197,12 @@
109 cfg.MaybeAddCloudArchiveCloudTools(c)
110
111 if cfg.StateServer {
112- // disable the default mongodb installed by the mongodb-server package.
113- c.AddBootCmd(`echo ENABLE_MONGODB="no" > /etc/default/mongodb`)
114+ // Disable the default mongodb installed by the mongodb-server package.
115+ // Only do this if the file doesn't exist already, so users can run
116+ // their own mongodb server if they wish to.
117+ c.AddBootCmd(
118+ `[ -f /etc/default/mongodb ] ||
119+ (echo ENABLE_MONGODB="no" > /etc/default/mongodb)`)
120
121 if cfg.NeedMongoPPA() {
122 const key = "" // key is loaded from PPA
123
124=== modified file 'environs/manual/agent.go'
125--- environs/manual/agent.go 2013-10-02 19:30:07 +0000
126+++ environs/manual/agent.go 2013-11-01 04:21:16 +0000
127@@ -93,31 +93,88 @@
128 // TODO(axw): 2013-08-23 bug 1215777
129 // Carry out configuration for ssh-keys-per-user,
130 // machine-updates-authkeys, using cloud-init config.
131-
132- // Convert runcmds to a series of shell commands.
133- script := []string{"#!/bin/sh"}
134- for _, cmd := range cloudcfg.RunCmds() {
135+ //
136+ // We should work with smoser to get a supported
137+ // command in (or next to) cloud-init for manually
138+ // invoking cloud-config. This would address the
139+ // above comment by removing the need to generate a
140+ // script "by hand".
141+
142+ // Bootcmds must be run before anything else,
143+ // as they may affect package installation.
144+ bootcmds, err := cmdlist(cloudcfg.BootCmds())
145+ if err != nil {
146+ return "", err
147+ }
148+
149+ // Add package sources and packages.
150+ pkgcmds, err := addPackageCommands(cloudcfg)
151+ if err != nil {
152+ return "", err
153+ }
154+
155+ // Runcmds come last.
156+ runcmds, err := cmdlist(cloudcfg.RunCmds())
157+ if err != nil {
158+ return "", err
159+ }
160+
161+ // We prepend "set -xe". This is already in runcmds,
162+ // but added here to avoid relying on that to be
163+ // invariant.
164+ script := []string{"#!/bin/bash", "set -xe"}
165+ script = append(script, bootcmds...)
166+ script = append(script, pkgcmds...)
167+ script = append(script, runcmds...)
168+ return strings.Join(script, "\n"), nil
169+}
170+
171+// addPackageCommands returns a slice of commands that, when run,
172+// will add the required apt repositories and packages.
173+func addPackageCommands(cfg *corecloudinit.Config) ([]string, error) {
174+ var cmds []string
175+ if len(cfg.AptSources()) > 0 {
176+ // Ensure apt-add-repository is available.
177+ cmds = append(cmds, "apt-get -y install python-software-properties")
178+ }
179+ for _, src := range cfg.AptSources() {
180+ // PPA keys are obtained by apt-add-repository, from launchpad.
181+ if !strings.HasPrefix(src.Source, "ppa:") {
182+ if src.Key != "" {
183+ key := utils.ShQuote(src.Key)
184+ cmd := fmt.Sprintf("printf '%%s\\n' %s | apt-key add -", key)
185+ cmds = append(cmds, cmd)
186+ }
187+ }
188+ cmds = append(cmds, "apt-add-repository -y "+utils.ShQuote(src.Source))
189+ }
190+ if cfg.AptUpdate() {
191+ cmds = append(cmds, "apt-get -y update")
192+ }
193+ // Note: explicitly ignoring apt_upgrade, so as not to trample the target
194+ // machine's existing configuration.
195+ for _, pkg := range cfg.Packages() {
196+ cmd := fmt.Sprintf("apt-get -y install %s", utils.ShQuote(pkg))
197+ cmds = append(cmds, cmd)
198+ }
199+ return cmds, nil
200+}
201+
202+func cmdlist(cmds []interface{}) ([]string, error) {
203+ result := make([]string, 0, len(cmds))
204+ for _, cmd := range cmds {
205 switch cmd := cmd.(type) {
206 case []string:
207 // Quote args, so shell meta-characters are not interpreted.
208 for i, arg := range cmd[1:] {
209 cmd[i] = utils.ShQuote(arg)
210 }
211- script = append(script, strings.Join(cmd, " "))
212+ result = append(result, strings.Join(cmd, " "))
213 case string:
214- script = append(script, cmd)
215+ result = append(result, cmd)
216 default:
217- return "", fmt.Errorf("unexpected runcmd type: %T", cmd)
218+ return nil, fmt.Errorf("unexpected command type: %T", cmd)
219 }
220 }
221-
222- // The first command is "set -xe", which we want to leave in place.
223- head := []string{script[0]}
224- tail := script[1:]
225- for _, pkg := range cloudcfg.Packages() {
226- cmd := fmt.Sprintf("apt-get -y install %s", utils.ShQuote(pkg))
227- head = append(head, cmd)
228- }
229- script = append(head, tail...)
230- return strings.Join(script, "\n"), nil
231+ return result, nil
232 }
233
234=== added file 'environs/manual/agent_test.go'
235--- environs/manual/agent_test.go 1970-01-01 00:00:00 +0000
236+++ environs/manual/agent_test.go 2013-11-01 04:21:16 +0000
237@@ -0,0 +1,100 @@
238+// Copyright 2013 Canonical Ltd.
239+// Licensed under the AGPLv3, see LICENCE file for details.
240+
241+package manual
242+
243+import (
244+ gc "launchpad.net/gocheck"
245+
246+ "launchpad.net/juju-core/environs/config"
247+ envtools "launchpad.net/juju-core/environs/tools"
248+ _ "launchpad.net/juju-core/provider/dummy"
249+ coretesting "launchpad.net/juju-core/testing"
250+ "launchpad.net/juju-core/testing/testbase"
251+ "launchpad.net/juju-core/tools"
252+ "launchpad.net/juju-core/version"
253+)
254+
255+type agentSuite struct {
256+ testbase.LoggingSuite
257+}
258+
259+var _ = gc.Suite(&agentSuite{})
260+
261+func dummyConfig(c *gc.C, stateServer bool, vers version.Binary) *config.Config {
262+ testConfig, err := config.New(config.UseDefaults, coretesting.FakeConfig())
263+ c.Assert(err, gc.IsNil)
264+ testConfig, err = testConfig.Apply(map[string]interface{}{
265+ "type": "dummy",
266+ "state-server": stateServer,
267+ "default-series": vers.Series,
268+ "agent-version": vers.Number.String(),
269+ })
270+ c.Assert(err, gc.IsNil)
271+ return testConfig
272+}
273+
274+func (s *agentSuite) getArgs(c *gc.C, stateServer bool, vers version.Binary) provisionMachineAgentArgs {
275+ tools := &tools.Tools{Version: vers}
276+ tools.URL = "file:///var/lib/juju/storage/" + envtools.StorageName(vers)
277+ return provisionMachineAgentArgs{
278+ bootstrap: stateServer,
279+ environConfig: dummyConfig(c, stateServer, vers),
280+ machineId: "0",
281+ nonce: "ya",
282+ stateFileURL: "http://whatever/dotcom",
283+ tools: tools,
284+ // stateInfo *state.Info
285+ // apiInfo *api.Info
286+ agentEnv: make(map[string]string),
287+ }
288+}
289+
290+var allSeries = [...]string{"precise", "quantal", "raring", "saucy"}
291+
292+func checkIff(checker gc.Checker, condition bool) gc.Checker {
293+ if condition {
294+ return checker
295+ }
296+ return gc.Not(checker)
297+}
298+
299+func (s *agentSuite) TestAptSources(c *gc.C) {
300+ for _, series := range allSeries {
301+ vers := version.MustParseBinary("1.16.0-" + series + "-amd64")
302+ script, err := provisionMachineAgentScript(s.getArgs(c, true, vers))
303+ c.Assert(err, gc.IsNil)
304+
305+ // Only Precise requires the cloud-tools pocket.
306+ //
307+ // The only source we add that requires an explicitly
308+ // specified key is cloud-tools.
309+ needsCloudTools := series == "precise"
310+ c.Assert(
311+ script,
312+ checkIff(gc.Matches, needsCloudTools),
313+ "(.|\n)*apt-key add.*(.|\n)*",
314+ )
315+ c.Assert(
316+ script,
317+ checkIff(gc.Matches, needsCloudTools),
318+ "(.|\n)*apt-add-repository.*cloud-tools(.|\n)*",
319+ )
320+
321+ // Only Quantal requires the PPA (for mongo).
322+ needsJujuPPA := series == "quantal"
323+ c.Assert(
324+ script,
325+ checkIff(gc.Matches, needsJujuPPA),
326+ "(.|\n)*apt-add-repository.*ppa:juju/stable(.|\n)*",
327+ )
328+
329+ // Only install python-software-properties (apt-add-repository)
330+ // if we need to.
331+ c.Assert(
332+ script,
333+ checkIff(gc.Matches, needsCloudTools || needsJujuPPA),
334+ "(.|\n)*apt-get -y install.*python-software-properties(.|\n)*",
335+ )
336+ }
337+}

Subscribers

People subscribed via source and target branches

to status/vote changes: