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