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