// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(info configstore.EnvironInfo, apiOpen api.OpenFunc, stop <-chan struct{}, bClient *httpbakery.Client) (api.Connection, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var modelTag names.ModelTag if names.IsValidModel(endpoint.ModelUUID) { modelTag = names.NewModelTag(endpoint.ModelUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, ModelTag: modelTag, } if apiInfo.Tag == nil { apiInfo.UseMacaroons = true } dialOpts := api.DefaultDialOpts() dialOpts.BakeryClient = bClient st, err := apiOpen(apiInfo, dialOpts) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// getControllerEnviron gets the bootstrap information required to destroy the // environment by first checking the config store, then querying the API if // the information is not in the store. func (c *destroyCommandBase) getControllerEnviron(info configstore.EnvironInfo, sysAPI destroyControllerAPI) (_ environs.Environ, err error) { bootstrapCfg := info.BootstrapConfig() if bootstrapCfg == nil { if sysAPI == nil { return nil, errors.New("unable to get bootstrap information from API") } bootstrapCfg, err = sysAPI.EnvironmentConfig() if params.IsCodeNotImplemented(err) { // Fallback to the client API. Better to encapsulate the logic for // old servers than worry about connecting twice. client, err := c.getClientAPI() if err != nil { return nil, errors.Trace(err) } defer client.Close() bootstrapCfg, err = client.EnvironmentGet() if err != nil { return nil, errors.Trace(err) } } else if err != nil { return nil, errors.Trace(err) } } cfg, err := config.New(config.NoDefaults, bootstrapCfg) if err != nil { return nil, errors.Trace(err) } return environs.New(cfg) }
// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(store configstore.Storage, info configstore.EnvironInfo, apiOpen apiOpenFunc, stop <-chan struct{}) (apiState, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var environTag names.EnvironTag if names.IsValidEnvironment(endpoint.EnvironUUID) { environTag = names.NewEnvironTag(endpoint.EnvironUUID) } else { // For backwards-compatibility, we have to allow connections // with an empty UUID. Login will work for the same reasons. logger.Warningf("ignoring invalid API endpoint environment UUID %v", endpoint.EnvironUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{err} } return st, nil }
func environInfoUserTag(info configstore.EnvironInfo) names.UserTag { var username string if info != nil { username = info.APICredentials().User } if username == "" { username = configstore.DefaultAdminUsername } return names.NewUserTag(username) }
// decorateAndWriteInfo decorates the info struct with information // from the given cfg, and the writes that out to the filesystem. func decorateAndWriteInfo(info configstore.EnvironInfo, cfg *config.Config) error { // Sanity check our config. var endpoint configstore.APIEndpoint if cert, ok := cfg.CACert(); !ok { return errors.Errorf("CACert is not set") } else if uuid, ok := cfg.UUID(); !ok { return errors.Errorf("UUID is not set") } else if adminSecret := cfg.AdminSecret(); adminSecret == "" { return errors.Errorf("admin-secret is not set") } else { endpoint = configstore.APIEndpoint{ CACert: cert, ModelUUID: uuid, } } creds := configstore.APICredentials{ User: configstore.DefaultAdminUsername, Password: cfg.AdminSecret(), } endpoint.ServerUUID = endpoint.ModelUUID info.SetAPICredentials(creds) info.SetAPIEndpoint(endpoint) info.SetBootstrapConfig(cfg.AllAttrs()) if err := info.Write(); err != nil { return errors.Annotatef(err, "cannot create model info %q", cfg.Name()) } return nil }
// decorateAndWriteInfo decorates the info struct with information // from the given cfg, and the writes that out to the filesystem. func decorateAndWriteInfo(info configstore.EnvironInfo, cfg *config.Config) error { // Sanity check our config. var endpoint configstore.APIEndpoint if cert, ok := cfg.CACert(); !ok { return errors.Errorf("CACert is not set") } else if uuid, ok := cfg.UUID(); !ok { return errors.Errorf("UUID is not set") } else if adminSecret := cfg.AdminSecret(); adminSecret == "" { return errors.Errorf("admin-secret is not set") } else { endpoint = configstore.APIEndpoint{ CACert: cert, EnvironUUID: uuid, } } creds := configstore.APICredentials{ User: "******", // TODO(waigani) admin@local once we have that set Password: cfg.AdminSecret(), } info.SetAPICredentials(creds) info.SetAPIEndpoint(endpoint) info.SetBootstrapConfig(cfg.AllAttrs()) if err := info.Write(); err != nil { return errors.Annotatef(err, "cannot create environment info %q", cfg.Name()) } return nil }
// getConfig looks for configuration info on the given environment func getConfig(info configstore.EnvironInfo, envs *environs.Environs, envName string) (*config.Config, error) { if info != nil && len(info.BootstrapConfig()) > 0 { cfg, err := config.New(config.NoDefaults, info.BootstrapConfig()) if err != nil { logger.Warningf("failed to parse bootstrap-config: %v", err) } return cfg, err } if envs != nil { cfg, err := envs.Config(envName) if err != nil && !errors.IsNotFound(err) { logger.Warningf("failed to get config for environment %q: %v", envName, err) } return cfg, err } return nil, errors.NotFoundf("environment %q", envName) }
func (s *CacheAPIEndpointsSuite) assertEndpointsPreferIPv6True(c *gc.C, info configstore.EnvironInfo) { c.Assert(s.resolveNumCalls, gc.Equals, 1) c.Assert(s.numResolved, gc.Equals, 10) endpoint := info.APIEndpoint() // Check Addresses after resolving. c.Check(endpoint.Addresses, jc.DeepEquals, []string{ s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. "[2001:db8::1]:1234", "[2001:db8::2]:1235", "0.1.2.1:1234", // From ipv4+4.example.com "0.1.2.2:1234", // From ipv4+4.example.com "0.1.2.3:1234", // From ipv4+6.example.com "0.1.2.5:1234", // From ipv4.example.com "0.1.2.6:1234", // From ipv6+4.example.com "1.0.0.1:1234", "1.0.0.2:1235", "192.0.0.1:1234", "localhost:1234", // Left intact on purpose. "localhost:1235", // Left intact on purpose. "[fc00::10]:1234", // From ipv6.example.com "[fc00::111]:1234", "[fc00::3]:1234", // From ipv4+6.example.com "[fc00::6]:1234", // From ipv6+4.example.com "[fc00::8]:1234", // From ipv6+6.example.com "[fc00::9]:1234", // From ipv6+6.example.com }) // Check Hostnames before resolving c.Check(endpoint.Hostnames, jc.DeepEquals, []string{ s.apiHostPort.NetAddr(), // Last endpoint successfully connected to is always on top. "[2001:db8::1]:1234", "[2001:db8::2]:1235", "1.0.0.1:1234", "1.0.0.2:1235", "192.0.0.1:1234", "invalid host:1234", "ipv4+4.example.com:1234", "ipv4+6.example.com:1234", "ipv4.example.com:1234", "ipv6+4.example.com:1235", "ipv6+6.example.com:1234", "ipv6.example.com:1234", "localhost:1234", "localhost:1235", "[fc00::111]:1234", }) }
// getControllerEnviron gets the bootstrap information required to destroy the // environment by first checking the config store, then querying the API if // the information is not in the store. func (c *destroyCommandBase) getControllerEnviron(info configstore.EnvironInfo, sysAPI destroyControllerAPI) (_ environs.Environ, err error) { bootstrapCfg := info.BootstrapConfig() if bootstrapCfg == nil { if sysAPI == nil { return nil, errors.New("unable to get bootstrap information from API") } bootstrapCfg, err = sysAPI.ModelConfig() if err != nil { return nil, errors.Trace(err) } } cfg, err := config.New(config.NoDefaults, bootstrapCfg) if err != nil { return nil, errors.Trace(err) } return environs.New(cfg) }
// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(store configstore.Storage, info configstore.EnvironInfo, apiOpen apiOpenFunc, stop <-chan struct{}) (apiState, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var environTag names.Tag if endpoint.EnvironUUID != "" { // Note: we should be validating that EnvironUUID contains a // valid UUID. environTag = names.NewEnvironTag(endpoint.EnvironUUID) } username := info.APICredentials().User if username == "" { username = "******" } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: names.NewUserTag(username), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// updateEnvironmentInfo updates the given environment info with the values // stored in the provided YAML encoded data. func updateEnvironmentInfo(info configstore.EnvironInfo, data []byte) error { var values configstore.EnvironInfoData if err := yaml.Unmarshal(data, &values); err != nil { return errors.Annotate(err, "cannot unmarshal jenv data") } // Ensure the required values are present. if missing := getMissingEnvironmentInfoFields(values); len(missing) != 0 { return errors.Errorf("missing required fields in jenv data: %s", strings.Join(missing, ", ")) } // Update the environment info. info.SetAPICredentials(configstore.APICredentials{ User: values.User, Password: values.Password, }) info.SetAPIEndpoint(configstore.APIEndpoint{ Addresses: values.StateServers, Hostnames: values.ServerHostnames, CACert: values.CACert, ModelUUID: values.ModelUUID, }) info.SetBootstrapConfig(values.Config) return nil }
// cacheAPIInfo updates the local environment settings (.jenv file) // with the provided apiInfo, assuming we've just successfully // connected to the API server. func cacheAPIInfo(info configstore.EnvironInfo, apiInfo *api.Info) (err error) { defer errors.Contextf(&err, "failed to cache API credentials") environUUID := "" if apiInfo.EnvironTag != "" { tag, err := names.ParseEnvironTag(apiInfo.Tag) if err != nil { return err } environUUID = tag.Id() } info.SetAPIEndpoint(configstore.APIEndpoint{ Addresses: apiInfo.Addrs, CACert: string(apiInfo.CACert), EnvironUUID: environUUID, }) tag, err := names.ParseUserTag(apiInfo.Tag) if err != nil { return err } info.SetAPICredentials(configstore.APICredentials{ User: tag.Id(), Password: apiInfo.Password, }) return info.Write() }
// cacheChangedAPIInfo updates the local environment settings (.jenv file) // with the provided API server addresses if they have changed. It will also // save the environment tag if it is available. func cacheChangedAPIInfo(info configstore.EnvironInfo, hostPorts [][]network.HostPort, addrConnectedTo network.HostPort, environUUID, serverUUID string) error { addrs, hosts, addrsChanged := PrepareEndpointsForCaching(info, hostPorts, addrConnectedTo) logger.Debugf("cacheChangedAPIInfo: serverUUID=%q", serverUUID) endpoint := info.APIEndpoint() needCaching := false if endpoint.EnvironUUID != environUUID && environUUID != "" { endpoint.EnvironUUID = environUUID needCaching = true } if endpoint.ServerUUID != serverUUID && serverUUID != "" { endpoint.ServerUUID = serverUUID needCaching = true } if addrsChanged { endpoint.Addresses = addrs endpoint.Hostnames = hosts needCaching = true } if !needCaching { return nil } info.SetAPIEndpoint(endpoint) if err := info.Write(); err != nil { return err } logger.Infof("updated API connection settings cache - endpoints %v", endpoint.Addresses) return nil }
func (c *UseEnvironmentCommand) updateCachedInfo(info configstore.EnvironInfo, envUUID string, creds configstore.APICredentials, endpoint configstore.APIEndpoint) error { info.SetAPICredentials(creds) // Specify the environment UUID. The server UUID will be the same as the // endpoint that we have just connected to, as will be the CACert, addresses // and hostnames. endpoint.EnvironUUID = envUUID info.SetAPIEndpoint(endpoint) return errors.Trace(info.Write()) }
// apiInfoConnect looks for endpoint on the given environment and // tries to connect to it, sending the result on the returned channel. func apiInfoConnect(info configstore.EnvironInfo, apiOpen apiOpenFunc, stop <-chan struct{}) (apiState, error) { endpoint := info.APIEndpoint() if info == nil || len(endpoint.Addresses) == 0 { return nil, &infoConnectError{fmt.Errorf("no cached addresses")} } logger.Infof("connecting to API addresses: %v", endpoint.Addresses) var environTag names.EnvironTag if names.IsValidEnvironment(endpoint.EnvironUUID) { environTag = names.NewEnvironTag(endpoint.EnvironUUID) } apiInfo := &api.Info{ Addrs: endpoint.Addresses, CACert: endpoint.CACert, Tag: environInfoUserTag(info), Password: info.APICredentials().Password, EnvironTag: environTag, } st, err := apiOpen(apiInfo, api.DefaultDialOpts()) if err != nil { return nil, &infoConnectError{err} } return st, nil }
// cacheAPIInfo updates the local environment settings (.jenv file) // with the provided apiInfo, assuming we've just successfully // connected to the API server. func cacheAPIInfo(st apiState, info configstore.EnvironInfo, apiInfo *api.Info) (err error) { defer errors.DeferredAnnotatef(&err, "failed to cache API credentials") var environUUID string if names.IsValidEnvironment(apiInfo.EnvironTag.Id()) { environUUID = apiInfo.EnvironTag.Id() } else { // For backwards-compatibility, we have to allow connections // with an empty UUID. Login will work for the same reasons. logger.Warningf("ignoring invalid cached API endpoint environment UUID %v", apiInfo.EnvironTag.Id()) } hostPorts, err := network.ParseHostPorts(apiInfo.Addrs...) if err != nil { return errors.Annotatef(err, "invalid API addresses %v", apiInfo.Addrs) } addrConnectedTo, err := network.ParseHostPorts(st.Addr()) if err != nil { // Should never happen, since we've just connected with it. return errors.Annotatef(err, "invalid API address %q", st.Addr()) } addrs, hostnames, addrsChanged := PrepareEndpointsForCaching( info, [][]network.HostPort{hostPorts}, addrConnectedTo[0], ) endpoint := configstore.APIEndpoint{ CACert: string(apiInfo.CACert), EnvironUUID: environUUID, } if addrsChanged { endpoint.Addresses = addrs endpoint.Hostnames = hostnames } info.SetAPIEndpoint(endpoint) tag, ok := apiInfo.Tag.(names.UserTag) if !ok { return errors.Errorf("apiInfo.Tag was of type %T, expecting names.UserTag", apiInfo.Tag) } info.SetAPICredentials(configstore.APICredentials{ // This looks questionable. We have a tag, say "user-admin", but then only // the Id portion of the tag is recorded, "admin", so this is really a // username, not a tag, and cannot be reconstructed accurately. User: tag.Id(), Password: apiInfo.Password, }) return info.Write() }
// cacheChangedAPIInfo updates the local environment settings (.jenv file) // with the provided API server addresses if they have changed. It will also // save the environment tag if it is available. func cacheChangedAPIInfo(info configstore.EnvironInfo, hostPorts [][]network.HostPort, newEnvironTag string) error { var addrs []string for _, serverHostPorts := range hostPorts { for _, hostPort := range serverHostPorts { // Only cache addresses that are likely to be usable, // exclude localhost style ones. if hostPort.Scope != network.ScopeMachineLocal && hostPort.Scope != network.ScopeLinkLocal { addrs = append(addrs, hostPort.NetAddr()) } } } endpoint := info.APIEndpoint() changed := false if newEnvironTag != "" { tag, err := names.ParseEnvironTag(newEnvironTag) if err == nil { if environUUID := tag.Id(); endpoint.EnvironUUID != environUUID { changed = true endpoint.EnvironUUID = environUUID } } else { logger.Debugf("cannot parse environ tag: %v", err) } } if len(addrs) != 0 && addrsChanged(endpoint.Addresses, addrs) { logger.Debugf("API addresses changed from %q to %q", endpoint.Addresses, addrs) changed = true endpoint.Addresses = addrs } if !changed { return nil } info.SetAPIEndpoint(endpoint) if err := info.Write(); err != nil { return err } logger.Infof("updated API connection settings cache") return nil }
func (c *LoginCommand) updatePassword(ctx *cmd.Context, conn api.Connection, userTag names.UserTag, serverInfo configstore.EnvironInfo) error { password, err := utils.RandomPassword() if err != nil { return errors.Annotate(err, "failed to generate random password") } userManager, err := c.getUserManager(conn) if err != nil { return errors.Trace(err) } if err := userManager.SetPassword(userTag.Name(), password); err != nil { errors.Trace(err) } ctx.Infof("password updated\n") creds := serverInfo.APICredentials() creds.Password = password serverInfo.SetAPICredentials(creds) if err = serverInfo.Write(); err != nil { return errors.Trace(err) } return nil }
// cacheAPIInfo updates the local environment settings (.jenv file) // with the provided apiInfo, assuming we've just successfully // connected to the API server. func cacheAPIInfo(info configstore.EnvironInfo, apiInfo *api.Info) (err error) { defer errors.Contextf(&err, "failed to cache API credentials") var environUUID string if apiInfo.EnvironTag != nil { environUUID = apiInfo.EnvironTag.Id() } info.SetAPIEndpoint(configstore.APIEndpoint{ Addresses: apiInfo.Addrs, CACert: string(apiInfo.CACert), EnvironUUID: environUUID, }) tag, ok := apiInfo.Tag.(names.UserTag) if !ok { return errors.Errorf("apiInfo.Tag was of type %T, expecting names.UserTag", apiInfo.Tag) } info.SetAPICredentials(configstore.APICredentials{ // This looks questionable. We have a tag, say "user-admin", but then only // the Id portion of the tag is recorded, "admin", so this is really a // username, not a tag, and cannot be reconstructed accurately. User: tag.Id(), Password: apiInfo.Password, }) return info.Write() }
// PrepareEndpointsForCaching performs the necessary operations on the // given API hostPorts so they are suitable for caching into the // environment's .jenv file, taking into account the addrConnectedTo // and the existing config store info: // // 1. Collapses hostPorts into a single slice. // 2. Filters out machine-local and link-local addresses. // 3. Removes any duplicates // 4. Call network.SortHostPorts() on the list, respecing prefer-ipv6 // flag. // 5. Puts the addrConnectedTo on top. // 6. Compares the result against info.APIEndpoint.Hostnames. // 7. If the addresses differ, call network.ResolveOrDropHostnames() // on the list and perform all steps again from step 1. // 8. Compare the list of resolved addresses against the cached info // APIEndpoint.Addresses, and if changed return both addresses and // hostnames as strings (so they can be cached on APIEndpoint) and // set haveChanged to true. // 9. If the hostnames haven't changed, return two empty slices and set // haveChanged to false. No DNS resolution is performed to save time. // // This is used right after bootstrap to cache the initial API // endpoints, as well as on each CLI connection to verify if the // cached endpoints need updating. func PrepareEndpointsForCaching(info configstore.EnvironInfo, hostPorts [][]network.HostPort, addrConnectedTo network.HostPort) (addresses, hostnames []string, haveChanged bool) { processHostPorts := func(allHostPorts [][]network.HostPort) []network.HostPort { collapsedHPs := network.CollapseHostPorts(allHostPorts) filteredHPs := network.FilterUnusableHostPorts(collapsedHPs) uniqueHPs := network.DropDuplicatedHostPorts(filteredHPs) // Sort the result to prefer public IPs on top (when prefer-ipv6 // is true, IPv6 addresses of the same scope will come before IPv4 // ones). preferIPv6 := maybePreferIPv6(info) network.SortHostPorts(uniqueHPs, preferIPv6) if addrConnectedTo.Value != "" { return network.EnsureFirstHostPort(addrConnectedTo, uniqueHPs) } // addrConnectedTo can be empty only right after bootstrap. return uniqueHPs } apiHosts := processHostPorts(hostPorts) hostsStrings := network.HostPortsToStrings(apiHosts) endpoint := info.APIEndpoint() needResolving := false // Verify if the unresolved addresses have changed. if len(apiHosts) > 0 && len(endpoint.Hostnames) > 0 { if addrsChanged(hostsStrings, endpoint.Hostnames) { logger.Debugf( "API hostnames changed from %v to %v - resolving hostnames", endpoint.Hostnames, hostsStrings, ) needResolving = true } } else if len(apiHosts) > 0 { // No cached hostnames, most likely right after bootstrap. logger.Debugf("API hostnames %v - resolving hostnames", hostsStrings) needResolving = true } if !needResolving { // We're done - nothing changed. logger.Debugf("API hostnames unchanged - not resolving") return nil, nil, false } // Perform DNS resolution and check against APIEndpoints.Addresses. resolved := resolveOrDropHostnames(apiHosts) apiAddrs := processHostPorts([][]network.HostPort{resolved}) addrsStrings := network.HostPortsToStrings(apiAddrs) if len(apiAddrs) > 0 && len(endpoint.Addresses) > 0 { if addrsChanged(addrsStrings, endpoint.Addresses) { logger.Infof( "API addresses changed from %v to %v", endpoint.Addresses, addrsStrings, ) return addrsStrings, hostsStrings, true } } else if len(apiAddrs) > 0 { // No cached addresses, most likely right after bootstrap. logger.Infof("new API addresses to cache %v", addrsStrings) return addrsStrings, hostsStrings, true } // No changes. logger.Debugf("API addresses unchanged") return nil, nil, false }
func (c *createEnvironmentCommand) Run(ctx *cmd.Context) (return_err error) { client, err := c.getAPI() if err != nil { return err } defer client.Close() creds, err := c.ConnectionCredentials() if err != nil { return errors.Trace(err) } creatingForSelf := true envOwner := creds.User if c.Owner != "" { owner := names.NewUserTag(c.Owner) user := names.NewUserTag(creds.User) creatingForSelf = owner == user envOwner = c.Owner } var info configstore.EnvironInfo var endpoint configstore.APIEndpoint if creatingForSelf { logger.Debugf("create cache entry for %q", c.Name) // Create the configstore entry and write it to disk, as this will error // if one with the same name already exists. endpoint, err = c.ConnectionEndpoint() if err != nil { return errors.Trace(err) } store, err := configstore.Default() if err != nil { return errors.Trace(err) } info = store.CreateInfo(c.Name) info.SetAPICredentials(creds) endpoint.EnvironUUID = "" if err := info.Write(); err != nil { if errors.Cause(err) == configstore.ErrEnvironInfoAlreadyExists { newErr := errors.AlreadyExistsf("environment %q", c.Name) return errors.Wrap(err, newErr) } return errors.Trace(err) } defer func() { if return_err != nil { logger.Debugf("error found, remove cache entry") e := info.Destroy() if e != nil { logger.Errorf("could not remove environment file: %v", e) } } }() } else { logger.Debugf("skipping cache entry for %q as owned %q", c.Name, c.Owner) } serverSkeleton, err := client.ConfigSkeleton("", "") if err != nil { return errors.Trace(err) } attrs, err := c.getConfigValues(ctx, serverSkeleton) if err != nil { return errors.Trace(err) } // We pass nil through for the account details until we implement that bit. env, err := client.CreateEnvironment(envOwner, nil, attrs) if err != nil { // cleanup configstore return errors.Trace(err) } if creatingForSelf { // update the cached details with the environment uuid endpoint.EnvironUUID = env.UUID info.SetAPIEndpoint(endpoint) if err := info.Write(); err != nil { return errors.Trace(err) } ctx.Infof("created environment %q", c.Name) return envcmd.SetCurrentEnvironment(ctx, c.Name) } else { ctx.Infof("created environment %q for %q", c.Name, c.Owner) } return nil }