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