github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/envcmd/files.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package envcmd
     5  
     6  import (
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/juju/cmd"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils/fslock"
    16  
    17  	"github.com/juju/juju/juju/osenv"
    18  )
    19  
    20  const (
    21  	CurrentEnvironmentFilename = "current-environment"
    22  	CurrentSystemFilename      = "current-system"
    23  
    24  	lockName = "current.lock"
    25  
    26  	systemSuffix = " (system)"
    27  )
    28  
    29  var (
    30  	// 5 seconds should be way more than enough to write or read any files
    31  	// even on heavily loaded systems.
    32  	lockTimeout = 5 * time.Second
    33  )
    34  
    35  // ServerFile describes the information that is needed for a user
    36  // to connect to an api server.
    37  type ServerFile struct {
    38  	Addresses []string `yaml:"addresses"`
    39  	CACert    string   `yaml:"ca-cert,omitempty"`
    40  	Username  string   `yaml:"username"`
    41  	Password  string   `yaml:"password"`
    42  }
    43  
    44  // NOTE: synchronisation across functions in this file.
    45  //
    46  // Each of the read and write functions use a fslock to synchronise calls
    47  // across both the current executable and across different executables.
    48  
    49  func getCurrentEnvironmentFilePath() string {
    50  	return filepath.Join(osenv.JujuHome(), CurrentEnvironmentFilename)
    51  }
    52  
    53  func getCurrentSystemFilePath() string {
    54  	return filepath.Join(osenv.JujuHome(), CurrentSystemFilename)
    55  }
    56  
    57  // Read the file $JUJU_HOME/current-environment and return the value stored
    58  // there.  If the file doesn't exist an empty string is returned and no error.
    59  func ReadCurrentEnvironment() (string, error) {
    60  	lock, err := acquireEnvironmentLock("read current-environment")
    61  	if err != nil {
    62  		return "", errors.Trace(err)
    63  	}
    64  	defer lock.Unlock()
    65  
    66  	current, err := ioutil.ReadFile(getCurrentEnvironmentFilePath())
    67  	if err != nil {
    68  		if os.IsNotExist(err) {
    69  			return "", nil
    70  		}
    71  		return "", errors.Trace(err)
    72  	}
    73  	return strings.TrimSpace(string(current)), nil
    74  }
    75  
    76  // Read the file $JUJU_HOME/current-system and return the value stored there.
    77  // If the file doesn't exist an empty string is returned and no error.
    78  func ReadCurrentSystem() (string, error) {
    79  	lock, err := acquireEnvironmentLock("read current-system")
    80  	if err != nil {
    81  		return "", errors.Trace(err)
    82  	}
    83  	defer lock.Unlock()
    84  
    85  	current, err := ioutil.ReadFile(getCurrentSystemFilePath())
    86  	if err != nil {
    87  		if os.IsNotExist(err) {
    88  			return "", nil
    89  		}
    90  		return "", errors.Trace(err)
    91  	}
    92  	return strings.TrimSpace(string(current)), nil
    93  }
    94  
    95  // Write the envName to the file $JUJU_HOME/current-environment file.
    96  func WriteCurrentEnvironment(envName string) error {
    97  	lock, err := acquireEnvironmentLock("write current-environment")
    98  	if err != nil {
    99  		return errors.Trace(err)
   100  	}
   101  	defer lock.Unlock()
   102  
   103  	path := getCurrentEnvironmentFilePath()
   104  	err = ioutil.WriteFile(path, []byte(envName+"\n"), 0644)
   105  	if err != nil {
   106  		return errors.Errorf("unable to write to the environment file: %q, %s", path, err)
   107  	}
   108  	// If there is a current system file, remove it.
   109  	if err := os.Remove(getCurrentSystemFilePath()); err != nil && !os.IsNotExist(err) {
   110  		logger.Debugf("removing the current environment file due to %s", err)
   111  		// Best attempt to remove the file we just wrote.
   112  		os.Remove(path)
   113  		return err
   114  	}
   115  	return nil
   116  }
   117  
   118  // Write the systemName to the file $JUJU_HOME/current-system file.
   119  func WriteCurrentSystem(systemName string) error {
   120  	lock, err := acquireEnvironmentLock("write current-system")
   121  	if err != nil {
   122  		return errors.Trace(err)
   123  	}
   124  	defer lock.Unlock()
   125  
   126  	path := getCurrentSystemFilePath()
   127  	err = ioutil.WriteFile(path, []byte(systemName+"\n"), 0644)
   128  	if err != nil {
   129  		return errors.Errorf("unable to write to the system file: %q, %s", path, err)
   130  	}
   131  	// If there is a current environment file, remove it.
   132  	if err := os.Remove(getCurrentEnvironmentFilePath()); err != nil && !os.IsNotExist(err) {
   133  		logger.Debugf("removing the current system file due to %s", err)
   134  		// Best attempt to remove the file we just wrote.
   135  		os.Remove(path)
   136  		return err
   137  	}
   138  	return nil
   139  }
   140  
   141  func acquireEnvironmentLock(operation string) (*fslock.Lock, error) {
   142  	// NOTE: any reading or writing from the directory should be done with a
   143  	// fslock to make sure we have a consistent read or write.  Also worth
   144  	// noting, we should use a very short timeout.
   145  	lock, err := fslock.NewLock(osenv.JujuHome(), lockName)
   146  	if err != nil {
   147  		return nil, errors.Trace(err)
   148  	}
   149  	err = lock.LockWithTimeout(lockTimeout, operation)
   150  	if err != nil {
   151  		return nil, errors.Trace(err)
   152  	}
   153  	return lock, nil
   154  }
   155  
   156  // CurrentConnectionName looks at both the current environment file
   157  // and the current system file to determine which is active.
   158  // The name of the current environment or system is returned along with
   159  // a boolean to express whether the name refers to a system or environment.
   160  func CurrentConnectionName() (name string, is_system bool, err error) {
   161  	currentEnv, err := ReadCurrentEnvironment()
   162  	if err != nil {
   163  		return "", false, errors.Trace(err)
   164  	} else if currentEnv != "" {
   165  		return currentEnv, false, nil
   166  	}
   167  
   168  	currentSystem, err := ReadCurrentSystem()
   169  	if err != nil {
   170  		return "", false, errors.Trace(err)
   171  	} else if currentSystem != "" {
   172  		return currentSystem, true, nil
   173  	}
   174  
   175  	return "", false, nil
   176  }
   177  
   178  func currentName() (string, error) {
   179  	name, isSystem, err := CurrentConnectionName()
   180  	if err != nil {
   181  		return "", errors.Trace(err)
   182  	}
   183  	if isSystem {
   184  		name = name + systemSuffix
   185  	}
   186  	if name != "" {
   187  		name += " "
   188  	}
   189  	return name, nil
   190  }
   191  
   192  // SetCurrentEnvironment writes out the current environment file and writes a
   193  // standard message to the command context.
   194  func SetCurrentEnvironment(context *cmd.Context, environmentName string) error {
   195  	current, err := currentName()
   196  	if err != nil {
   197  		return errors.Trace(err)
   198  	}
   199  	err = WriteCurrentEnvironment(environmentName)
   200  	if err != nil {
   201  		return errors.Trace(err)
   202  	}
   203  	context.Infof("%s-> %s", current, environmentName)
   204  	return nil
   205  }
   206  
   207  // SetCurrentSystem writes out the current system file and writes a standard
   208  // message to the command context.
   209  func SetCurrentSystem(context *cmd.Context, systemName string) error {
   210  	current, err := currentName()
   211  	if err != nil {
   212  		return errors.Trace(err)
   213  	}
   214  	err = WriteCurrentSystem(systemName)
   215  	if err != nil {
   216  		return errors.Trace(err)
   217  	}
   218  	context.Infof("%s-> %s%s", current, systemName, systemSuffix)
   219  	return nil
   220  }