LEFT | RIGHT |
| 1 // Copyright 2013 Canonical Ltd. |
| 2 // Licensed under the AGPLv3, see LICENCE file for details. |
| 3 |
| 4 package configstore |
| 5 |
| 6 import ( |
| 7 "fmt" |
| 8 "io/ioutil" |
| 9 "os" |
| 10 "path/filepath" |
| 11 |
| 12 "launchpad.net/goyaml" |
| 13 |
| 14 "launchpad.net/juju-core/environs/config" |
| 15 "launchpad.net/juju-core/errors" |
| 16 ) |
| 17 |
| 18 // Default returns disk-based environment config storage |
| 19 // rooted at JujuHome. |
| 20 func Default() (Storage, error) { |
| 21 return NewDisk(config.JujuHome()) |
| 22 } |
| 23 |
| 24 type diskStore struct { |
| 25 dir string |
| 26 } |
| 27 |
| 28 type environInfo struct { |
| 29 path string |
| 30 // initialized signifies whether the info has been written. |
| 31 initialized bool |
| 32 |
| 33 // created signifies whether the info was returned from |
| 34 // a CreateInfo call. |
| 35 created bool |
| 36 User string |
| 37 Password string |
| 38 StateServers []string `yaml:"state-servers"` |
| 39 CACert string `yaml:"ca-cert"` |
| 40 Config map[string]interface{} `yaml:"bootstrap-config,omitempty"` |
| 41 } |
| 42 |
| 43 // NewDisk returns a ConfigStorage implementation that stores |
| 44 // configuration in the given directory. The parent of the directory |
| 45 // must already exist; the directory itself is created on demand. |
| 46 func NewDisk(dir string) (Storage, error) { |
| 47 if _, err := os.Stat(dir); err != nil { |
| 48 return nil, err |
| 49 } |
| 50 return &diskStore{dir}, nil |
1 } | 51 } |
2 | 52 |
3 func (d *diskStore) envPath(envName string) string { | 53 func (d *diskStore) envPath(envName string) string { |
4 » return filepath.Join(d.dir, "environments", envName+".yaml") | 54 » return filepath.Join(d.dir, "environments", envName+".jenv") |
5 } | 55 } |
6 | 56 |
7 func (d *diskStore) mkEnvironmentsDir() error { | 57 func (d *diskStore) mkEnvironmentsDir() error { |
| 58 err := os.Mkdir(filepath.Join(d.dir, "environments"), 0700) |
| 59 if err == nil || os.IsExist(err) { |
| 60 return nil |
| 61 } |
| 62 return err |
| 63 } |
| 64 |
| 65 // CreateInfo implements Storage.CreateInfo. |
| 66 func (d *diskStore) CreateInfo(envName string) (EnvironInfo, error) { |
| 67 if err := d.mkEnvironmentsDir(); err != nil { |
| 68 return nil, err |
| 69 } |
| 70 // We create an empty file so that any subsequent CreateInfos |
| 71 // will fail. |
| 72 path := d.envPath(envName) |
| 73 file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) |
| 74 if os.IsExist(err) { |
| 75 return nil, ErrEnvironInfoAlreadyExists |
| 76 } |
| 77 if err != nil { |
| 78 return nil, err |
| 79 } |
| 80 file.Close() |
| 81 return &environInfo{ |
| 82 created: true, |
| 83 path: path, |
| 84 }, nil |
| 85 } |
| 86 |
| 87 // ReadInfo implements Storage.ReadInfo. |
| 88 func (d *diskStore) ReadInfo(envName string) (EnvironInfo, error) { |
| 89 path := d.envPath(envName) |
| 90 data, err := ioutil.ReadFile(path) |
| 91 if err != nil { |
| 92 if os.IsNotExist(err) { |
| 93 return nil, errors.NotFoundf("environment %q", envName) |
| 94 } |
| 95 return nil, err |
| 96 } |
| 97 var info environInfo |
| 98 info.path = path |
| 99 if len(data) == 0 { |
| 100 return &info, nil |
| 101 } |
| 102 if err := goyaml.Unmarshal(data, &info); err != nil { |
| 103 return nil, fmt.Errorf("error unmarshalling %q: %v", path, err) |
| 104 } |
| 105 info.initialized = true |
| 106 return &info, nil |
| 107 } |
| 108 |
| 109 // Initialized implements EnvironInfo.Initialized. |
| 110 func (info *environInfo) Initialized() bool { |
| 111 return info.initialized |
| 112 } |
| 113 |
| 114 // BootstrapConfig implements EnvironInfo.BootstrapConfig. |
| 115 func (info *environInfo) BootstrapConfig() map[string]interface{} { |
| 116 return info.Config |
| 117 } |
| 118 |
| 119 // APICredentials implements EnvironInfo.APICredentials. |
| 120 func (info *environInfo) APICredentials() APICredentials { |
| 121 return APICredentials{ |
| 122 User: info.User, |
| 123 Password: info.Password, |
| 124 } |
| 125 } |
| 126 |
| 127 // APIEndpoint implements EnvironInfo.APIEndpoint. |
| 128 func (info *environInfo) APIEndpoint() APIEndpoint { |
| 129 return APIEndpoint{ |
| 130 Addresses: info.StateServers, |
| 131 CACert: info.CACert, |
| 132 } |
| 133 } |
| 134 |
| 135 // SetExtraConfig implements EnvironInfo.SetBootstrapConfig. |
| 136 func (info *environInfo) SetBootstrapConfig(attrs map[string]interface{}) { |
| 137 if !info.created { |
| 138 panic("bootstrap config set on environment info that has not jus
t been created") |
| 139 } |
| 140 info.Config = attrs |
| 141 } |
| 142 |
| 143 // SetAPIEndpoint implements EnvironInfo.SetAPIEndpoint. |
| 144 func (info *environInfo) SetAPIEndpoint(endpoint APIEndpoint) { |
| 145 info.StateServers = endpoint.Addresses |
| 146 info.CACert = endpoint.CACert |
| 147 } |
| 148 |
| 149 // SetAPICredentials implements EnvironInfo.SetAPICredentials. |
| 150 func (info *environInfo) SetAPICredentials(creds APICredentials) { |
| 151 info.User = creds.User |
| 152 info.Password = creds.Password |
| 153 } |
| 154 |
| 155 // Write implements EnvironInfo.Write. |
| 156 func (info *environInfo) Write() error { |
| 157 data, err := goyaml.Marshal(info) |
| 158 if err != nil { |
| 159 return fmt.Errorf("cannot marshal environment info: %v", err) |
| 160 } |
| 161 // Create a temporary file and rename it, so that the data |
| 162 // changes atomically. |
| 163 parent, _ := filepath.Split(info.path) |
| 164 tmpFile, err := ioutil.TempFile(parent, "") |
| 165 if err != nil { |
| 166 return fmt.Errorf("cannot create temporary file: %v", err) |
| 167 } |
| 168 defer tmpFile.Close() |
| 169 _, err = tmpFile.Write(data) |
| 170 if err != nil { |
| 171 return fmt.Errorf("cannot write temporary file: %v", err) |
| 172 } |
| 173 if err := os.Rename(tmpFile.Name(), info.path); err != nil { |
| 174 os.Remove(tmpFile.Name()) |
| 175 return fmt.Errorf("cannot rename new environment info file: %v",
err) |
| 176 } |
| 177 info.initialized = true |
| 178 return nil |
| 179 } |
| 180 |
| 181 // Destroy implements EnvironInfo.Destroy. |
| 182 func (info *environInfo) Destroy() error { |
| 183 err := os.Remove(info.path) |
| 184 if os.IsNotExist(err) { |
| 185 return fmt.Errorf("environment info has already been removed") |
| 186 } |
| 187 return err |
| 188 } |
LEFT | RIGHT |