OLD | NEW |
1 // Copyright 2013 Canonical Ltd. | 1 // Copyright 2013 Canonical Ltd. |
2 // Licensed under the AGPLv3, see LICENCE file for details. | 2 // Licensed under the AGPLv3, see LICENCE file for details. |
3 | 3 |
4 package configstore | 4 package configstore |
5 | 5 |
6 import ( | 6 import ( |
7 "fmt" | 7 "fmt" |
8 "io/ioutil" | 8 "io/ioutil" |
9 "os" | 9 "os" |
10 "path/filepath" | 10 "path/filepath" |
11 | 11 |
12 "launchpad.net/goyaml" | 12 "launchpad.net/goyaml" |
13 | 13 |
14 "launchpad.net/juju-core/environs" | 14 "launchpad.net/juju-core/environs" |
15 "launchpad.net/juju-core/errors" | 15 "launchpad.net/juju-core/errors" |
16 ) | 16 ) |
17 | 17 |
18 type diskStore struct { | 18 type diskStore struct { |
19 dir string | 19 dir string |
20 } | 20 } |
21 | 21 |
22 type environInfo struct { | 22 type environInfo struct { |
| 23 path string |
23 User string | 24 User string |
24 Password string | 25 Password string |
25 StateServers []string `yaml:"state-servers"` | 26 StateServers []string `yaml:"state-servers"` |
26 CACert string `yaml:"ca-cert"` | 27 CACert string `yaml:"ca-cert"` |
27 } | 28 } |
28 | 29 |
29 // NewDisk returns a ConfigStorage implementation that | 30 // NewDisk returns a ConfigStorage implementation that |
30 // stores configuration in the given directory. | 31 // stores configuration in the given directory. |
31 // The parent of the directory must already exist; | 32 // The parent of the directory must already exist; |
32 // the directory itself is created on demand. | 33 // the directory itself is created on demand. |
33 func NewDisk(dir string) (environs.ConfigStorage, error) { | 34 func NewDisk(dir string) (environs.ConfigStorage, error) { |
34 parent, _ := filepath.Split(dir) | 35 parent, _ := filepath.Split(dir) |
35 if _, err := os.Stat(parent); err != nil { | 36 if _, err := os.Stat(parent); err != nil { |
36 return nil, err | 37 return nil, err |
37 } | 38 } |
38 return &diskStore{dir}, nil | 39 return &diskStore{dir}, nil |
39 } | 40 } |
40 | 41 |
41 func (d *diskStore) envPath(envName string) string { | 42 func (d *diskStore) envPath(envName string) string { |
42 return filepath.Join(d.dir, envName+".yaml") | 43 return filepath.Join(d.dir, envName+".yaml") |
43 } | 44 } |
44 | 45 |
45 // EnvironInfo implements environs.ConfigStorage.EnvironInfo. | 46 // CreateInfo implements environs.ConfigStorage.CreateInfo. |
| 47 func (d *diskStore) CreateInfo(envName string) (environs.EnvironInfo, error) { |
| 48 » // We create an empty file so that any subsequent CreateInfos |
| 49 » // will fail. |
| 50 » path := d.envPath(envName) |
| 51 » file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) |
| 52 » if os.IsExist(err) { |
| 53 » » return nil, environs.ErrEnvironInfoAlreadyExists |
| 54 » } |
| 55 » if err != nil { |
| 56 » » return nil, err |
| 57 » } |
| 58 » file.Close() |
| 59 » return &environInfo{ |
| 60 » » path: path, |
| 61 » }, nil |
| 62 } |
| 63 |
| 64 // ReadInfo implements environs.ConfigStorage.ReadInfo. |
46 func (d *diskStore) ReadInfo(envName string) (environs.EnvironInfo, error) { | 65 func (d *diskStore) ReadInfo(envName string) (environs.EnvironInfo, error) { |
47 path := d.envPath(envName) | 66 path := d.envPath(envName) |
48 data, err := ioutil.ReadFile(path) | 67 data, err := ioutil.ReadFile(path) |
49 if err != nil { | 68 if err != nil { |
50 if os.IsNotExist(err) { | 69 if os.IsNotExist(err) { |
51 return nil, errors.NotFoundf("environment %q", envName) | 70 return nil, errors.NotFoundf("environment %q", envName) |
52 } | 71 } |
53 return nil, err | 72 return nil, err |
54 } | 73 } |
| 74 if len(data) == 0 { |
| 75 return nil, fmt.Errorf("empty environment information (possibly
because bootstrap in progress or interrupted)") |
| 76 } |
55 var info environInfo | 77 var info environInfo |
56 if err := goyaml.Unmarshal(data, &info); err != nil { | 78 if err := goyaml.Unmarshal(data, &info); err != nil { |
57 return nil, fmt.Errorf("error unmarshalling %q: %v", path, err) | 79 return nil, fmt.Errorf("error unmarshalling %q: %v", path, err) |
58 } | 80 } |
| 81 info.path = path |
59 return &info, nil | 82 return &info, nil |
60 } | 83 } |
61 | 84 |
| 85 // APICredentials implements environs.EnvironInfo.APICredentials. |
62 func (info *environInfo) APICredentials() environs.APICredentials { | 86 func (info *environInfo) APICredentials() environs.APICredentials { |
63 return environs.APICredentials{ | 87 return environs.APICredentials{ |
64 User: info.User, | 88 User: info.User, |
65 Password: info.Password, | 89 Password: info.Password, |
66 } | 90 } |
67 } | 91 } |
68 | 92 |
| 93 // APIEndpoint implements environs.EnvironInfo.APIEndpoint. |
69 func (info *environInfo) APIEndpoint() environs.APIEndpoint { | 94 func (info *environInfo) APIEndpoint() environs.APIEndpoint { |
70 return environs.APIEndpoint{ | 95 return environs.APIEndpoint{ |
71 Addresses: info.StateServers, | 96 Addresses: info.StateServers, |
72 CACert: info.CACert, | 97 CACert: info.CACert, |
73 } | 98 } |
74 } | 99 } |
| 100 |
| 101 // SetAPIEndpoint implements environs.EnvironInfo.SetAPIEndpoint. |
| 102 func (info *environInfo) SetAPIEndpoint(endpoint environs.APIEndpoint) { |
| 103 info.StateServers = endpoint.Addresses |
| 104 info.CACert = endpoint.CACert |
| 105 } |
| 106 |
| 107 // SetAPICredentials implements environs.EnvironInfo.SetAPICredentials. |
| 108 func (info *environInfo) SetAPICredentials(creds environs.APICredentials) { |
| 109 info.User = creds.User |
| 110 info.Password = creds.Password |
| 111 } |
| 112 |
| 113 // Write implements environs.EnvironInfo.Write. |
| 114 func (info *environInfo) Write() error { |
| 115 data, err := goyaml.Marshal(info) |
| 116 if err != nil { |
| 117 return fmt.Errorf("cannot marshal environment info: %v", err) |
| 118 } |
| 119 // Create a temporary file and rename it, so that the data |
| 120 // changes atomically. |
| 121 parent, _ := filepath.Split(info.path) |
| 122 tmpFile, err := ioutil.TempFile(parent, "") |
| 123 if err != nil { |
| 124 return fmt.Errorf("cannot create temporary file: %v", err) |
| 125 } |
| 126 defer tmpFile.Close() |
| 127 _, err = tmpFile.Write(data) |
| 128 if err != nil { |
| 129 return fmt.Errorf("cannot write temporary file: %v", err) |
| 130 } |
| 131 if err := os.Rename(tmpFile.Name(), info.path); err != nil { |
| 132 os.Remove(tmpFile.Name()) |
| 133 return fmt.Errorf("cannot rename new environment info file: %v",
err) |
| 134 } |
| 135 return nil |
| 136 } |
| 137 |
| 138 // Destroy implements environs.EnvironInfo.Destroy. |
| 139 func (info *environInfo) Destroy() error { |
| 140 err := os.Remove(info.path) |
| 141 if os.IsNotExist(err) { |
| 142 return fmt.Errorf("environment info has already been removed") |
| 143 } |
| 144 return err |
| 145 } |
OLD | NEW |