github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/jujuclient/file.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package jujuclient provides functionality to support
     5  // connections to Juju such as controllers cache, accounts cache, etc.
     6  
     7  package jujuclient
     8  
     9  import (
    10  	"crypto/sha256"
    11  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/juju/clock"
    19  	"github.com/juju/collections/set"
    20  	"github.com/juju/errors"
    21  	"github.com/juju/mutex/v2"
    22  	cookiejar "github.com/juju/persistent-cookiejar"
    23  
    24  	"github.com/juju/juju/cloud"
    25  	"github.com/juju/juju/core/model"
    26  	"github.com/juju/juju/juju/osenv"
    27  )
    28  
    29  var (
    30  	_ ClientStore = (*store)(nil)
    31  
    32  	// A second should be enough to write or read any files. But
    33  	// some disks are slow when under load, so lets give the disk a
    34  	// reasonable time to get the lock.
    35  	lockTimeout = 5 * time.Second
    36  )
    37  
    38  // NewFileClientStore returns a new filesystem-based client store
    39  // that manages files in $XDG_DATA_HOME/juju.
    40  func NewFileClientStore() ClientStore {
    41  	return &store{
    42  		lockName: generateStoreLockName(),
    43  	}
    44  }
    45  
    46  // NewFileCredentialStore returns a new filesystem-based credentials store
    47  // that manages credentials in $XDG_DATA_HOME/juju.
    48  func NewFileCredentialStore() CredentialStore {
    49  	return &store{
    50  		lockName: generateStoreLockName(),
    51  	}
    52  }
    53  
    54  type store struct {
    55  	lockName string
    56  }
    57  
    58  // generateStoreLockName uses part of the hash of the controller path as the
    59  // name of the lock. This is to avoid contention between multiple users on a
    60  // single machine with different controller files, but also helps with
    61  // contention in tests.
    62  func generateStoreLockName() string {
    63  	h := sha256.New()
    64  	_, _ = h.Write([]byte(JujuControllersPath()))
    65  	fullHash := fmt.Sprintf("%x", h.Sum(nil))
    66  	return fmt.Sprintf("store-lock-%x", fullHash[:8])
    67  }
    68  
    69  func (s *store) acquireLock() (mutex.Releaser, error) {
    70  	spec := mutex.Spec{
    71  		Name:    s.lockName,
    72  		Clock:   clock.WallClock,
    73  		Delay:   20 * time.Millisecond,
    74  		Timeout: lockTimeout,
    75  	}
    76  	releaser, err := mutex.Acquire(spec)
    77  	if err != nil {
    78  		return nil, errors.Trace(err)
    79  	}
    80  	return releaser, nil
    81  }
    82  
    83  // AllControllers implements ControllersGetter.
    84  func (s *store) AllControllers() (map[string]ControllerDetails, error) {
    85  	releaser, err := s.acquireLock()
    86  	if err != nil {
    87  		return nil, errors.Annotate(err,
    88  			"cannot acquire lock file to read all the controllers",
    89  		)
    90  	}
    91  	defer releaser.Release()
    92  	controllers, err := ReadControllersFile(JujuControllersPath())
    93  	if err != nil {
    94  		return nil, errors.Trace(err)
    95  	}
    96  	return controllers.Controllers, nil
    97  }
    98  
    99  // CurrentController implements ControllersGetter.
   100  func (s *store) CurrentController() (string, error) {
   101  	releaser, err := s.acquireLock()
   102  	if err != nil {
   103  		return "", errors.Annotate(err,
   104  			"cannot acquire lock file to get the current controller name",
   105  		)
   106  	}
   107  	defer releaser.Release()
   108  	controllers, err := ReadControllersFile(JujuControllersPath())
   109  	if err != nil {
   110  		return "", errors.Trace(err)
   111  	}
   112  	if controllers.CurrentController == "" {
   113  		return "", errors.NotFoundf("current controller")
   114  	}
   115  	return controllers.CurrentController, nil
   116  }
   117  
   118  // ControllerByName implements ControllersGetter.
   119  func (s *store) ControllerByName(name string) (*ControllerDetails, error) {
   120  	if err := ValidateControllerName(name); err != nil {
   121  		return nil, errors.Trace(err)
   122  	}
   123  
   124  	releaser, err := s.acquireLock()
   125  	if err != nil {
   126  		return nil, errors.Annotatef(err,
   127  			"cannot acquire lock file to read controller %s", name,
   128  		)
   129  	}
   130  	defer releaser.Release()
   131  
   132  	controllers, err := ReadControllersFile(JujuControllersPath())
   133  	if err != nil {
   134  		return nil, errors.Trace(err)
   135  	}
   136  	if result, ok := controllers.Controllers[name]; ok {
   137  		return &result, nil
   138  	}
   139  	return nil, errors.NotFoundf("controller %s", name)
   140  }
   141  
   142  // ControllerByEndpoints implements ControllersGetter.
   143  func (s *store) ControllerByAPIEndpoints(endpoints ...string) (*ControllerDetails, string, error) {
   144  	releaser, err := s.acquireLock()
   145  	if err != nil {
   146  		return nil, "", errors.Annotatef(err, "cannot acquire lock file to read controllers")
   147  	}
   148  	defer releaser.Release()
   149  
   150  	controllers, err := ReadControllersFile(JujuControllersPath())
   151  	if err != nil {
   152  		return nil, "", errors.Trace(err)
   153  	}
   154  
   155  	matchEps := set.NewStrings(endpoints...)
   156  	for name, ctrl := range controllers.Controllers {
   157  		if matchEps.Intersection(set.NewStrings(ctrl.APIEndpoints...)).IsEmpty() {
   158  			continue
   159  		}
   160  
   161  		return &ctrl, name, nil
   162  	}
   163  	return nil, "", errors.NotFoundf("controller with API endpoints %v", endpoints)
   164  }
   165  
   166  // AddController implements ControllerUpdater.
   167  func (s *store) AddController(name string, details ControllerDetails) error {
   168  	if err := ValidateControllerName(name); err != nil {
   169  		return errors.Trace(err)
   170  	}
   171  	if err := ValidateControllerDetails(details); err != nil {
   172  		return errors.Trace(err)
   173  	}
   174  
   175  	releaser, err := s.acquireLock()
   176  	if err != nil {
   177  		return errors.Annotatef(err,
   178  			"cannot acquire lock file to add controller %s", name,
   179  		)
   180  	}
   181  	defer releaser.Release()
   182  
   183  	all, err := ReadControllersFile(JujuControllersPath())
   184  	if err != nil {
   185  		return errors.Annotate(err, "cannot get controllers")
   186  	}
   187  
   188  	if len(all.Controllers) == 0 {
   189  		all.Controllers = make(map[string]ControllerDetails)
   190  	}
   191  
   192  	if _, ok := all.Controllers[name]; ok {
   193  		return errors.AlreadyExistsf("controller with name %s", name)
   194  	}
   195  
   196  	for k, v := range all.Controllers {
   197  		if v.ControllerUUID == details.ControllerUUID {
   198  			return errors.AlreadyExistsf("controller with UUID %s (%s)",
   199  				details.ControllerUUID, k)
   200  		}
   201  	}
   202  
   203  	all.Controllers[name] = details
   204  	return WriteControllersFile(all)
   205  }
   206  
   207  // UpdateController implements ControllerUpdater.
   208  func (s *store) UpdateController(name string, details ControllerDetails) error {
   209  	if err := ValidateControllerName(name); err != nil {
   210  		return errors.Trace(err)
   211  	}
   212  	if err := ValidateControllerDetails(details); err != nil {
   213  		return errors.Trace(err)
   214  	}
   215  
   216  	releaser, err := s.acquireLock()
   217  	if err != nil {
   218  		return errors.Annotatef(err,
   219  			"cannot acquire lock file to update controller %s", name,
   220  		)
   221  	}
   222  	defer releaser.Release()
   223  
   224  	all, err := ReadControllersFile(JujuControllersPath())
   225  	if err != nil {
   226  		return errors.Annotate(err, "cannot get controllers")
   227  	}
   228  
   229  	if len(all.Controllers) == 0 {
   230  		return errors.NotFoundf("controllers")
   231  	}
   232  
   233  	for k, v := range all.Controllers {
   234  		if v.ControllerUUID == details.ControllerUUID && k != name {
   235  			return errors.AlreadyExistsf("controller %s with UUID %s",
   236  				k, v.ControllerUUID)
   237  		}
   238  	}
   239  
   240  	if _, ok := all.Controllers[name]; !ok {
   241  		return errors.NotFoundf("controller %s", name)
   242  	}
   243  
   244  	all.Controllers[name] = details
   245  	return WriteControllersFile(all)
   246  }
   247  
   248  // SetCurrentController implements ControllerUpdater.
   249  func (s *store) SetCurrentController(name string) error {
   250  	if err := ValidateControllerName(name); err != nil {
   251  		return errors.Trace(err)
   252  	}
   253  
   254  	releaser, err := s.acquireLock()
   255  	if err != nil {
   256  		return errors.Annotate(err,
   257  			"cannot acquire lock file to set the current controller name",
   258  		)
   259  	}
   260  	defer releaser.Release()
   261  
   262  	controllers, err := ReadControllersFile(JujuControllersPath())
   263  	if err != nil {
   264  		return errors.Trace(err)
   265  	}
   266  	if _, ok := controllers.Controllers[name]; !ok {
   267  		return errors.NotFoundf("controller %v", name)
   268  	}
   269  	if controllers.CurrentController == name {
   270  		return nil
   271  	}
   272  	controllers.CurrentController = name
   273  	return WriteControllersFile(controllers)
   274  }
   275  
   276  // RemoveController implements ControllersRemover
   277  func (s *store) RemoveController(name string) error {
   278  	if err := ValidateControllerName(name); err != nil {
   279  		return errors.Trace(err)
   280  	}
   281  
   282  	releaser, err := s.acquireLock()
   283  	if err != nil {
   284  		return errors.Annotatef(err,
   285  			"cannot acquire lock file to remove controller %s", name,
   286  		)
   287  	}
   288  	defer releaser.Release()
   289  
   290  	controllers, err := ReadControllersFile(JujuControllersPath())
   291  	if err != nil {
   292  		return errors.Annotate(err, "cannot get controllers")
   293  	}
   294  
   295  	// We remove all controllers with the same UUID as the named one.
   296  	namedControllerDetails, ok := controllers.Controllers[name]
   297  	if !ok {
   298  		return nil
   299  	}
   300  	var names []string
   301  	for name, details := range controllers.Controllers {
   302  		if details.ControllerUUID == namedControllerDetails.ControllerUUID {
   303  			names = append(names, name)
   304  			delete(controllers.Controllers, name)
   305  			if controllers.CurrentController == name {
   306  				controllers.CurrentController = ""
   307  			}
   308  		}
   309  	}
   310  
   311  	// Remove models for the controller.
   312  	controllerModels, err := ReadModelsFile(JujuModelsPath())
   313  	if err != nil {
   314  		return errors.Trace(err)
   315  	}
   316  	for _, name := range names {
   317  		if _, ok := controllerModels[name]; ok {
   318  			delete(controllerModels, name)
   319  			if err := WriteModelsFile(controllerModels); err != nil {
   320  				return errors.Trace(err)
   321  			}
   322  		}
   323  	}
   324  
   325  	// Remove accounts for the controller.
   326  	controllerAccounts, err := ReadAccountsFile(JujuAccountsPath())
   327  	if err != nil {
   328  		return errors.Trace(err)
   329  	}
   330  	for _, name := range names {
   331  		if _, ok := controllerAccounts[name]; ok {
   332  			delete(controllerAccounts, name)
   333  			if err := WriteAccountsFile(controllerAccounts); err != nil {
   334  				return errors.Trace(err)
   335  			}
   336  		}
   337  	}
   338  
   339  	// Remove bootstrap config for the controller.
   340  	bootstrapConfigurations, err := ReadBootstrapConfigFile(JujuBootstrapConfigPath())
   341  	if err != nil {
   342  		return errors.Trace(err)
   343  	}
   344  	for _, name := range names {
   345  		if _, ok := bootstrapConfigurations[name]; ok {
   346  			delete(bootstrapConfigurations, name)
   347  			if err := WriteBootstrapConfigFile(bootstrapConfigurations); err != nil {
   348  				return errors.Trace(err)
   349  			}
   350  		}
   351  	}
   352  
   353  	// Remove the controller cookie jars.
   354  	for _, name := range names {
   355  		err := os.Remove(JujuCookiePath(name))
   356  		if err != nil && !os.IsNotExist(err) {
   357  			return errors.Trace(err)
   358  		}
   359  	}
   360  
   361  	// Finally, remove the controllers. This must be done last
   362  	// so we don't end up with dangling entries in other files.
   363  	return WriteControllersFile(controllers)
   364  }
   365  
   366  // UpdateModel implements ModelUpdater.
   367  func (s *store) UpdateModel(controllerName, modelName string, details ModelDetails) error {
   368  	if err := ValidateControllerName(controllerName); err != nil {
   369  		return errors.Trace(err)
   370  	}
   371  	if err := ValidateModel(modelName, details); err != nil {
   372  		return errors.Trace(err)
   373  	}
   374  
   375  	releaser, err := s.acquireLock()
   376  	if err != nil {
   377  		return errors.Annotatef(err,
   378  			"cannot acquire lock file for updating model %s on controller %s", modelName, controllerName,
   379  		)
   380  	}
   381  	defer releaser.Release()
   382  
   383  	return errors.Trace(updateModels(
   384  		controllerName,
   385  		func(models *ControllerModels) (bool, error) {
   386  			oldDetails, ok := models.Models[modelName]
   387  			if ok && details == oldDetails {
   388  				return false, nil
   389  			}
   390  			if ok && oldDetails.ModelType == "" && details.ModelType != model.IAAS ||
   391  				oldDetails.ModelType != "" && oldDetails.ModelType != details.ModelType {
   392  				oldModelType := oldDetails.ModelType
   393  				if oldModelType == "" {
   394  					oldModelType = model.IAAS
   395  				}
   396  				return false, errors.Errorf(
   397  					"model type was %q, cannot change to %q", oldModelType, details.ModelType)
   398  			}
   399  			models.Models[modelName] = details
   400  			return true, nil
   401  		},
   402  	))
   403  }
   404  
   405  // SetCurrentModel implements ModelUpdater.
   406  func (s *store) SetCurrentModel(controllerName, modelName string) error {
   407  	if err := ValidateControllerName(controllerName); err != nil {
   408  		return errors.Trace(err)
   409  	}
   410  
   411  	releaser, err := s.acquireLock()
   412  	if err != nil {
   413  		return errors.Annotatef(err,
   414  			"cannot acquire lock file for setting current model %s on controller %s", modelName, controllerName,
   415  		)
   416  	}
   417  	defer releaser.Release()
   418  
   419  	if modelName != "" {
   420  		if err := ValidateModelName(modelName); err != nil {
   421  			return errors.Trace(err)
   422  		}
   423  	}
   424  	return errors.Trace(updateModels(
   425  		controllerName,
   426  		func(models *ControllerModels) (bool, error) {
   427  			if modelName == "" {
   428  				// We just want to reset
   429  				models.CurrentModel = ""
   430  				return true, nil
   431  			}
   432  			if _, ok := models.Models[modelName]; !ok {
   433  				return false, errors.NotFoundf(
   434  					"model %s:%s",
   435  					controllerName,
   436  					modelName,
   437  				)
   438  			}
   439  			models.CurrentModel = modelName
   440  			return true, nil
   441  		},
   442  	))
   443  }
   444  
   445  // AllModels implements ModelGetter.
   446  func (s *store) AllModels(controllerName string) (map[string]ModelDetails, error) {
   447  	if err := ValidateControllerName(controllerName); err != nil {
   448  		return nil, errors.Trace(err)
   449  	}
   450  
   451  	releaser, err := s.acquireLock()
   452  	if err != nil {
   453  		return nil, errors.Annotatef(err,
   454  			"cannot acquire lock file for getting all models for controller %s", controllerName,
   455  		)
   456  	}
   457  	defer releaser.Release()
   458  
   459  	all, err := ReadModelsFile(JujuModelsPath())
   460  	if err != nil {
   461  		return nil, errors.Trace(err)
   462  	}
   463  	controllerModels, ok := all[controllerName]
   464  	if !ok {
   465  		return nil, errors.NotFoundf(
   466  			"models for controller %s",
   467  			controllerName,
   468  		)
   469  	}
   470  	return controllerModels.Models, nil
   471  }
   472  
   473  // CurrentModel implements ModelGetter.
   474  func (s *store) CurrentModel(controllerName string) (string, error) {
   475  	if err := ValidateControllerName(controllerName); err != nil {
   476  		return "", errors.Trace(err)
   477  	}
   478  
   479  	releaser, err := s.acquireLock()
   480  	if err != nil {
   481  		return "", errors.Annotatef(err,
   482  			"cannot acquire lock file for getting current model for controller %s", controllerName,
   483  		)
   484  	}
   485  	defer releaser.Release()
   486  
   487  	all, err := ReadModelsFile(JujuModelsPath())
   488  	if err != nil {
   489  		return "", errors.Trace(err)
   490  	}
   491  	controllerModels, ok := all[controllerName]
   492  	if !ok {
   493  		return "", errors.NotFoundf(
   494  			"current model for controller %s",
   495  			controllerName,
   496  		)
   497  	}
   498  
   499  	var controller bool
   500  	var modelNames []string
   501  	for ns := range controllerModels.Models {
   502  		name, _, err := SplitModelName(ns)
   503  		if err != nil {
   504  			continue
   505  		}
   506  		if name == "controller" {
   507  			controller = true
   508  			continue
   509  		}
   510  		modelNames = append(modelNames, controllerName+":"+ns)
   511  	}
   512  
   513  	if controllerModels.CurrentModel == "" {
   514  		num := len(modelNames)
   515  		if num == 0 {
   516  			if !controller {
   517  				return "", errors.NotFoundf(
   518  					"current model for controller %s",
   519  					controllerName,
   520  				)
   521  			}
   522  			return "", errors.NewNotFound(nil, `No selected model.
   523  
   524  Only the controller model exists. Use "juju add-model" to create an initial model.
   525  `)
   526  		}
   527  
   528  		msg := `No selected model.
   529  
   530  Use "juju switch" to select one of the following models:
   531  
   532    - ` + strings.Join(modelNames, "\n  - ")
   533  		return "", errors.NewNotFound(nil, msg)
   534  	}
   535  	return controllerModels.CurrentModel, nil
   536  }
   537  
   538  // ModelByName implements ModelGetter.
   539  func (s *store) ModelByName(controllerName, modelName string) (*ModelDetails, error) {
   540  	if err := ValidateControllerName(controllerName); err != nil {
   541  		return nil, errors.Trace(err)
   542  	}
   543  	if err := ValidateModelName(modelName); err != nil {
   544  		return nil, errors.Trace(err)
   545  	}
   546  
   547  	releaser, err := s.acquireLock()
   548  	if err != nil {
   549  		return nil, errors.Annotatef(err,
   550  			"cannot acquire lock file for getting model %s for controller %s", modelName, controllerName,
   551  		)
   552  	}
   553  	defer releaser.Release()
   554  
   555  	all, err := ReadModelsFile(JujuModelsPath())
   556  	if err != nil {
   557  		return nil, errors.Trace(err)
   558  	}
   559  	controllerModels, ok := all[controllerName]
   560  	if !ok {
   561  		return nil, errors.NotFoundf(
   562  			"model %s:%s",
   563  			controllerName,
   564  			modelName,
   565  		)
   566  	}
   567  	details, ok := controllerModels.Models[modelName]
   568  	if !ok {
   569  		return nil, errors.NotFoundf(
   570  			"model %s:%s",
   571  			controllerName,
   572  			modelName,
   573  		)
   574  	}
   575  	return &details, nil
   576  }
   577  
   578  // RemoveModel implements ModelRemover.
   579  func (s *store) RemoveModel(controllerName, modelName string) error {
   580  	if err := ValidateControllerName(controllerName); err != nil {
   581  		return errors.Trace(err)
   582  	}
   583  	if err := ValidateModelName(modelName); err != nil {
   584  		return errors.Trace(err)
   585  	}
   586  
   587  	releaser, err := s.acquireLock()
   588  	if err != nil {
   589  		return errors.Annotatef(err,
   590  			"cannot acquire lock file for removing model %s on controller %s", modelName, controllerName,
   591  		)
   592  	}
   593  	defer releaser.Release()
   594  
   595  	return errors.Trace(updateModels(
   596  		controllerName,
   597  		func(models *ControllerModels) (bool, error) {
   598  			if _, ok := models.Models[modelName]; !ok {
   599  				return false, errors.NotFoundf(
   600  					"model %s:%s",
   601  					controllerName,
   602  					modelName,
   603  				)
   604  			}
   605  			delete(models.Models, modelName)
   606  			return true, nil
   607  		},
   608  	))
   609  }
   610  
   611  type updateModelFunc func(storedModels *ControllerModels) (bool, error)
   612  
   613  func updateModels(controllerName string, update updateModelFunc) error {
   614  	all, err := ReadModelsFile(JujuModelsPath())
   615  	if err != nil {
   616  		return errors.Trace(err)
   617  	}
   618  	controllerModels, ok := all[controllerName]
   619  	if !ok {
   620  		if all == nil {
   621  			all = make(map[string]*ControllerModels)
   622  		}
   623  		controllerModels = &ControllerModels{}
   624  		all[controllerName] = controllerModels
   625  	}
   626  	if controllerModels.Models == nil {
   627  		controllerModels.Models = make(map[string]ModelDetails)
   628  	}
   629  	updated, err := update(controllerModels)
   630  	if err != nil {
   631  		return errors.Trace(err)
   632  	}
   633  	if updated {
   634  		return errors.Trace(WriteModelsFile(all))
   635  	}
   636  	return nil
   637  }
   638  
   639  // SetModels implements ModelUpdater.
   640  func (s *store) SetModels(controllerName string, models map[string]ModelDetails) error {
   641  	if err := ValidateControllerName(controllerName); err != nil {
   642  		return errors.Trace(err)
   643  	}
   644  
   645  	for modelName, details := range models {
   646  		if err := ValidateModel(modelName, details); err != nil {
   647  			return errors.Trace(err)
   648  		}
   649  	}
   650  
   651  	releaser, err := s.acquireLock()
   652  	if err != nil {
   653  		return errors.Annotatef(err,
   654  			"cannot acquire lock file for setting models on controller %s", controllerName,
   655  		)
   656  	}
   657  	defer releaser.Release()
   658  
   659  	err = updateModels(controllerName, func(storedModels *ControllerModels) (bool, error) {
   660  		changed := len(storedModels.Models) != len(models)
   661  		// Add or update controller models based on a new collection.
   662  		for modelName, details := range models {
   663  			oldDetails, ok := storedModels.Models[modelName]
   664  			if ok && details == oldDetails {
   665  				continue
   666  			}
   667  			if details.ActiveBranch == "" {
   668  				if oldDetails.ActiveBranch != "" {
   669  					details.ActiveBranch = oldDetails.ActiveBranch
   670  				} else {
   671  					details.ActiveBranch = model.GenerationMaster
   672  				}
   673  			}
   674  			storedModels.Models[modelName] = details
   675  			changed = true
   676  		}
   677  		// Delete models that are not in the new collection.
   678  		for modelName := range storedModels.Models {
   679  			if _, ok := models[modelName]; !ok {
   680  				delete(storedModels.Models, modelName)
   681  			}
   682  		}
   683  		return changed, nil
   684  	})
   685  	if err != nil {
   686  		return errors.Trace(err)
   687  	}
   688  	return nil
   689  }
   690  
   691  // UpdateAccount implements AccountUpdater.
   692  func (s *store) UpdateAccount(controllerName string, details AccountDetails) error {
   693  	if err := ValidateControllerName(controllerName); err != nil {
   694  		return errors.Trace(err)
   695  	}
   696  	if err := ValidateAccountDetails(details); err != nil {
   697  		return errors.Trace(err)
   698  	}
   699  
   700  	releaser, err := s.acquireLock()
   701  	if err != nil {
   702  		return errors.Annotatef(err,
   703  			"cannot acquire lock file for updating an account on controller %s", controllerName,
   704  		)
   705  	}
   706  	defer releaser.Release()
   707  
   708  	accounts, err := ReadAccountsFile(JujuAccountsPath())
   709  	if err != nil {
   710  		return errors.Trace(err)
   711  	}
   712  	if accounts == nil {
   713  		accounts = make(map[string]AccountDetails)
   714  	}
   715  	if oldDetails, ok := accounts[controllerName]; ok && reflect.DeepEqual(details, oldDetails) {
   716  		return nil
   717  	} else {
   718  		// Only update last known access if it has a value.
   719  		if details.LastKnownAccess == "" {
   720  			details.LastKnownAccess = oldDetails.LastKnownAccess
   721  		}
   722  	}
   723  
   724  	accounts[controllerName] = details
   725  	return errors.Trace(WriteAccountsFile(accounts))
   726  }
   727  
   728  // AccountDetails implements AccountGetter.
   729  func (s *store) AccountDetails(controllerName string) (*AccountDetails, error) {
   730  	if err := ValidateControllerName(controllerName); err != nil {
   731  		return nil, errors.Trace(err)
   732  	}
   733  
   734  	releaser, err := s.acquireLock()
   735  	if err != nil {
   736  		return nil, errors.Annotatef(err,
   737  			"cannot acquire lock file for getting an account details on controller %s", controllerName,
   738  		)
   739  	}
   740  	defer releaser.Release()
   741  
   742  	accounts, err := ReadAccountsFile(JujuAccountsPath())
   743  	if err != nil {
   744  		return nil, errors.Trace(err)
   745  	}
   746  	details, ok := accounts[controllerName]
   747  	if !ok {
   748  		return nil, errors.NotFoundf("account details for controller %s", controllerName)
   749  	}
   750  	return &details, nil
   751  }
   752  
   753  // RemoveAccount implements AccountRemover.
   754  func (s *store) RemoveAccount(controllerName string) error {
   755  	if err := ValidateControllerName(controllerName); err != nil {
   756  		return errors.Trace(err)
   757  	}
   758  
   759  	releaser, err := s.acquireLock()
   760  	if err != nil {
   761  		return errors.Annotatef(err,
   762  			"cannot acquire lock file for removing an account on controller %s", controllerName,
   763  		)
   764  	}
   765  	defer releaser.Release()
   766  
   767  	accounts, err := ReadAccountsFile(JujuAccountsPath())
   768  	if err != nil {
   769  		return errors.Trace(err)
   770  	}
   771  	if _, ok := accounts[controllerName]; !ok {
   772  		return errors.NotFoundf("account details for controller %s", controllerName)
   773  	}
   774  
   775  	delete(accounts, controllerName)
   776  	return errors.Trace(WriteAccountsFile(accounts))
   777  }
   778  
   779  // UpdateCredential implements CredentialUpdater.
   780  func (s *store) UpdateCredential(cloudName string, details cloud.CloudCredential) error {
   781  	releaser, err := s.acquireLock()
   782  	if err != nil {
   783  		return errors.Annotatef(err,
   784  			"cannot acquire lock file for updating credentials for %s", cloudName,
   785  		)
   786  	}
   787  	defer releaser.Release()
   788  
   789  	credentials, err := ReadCredentialsFile(JujuCredentialsPath())
   790  	if err != nil {
   791  		return errors.Annotate(err, "cannot get credentials")
   792  	}
   793  
   794  	credentials.UpdateCloudCredential(cloudName, details)
   795  	return WriteCredentialsFile(credentials)
   796  }
   797  
   798  // CredentialForCloud implements CredentialGetter.
   799  func (s *store) CredentialForCloud(cloudName string) (*cloud.CloudCredential, error) {
   800  	credentialCollection, err := ReadCredentialsFile(JujuCredentialsPath())
   801  	if err != nil {
   802  		return nil, errors.Trace(err)
   803  	}
   804  	credential, err := credentialCollection.CloudCredential(cloudName)
   805  	if err != nil {
   806  		return nil, errors.Trace(err)
   807  	}
   808  	return credential, nil
   809  }
   810  
   811  // AllCredentials implements CredentialGetter.
   812  func (s *store) AllCredentials() (map[string]cloud.CloudCredential, error) {
   813  	credentialCollection, err := ReadCredentialsFile(JujuCredentialsPath())
   814  	if err != nil {
   815  		return nil, errors.Trace(err)
   816  	}
   817  	cloudNames := credentialCollection.CloudNames()
   818  	cloudCredentials := make(map[string]cloud.CloudCredential)
   819  	for _, cloud := range cloudNames {
   820  		v, err := credentialCollection.CloudCredential(cloud)
   821  		if err != nil {
   822  			return nil, errors.Trace(err)
   823  		}
   824  		cloudCredentials[cloud] = *v
   825  	}
   826  	return cloudCredentials, nil
   827  }
   828  
   829  // UpdateBootstrapConfig implements BootstrapConfigUpdater.
   830  func (s *store) UpdateBootstrapConfig(controllerName string, cfg BootstrapConfig) error {
   831  	if err := ValidateControllerName(controllerName); err != nil {
   832  		return errors.Trace(err)
   833  	}
   834  	if err := ValidateBootstrapConfig(cfg); err != nil {
   835  		return errors.Trace(err)
   836  	}
   837  
   838  	releaser, err := s.acquireLock()
   839  	if err != nil {
   840  		return errors.Annotatef(err,
   841  			"cannot acquire lock file for updating the bootstrap config for controller %s", controllerName,
   842  		)
   843  	}
   844  	defer releaser.Release()
   845  
   846  	all, err := ReadBootstrapConfigFile(JujuBootstrapConfigPath())
   847  	if err != nil {
   848  		return errors.Annotate(err, "cannot get bootstrap config")
   849  	}
   850  
   851  	if all == nil {
   852  		all = make(map[string]BootstrapConfig)
   853  	}
   854  	all[controllerName] = cfg
   855  	return WriteBootstrapConfigFile(all)
   856  }
   857  
   858  // BootstrapConfigForController implements BootstrapConfigGetter.
   859  func (s *store) BootstrapConfigForController(controllerName string) (*BootstrapConfig, error) {
   860  	configs, err := ReadBootstrapConfigFile(JujuBootstrapConfigPath())
   861  	if err != nil {
   862  		return nil, errors.Trace(err)
   863  	}
   864  	cfg, ok := configs[controllerName]
   865  	if !ok {
   866  		return nil, errors.NotFoundf("bootstrap config for controller %s", controllerName)
   867  	}
   868  	return &cfg, nil
   869  }
   870  
   871  // CookieJar returns the cookie jar associated with the given controller.
   872  func (s *store) CookieJar(controllerName string) (CookieJar, error) {
   873  	if err := ValidateControllerName(controllerName); err != nil {
   874  		return nil, errors.Trace(err)
   875  	}
   876  	path := JujuCookiePath(controllerName)
   877  	jar, err := cookiejar.New(&cookiejar.Options{
   878  		Filename: path,
   879  	})
   880  	if err != nil {
   881  		return nil, errors.Trace(err)
   882  	}
   883  	return &cookieJar{
   884  		path: path,
   885  		Jar:  jar,
   886  	}, nil
   887  }
   888  
   889  type cookieJar struct {
   890  	path string
   891  	*cookiejar.Jar
   892  }
   893  
   894  func (jar *cookieJar) Save() error {
   895  	// Ensure that the directory exists before saving.
   896  	if err := os.MkdirAll(filepath.Dir(jar.path), 0700); err != nil {
   897  		return errors.Annotatef(err, "cannot make cookies directory")
   898  	}
   899  	return jar.Jar.Save()
   900  }
   901  
   902  // JujuCookiePath is the location where cookies associated
   903  // with the given controller are expected to be found.
   904  func JujuCookiePath(controllerName string) string {
   905  	return osenv.JujuXDGDataHomePath("cookies", controllerName+".json")
   906  }