github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/environs/configstore/disk.go (about) 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 "sync" 12 "time" 13 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/utils/fslock" 17 goyaml "gopkg.in/yaml.v1" 18 19 "github.com/juju/juju/juju/osenv" 20 ) 21 22 var logger = loggo.GetLogger("juju.environs.configstore") 23 24 const lockName = "env.lock" 25 26 // A second should be way more than enough to write or read any files. 27 var lockTimeout = time.Second 28 29 // Default returns disk-based environment config storage 30 // rooted at JujuHome. 31 var Default = func() (Storage, error) { 32 return NewDisk(osenv.JujuHome()) 33 } 34 35 type diskStore struct { 36 dir string 37 } 38 39 // EnvironInfoData is the serialisation structure for the original JENV file. 40 type EnvironInfoData struct { 41 User string 42 Password string 43 EnvironUUID string `json:"environ-uuid,omitempty" yaml:"environ-uuid,omitempty"` 44 StateServers []string `json:"state-servers" yaml:"state-servers"` 45 ServerHostnames []string `json:"server-hostnames,omitempty" yaml:"server-hostnames,omitempty"` 46 CACert string `json:"ca-cert" yaml:"ca-cert"` 47 Config map[string]interface{} `json:"bootstrap-config,omitempty" yaml:"bootstrap-config,omitempty"` 48 } 49 50 type environInfo struct { 51 mu sync.Mutex 52 53 // environmentDir is the directory where the files are written. 54 environmentDir string 55 56 // path is the location of the file that we read to load the info. 57 path string 58 59 // initialized signifies whether the info has been written. 60 initialized bool 61 62 // created signifies whether the info was returned from 63 // a CreateInfo call. 64 created bool 65 66 name string 67 user string 68 credentials string 69 environmentUUID string 70 apiEndpoints []string 71 apiHostnames []string 72 caCert string 73 bootstrapConfig map[string]interface{} 74 } 75 76 // NewDisk returns a ConfigStorage implementation that stores configuration in 77 // the given directory. The parent of the directory must already exist; the 78 // directory itself is created if it doesn't already exist. 79 func NewDisk(dir string) (Storage, error) { 80 if _, err := os.Stat(dir); err != nil { 81 return nil, err 82 } 83 if err := os.MkdirAll(dir, 0755); err != nil { 84 return nil, err 85 } 86 d := &diskStore{ 87 dir: filepath.Join(dir, "environments"), 88 } 89 if err := d.mkEnvironmentsDir(); err != nil { 90 return nil, err 91 } 92 return d, nil 93 } 94 95 func (d *diskStore) mkEnvironmentsDir() error { 96 err := os.Mkdir(d.dir, 0700) 97 if os.IsExist(err) { 98 return nil 99 } 100 logger.Debugf("Made dir %v", d.dir) 101 return err 102 } 103 104 // CreateInfo implements Storage.CreateInfo. 105 func (d *diskStore) CreateInfo(envName string) EnvironInfo { 106 return &environInfo{ 107 environmentDir: d.dir, 108 created: true, 109 name: envName, 110 } 111 } 112 113 // List implements Storage.List 114 func (d *diskStore) List() ([]string, error) { 115 116 // awkward - list both jenv files and connection files. 117 118 var envs []string 119 files, err := filepath.Glob(d.dir + "/*" + jenvExtension) 120 if err != nil { 121 return nil, err 122 } 123 for _, file := range files { 124 fName := filepath.Base(file) 125 name := fName[:len(fName)-len(jenvExtension)] 126 envs = append(envs, name) 127 } 128 return envs, nil 129 } 130 131 // ReadInfo implements Storage.ReadInfo. 132 func (d *diskStore) ReadInfo(envName string) (EnvironInfo, error) { 133 // TODO: first try the new format, and if it doesn't exist, read the old format. 134 // NOTE: any reading or writing from the directory should be done with a fslock 135 // to make sure we have a consistent read or write. Also worth noting, we should 136 // use a very short timeout. 137 138 lock, err := fslock.NewLock(d.dir, lockName) 139 if err != nil { 140 return nil, errors.Trace(err) 141 } 142 err = lock.LockWithTimeout(lockTimeout, "reading") 143 if err != nil { 144 return nil, errors.Annotatef(err, "cannot read info") 145 } 146 defer lock.Unlock() 147 148 info, err := d.readConnectionFile(envName) 149 if err != nil { 150 if errors.IsNotFound(err) { 151 info, err = d.readJENVFile(envName) 152 } 153 } 154 if err != nil { 155 return nil, errors.Trace(err) 156 } 157 info.environmentDir = d.dir 158 return info, nil 159 } 160 161 func (d *diskStore) readConnectionFile(envName string) (*environInfo, error) { 162 return nil, errors.NotFoundf("connection file") 163 } 164 165 // Initialized implements EnvironInfo.Initialized. 166 func (info *environInfo) Initialized() bool { 167 info.mu.Lock() 168 defer info.mu.Unlock() 169 return info.initialized 170 } 171 172 // BootstrapConfig implements EnvironInfo.BootstrapConfig. 173 func (info *environInfo) BootstrapConfig() map[string]interface{} { 174 info.mu.Lock() 175 defer info.mu.Unlock() 176 return info.bootstrapConfig 177 } 178 179 // APICredentials implements EnvironInfo.APICredentials. 180 func (info *environInfo) APICredentials() APICredentials { 181 info.mu.Lock() 182 defer info.mu.Unlock() 183 return APICredentials{ 184 User: info.user, 185 Password: info.credentials, 186 } 187 } 188 189 // APIEndpoint implements EnvironInfo.APIEndpoint. 190 func (info *environInfo) APIEndpoint() APIEndpoint { 191 info.mu.Lock() 192 defer info.mu.Unlock() 193 return APIEndpoint{ 194 Addresses: info.apiEndpoints, 195 Hostnames: info.apiHostnames, 196 CACert: info.caCert, 197 EnvironUUID: info.environmentUUID, 198 } 199 } 200 201 // SetBootstrapConfig implements EnvironInfo.SetBootstrapConfig. 202 func (info *environInfo) SetBootstrapConfig(attrs map[string]interface{}) { 203 info.mu.Lock() 204 defer info.mu.Unlock() 205 if !info.created { 206 panic("bootstrap config set on environment info that has not just been created") 207 } 208 info.bootstrapConfig = attrs 209 } 210 211 // SetAPIEndpoint implements EnvironInfo.SetAPIEndpoint. 212 func (info *environInfo) SetAPIEndpoint(endpoint APIEndpoint) { 213 info.mu.Lock() 214 defer info.mu.Unlock() 215 info.apiEndpoints = endpoint.Addresses 216 info.apiHostnames = endpoint.Hostnames 217 info.caCert = endpoint.CACert 218 info.environmentUUID = endpoint.EnvironUUID 219 } 220 221 // SetAPICredentials implements EnvironInfo.SetAPICredentials. 222 func (info *environInfo) SetAPICredentials(creds APICredentials) { 223 info.mu.Lock() 224 defer info.mu.Unlock() 225 info.user = creds.User 226 info.credentials = creds.Password 227 } 228 229 // Location returns the location of the environInfo in human readable format. 230 func (info *environInfo) Location() string { 231 info.mu.Lock() 232 defer info.mu.Unlock() 233 return fmt.Sprintf("file %q", info.path) 234 } 235 236 // Write implements EnvironInfo.Write. 237 func (info *environInfo) Write() error { 238 info.mu.Lock() 239 defer info.mu.Unlock() 240 lock, err := fslock.NewLock(info.environmentDir, lockName) 241 if err != nil { 242 return errors.Trace(err) 243 } 244 err = lock.LockWithTimeout(lockTimeout, "writing") 245 if err != nil { 246 return errors.Annotatef(err, "cannot write info") 247 } 248 defer lock.Unlock() 249 250 if err := info.writeJENVFile(); err != nil { 251 return errors.Trace(err) 252 } 253 254 info.initialized = true 255 return nil 256 } 257 258 // Destroy implements EnvironInfo.Destroy. 259 func (info *environInfo) Destroy() error { 260 info.mu.Lock() 261 defer info.mu.Unlock() 262 if info.initialized { 263 err := os.Remove(info.path) 264 if os.IsNotExist(err) { 265 return errors.New("environment info has already been removed") 266 } 267 return err 268 } 269 return nil 270 } 271 272 const jenvExtension = ".jenv" 273 274 func jenvFilename(basedir, envName string) string { 275 return filepath.Join(basedir, envName+jenvExtension) 276 } 277 278 func (d *diskStore) readJENVFile(envName string) (*environInfo, error) { 279 path := jenvFilename(d.dir, envName) 280 data, err := ioutil.ReadFile(path) 281 if err != nil { 282 if os.IsNotExist(err) { 283 return nil, errors.NotFoundf("environment %q", envName) 284 } 285 return nil, err 286 } 287 var info environInfo 288 info.path = path 289 if len(data) == 0 { 290 return &info, nil 291 } 292 var values EnvironInfoData 293 if err := goyaml.Unmarshal(data, &values); err != nil { 294 return nil, errors.Annotatef(err, "error unmarshalling %q", path) 295 } 296 info.name = envName 297 info.user = values.User 298 info.credentials = values.Password 299 info.environmentUUID = values.EnvironUUID 300 info.caCert = values.CACert 301 info.apiEndpoints = values.StateServers 302 info.apiHostnames = values.ServerHostnames 303 info.bootstrapConfig = values.Config 304 305 info.initialized = true 306 return &info, nil 307 } 308 309 // Kept primarily for testing purposes now. 310 func (info *environInfo) writeJENVFile() error { 311 312 infoData := EnvironInfoData{ 313 User: info.user, 314 Password: info.credentials, 315 EnvironUUID: info.environmentUUID, 316 StateServers: info.apiEndpoints, 317 ServerHostnames: info.apiHostnames, 318 CACert: info.caCert, 319 Config: info.bootstrapConfig, 320 } 321 322 data, err := goyaml.Marshal(infoData) 323 if err != nil { 324 return errors.Annotate(err, "cannot marshal environment info") 325 } 326 // We now use a fslock to sync reads and writes across the environment, 327 // so we don't need to use a temporary file any more. 328 329 flags := os.O_WRONLY 330 if info.created { 331 flags |= os.O_CREATE | os.O_EXCL 332 } else { 333 flags |= os.O_TRUNC 334 } 335 path := jenvFilename(info.environmentDir, info.name) 336 logger.Debugf("writing jenv file to %s", path) 337 file, err := os.OpenFile(path, flags, 0600) 338 if os.IsExist(err) { 339 return ErrEnvironInfoAlreadyExists 340 } 341 342 _, err = file.Write(data) 343 file.Close() 344 info.path = path 345 return errors.Annotate(err, "cannot write file") 346 }