Merge lp:~thumper/juju-core/agent-config-formatters into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 1748
Proposed branch: lp:~thumper/juju-core/agent-config-formatters
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 815 lines (+462/-169)
5 files modified
agent/agent.go (+108/-169)
agent/format-1.12.go (+175/-0)
agent/format-1.12_whitebox_test.go (+96/-0)
agent/format.go (+51/-0)
agent/format_whitebox_test.go (+32/-0)
To merge this branch: bzr merge lp:~thumper/juju-core/agent-config-formatters
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+183074@code.launchpad.net

Commit message

Add the formatter concept to agent config.

In order to decouple the internal structure of the agent config from how it is
stored on disk, we introduce formatters. They are responsible for moving the
internal config to and from a serialization format.

The existing format is called "format 1.12", as that is the stable release
that the format was introduced.

Some of the methods are purposefully trivial at this stage, and they are
flushed out in the next branch.

https://codereview.appspot.com/13237050/

Description of the change

Add the formatter concept to agent config.

In order to decouple the internal structure of the agent config from how it is
stored on disk, we introduce formatters. They are responsible for moving the
internal config to and from a serialization format.

The existing format is called "format 1.12", as that is the stable release
that the format was introduced.

Some of the methods are purposefully trivial at this stage, and they are
flushed out in the next branch.

https://codereview.appspot.com/13237050/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+183074_code.launchpad.net,

Message:
Please take a look.

Description:
Add the formatter concept to agent config.

In order to decouple the internal structure of the agent config from how
it is
stored on disk, we introduce formatters. They are responsible for
moving the
internal config to and from a serialization format.

The existing format is called "format 1.12", as that is the stable
release
that the format was introduced.

Some of the methods are purposefully trivial at this stage, and they are
flushed out in the next branch.

https://code.launchpad.net/~thumper/juju-core/agent-config-formatters/+merge/183074

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M agent/agent.go
   A agent/format-1.12.go
   A agent/format-1.12_whitebox_test.go
   A agent/format.go
   A agent/format_whitebox_test.go

Revision history for this message
William Reade (fwereade) wrote :

LGTM, just quibbles

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12.go
File agent/format-1.12.go (right):

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12.go#newcode19
agent/format-1.12.go:19: const format112 = "format 1.12"
1_12 throughout?

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12_whitebox_test.go
File agent/format-1.12_whitebox_test.go (right):

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12_whitebox_test.go#newcode6
agent/format-1.12_whitebox_test.go:6: // package.
I don't personally feel we need to make a big deal out of this.

https://codereview.appspot.com/13237050/

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

Please take a look.

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12.go
File agent/format-1.12.go (right):

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12.go#newcode19
agent/format-1.12.go:19: const format112 = "format 1.12"
On 2013/08/30 07:06:22, fwereade wrote:
> 1_12 throughout?

