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