github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/juju/controller/addmodel.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  	"launchpad.net/gnuflag"
    13  
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/cloud"
    16  	"github.com/juju/juju/cmd/juju/common"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	"github.com/juju/juju/jujuclient"
    19  )
    20  
    21  // NewAddModelCommand returns a command to add a model.
    22  func NewAddModelCommand() cmd.Command {
    23  	return modelcmd.WrapController(&addModelCommand{
    24  		credentialStore: jujuclient.NewFileCredentialStore(),
    25  	})
    26  }
    27  
    28  // addModelCommand calls the API to add a new model.
    29  type addModelCommand struct {
    30  	modelcmd.ControllerCommandBase
    31  	api             CreateModelAPI
    32  	credentialStore jujuclient.CredentialStore
    33  
    34  	Name           string
    35  	Owner          string
    36  	CredentialSpec string
    37  	CloudName      string
    38  	CloudType      string
    39  	CredentialName string
    40  	Config         common.ConfigFlag
    41  }
    42  
    43  const addModelHelpDoc = `
    44  This command will add another model within the current Juju
    45  Controller. The provider has to match, and the model config must
    46  specify all the required configuration values for the provider.
    47  
    48  If configuration values are passed by both extra command line
    49  arguments and the --config option, the command line args take
    50  priority.
    51  
    52  If adding a model in a controller for which you are not the
    53  administrator, the cloud credentials and authorized ssh keys must
    54  be specified. The credentials are specified using the argument
    55  --credential <cloud>:<credential>. The authorized ssh keys are
    56  specified using a --config argument, either authorized=keys=value
    57  or via a config yaml file.
    58   
    59  Any credentials used must be for a cloud with the same provider
    60  type as the controller. Controller administrators do not have to
    61  specify credentials or ssh keys; by default, the credentials and
    62  keys used to bootstrap the controller are used if no others are
    63  specified.
    64  
    65  Examples:
    66  
    67      juju add-model new-model
    68  
    69      juju add-model new-model --config aws-creds.yaml --config image-stream=daily
    70      
    71      juju add-model new-model --credential aws:mysekrets --config authorized-keys="ssh-rsa ..."
    72  
    73  See Also:
    74      juju help grant
    75  `
    76  
    77  func (c *addModelCommand) Info() *cmd.Info {
    78  	return &cmd.Info{
    79  		Name:    "add-model",
    80  		Args:    "<name> [--config key=[value] ...] [--credential <cloud>:<credential>]",
    81  		Purpose: "Add a model within the Juju Model Server",
    82  		Doc:     strings.TrimSpace(addModelHelpDoc),
    83  	}
    84  }
    85  
    86  func (c *addModelCommand) SetFlags(f *gnuflag.FlagSet) {
    87  	f.StringVar(&c.Owner, "owner", "", "The owner of the new model if not the current user")
    88  	f.StringVar(&c.CredentialSpec, "credential", "", "The name of the cloud and credentials the new model uses to create cloud resources")
    89  	f.Var(&c.Config, "config", "Specify a controller config file, or one or more controller configuration options (--config config.yaml [--config k=v ...])")
    90  }
    91  
    92  func (c *addModelCommand) Init(args []string) error {
    93  	if len(args) == 0 {
    94  		return errors.New("model name is required")
    95  	}
    96  	c.Name, args = args[0], args[1:]
    97  
    98  	if c.Owner != "" && !names.IsValidUser(c.Owner) {
    99  		return errors.Errorf("%q is not a valid user", c.Owner)
   100  	}
   101  
   102  	if c.CredentialSpec != "" {
   103  		parts := strings.Split(c.CredentialSpec, ":")
   104  		if len(parts) < 2 {
   105  			return errors.Errorf("invalid cloud credential %s, expected <cloud>:<credential-name>", c.CredentialSpec)
   106  		}
   107  		c.CloudName = parts[0]
   108  		if cloud, err := common.CloudOrProvider(c.CloudName, cloud.CloudByName); err != nil {
   109  			return errors.Trace(err)
   110  		} else {
   111  			c.CloudType = cloud.Type
   112  		}
   113  		c.CredentialName = parts[1]
   114  	}
   115  	return nil
   116  }
   117  
   118  type CreateModelAPI interface {
   119  	Close() error
   120  	ConfigSkeleton(provider, region string) (params.ModelConfig, error)
   121  	CreateModel(owner string, account, config map[string]interface{}) (params.Model, error)
   122  }
   123  
   124  func (c *addModelCommand) getAPI() (CreateModelAPI, error) {
   125  	if c.api != nil {
   126  		return c.api, nil
   127  	}
   128  	return c.NewModelManagerAPIClient()
   129  }
   130  
   131  func (c *addModelCommand) Run(ctx *cmd.Context) error {
   132  	client, err := c.getAPI()
   133  	if err != nil {
   134  		return errors.Trace(err)
   135  	}
   136  	defer client.Close()
   137  
   138  	store := c.ClientStore()
   139  	controllerName := c.ControllerName()
   140  	accountName, err := store.CurrentAccount(controllerName)
   141  	if err != nil {
   142  		return errors.Trace(err)
   143  	}
   144  	currentAccount, err := store.AccountByName(controllerName, accountName)
   145  	if err != nil {
   146  		return errors.Trace(err)
   147  	}
   148  
   149  	modelOwner := currentAccount.User
   150  	if c.Owner != "" {
   151  		if !names.IsValidUser(c.Owner) {
   152  			return errors.Errorf("%q is not a valid user name", c.Owner)
   153  		}
   154  		modelOwner = names.NewUserTag(c.Owner).Canonical()
   155  	}
   156  
   157  	serverSkeleton, err := client.ConfigSkeleton(c.CloudType, "")
   158  	if err != nil {
   159  		return errors.Trace(err)
   160  	}
   161  
   162  	attrs, err := c.getConfigValues(ctx, serverSkeleton)
   163  	if err != nil {
   164  		return errors.Trace(err)
   165  	}
   166  
   167  	accountDetails := map[string]interface{}{}
   168  	if c.CredentialName != "" {
   169  		cred, _, _, err := modelcmd.GetCredentials(
   170  			c.credentialStore, "", c.CredentialName, c.CloudName, c.CloudType,
   171  		)
   172  		if err != nil {
   173  			return errors.Trace(err)
   174  		}
   175  		for k, v := range cred.Attributes() {
   176  			accountDetails[k] = v
   177  		}
   178  	}
   179  	model, err := client.CreateModel(modelOwner, accountDetails, attrs)
   180  	if err != nil {
   181  		return errors.Trace(err)
   182  	}
   183  	if modelOwner == currentAccount.User {
   184  		controllerName := c.ControllerName()
   185  		accountName := c.AccountName()
   186  		if err := store.UpdateModel(controllerName, accountName, c.Name, jujuclient.ModelDetails{
   187  			model.UUID,
   188  		}); err != nil {
   189  			return errors.Trace(err)
   190  		}
   191  		if err := store.SetCurrentModel(controllerName, accountName, c.Name); err != nil {
   192  			return errors.Trace(err)
   193  		}
   194  		ctx.Infof("added model %q", c.Name)
   195  	} else {
   196  		ctx.Infof("added model %q for %q", c.Name, c.Owner)
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func (c *addModelCommand) getConfigValues(ctx *cmd.Context, serverSkeleton params.ModelConfig) (map[string]interface{}, error) {
   203  	configValues := make(map[string]interface{})
   204  	for key, value := range serverSkeleton {
   205  		configValues[key] = value
   206  	}
   207  	configAttr, err := c.Config.ReadAttrs(ctx)
   208  	if err != nil {
   209  		return nil, errors.Annotate(err, "unable to parse config")
   210  	}
   211  	for key, value := range configAttr {
   212  		configValues[key] = value
   213  	}
   214  	configValues["name"] = c.Name
   215  	coercedValues, err := common.ConformYAML(configValues)
   216  	if err != nil {
   217  		return nil, errors.Annotatef(err, "unable to parse config")
   218  	}
   219  	stringParams, ok := coercedValues.(map[string]interface{})
   220  	if !ok {
   221  		return nil, errors.New("params must contain a YAML map with string keys")
   222  	}
   223  
   224  	return stringParams, nil
   225  }