9
9
"github.com/juju/cmd"
10
10
"github.com/juju/errors"
11
11
"github.com/juju/names"
12
"github.com/juju/utils/keyvalues"
14
12
"launchpad.net/gnuflag"
16
14
"github.com/juju/juju/apiserver/params"
15
"github.com/juju/juju/cloud"
17
16
"github.com/juju/juju/cmd/juju/common"
18
17
"github.com/juju/juju/cmd/modelcmd"
19
"github.com/juju/juju/environs/config"
20
18
"github.com/juju/juju/jujuclient"
23
21
// NewCreateModelCommand returns a command to create an model.
24
22
func NewCreateModelCommand() cmd.Command {
25
return modelcmd.WrapController(&createModelCommand{})
23
return modelcmd.WrapController(&createModelCommand{
24
credentialStore: jujuclient.NewFileCredentialStore(),
28
28
// createModelCommand calls the API to create a new model.
29
29
type createModelCommand struct {
30
30
modelcmd.ControllerCommandBase
32
credentialStore jujuclient.CredentialStore
35
ConfigFile cmd.FileVar
36
ConfValues map[string]string
37
configParser func(interface{}) (interface{}, error)
40
Config common.ConfigFlag
40
43
const createModelHelpDoc = `
41
44
This command will create another model within the current Juju
42
45
Controller. The provider has to match, and the model config must
43
specify all the required configuration values for the provider. In the cases
44
of ‘ec2’ and ‘openstack’, the same model variables are checked for the
45
access and secret keys.
47
If configuration values are passed by both extra command line arguments and
48
the --config option, the command line args take priority.
46
specify all the required configuration values for the provider.
48
If configuration values are passed by both extra command line
49
arguments and the --config option, the command line args take
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.
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
52
67
juju create-model new-model
54
juju create-model new-model --config=aws-creds.yaml
69
juju create-model new-model --config aws-creds.yaml --config image-stream=daily
71
juju create-model new-model --credential aws:mysekrets --config authorized-keys="ssh-rsa ..."
57
74
juju help model share
60
77
func (c *createModelCommand) Info() *cmd.Info {
62
79
Name: "create-model",
63
Args: "<name> [key=[value] ...]",
80
Args: "<name> [--config key=[value] ...] [--credential <cloud>:<credential>]",
64
81
Purpose: "create an model within the Juju Model Server",
65
82
Doc: strings.TrimSpace(createModelHelpDoc),
69
86
func (c *createModelCommand) SetFlags(f *gnuflag.FlagSet) {
70
87
f.StringVar(&c.Owner, "owner", "", "the owner of the new model if not the current user")
71
f.Var(&c.ConfigFile, "config", "path to yaml-formatted file containing model config values")
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 ...])")
74
92
func (c *createModelCommand) Init(args []string) error {
78
96
c.Name, args = args[0], args[1:]
80
values, err := keyvalues.Parse(args, true)
86
98
if c.Owner != "" && !names.IsValidUser(c.Owner) {
87
99
return errors.Errorf("%q is not a valid user", c.Owner)
90
if c.configParser == nil {
91
c.configParser = common.ConformYAML
102
if c.CredentialSpec != "" {
103
parts := strings.Split(c.CredentialSpec, ":")
105
return errors.Errorf("invalid cloud credential %s, expected <cloud>:<credential-name>", c.CredentialSpec)
107
c.CloudName = parts[0]
108
if cloud, err := cloud.CloudByName(c.CloudName); err != nil {
109
return errors.Trace(err)
111
c.CloudType = cloud.Type
113
c.CredentialName = parts[1]
143
164
return errors.Trace(err)
146
// TODO we pass nil for the account details until we implement that bit.
147
model, err := client.CreateModel(modelOwner, nil, attrs)
167
accountDetails := map[string]interface{}{}
168
if c.CredentialName != "" {
169
cred, _, _, err := modelcmd.GetCredentials(
170
c.credentialStore, "", c.CredentialName, c.CloudName, c.CloudType,
173
return errors.Trace(err)
175
for k, v := range cred.Attributes() {
176
accountDetails[k] = v
179
model, err := client.CreateModel(modelOwner, accountDetails, attrs)
149
181
return errors.Trace(err)
170
202
func (c *createModelCommand) getConfigValues(ctx *cmd.Context, serverSkeleton params.ModelConfig) (map[string]interface{}, error) {
171
// The reading of the config YAML is done in the Run
172
// method because the Read method requires the cmd Context
173
// for the current directory.
174
fileConfig := make(map[string]interface{})
175
if c.ConfigFile.Path != "" {
176
configYAML, err := c.ConfigFile.Read(ctx)
178
return nil, errors.Annotate(err, "unable to read config file")
181
rawFileConfig := make(map[string]interface{})
182
err = yaml.Unmarshal(configYAML, &rawFileConfig)
184
return nil, errors.Annotate(err, "unable to parse config file")
187
conformantConfig, err := c.configParser(rawFileConfig)
189
return nil, errors.Annotate(err, "unable to parse config file")
191
betterConfig, ok := conformantConfig.(map[string]interface{})
193
return nil, errors.New("config must contain a YAML map with string keys")
196
fileConfig = betterConfig
199
203
configValues := make(map[string]interface{})
200
204
for key, value := range serverSkeleton {
201
205
configValues[key] = value
203
for key, value := range fileConfig {
204
configValues[key] = value
207
configAttr, err := c.Config.ReadAttrs(ctx)
209
return nil, errors.Annotate(err, "unable to parse config")
206
for key, value := range c.ConfValues {
211
for key, value := range configAttr {
207
212
configValues[key] = value
209
214
configValues["name"] = c.Name
211
// TODO: allow version to be specified on the command line and add here.
212
cfg, err := config.New(config.UseDefaults, configValues)
215
coercedValues, err := common.ConformYAML(configValues)
214
return nil, errors.Trace(err)
217
return nil, errors.Annotatef(err, "unable to parse config")
219
stringParams, ok := coercedValues.(map[string]interface{})
221
return nil, errors.New("params must contain a YAML map with string keys")
217
return cfg.AllAttrs(), nil
224
return stringParams, nil