github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 "github.com/juju/utils" 15 "launchpad.net/goyaml" 16 17 "github.com/juju/juju/juju/osenv" 18 ) 19 20 var logger = loggo.GetLogger("juju.environs.configstore") 21 22 // Default returns disk-based environment config storage 23 // rooted at JujuHome. 24 func Default() (Storage, error) { 25 return NewDisk(osenv.JujuHome()) 26 } 27 28 type diskStore struct { 29 dir string 30 } 31 32 type EnvironInfoData struct { 33 User string 34 Password string 35 EnvironUUID string `json:"environ-uuid,omitempty" yaml:"environ-uuid,omitempty"` 36 StateServers []string `json:"state-servers" yaml:"state-servers"` 37 CACert string `json:"ca-cert" yaml:"ca-cert"` 38 Config map[string]interface{} `json:"bootstrap-config,omitempty" yaml:"bootstrap-config,omitempty"` 39 } 40 41 type environInfo struct { 42 path string 43 // initialized signifies whether the info has been written. 44 initialized bool 45 46 // created signifies whether the info was returned from 47 // a CreateInfo call. 48 created bool 49 50 EnvInfo EnvironInfoData 51 } 52 53 // NewDisk returns a ConfigStorage implementation that stores 54 // configuration in the given directory. The parent of the directory 55 // must already exist; the directory itself is created on demand. 56 func NewDisk(dir string) (Storage, error) { 57 if _, err := os.Stat(dir); err != nil { 58 return nil, err 59 } 60 return &diskStore{dir}, nil 61 } 62 63 func (d *diskStore) envPath(envName string) string { 64 return filepath.Join(d.dir, "environments", envName+".jenv") 65 } 66 67 func (d *diskStore) mkEnvironmentsDir() error { 68 path := filepath.Join(d.dir, "environments") 69 logger.Debugf("Making %v", path) 70 err := os.Mkdir(path, 0700) 71 if os.IsExist(err) { 72 return nil 73 } 74 return err 75 } 76 77 // CreateInfo implements Storage.CreateInfo. 78 func (d *diskStore) CreateInfo(envName string) (EnvironInfo, error) { 79 if err := d.mkEnvironmentsDir(); err != nil { 80 return nil, err 81 } 82 // We create an empty file so that any subsequent CreateInfos 83 // will fail. 84 path := d.envPath(envName) 85 file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) 86 if os.IsExist(err) { 87 return nil, ErrEnvironInfoAlreadyExists 88 } 89 if err != nil { 90 return nil, err 91 } 92 file.Close() 93 return &environInfo{ 94 created: true, 95 path: path, 96 }, nil 97 } 98 99 // ReadInfo implements Storage.ReadInfo. 100 func (d *diskStore) ReadInfo(envName string) (EnvironInfo, error) { 101 path := d.envPath(envName) 102 data, err := ioutil.ReadFile(path) 103 if err != nil { 104 if os.IsNotExist(err) { 105 return nil, errors.NotFoundf("environment %q", envName) 106 } 107 return nil, err 108 } 109 var info environInfo 110 info.path = path 111 if len(data) == 0 { 112 return &info, nil 113 } 114 if err := goyaml.Unmarshal(data, &info.EnvInfo); err != nil { 115 return nil, fmt.Errorf("error unmarshalling %q: %v", path, err) 116 } 117 info.initialized = true 118 return &info, nil 119 } 120 121 // Initialized implements EnvironInfo.Initialized. 122 func (info *environInfo) Initialized() bool { 123 return info.initialized 124 } 125 126 // BootstrapConfig implements EnvironInfo.BootstrapConfig. 127 func (info *environInfo) BootstrapConfig() map[string]interface{} { 128 return info.EnvInfo.Config 129 } 130 131 // APICredentials implements EnvironInfo.APICredentials. 132 func (info *environInfo) APICredentials() APICredentials { 133 return APICredentials{ 134 User: info.EnvInfo.User, 135 Password: info.EnvInfo.Password, 136 } 137 } 138 139 // APIEndpoint implements EnvironInfo.APIEndpoint. 140 func (info *environInfo) APIEndpoint() APIEndpoint { 141 return APIEndpoint{ 142 Addresses: info.EnvInfo.StateServers, 143 CACert: info.EnvInfo.CACert, 144 EnvironUUID: info.EnvInfo.EnvironUUID, 145 } 146 } 147 148 // SetBootstrapConfig implements EnvironInfo.SetBootstrapConfig. 149 func (info *environInfo) SetBootstrapConfig(attrs map[string]interface{}) { 150 if !info.created { 151 panic("bootstrap config set on environment info that has not just been created") 152 } 153 info.EnvInfo.Config = attrs 154 } 155 156 // SetAPIEndpoint implements EnvironInfo.SetAPIEndpoint. 157 func (info *environInfo) SetAPIEndpoint(endpoint APIEndpoint) { 158 info.EnvInfo.StateServers = endpoint.Addresses 159 info.EnvInfo.CACert = endpoint.CACert 160 info.EnvInfo.EnvironUUID = endpoint.EnvironUUID 161 } 162 163 // SetAPICredentials implements EnvironInfo.SetAPICredentials. 164 func (info *environInfo) SetAPICredentials(creds APICredentials) { 165 info.EnvInfo.User = creds.User 166 info.EnvInfo.Password = creds.Password 167 } 168 169 // Location returns the location of the environInfo in human readable format. 170 func (info *environInfo) Location() string { 171 return fmt.Sprintf("file %q", info.path) 172 } 173 174 // Write implements EnvironInfo.Write. 175 func (info *environInfo) Write() error { 176 data, err := goyaml.Marshal(info.EnvInfo) 177 if err != nil { 178 return errors.Annotate(err, "cannot marshal environment info") 179 } 180 // Create a temporary file and rename it, so that the data 181 // changes atomically. 182 parent, _ := filepath.Split(info.path) 183 tmpFile, err := ioutil.TempFile(parent, "") 184 if err != nil { 185 return errors.Annotate(err, "cannot create temporary file") 186 } 187 _, err = tmpFile.Write(data) 188 // N.B. We need to close the file before renaming it 189 // otherwise it will fail under Windows with a file-in-use 190 // error. 191 tmpFile.Close() 192 if err != nil { 193 return errors.Annotate(err, "cannot write temporary file") 194 } 195 if err := utils.ReplaceFile(tmpFile.Name(), info.path); err != nil { 196 os.Remove(tmpFile.Name()) 197 return errors.Annotate(err, "cannot rename new environment info file") 198 } 199 info.initialized = true 200 return nil 201 } 202 203 // Destroy implements EnvironInfo.Destroy. 204 func (info *environInfo) Destroy() error { 205 err := os.Remove(info.path) 206 if os.IsNotExist(err) { 207 return fmt.Errorf("environment info has already been removed") 208 } 209 return err 210 }