github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/environment/jenv.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environment
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/juju/cmd"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names"
    15  	"gopkg.in/yaml.v1"
    16  
    17  	"github.com/juju/juju/cmd/envcmd"
    18  	"github.com/juju/juju/environs/configstore"
    19  	"github.com/juju/juju/juju/osenv"
    20  )
    21  
    22  // JenvCommand imports the given Juju generated jenv file into the local
    23  // JUJU_HOME environments directory.
    24  type JenvCommand struct {
    25  	cmd.CommandBase
    26  	jenvFile cmd.FileVar
    27  	envName  string
    28  }
    29  
    30  const jenvHelpDoc = `
    31  Copy the provided Juju generated jenv file to the proper location inside your
    32  local JUJU_HOME. Also switch to using the resulting environment as the default
    33  one. This way it is possible to import and use jenv files generated by other
    34  Juju commands such as "juju user add".
    35  
    36  Example:
    37  
    38    juju environment jenv my-env.jenv (my-env.jenv is the path to the jenv file)
    39    juju environment jenv my-env.jenv ec2 (copy and rename the environment)
    40  `
    41  
    42  func (c *JenvCommand) Info() *cmd.Info {
    43  	return &cmd.Info{
    44  		Name:    "jenv",
    45  		Args:    "<path/to/file.jenv> [<environment name>]",
    46  		Purpose: "import previously generated Juju environment files",
    47  		Doc:     strings.TrimSpace(jenvHelpDoc),
    48  	}
    49  }
    50  
    51  func (c *JenvCommand) Init(args []string) error {
    52  	if len(args) == 0 {
    53  		return errors.New("no jenv file provided")
    54  	}
    55  
    56  	// Store the path to the jenv file.
    57  	if err := c.jenvFile.Set(args[0]); err != nil {
    58  		return errors.Annotate(err, "invalid jenv path")
    59  	}
    60  	args = args[1:]
    61  
    62  	if len(args) > 0 {
    63  		// Store and validate the provided environment name.
    64  		c.envName, args = args[0], args[1:]
    65  		if !names.IsValidUser(c.envName) {
    66  			return errors.Errorf("invalid environment name %q", c.envName)
    67  		}
    68  	} else {
    69  		// Retrieve the environment name from the jenv file name.
    70  		base := filepath.Base(c.jenvFile.Path)
    71  		c.envName = base[:len(base)-len(filepath.Ext(base))]
    72  	}
    73  
    74  	// No other arguments are expected.
    75  	return cmd.CheckEmpty(args)
    76  }
    77  
    78  func (c *JenvCommand) Run(ctx *cmd.Context) error {
    79  	// Read data from the provided jenv file.
    80  	data, err := c.jenvFile.Read(ctx)
    81  	if err != nil {
    82  		if os.IsNotExist(errors.Cause(err)) {
    83  			return errors.NotFoundf("jenv file %q", c.jenvFile.Path)
    84  		}
    85  		return errors.Annotatef(err, "cannot read the provided jenv file %q", c.jenvFile.Path)
    86  	}
    87  
    88  	// Open the config store.
    89  	store, err := configstore.Default()
    90  	if err != nil {
    91  		return errors.Annotate(err, "cannot get config store")
    92  	}
    93  
    94  	// Create and update the new environment info object.
    95  	info := store.CreateInfo(c.envName)
    96  	if err := updateEnvironmentInfo(info, data); err != nil {
    97  		return errors.Annotatef(err, "invalid jenv file %q", c.jenvFile.Path)
    98  	}
    99  
   100  	// Write the environment info to JUJU_HOME.
   101  	if err := info.Write(); err != nil {
   102  		if errors.Cause(err) == configstore.ErrEnvironInfoAlreadyExists {
   103  			descriptiveErr := errors.Errorf("an environment named %q already exists: "+
   104  				"you can provide a second parameter to rename the environment",
   105  				c.envName)
   106  			return errors.Wrap(err, descriptiveErr)
   107  		}
   108  		return errors.Annotate(err, "cannot write the jenv file")
   109  	}
   110  
   111  	// Switch to the new environment.
   112  	oldEnvName, err := switchEnvironment(c.envName)
   113  	if err != nil {
   114  		return errors.Annotatef(err, "cannot switch to the new environment %q", c.envName)
   115  	}
   116  	if oldEnvName == "" {
   117  		fmt.Fprintf(ctx.Stdout, "-> %s\n", c.envName)
   118  	} else {
   119  		fmt.Fprintf(ctx.Stdout, "%s -> %s\n", oldEnvName, c.envName)
   120  	}
   121  	return nil
   122  }
   123  
   124  // updateEnvironmentInfo updates the given environment info with the values
   125  // stored in the provided YAML encoded data.
   126  func updateEnvironmentInfo(info configstore.EnvironInfo, data []byte) error {
   127  	var values configstore.EnvironInfoData
   128  	if err := yaml.Unmarshal(data, &values); err != nil {
   129  		return errors.Annotate(err, "cannot unmarshal jenv data")
   130  	}
   131  
   132  	// Ensure the required values are present.
   133  	if missing := getMissingEnvironmentInfoFields(values); len(missing) != 0 {
   134  		return errors.Errorf("missing required fields in jenv data: %s", strings.Join(missing, ", "))
   135  	}
   136  
   137  	// Update the environment info.
   138  	info.SetAPICredentials(configstore.APICredentials{
   139  		User:     values.User,
   140  		Password: values.Password,
   141  	})
   142  	info.SetAPIEndpoint(configstore.APIEndpoint{
   143  		Addresses:   values.StateServers,
   144  		Hostnames:   values.ServerHostnames,
   145  		CACert:      values.CACert,
   146  		EnvironUUID: values.EnvironUUID,
   147  	})
   148  	info.SetBootstrapConfig(values.Config)
   149  	return nil
   150  }
   151  
   152  // getMissingEnvironmentInfoFields returns a list of field names missing in the
   153  // given environment info values. The only fields taken into consideration here
   154  // are the ones explicitly set by the "juju user add" command.
   155  func getMissingEnvironmentInfoFields(values configstore.EnvironInfoData) (missing []string) {
   156  	if values.User == "" {
   157  		missing = append(missing, "User")
   158  	}
   159  	if values.Password == "" {
   160  		missing = append(missing, "Password")
   161  	}
   162  	if values.EnvironUUID == "" {
   163  		missing = append(missing, "EnvironUUID")
   164  	}
   165  	if len(values.StateServers) == 0 {
   166  		missing = append(missing, "StateServers")
   167  	}
   168  	if values.CACert == "" {
   169  		missing = append(missing, "CACert")
   170  	}
   171  	return missing
   172  }
   173  
   174  // switchEnvironment changes the default environment to the given name and
   175  // return, if set, the current default environment name.
   176  func switchEnvironment(envName string) (string, error) {
   177  	if defaultEnv := os.Getenv(osenv.JujuEnvEnvKey); defaultEnv != "" {
   178  		return "", errors.Errorf("cannot switch when %s is overriding the environment (set to %q)", osenv.JujuEnvEnvKey, defaultEnv)
   179  	}
   180  	currentEnv, err := envcmd.GetDefaultEnvironment()
   181  	if err != nil {
   182  		return "", errors.Annotate(err, "cannot get the default environment")
   183  	}
   184  	if err := envcmd.WriteCurrentEnvironment(envName); err != nil {
   185  		return "", errors.Trace(err)
   186  	}
   187  	return currentEnv, nil
   188  }