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