github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/configstore/cachefile.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package configstore 5 6 import ( 7 "io/ioutil" 8 "os" 9 10 "github.com/juju/errors" 11 goyaml "gopkg.in/yaml.v1" 12 ) 13 14 // CacheFile represents the YAML structure of the file 15 // $JUJU_HOME/environments/cache.yaml 16 type CacheFile struct { 17 // Server maps the name of the server to the server-uuid 18 Server map[string]ServerUser `yaml:"server-user"` 19 // ServerData is a map of server-uuid to the data for that server. 20 ServerData map[string]ServerData `yaml:"server-data"` 21 // Environment maps the local name of the environment to the details 22 // for that environment 23 Environment map[string]EnvironmentData `yaml:"environment"` 24 } 25 26 // ServerUser represents a user on a server, but not an environment on that 27 // server. Used for server based commands like login, list and use. 28 type ServerUser struct { 29 ServerUUID string `yaml:"server-uuid"` 30 User string `yaml:"user"` 31 } 32 33 // ServerData holds the end point details for the API servers running 34 // in the state server environment. 35 type ServerData struct { 36 APIEndpoints []string `yaml:"api-endpoints"` 37 ServerHostnames []string `yaml:"server-hostnames,omitempty"` 38 CACert string `yaml:"ca-cert"` 39 // Identities is a mapping of full username to credentials. 40 Identities map[string]string `yaml:"identities"` 41 BootstrapConfig map[string]interface{} `yaml:"bootstrap-config,omitempty"` 42 } 43 44 // EnvironmentData represents a single environment running in a Juju 45 // Environment Server. 46 type EnvironmentData struct { 47 User string `yaml:"user"` 48 EnvironmentUUID string `yaml:"env-uuid"` 49 ServerUUID string `yaml:"server-uuid"` 50 } 51 52 // All synchronisation locking is expected to be done outside the 53 // read and write methods. 54 func readCacheFile(filename string) (CacheFile, error) { 55 data, err := ioutil.ReadFile(filename) 56 var content CacheFile 57 if err != nil { 58 if os.IsNotExist(err) { 59 // If the file doesn't exist, then we return an empty 60 // CacheFile. 61 return CacheFile{ 62 Server: make(map[string]ServerUser), 63 ServerData: make(map[string]ServerData), 64 Environment: make(map[string]EnvironmentData), 65 }, nil 66 } 67 return content, err 68 } 69 if err := goyaml.Unmarshal(data, &content); err != nil { 70 return content, errors.Annotatef(err, "error unmarshalling %q", filename) 71 } 72 return content, nil 73 } 74 75 func writeCacheFile(filename string, content CacheFile) error { 76 data, err := goyaml.Marshal(content) 77 if err != nil { 78 return errors.Annotate(err, "cannot marshal cache file") 79 } 80 err = ioutil.WriteFile(filename, data, 0600) 81 return errors.Annotate(err, "cannot write file") 82 } 83 84 func (cache CacheFile) readInfo(envName string) (*environInfo, error) { 85 info := &environInfo{ 86 name: envName, 87 source: sourceCache, 88 } 89 var srvData ServerData 90 if envData, ok := cache.Environment[envName]; ok { 91 srvData, ok = cache.ServerData[envData.ServerUUID] 92 if !ok { 93 return nil, errors.Errorf("missing server data for environment %q", envName) 94 } 95 info.user = envData.User 96 info.environmentUUID = envData.EnvironmentUUID 97 info.serverUUID = envData.ServerUUID 98 } else { 99 srvUser, ok := cache.Server[envName] 100 if !ok { 101 return nil, errors.NotFoundf("environment %q", envName) 102 } 103 srvData, ok = cache.ServerData[srvUser.ServerUUID] 104 if !ok { 105 return nil, errors.Errorf("missing server data for environment %q", envName) 106 } 107 info.user = srvUser.User 108 info.serverUUID = srvUser.ServerUUID 109 } 110 111 info.credentials = srvData.Identities[info.user] 112 info.caCert = srvData.CACert 113 info.apiEndpoints = srvData.APIEndpoints 114 info.apiHostnames = srvData.ServerHostnames 115 if info.serverUUID == info.environmentUUID { 116 info.bootstrapConfig = srvData.BootstrapConfig 117 } 118 return info, nil 119 } 120 121 func (cache *CacheFile) updateInfo(info *environInfo) error { 122 // If the info is new, then check for name clashes. 123 if info.source == sourceCreated { 124 if _, found := cache.Environment[info.name]; found { 125 return ErrEnvironInfoAlreadyExists 126 } 127 if server, found := cache.Server[info.name]; found { 128 // Error if we are not trying to add 129 // in the initial environment for this 130 // server. 131 if info.environmentUUID != server.ServerUUID { 132 return ErrEnvironInfoAlreadyExists 133 } 134 } 135 } 136 137 // If the serverUUID and environmentUUID are the same, or the 138 // environmentUUID is not specified, then add a name entry 139 // under the server. 140 serverUser := ServerUser{ 141 User: info.user, 142 ServerUUID: info.serverUUID, 143 } 144 if info.environmentUUID == "" || info.environmentUUID == info.serverUUID { 145 cache.Server[info.name] = serverUser 146 } 147 148 // Only add environment entries if the environmentUUID was specified 149 if info.environmentUUID != "" { 150 cache.Environment[info.name] = EnvironmentData{ 151 User: info.user, 152 EnvironmentUUID: info.environmentUUID, 153 ServerUUID: info.serverUUID, 154 } 155 } 156 157 // Check to see if the cache file has some info for the server already. 158 serverData := cache.ServerData[info.serverUUID] 159 serverData.APIEndpoints = info.apiEndpoints 160 serverData.ServerHostnames = info.apiHostnames 161 serverData.CACert = info.caCert 162 if info.bootstrapConfig != nil { 163 serverData.BootstrapConfig = info.bootstrapConfig 164 } 165 if serverData.Identities == nil { 166 serverData.Identities = make(map[string]string) 167 } 168 serverData.Identities[info.user] = info.credentials 169 cache.ServerData[info.serverUUID] = serverData 170 return nil 171 } 172 173 func (cache *CacheFile) removeInfo(info *environInfo) error { 174 if srvUser, srvFound := cache.Server[info.name]; srvFound { 175 cache.cleanupAllServerReferences(srvUser.ServerUUID) 176 return nil 177 } 178 envUser, envFound := cache.Environment[info.name] 179 if !envFound { 180 return errors.New("environment info has already been removed") 181 } 182 serverUUID := envUser.ServerUUID 183 184 delete(cache.Environment, info.name) 185 delete(cache.Server, info.name) 186 // Look to see if there are any other environments using the serverUUID. 187 // If there aren't, then we also clean up the server data, otherwise we 188 // need to leave the server data there. 189 for _, envUser := range cache.Environment { 190 if envUser.ServerUUID == serverUUID { 191 return nil 192 } 193 } 194 delete(cache.ServerData, serverUUID) 195 return nil 196 } 197 198 func (cache *CacheFile) cleanupAllServerReferences(serverUUID string) { 199 // NOTE: it is safe in Go to remove elements from a map while iterating. 200 for name, envUser := range cache.Environment { 201 if envUser.ServerUUID == serverUUID { 202 delete(cache.Environment, name) 203 } 204 } 205 for name, srvUser := range cache.Server { 206 if srvUser.ServerUUID == serverUUID { 207 delete(cache.Server, name) 208 } 209 } 210 delete(cache.ServerData, serverUUID) 211 }