github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"sync"
    12  	"time"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/utils/fslock"
    17  	goyaml "gopkg.in/yaml.v1"
    18  
    19  	"github.com/juju/juju/juju/osenv"
    20  )
    21  
    22  var logger = loggo.GetLogger("juju.environs.configstore")
    23  
    24  const lockName = "env.lock"
    25  
    26  // A second should be way more than enough to write or read any files.
    27  var lockTimeout = time.Second
    28  
    29  // Default returns disk-based environment config storage
    30  // rooted at JujuHome.
    31  var Default = func() (Storage, error) {
    32  	return NewDisk(osenv.JujuHome())
    33  }
    34  
    35  type diskStore struct {
    36  	dir string
    37  }
    38  
    39  // EnvironInfoData is the serialisation structure for the original JENV file.
    40  type EnvironInfoData struct {
    41  	User            string
    42  	Password        string
    43  	EnvironUUID     string                 `json:"environ-uuid,omitempty" yaml:"environ-uuid,omitempty"`
    44  	StateServers    []string               `json:"state-servers" yaml:"state-servers"`
    45  	ServerHostnames []string               `json:"server-hostnames,omitempty" yaml:"server-hostnames,omitempty"`
    46  	CACert          string                 `json:"ca-cert" yaml:"ca-cert"`
    47  	Config          map[string]interface{} `json:"bootstrap-config,omitempty" yaml:"bootstrap-config,omitempty"`
    48  }
    49  
    50  type environInfo struct {
    51  	mu sync.Mutex
    52  
    53  	// environmentDir is the directory where the files are written.
    54  	environmentDir string
    55  
    56  	// path is the location of the file that we read to load the info.
    57  	path string
    58  
    59  	// initialized signifies whether the info has been written.
    60  	initialized bool
    61  
    62  	// created signifies whether the info was returned from
    63  	// a CreateInfo call.
    64  	created bool
    65  
    66  	name            string
    67  	user            string
    68  	credentials     string
    69  	environmentUUID string
    70  	apiEndpoints    []string
    71  	apiHostnames    []string
    72  	caCert          string
    73  	bootstrapConfig map[string]interface{}
    74  }
    75  
    76  // NewDisk returns a ConfigStorage implementation that stores configuration in
    77  // the given directory. The parent of the directory must already exist; the
    78  // directory itself is created if it doesn't already exist.
    79  func NewDisk(dir string) (Storage, error) {
    80  	if _, err := os.Stat(dir); err != nil {
    81  		return nil, err
    82  	}
    83  	if err := os.MkdirAll(dir, 0755); err != nil {
    84  		return nil, err
    85  	}
    86  	d := &diskStore{
    87  		dir: filepath.Join(dir, "environments"),
    88  	}
    89  	if err := d.mkEnvironmentsDir(); err != nil {
    90  		return nil, err
    91  	}
    92  	return d, nil
    93  }
    94  
    95  func (d *diskStore) mkEnvironmentsDir() error {
    96  	err := os.Mkdir(d.dir, 0700)
    97  	if os.IsExist(err) {
    98  		return nil
    99  	}
   100  	logger.Debugf("Made dir %v", d.dir)
   101  	return err
   102  }
   103  
   104  // CreateInfo implements Storage.CreateInfo.
   105  func (d *diskStore) CreateInfo(envName string) EnvironInfo {
   106  	return &environInfo{
   107  		environmentDir: d.dir,
   108  		created:        true,
   109  		name:           envName,
   110  	}
   111  }
   112  
   113  // List implements Storage.List
   114  func (d *diskStore) List() ([]string, error) {
   115  
   116  	// awkward -  list both jenv files and connection files.
   117  
   118  	var envs []string
   119  	files, err := filepath.Glob(d.dir + "/*" + jenvExtension)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	for _, file := range files {
   124  		fName := filepath.Base(file)
   125  		name := fName[:len(fName)-len(jenvExtension)]
   126  		envs = append(envs, name)
   127  	}
   128  	return envs, nil
   129  }
   130  
   131  // ReadInfo implements Storage.ReadInfo.
   132  func (d *diskStore) ReadInfo(envName string) (EnvironInfo, error) {
   133  	// TODO: first try the new format, and if it doesn't exist, read the old format.
   134  	// NOTE: any reading or writing from the directory should be done with a fslock
   135  	// to make sure we have a consistent read or write.  Also worth noting, we should
   136  	// use a very short timeout.
   137  
   138  	lock, err := fslock.NewLock(d.dir, lockName)
   139  	if err != nil {
   140  		return nil, errors.Trace(err)
   141  	}
   142  	err = lock.LockWithTimeout(lockTimeout, "reading")
   143  	if err != nil {
   144  		return nil, errors.Annotatef(err, "cannot read info")
   145  	}
   146  	defer lock.Unlock()
   147  
   148  	info, err := d.readConnectionFile(envName)
   149  	if err != nil {
   150  		if errors.IsNotFound(err) {
   151  			info, err = d.readJENVFile(envName)
   152  		}
   153  	}
   154  	if err != nil {
   155  		return nil, errors.Trace(err)
   156  	}
   157  	info.environmentDir = d.dir
   158  	return info, nil
   159  }
   160  
   161  func (d *diskStore) readConnectionFile(envName string) (*environInfo, error) {
   162  	return nil, errors.NotFoundf("connection file")
   163  }
   164  
   165  // Initialized implements EnvironInfo.Initialized.
   166  func (info *environInfo) Initialized() bool {
   167  	info.mu.Lock()
   168  	defer info.mu.Unlock()
   169  	return info.initialized
   170  }
   171  
   172  // BootstrapConfig implements EnvironInfo.BootstrapConfig.
   173  func (info *environInfo) BootstrapConfig() map[string]interface{} {
   174  	info.mu.Lock()
   175  	defer info.mu.Unlock()
   176  	return info.bootstrapConfig
   177  }
   178  
   179  // APICredentials implements EnvironInfo.APICredentials.
   180  func (info *environInfo) APICredentials() APICredentials {
   181  	info.mu.Lock()
   182  	defer info.mu.Unlock()
   183  	return APICredentials{
   184  		User:     info.user,
   185  		Password: info.credentials,
   186  	}
   187  }
   188  
   189  // APIEndpoint implements EnvironInfo.APIEndpoint.
   190  func (info *environInfo) APIEndpoint() APIEndpoint {
   191  	info.mu.Lock()
   192  	defer info.mu.Unlock()
   193  	return APIEndpoint{
   194  		Addresses:   info.apiEndpoints,
   195  		Hostnames:   info.apiHostnames,
   196  		CACert:      info.caCert,
   197  		EnvironUUID: info.environmentUUID,
   198  	}
   199  }
   200  
   201  // SetBootstrapConfig implements EnvironInfo.SetBootstrapConfig.
   202  func (info *environInfo) SetBootstrapConfig(attrs map[string]interface{}) {
   203  	info.mu.Lock()
   204  	defer info.mu.Unlock()
   205  	if !info.created {
   206  		panic("bootstrap config set on environment info that has not just been created")
   207  	}
   208  	info.bootstrapConfig = attrs
   209  }
   210  
   211  // SetAPIEndpoint implements EnvironInfo.SetAPIEndpoint.
   212  func (info *environInfo) SetAPIEndpoint(endpoint APIEndpoint) {
   213  	info.mu.Lock()
   214  	defer info.mu.Unlock()
   215  	info.apiEndpoints = endpoint.Addresses
   216  	info.apiHostnames = endpoint.Hostnames
   217  	info.caCert = endpoint.CACert
   218  	info.environmentUUID = endpoint.EnvironUUID
   219  }
   220  
   221  // SetAPICredentials implements EnvironInfo.SetAPICredentials.
   222  func (info *environInfo) SetAPICredentials(creds APICredentials) {
   223  	info.mu.Lock()
   224  	defer info.mu.Unlock()
   225  	info.user = creds.User
   226  	info.credentials = creds.Password
   227  }
   228  
   229  // Location returns the location of the environInfo in human readable format.
   230  func (info *environInfo) Location() string {
   231  	info.mu.Lock()
   232  	defer info.mu.Unlock()
   233  	return fmt.Sprintf("file %q", info.path)
   234  }
   235  
   236  // Write implements EnvironInfo.Write.
   237  func (info *environInfo) Write() error {
   238  	info.mu.Lock()
   239  	defer info.mu.Unlock()
   240  	lock, err := fslock.NewLock(info.environmentDir, lockName)
   241  	if err != nil {
   242  		return errors.Trace(err)
   243  	}
   244  	err = lock.LockWithTimeout(lockTimeout, "writing")
   245  	if err != nil {
   246  		return errors.Annotatef(err, "cannot write info")
   247  	}
   248  	defer lock.Unlock()
   249  
   250  	if err := info.writeJENVFile(); err != nil {
   251  		return errors.Trace(err)
   252  	}
   253  
   254  	info.initialized = true
   255  	return nil
   256  }
   257  
   258  // Destroy implements EnvironInfo.Destroy.
   259  func (info *environInfo) Destroy() error {
   260  	info.mu.Lock()
   261  	defer info.mu.Unlock()
   262  	if info.initialized {
   263  		err := os.Remove(info.path)
   264  		if os.IsNotExist(err) {
   265  			return errors.New("environment info has already been removed")
   266  		}
   267  		return err
   268  	}
   269  	return nil
   270  }
   271  
   272  const jenvExtension = ".jenv"
   273  
   274  func jenvFilename(basedir, envName string) string {
   275  	return filepath.Join(basedir, envName+jenvExtension)
   276  }
   277  
   278  func (d *diskStore) readJENVFile(envName string) (*environInfo, error) {
   279  	path := jenvFilename(d.dir, envName)
   280  	data, err := ioutil.ReadFile(path)
   281  	if err != nil {
   282  		if os.IsNotExist(err) {
   283  			return nil, errors.NotFoundf("environment %q", envName)
   284  		}
   285  		return nil, err
   286  	}
   287  	var info environInfo
   288  	info.path = path
   289  	if len(data) == 0 {
   290  		return &info, nil
   291  	}
   292  	var values EnvironInfoData
   293  	if err := goyaml.Unmarshal(data, &values); err != nil {
   294  		return nil, errors.Annotatef(err, "error unmarshalling %q", path)
   295  	}
   296  	info.name = envName
   297  	info.user = values.User
   298  	info.credentials = values.Password
   299  	info.environmentUUID = values.EnvironUUID
   300  	info.caCert = values.CACert
   301  	info.apiEndpoints = values.StateServers
   302  	info.apiHostnames = values.ServerHostnames
   303  	info.bootstrapConfig = values.Config
   304  
   305  	info.initialized = true
   306  	return &info, nil
   307  }
   308  
   309  // Kept primarily for testing purposes now.
   310  func (info *environInfo) writeJENVFile() error {
   311  
   312  	infoData := EnvironInfoData{
   313  		User:            info.user,
   314  		Password:        info.credentials,
   315  		EnvironUUID:     info.environmentUUID,
   316  		StateServers:    info.apiEndpoints,
   317  		ServerHostnames: info.apiHostnames,
   318  		CACert:          info.caCert,
   319  		Config:          info.bootstrapConfig,
   320  	}
   321  
   322  	data, err := goyaml.Marshal(infoData)
   323  	if err != nil {
   324  		return errors.Annotate(err, "cannot marshal environment info")
   325  	}
   326  	// We now use a fslock to sync reads and writes across the environment,
   327  	// so we don't need to use a temporary file any more.
   328  
   329  	flags := os.O_WRONLY
   330  	if info.created {
   331  		flags |= os.O_CREATE | os.O_EXCL
   332  	} else {
   333  		flags |= os.O_TRUNC
   334  	}
   335  	path := jenvFilename(info.environmentDir, info.name)
   336  	logger.Debugf("writing jenv file to %s", path)
   337  	file, err := os.OpenFile(path, flags, 0600)
   338  	if os.IsExist(err) {
   339  		return ErrEnvironInfoAlreadyExists
   340  	}
   341  
   342  	_, err = file.Write(data)
   343  	file.Close()
   344  	info.path = path
   345  	return errors.Annotate(err, "cannot write file")
   346  }