github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/controller/createmodel.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 // NewCreateModelCommand returns a command to create an model. 22 func NewCreateModelCommand() cmd.Command { 23 return modelcmd.WrapController(&createModelCommand{ 24 credentialStore: jujuclient.NewFileCredentialStore(), 25 }) 26 } 27 28 // createModelCommand calls the API to create a new model. 29 type createModelCommand 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 createModelHelpDoc = ` 44 This command will create 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 creating 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 create-model new-model 68 69 juju create-model new-model --config aws-creds.yaml --config image-stream=daily 70 71 juju create-model new-model --credential aws:mysekrets --config authorized-keys="ssh-rsa ..." 72 73 See Also: 74 juju help model share 75 ` 76 77 func (c *createModelCommand) Info() *cmd.Info { 78 return &cmd.Info{ 79 Name: "create-model", 80 Args: "<name> [--config key=[value] ...] [--credential <cloud>:<credential>]", 81 Purpose: "create an model within the Juju Model Server", 82 Doc: strings.TrimSpace(createModelHelpDoc), 83 } 84 } 85 86 func (c *createModelCommand) 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 *createModelCommand) 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 *createModelCommand) getAPI() (CreateModelAPI, error) { 125 if c.api != nil { 126 return c.api, nil 127 } 128 return c.NewModelManagerAPIClient() 129 } 130 131 func (c *createModelCommand) 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("created model %q", c.Name) 195 } else { 196 ctx.Infof("created model %q for %q", c.Name, c.Owner) 197 } 198 199 return nil 200 } 201 202 func (c *createModelCommand) 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 }