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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package system
     5  
     6  import (
     7  	"os"
     8  	"os/user"
     9  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names"
    14  	"github.com/juju/utils/keyvalues"
    15  	"gopkg.in/yaml.v1"
    16  	"launchpad.net/gnuflag"
    17  
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/envcmd"
    20  	"github.com/juju/juju/environs/config"
    21  	"github.com/juju/juju/environs/configstore"
    22  	localProvider "github.com/juju/juju/provider/local"
    23  )
    24  
    25  // CreateEnvironmentCommand calls the API to create a new environment.
    26  type CreateEnvironmentCommand struct {
    27  	envcmd.SysCommandBase
    28  	api CreateEnvironmentAPI
    29  
    30  	name       string
    31  	owner      string
    32  	configFile cmd.FileVar
    33  	confValues map[string]string
    34  }
    35  
    36  const createEnvHelpDoc = `
    37  This command will create another environment within the current Juju
    38  Environment Server. The provider has to match, and the environment config must
    39  specify all the required configuration values for the provider. In the cases
    40  of ‘ec2’ and ‘openstack’, the same environment variables are checked for the
    41  access and secret keys.
    42  
    43  If configuration values are passed by both extra command line arguments and
    44  the --config option, the command line args take priority.
    45  
    46  Examples:
    47  
    48      juju system create-environment new-env
    49  
    50      juju system create-environment new-env --config=aws-creds.yaml
    51  
    52  See Also:
    53      juju help environment share
    54  `
    55  
    56  func (c *CreateEnvironmentCommand) Info() *cmd.Info {
    57  	return &cmd.Info{
    58  		Name:    "create-environment",
    59  		Args:    "<name> [key=[value] ...]",
    60  		Purpose: "create an environment within the Juju Environment Server",
    61  		Doc:     strings.TrimSpace(createEnvHelpDoc),
    62  		Aliases: []string{"create-env"},
    63  	}
    64  }
    65  
    66  func (c *CreateEnvironmentCommand) SetFlags(f *gnuflag.FlagSet) {
    67  	f.StringVar(&c.owner, "owner", "", "the owner of the new environment if not the current user")
    68  	f.Var(&c.configFile, "config", "path to yaml-formatted file containing environment config values")
    69  }
    70  
    71  func (c *CreateEnvironmentCommand) Init(args []string) error {
    72  	if len(args) == 0 {
    73  		return errors.New("environment name is required")
    74  	}
    75  	c.name, args = args[0], args[1:]
    76  
    77  	values, err := keyvalues.Parse(args, true)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	c.confValues = values
    82  
    83  	if c.owner != "" && !names.IsValidUser(c.owner) {
    84  		return errors.Errorf("%q is not a valid user", c.owner)
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  type CreateEnvironmentAPI interface {
    91  	Close() error
    92  	ConfigSkeleton(provider, region string) (params.EnvironConfig, error)
    93  	CreateEnvironment(owner string, account, config map[string]interface{}) (params.Environment, error)
    94  }
    95  
    96  func (c *CreateEnvironmentCommand) getAPI() (CreateEnvironmentAPI, error) {
    97  	if c.api != nil {
    98  		return c.api, nil
    99  	}
   100  	return c.NewEnvironmentManagerAPIClient()
   101  }
   102  
   103  func (c *CreateEnvironmentCommand) Run(ctx *cmd.Context) (return_err error) {
   104  	client, err := c.getAPI()
   105  	if err != nil {
   106  		return err
   107  	}
   108  	defer client.Close()
   109  
   110  	creds, err := c.ConnectionCredentials()
   111  	if err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  
   115  	creatingForSelf := true
   116  	envOwner := creds.User
   117  	if c.owner != "" {
   118  		owner := names.NewUserTag(c.owner)
   119  		user := names.NewUserTag(creds.User)
   120  		creatingForSelf = owner == user
   121  		envOwner = c.owner
   122  	}
   123  
   124  	var info configstore.EnvironInfo
   125  	var endpoint configstore.APIEndpoint
   126  	if creatingForSelf {
   127  		logger.Debugf("create cache entry for %q", c.name)
   128  		// Create the configstore entry and write it to disk, as this will error
   129  		// if one with the same name already exists.
   130  		endpoint, err = c.ConnectionEndpoint()
   131  		if err != nil {
   132  			return errors.Trace(err)
   133  		}
   134  
   135  		store, err := configstore.Default()
   136  		if err != nil {
   137  			return errors.Trace(err)
   138  		}
   139  		info = store.CreateInfo(c.name)
   140  		info.SetAPICredentials(creds)
   141  		endpoint.EnvironUUID = ""
   142  		if err := info.Write(); err != nil {
   143  			if errors.Cause(err) == configstore.ErrEnvironInfoAlreadyExists {
   144  				newErr := errors.AlreadyExistsf("environment %q", c.name)
   145  				return errors.Wrap(err, newErr)
   146  			}
   147  			return errors.Trace(err)
   148  		}
   149  		defer func() {
   150  			if return_err != nil {
   151  				logger.Debugf("error found, remove cache entry")
   152  				e := info.Destroy()
   153  				if e != nil {
   154  					logger.Errorf("could not remove environment file: %v", e)
   155  				}
   156  			}
   157  		}()
   158  	} else {
   159  		logger.Debugf("skipping cache entry for %q as owned %q", c.name, c.owner)
   160  	}
   161  
   162  	serverSkeleton, err := client.ConfigSkeleton("", "")
   163  	if err != nil {
   164  		return errors.Trace(err)
   165  	}
   166  
   167  	attrs, err := c.getConfigValues(ctx, serverSkeleton)
   168  	if err != nil {
   169  		return errors.Trace(err)
   170  	}
   171  
   172  	// We pass nil through for the account details until we implement that bit.
   173  	env, err := client.CreateEnvironment(envOwner, nil, attrs)
   174  	if err != nil {
   175  		// cleanup configstore
   176  		return errors.Trace(err)
   177  	}
   178  	if creatingForSelf {
   179  		// update the cached details with the environment uuid
   180  		endpoint.EnvironUUID = env.UUID
   181  		info.SetAPIEndpoint(endpoint)
   182  		if err := info.Write(); err != nil {
   183  			return errors.Trace(err)
   184  		}
   185  		ctx.Infof("created environment %q", c.name)
   186  		return envcmd.SetCurrentEnvironment(ctx, c.name)
   187  	} else {
   188  		ctx.Infof("created environment %q for %q", c.name, c.owner)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func (c *CreateEnvironmentCommand) getConfigValues(ctx *cmd.Context, serverSkeleton params.EnvironConfig) (map[string]interface{}, error) {
   195  	// The reading of the config YAML is done in the Run
   196  	// method because the Read method requires the cmd Context
   197  	// for the current directory.
   198  	fileConfig := make(map[string]interface{})
   199  	if c.configFile.Path != "" {
   200  		configYAML, err := c.configFile.Read(ctx)
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  		err = yaml.Unmarshal(configYAML, &fileConfig)
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  	}
   209  
   210  	configValues := make(map[string]interface{})
   211  	for key, value := range serverSkeleton {
   212  		configValues[key] = value
   213  	}
   214  	for key, value := range fileConfig {
   215  		configValues[key] = value
   216  	}
   217  	for key, value := range c.confValues {
   218  		configValues[key] = value
   219  	}
   220  	configValues["name"] = c.name
   221  
   222  	if err := setConfigSpecialCaseDefaults(c.name, configValues); err != nil {
   223  		return nil, errors.Trace(err)
   224  	}
   225  	// TODO: allow version to be specified on the command line and add here.
   226  	cfg, err := config.New(config.UseDefaults, configValues)
   227  	if err != nil {
   228  		return nil, errors.Trace(err)
   229  	}
   230  
   231  	return cfg.AllAttrs(), nil
   232  }
   233  
   234  var userCurrent = user.Current
   235  
   236  func setConfigSpecialCaseDefaults(envName string, cfg map[string]interface{}) error {
   237  	// As a special case, the local provider's namespace value
   238  	// comes from the user's name and the environment name.
   239  	switch cfg["type"] {
   240  	case "local":
   241  		if _, ok := cfg[localProvider.NamespaceKey]; ok {
   242  			return nil
   243  		}
   244  		username := os.Getenv("USER")
   245  		if username == "" {
   246  			u, err := userCurrent()
   247  			if err != nil {
   248  				return errors.Annotatef(err, "failed to determine username for namespace")
   249  			}
   250  			username = u.Username
   251  		}
   252  		cfg[localProvider.NamespaceKey] = username + "-" + envName
   253  	}
   254  	return nil
   255  }