I have renamed "format112" to "format_1_12" because "format1_12" looked
too weird.

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12.go#newcode22
agent/format-1.12.go:22: type formatter112 struct {
formatter112 becomes formatter_1_12

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12.go#newcode26
agent/format-1.12.go:26: type format112Serialization struct {
format112Serialization becomes format_1_12Serialization

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12_whitebox_test.go
File agent/format-1.12_whitebox_test.go (right):

https://codereview.appspot.com/13237050/diff/1/agent/format-1.12_whitebox_test.go#newcode6
agent/format-1.12_whitebox_test.go:6: // package.
On 2013/08/30 07:06:22, fwereade wrote:
> I don't personally feel we need to make a big deal out of this.

Removed then.

https://codereview.appspot.com/13237050/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'agent/agent.go'
--- agent/agent.go 2013-08-28 22:59:45 +0000
+++ agent/agent.go 2013-09-02 21:47:29 +0000
@@ -5,12 +5,9 @@
55
6import (6import (
7 "fmt"7 "fmt"
8 "io/ioutil"
9 "os"
10 "path"8 "path"
11 "regexp"9 "regexp"
1210
13 "launchpad.net/goyaml"
14 "launchpad.net/loggo"11 "launchpad.net/loggo"
1512
16 "launchpad.net/juju-core/environs/config"13 "launchpad.net/juju-core/environs/config"
@@ -69,46 +66,27 @@
69 APIServerDetails() (port int, cert, key []byte)66 APIServerDetails() (port int, cert, key []byte)
70}67}
7168
72// conf holds information for a given agent.69// Ensure that the configInternal struct implements the Config interface.
73type conf struct {70var _ Config = (*configInternal)(nil)
74 // DataDir specifies the path of the data directory used by all71
75 // agents72type connectionDetails struct {
76 dataDir string73 addresses []string
7774 password string
78 // StateServerCert and StateServerKey hold the state server75}
79 // certificate and private key in PEM format.76
80 StateServerCert []byte `yaml:",omitempty"`77type configInternal struct {
81 StateServerKey []byte `yaml:",omitempty"`78 dataDir string
8279 tag string
83 StatePort int `yaml:",omitempty"`80 nonce string
84 APIPort int `yaml:",omitempty"`81 caCert []byte
8582 stateDetails *connectionDetails
86 // OldPassword specifies a password that should be83 apiDetails *connectionDetails
87 // used to connect to the state if StateInfo.Password84 oldPassword string
88 // is blank or invalid.85 stateServerCert []byte
89 OldPassword string86 stateServerKey []byte
9087 statePort int
91 // MachineNonce is set at provisioning/bootstrap time and used to88 apiPort int
92 // ensure the agent is running on the correct instance.89}
93 MachineNonce string
94
95 // StateInfo specifies how the agent should connect to the
96 // state. The password may be empty if an old password is
97 // specified, or when bootstrapping.
98 StateInfo *state.Info `yaml:",omitempty"`
99
100 // OldAPIPassword specifies a password that should
101 // be used to connect to the API if APIInfo.Password
102 // is blank or invalid.
103 OldAPIPassword string
104
105 // APIInfo specifies how the agent should connect to the
106 // state through the API.
107 APIInfo *api.Info `yaml:",omitempty"`
108}
109
110// Ensure that conf implements Config.
111var _ Config = (*conf)(nil)
11290
113type AgentConfigParams struct {91type AgentConfigParams struct {
114 DataDir string92 DataDir string
@@ -120,7 +98,7 @@
120 CACert []byte98 CACert []byte
121}99}
122100
123func newConfig(params AgentConfigParams) (*conf, error) {101func newConfig(params AgentConfigParams) (*configInternal, error) {
124 if params.DataDir == "" {102 if params.DataDir == "" {
125 return nil, requiredError("data directory")103 return nil, requiredError("data directory")
126 }104 }
@@ -135,33 +113,27 @@
135 }113 }
136 // Note that the password parts of the state and api information are114 // Note that the password parts of the state and api information are
137 // blank. This is by design.115 // blank. This is by design.
138 var stateInfo *state.Info116 config := &configInternal{
117 dataDir: params.DataDir,
118 tag: params.Tag,
119 nonce: params.Nonce,
120 caCert: params.CACert,
121 oldPassword: params.Password,
122 }
139 if len(params.StateAddresses) > 0 {123 if len(params.StateAddresses) > 0 {
140 stateInfo = &state.Info{124 config.stateDetails = &connectionDetails{
141 Addrs: params.StateAddresses,125 addresses: params.StateAddresses,
142 Tag: params.Tag,
143 CACert: params.CACert,
144 }126 }
145 }127 }
146 var apiInfo *api.Info
147 if len(params.APIAddresses) > 0 {128 if len(params.APIAddresses) > 0 {
148 apiInfo = &api.Info{129 config.apiDetails = &connectionDetails{
149 Addrs: params.APIAddresses,130 addresses: params.APIAddresses,
150 Tag: params.Tag,
151 CACert: params.CACert,
152 }131 }
153 }132 }
154 conf := &conf{133 if err := config.check(); err != nil {
155 dataDir: params.DataDir,
156 OldPassword: params.Password,
157 StateInfo: stateInfo,
158 APIInfo: apiInfo,
159 MachineNonce: params.Nonce,
160 }
161 if err := conf.check(); err != nil {
162 return nil, err134 return nil, err
163 }135 }
164 return conf, nil136 return config, nil
165}137}
166138
167// NewAgentConfig returns a new config object suitable for use for a unit139// NewAgentConfig returns a new config object suitable for use for a unit
@@ -186,15 +158,15 @@
186 if params.StateServerKey == nil {158 if params.StateServerKey == nil {
187 return nil, requiredError("state server key")159 return nil, requiredError("state server key")
188 }160 }
189 conf, err := newConfig(params.AgentConfigParams)161 config, err := newConfig(params.AgentConfigParams)
190 if err != nil {162 if err != nil {
191 return nil, err163 return nil, err
192 }164 }
193 conf.StateServerCert = params.StateServerCert165 config.stateServerCert = params.StateServerCert
194 conf.StateServerKey = params.StateServerKey166 config.stateServerKey = params.StateServerKey
195 conf.StatePort = params.StatePort167 config.statePort = params.StatePort
196 conf.APIPort = params.APIPort168 config.apiPort = params.APIPort
197 return conf, nil169 return config, nil
198}170}
199171
200// Dir returns the agent-specific data directory.172// Dir returns the agent-specific data directory.
@@ -206,25 +178,20 @@
206// entity from the given data directory.178// entity from the given data directory.
207func ReadConf(dataDir, tag string) (Config, error) {179func ReadConf(dataDir, tag string) (Config, error) {
208 dir := Dir(dataDir, tag)180 dir := Dir(dataDir, tag)
209 data, err := ioutil.ReadFile(path.Join(dir, "agent.conf"))181 config, err := currentFormatter.read(dir)
210 if err != nil {182 if err != nil {
211 return nil, err183 return nil, err
212 }184 }
213 var c conf185 config.dataDir = dataDir
214 if err := goyaml.Unmarshal(data, &c); err != nil {186 if err := config.check(); err != nil {
215 return nil, err187 return nil, err
216 }188 }
217 c.dataDir = dataDir189 return config, nil
218 if err := c.check(); err != nil {190 // Read the format for the dir.
219 return nil, err191 // Create a formatter for the format
220 }192 // Read in the config
221 if c.StateInfo != nil {193 // If the format isn't the current format, call the upgrade function, and
222 c.StateInfo.Tag = tag194 // write out using the new formatter.
223 }
224 if c.APIInfo != nil {
225 c.APIInfo.Tag = tag
226 }
227 return &c, nil
228}195}
229196
230func requiredError(what string) error {197func requiredError(what string) error {
@@ -232,53 +199,45 @@
232}199}
233200
234// File returns the path of the given file in the agent's directory.201// File returns the path of the given file in the agent's directory.
235func (c *conf) File(name string) string {202func (c *configInternal) File(name string) string {
236 return path.Join(c.Dir(), name)203 return path.Join(c.Dir(), name)
237}204}
238205
239func (c *conf) confFile() string {206func (c *configInternal) DataDir() string {
240 return c.File("agent.conf")
241}
242
243func (c *conf) DataDir() string {
244 return c.dataDir207 return c.dataDir
245}208}
246209
247func (c *conf) Nonce() string {210func (c *configInternal) Nonce() string {
248 return c.MachineNonce211 return c.nonce
249}212}
250213
251func (c *conf) APIServerDetails() (port int, cert, key []byte) {214func (c *configInternal) APIServerDetails() (port int, cert, key []byte) {
252 return c.APIPort, c.StateServerCert, c.StateServerKey215 return c.apiPort, c.stateServerCert, c.stateServerKey
253}216}
254217
255// Tag returns the tag of the entity on whose behalf the state connection will218// Tag returns the tag of the entity on whose behalf the state connection will
256// be made.219// be made.
257func (c *conf) Tag() string {220func (c *configInternal) Tag() string {
258 if c.StateInfo != nil {221 return c.tag
259 return c.StateInfo.Tag
260 }
261 return c.APIInfo.Tag
262}222}
263223
264// Dir returns the agent's directory.224// Dir returns the agent's directory.
265func (c *conf) Dir() string {225func (c *configInternal) Dir() string {
266 return Dir(c.dataDir, c.Tag())226 return Dir(c.dataDir, c.tag)
267}227}
268228
269// Check checks that the configuration has all the required elements.229// Check checks that the configuration has all the required elements.
270func (c *conf) check() error {230func (c *configInternal) check() error {
271 if c.StateInfo == nil && c.APIInfo == nil {231 if c.stateDetails == nil && c.apiDetails == nil {
272 return requiredError("state or API addresses")232 return requiredError("state or API addresses")
273 }233 }
274 if c.StateInfo != nil {234 if c.stateDetails != nil {
275 if err := checkAddrs(c.StateInfo.Addrs, "state server address"); err != nil {235 if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil {
276 return err236 return err
277 }237 }
278 }238 }
279 // TODO(rog) make APIInfo mandatory239 if c.apiDetails != nil {
280 if c.APIInfo != nil {240 if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil {
281 if err := checkAddrs(c.APIInfo.Addrs, "API server address"); err != nil {
282 return err241 return err
283 }242 }
284 }243 }
@@ -299,17 +258,17 @@
299 return nil258 return nil
300}259}
301260
302func (c *conf) PasswordHash() string {261func (c *configInternal) PasswordHash() string {
303 var password string262 var password string
304 if c.StateInfo == nil {263 if c.stateDetails == nil {
305 password = c.APIInfo.Password264 password = c.apiDetails.password
306 } else {265 } else {
307 password = c.StateInfo.Password266 password = c.stateDetails.password
308 }267 }
309 return utils.PasswordHash(password)268 return utils.PasswordHash(password)
310}269}
311270
312func (c *conf) GenerateNewPassword() (string, error) {271func (c *configInternal) GenerateNewPassword() (string, error) {
313 newPassword, err := utils.RandomPassword()272 newPassword, err := utils.RandomPassword()
314 if err != nil {273 if err != nil {
315 return "", err274 return "", err
@@ -318,15 +277,15 @@
318 // to write the configuration file, the configuration will277 // to write the configuration file, the configuration will
319 // still be valid.278 // still be valid.
320 other := *c279 other := *c
321 if c.StateInfo != nil {280 if c.stateDetails != nil {
322 stateInfo := *c.StateInfo281 stateDetails := *c.stateDetails
323 stateInfo.Password = newPassword282 stateDetails.password = newPassword
324 other.StateInfo = &stateInfo283 other.stateDetails = &stateDetails
325 }284 }
326 if c.APIInfo != nil {285 if c.apiDetails != nil {
327 apiInfo := *c.APIInfo286 apiDetails := *c.apiDetails
328 apiInfo.Password = newPassword287 apiDetails.password = newPassword
329 other.APIInfo = &apiInfo288 other.apiDetails = &apiDetails
330 }289 }
331290
332 if err := other.Write(); err != nil {291 if err := other.Write(); err != nil {
@@ -337,47 +296,15 @@
337}296}
338297
339// Write writes the agent configuration.298// Write writes the agent configuration.
340func (c *conf) Write() error {299func (c *configInternal) Write() error {
341 if err := c.check(); err != nil {300 return currentFormatter.write(c)
342 return err
343 }
344 data, err := goyaml.Marshal(c)
345 if err != nil {
346 return err
347 }
348 if err := os.MkdirAll(c.Dir(), 0755); err != nil {
349 return err
350 }
351 f := c.File("agent.conf-new")
352 if err := ioutil.WriteFile(f, data, 0600); err != nil {
353 return err
354 }
355 if err := os.Rename(f, c.confFile()); err != nil {
356 return err
357 }
358 return nil
359}301}
360302
361// WriteCommands returns shell commands to write the agent303// WriteCommands returns shell commands to write the agent
362// configuration. It returns an error if the configuration does not304// configuration. It returns an error if the configuration does not
363// have all the right elements.305// have all the right elements.
364func (c *conf) WriteCommands() ([]string, error) {306func (c *configInternal) WriteCommands() ([]string, error) {
365 if err := c.check(); err != nil {307 return currentFormatter.writeCommands(c)
366 return nil, err
367 }
368 data, err := goyaml.Marshal(c)
369 if err != nil {
370 return nil, err
371 }
372 var cmds []string
373 addCmd := func(f string, a ...interface{}) {
374 cmds = append(cmds, fmt.Sprintf(f, a...))
375 }
376 f := utils.ShQuote(c.confFile())
377 addCmd("mkdir -p %s", utils.ShQuote(c.Dir()))
378 addCmd("install -m %o /dev/null %s", 0600, f)
379 addCmd(`printf '%%s\n' %s > %s`, utils.ShQuote(string(data)), f)
380 return cmds, nil
381}308}
382309
383// OpenAPI tries to open the state using the given Conf. If it310// OpenAPI tries to open the state using the given Conf. If it
@@ -385,9 +312,14 @@
385// to the state should be changed accordingly - the caller should write the312// to the state should be changed accordingly - the caller should write the
386// configuration with StateInfo.Password set to newPassword, then313// configuration with StateInfo.Password set to newPassword, then
387// set the entity's password accordingly.314// set the entity's password accordingly.
388func (c *conf) OpenAPI(dialOpts api.DialOpts) (st *api.State, newPassword string, err error) {315func (c *configInternal) OpenAPI(dialOpts api.DialOpts) (st *api.State, newPassword string, err error) {
389 info := *c.APIInfo316 info := api.Info{
390 info.Nonce = c.MachineNonce317 Addrs: c.apiDetails.addresses,
318 Password: c.apiDetails.password,
319 CACert: c.caCert,
320 Tag: c.tag,
321 Nonce: c.nonce,
322 }
391 if info.Password != "" {323 if info.Password != "" {
392 st, err := api.Open(&info, dialOpts)324 st, err := api.Open(&info, dialOpts)
393 if err == nil {325 if err == nil {
@@ -401,7 +333,7 @@
401 // password but before changing it, so we'll try again333 // password but before changing it, so we'll try again
402 // with the old password.334 // with the old password.
403 }335 }
404 info.Password = c.OldPassword336 info.Password = c.oldPassword
405 st, err = api.Open(&info, dialOpts)337 st, err = api.Open(&info, dialOpts)
406 if err != nil {338 if err != nil {
407 return nil, "", err339 return nil, "", err
@@ -418,8 +350,13 @@
418}350}
419351
420// OpenState tries to open the state using the given Conf.352// OpenState tries to open the state using the given Conf.
421func (c *conf) OpenState() (*state.State, error) {353func (c *configInternal) OpenState() (*state.State, error) {
422 info := *c.StateInfo354 info := state.Info{
355 Addrs: c.stateDetails.addresses,
356 Password: c.stateDetails.password,
357 CACert: c.caCert,
358 Tag: c.tag,
359 }
423 if info.Password != "" {360 if info.Password != "" {
424 st, err := state.Open(&info, state.DefaultDialOpts())361 st, err := state.Open(&info, state.DefaultDialOpts())
425 if err == nil {362 if err == nil {
@@ -431,14 +368,16 @@
431 return nil, err368 return nil, err
432 }369 }
433 }370 }
434 info.Password = c.OldPassword371 info.Password = c.oldPassword
435 return state.Open(&info, state.DefaultDialOpts())372 return state.Open(&info, state.DefaultDialOpts())
436}373}
437374
438func InitialStateConfiguration(agentConfig Config, cfg *config.Config, timeout state.DialOpts) (*state.State, error) {375func InitialStateConfiguration(agentConfig Config, cfg *config.Config, timeout state.DialOpts) (*state.State, error) {
439 c := agentConfig.(*conf)376 c := agentConfig.(*configInternal)
440 info := *c.StateInfo377 info := state.Info{
441 info.Tag = ""378 Addrs: c.stateDetails.addresses,
379 CACert: c.caCert,
380 }
442 logger.Debugf("initializing address %v", info.Addrs)381 logger.Debugf("initializing address %v", info.Addrs)
443 st, err := state.Initialize(&info, cfg, timeout)382 st, err := state.Initialize(&info, cfg, timeout)
444 if err != nil {383 if err != nil {
@@ -451,7 +390,7 @@
451 }390 }
452 logger.Debugf("state initialized")391 logger.Debugf("state initialized")
453392
454 if err := bootstrapUsers(st, cfg, c.OldPassword); err != nil {393 if err := bootstrapUsers(st, cfg, c.oldPassword); err != nil {
455 st.Close()394 st.Close()
456 return nil, err395 return nil, err
457 }396 }
458397
=== added file 'agent/format-1.12.go'
--- agent/format-1.12.go 1970-01-01 00:00:00 +0000
+++ agent/format-1.12.go 2013-09-02 21:47:29 +0000
@@ -0,0 +1,175 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package agent
5
6import (
7 "fmt"
8 "io/ioutil"
9 "os"
10 "path"
11
12 "launchpad.net/goyaml"
13
14 "launchpad.net/juju-core/state"
15 "launchpad.net/juju-core/state/api"
16 "launchpad.net/juju-core/utils"
17)
18
19const format_1_12 = "format 1.12"
20
21// formatter_1_12 is the formatter for the 1.12 format.
22type formatter_1_12 struct {
23}
24
25// format_1_12Serialization holds information stored in the agent.conf file.
26type format_1_12Serialization struct {
27 // StateServerCert and StateServerKey hold the state server
28 // certificate and private key in PEM format.
29 StateServerCert []byte `yaml:",omitempty"`
30 StateServerKey []byte `yaml:",omitempty"`
31
32 StatePort int `yaml:",omitempty"`
33 APIPort int `yaml:",omitempty"`
34
35 // OldPassword specifies a password that should be
36 // used to connect to the state if StateInfo.Password
37 // is blank or invalid.
38 OldPassword string
39
40 // MachineNonce is set at provisioning/bootstrap time and used to
41 // ensure the agent is running on the correct instance.
42 MachineNonce string
43
44 // StateInfo specifies how the agent should connect to the
45 // state. The password may be empty if an old password is
46 // specified, or when bootstrapping.
47 StateInfo *state.Info `yaml:",omitempty"`
48
49 // OldAPIPassword specifies a password that should
50 // be used to connect to the API if APIInfo.Password
51 // is blank or invalid.
52 OldAPIPassword string
53
54 // APIInfo specifies how the agent should connect to the
55 // state through the API.
56 APIInfo *api.Info `yaml:",omitempty"`
57}
58
59// Ensure that the formatter_1_12 struct implements the formatter interface.
60var _ formatter = (*formatter_1_12)(nil)
61
62func (*formatter_1_12) configFile(dirName string) string {
63 return path.Join(dirName, "agent.conf")
64}
65
66func (formatter *formatter_1_12) read(dirName string) (*configInternal, error) {
67 data, err := ioutil.ReadFile(formatter.configFile(dirName))
68 if err != nil {
69 return nil, err
70 }
71 var conf format_1_12Serialization
72 if err := goyaml.Unmarshal(data, &conf); err != nil {
73 return nil, err
74 }
75
76 var stateDetails *connectionDetails
77 var caCert []byte
78 var tag string
79 if conf.StateInfo != nil {
80 stateDetails = &connectionDetails{
81 conf.StateInfo.Addrs,
82 conf.StateInfo.Password,
83 }
84 tag = conf.StateInfo.Tag
85 caCert = conf.StateInfo.CACert
86 }
87 var apiDetails *connectionDetails
88 if conf.APIInfo != nil {
89 apiDetails = &connectionDetails{
90 conf.APIInfo.Addrs,
91 conf.APIInfo.Password,
92 }
93 tag = conf.APIInfo.Tag
94 caCert = conf.APIInfo.CACert
95 }
96 return &configInternal{
97 tag: tag,
98 nonce: conf.MachineNonce,
99 caCert: caCert,
100 stateDetails: stateDetails,
101 apiDetails: apiDetails,
102 oldPassword: conf.OldPassword,
103 stateServerCert: conf.StateServerCert,
104 stateServerKey: conf.StateServerKey,
105 statePort: conf.StatePort,
106 apiPort: conf.APIPort,
107 }, nil
108}
109
110func (formatter *formatter_1_12) makeAgentConf(config *configInternal) *format_1_12Serialization {
111 format := &format_1_12Serialization{
112 StateServerCert: config.stateServerCert,
113 StateServerKey: config.stateServerKey,
114 StatePort: config.statePort,
115 APIPort: config.apiPort,
116 OldPassword: config.oldPassword,
117 MachineNonce: config.nonce,
118 }
119 if config.stateDetails != nil {
120 // It is fine that we are copying the slices for the addresses.
121 format.StateInfo = &state.Info{
122 Addrs: config.stateDetails.addresses,
123 Password: config.stateDetails.password,
124 Tag: config.tag,
125 CACert: config.caCert,
126 }
127 }
128 if config.apiDetails != nil {
129 format.APIInfo = &api.Info{
130 Addrs: config.apiDetails.addresses,
131 Password: config.apiDetails.password,
132 Tag: config.tag,
133 CACert: config.caCert,
134 }
135 }
136 return format
137}
138
139func (formatter *formatter_1_12) write(config *configInternal) error {
140 dirName := config.Dir()
141 conf := formatter.makeAgentConf(config)
142 data, err := goyaml.Marshal(conf)
143 if err != nil {
144 return err
145 }
146 if err := os.MkdirAll(dirName, 0755); err != nil {
147 return err
148 }
149 newFile := path.Join(dirName, "agent.conf-new")
150 if err := ioutil.WriteFile(newFile, data, 0600); err != nil {
151 return err
152 }
153 if err := os.Rename(newFile, formatter.configFile(dirName)); err != nil {
154 return err
155 }
156 return nil
157}
158
159func (formatter *formatter_1_12) writeCommands(config *configInternal) ([]string, error) {
160 dirName := config.Dir()
161 conf := formatter.makeAgentConf(config)
162 data, err := goyaml.Marshal(conf)
163 if err != nil {
164 return nil, err
165 }
166 var commands []string
167 addCommand := func(f string, a ...interface{}) {
168 commands = append(commands, fmt.Sprintf(f, a...))
169 }
170 filename := utils.ShQuote(formatter.configFile(dirName))
171 addCommand("mkdir -p %s", utils.ShQuote(dirName))
172 addCommand("install -m %o /dev/null %s", 0600, filename)
173 addCommand(`printf '%%s\n' %s > %s`, utils.ShQuote(string(data)), filename)
174 return commands, nil
175}
0176
=== added file 'agent/format-1.12_whitebox_test.go'
--- agent/format-1.12_whitebox_test.go 1970-01-01 00:00:00 +0000
+++ agent/format-1.12_whitebox_test.go 2013-09-02 21:47:29 +0000
@@ -0,0 +1,96 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package agent
5
6import (
7 "os"
8 "path"
9
10 gc "launchpad.net/gocheck"
11
12 "launchpad.net/juju-core/testing"
13 jc "launchpad.net/juju-core/testing/checkers"
14)
15
16type format_1_12Suite struct {
17 testing.LoggingSuite
18 formatter formatter_1_12
19}
20
21var _ = gc.Suite(&format_1_12Suite{})
22
23var agentParams = AgentConfigParams{
24 Tag: "omg",
25 Password: "sekrit",
26 CACert: []byte("ca cert"),
27 StateAddresses: []string{"localhost:1234"},
28 APIAddresses: []string{"localhost:1235"},
29 Nonce: "a nonce",
30}
31
32func (s *format_1_12Suite) newConfig(c *gc.C) *configInternal {
33 params := agentParams
34 params.DataDir = c.MkDir()
35 config, err := newConfig(params)
36 c.Assert(err, gc.IsNil)
37 return config
38}
39
40func (s *format_1_12Suite) TestWriteAgentConfig(c *gc.C) {
41 config := s.newConfig(c)
42 err := s.formatter.write(config)
43 c.Assert(err, gc.IsNil)
44
45 expectedLocation := path.Join(config.Dir(), "agent.conf")
46 fileInfo, err := os.Stat(expectedLocation)
47 c.Assert(err, gc.IsNil)
48 c.Assert(fileInfo.Mode().IsRegular(), jc.IsTrue)
49 c.Assert(fileInfo.Mode().Perm(), gc.Equals, os.FileMode(0600))
50 c.Assert(fileInfo.Size(), jc.GreaterThan, 0)
51}
52
53func (s *format_1_12Suite) assertWriteAndRead(c *gc.C, config *configInternal) {
54 err := s.formatter.write(config)
55 c.Assert(err, gc.IsNil)
56 // The readConfig is missing the dataDir initially.
57 readConfig, err := s.formatter.read(config.Dir())
58 c.Assert(err, gc.IsNil)
59 c.Assert(readConfig.dataDir, gc.Equals, "")
60 // This is put in by the ReadConf method that we are avoiding using
61 // becuase it will have side-effects soon around migrating configs.
62 readConfig.dataDir = config.dataDir
63 c.Assert(readConfig, gc.DeepEquals, config)
64}
65
66func (s *format_1_12Suite) TestRead(c *gc.C) {
67 config := s.newConfig(c)
68 s.assertWriteAndRead(c, config)
69}
70
71func (s *format_1_12Suite) TestWriteCommands(c *gc.C) {
72 config := s.newConfig(c)
73 commands, err := s.formatter.writeCommands(config)
74 c.Assert(err, gc.IsNil)
75 c.Assert(commands, gc.HasLen, 3)
76 c.Assert(commands[0], gc.Matches, `mkdir -p '\S+/agents/omg'`)
77 c.Assert(commands[1], gc.Matches, `install -m 600 /dev/null '\S+/agents/omg/agent.conf'`)
78 c.Assert(commands[2], gc.Matches, `printf '%s\\n' '(.|\n)*' > '\S+/agents/omg/agent.conf'`)
79}
80
81func (s *format_1_12Suite) TestReadWriteStateConfig(c *gc.C) {
82 stateParams := StateMachineConfigParams{
83 AgentConfigParams: agentParams,
84 StateServerCert: []byte("some special cert"),
85 StateServerKey: []byte("a special key"),
86 StatePort: 12345,
87 APIPort: 23456,
88 }
89 stateParams.DataDir = c.MkDir()
90 configInterface, err := NewStateMachineConfig(stateParams)
91 c.Assert(err, gc.IsNil)
92 config, ok := configInterface.(*configInternal)
93 c.Assert(ok, jc.IsTrue)
94
95 s.assertWriteAndRead(c, config)
96}
097
=== added file 'agent/format.go'
--- agent/format.go 1970-01-01 00:00:00 +0000
+++ agent/format.go 2013-09-02 21:47:29 +0000
@@ -0,0 +1,51 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package agent
5
6import (
7 "fmt"
8)
9
10// The format file in the agent config directory is used to identify the
11// method of serialization. This is used by individual format readers and
12// writers to be able to translate from the file format to the in-memory
13// structure.
14//
15// Juju only supports upgrading from single steps, so Juju only needs to know
16// about the current format and the format of the previous stable release.
17// For convenience, the format name includes the version number of the stable
18// release that it will be released with. Once this release has happened, the
19// format should be considered FIXED, and should no longer be modified. If
20// changes are necessary to the format, a new format should be created.
21//
22// We don't need to create new formats for each release, the version number is
23// just a convenience for us to know which stable release introduced that
24// format.
25
26const (
27 formatFilename = "format"
28 currentFormat = format_1_12
29)
30
31var currentFormatter = &formatter_1_12{}
32
33// The formatter defines the two methods needed by the formatters for
34// translating to and from the internal, format agnostic, structure.
35type formatter interface {
36 read(dirName string) (*configInternal, error)
37 write(config *configInternal) error
38 writeCommands(config *configInternal) ([]string, error)
39}
40
41func readFormat(dirName string) (string, error) {
42 return currentFormat, nil
43}
44
45func newFormatter(format string) (formatter, error) {
46 switch format {
47 case currentFormat:
48 return currentFormatter, nil
49 }
50 return nil, fmt.Errorf("unknown agent config format")
51}
052
=== added file 'agent/format_whitebox_test.go'
--- agent/format_whitebox_test.go 1970-01-01 00:00:00 +0000
+++ agent/format_whitebox_test.go 2013-09-02 21:47:29 +0000
@@ -0,0 +1,32 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package agent
5
6import (
7 gc "launchpad.net/gocheck"
8
9 "launchpad.net/juju-core/testing"
10)
11
12type formatSuite struct {
13 testing.LoggingSuite
14}
15
16var _ = gc.Suite(&formatSuite{})
17
18func (*formatSuite) TestReadFormat(c *gc.C) {
19 format, err := readFormat("ignored")
20 c.Assert(format, gc.Equals, currentFormat)
21 c.Assert(err, gc.IsNil)
22}
23
24func (*formatSuite) TestNewFormatter(c *gc.C) {
25 formatter, err := newFormatter(currentFormat)
26 c.Assert(formatter, gc.NotNil)
27 c.Assert(err, gc.IsNil)
28
29 formatter, err = newFormatter("other")
30 c.Assert(formatter, gc.IsNil)
31 c.Assert(err, gc.ErrorMatches, "unknown agent config format")
32}

Subscribers

People subscribed via source and target branches

to status/vote changes: