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 }