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  }