func (s *cmdControllerSuite) TestControllerLoginCommand(c *gc.C) { user := s.Factory.MakeUser(c, &factory.UserParams{ NoEnvUser: true, Password: "******", }) apiInfo := s.APIInfo(c) serverFile := envcmd.ServerFile{ Addresses: apiInfo.Addrs, CACert: apiInfo.CACert, Username: user.Name(), Password: "******", } serverFilePath := filepath.Join(c.MkDir(), "server.yaml") content, err := goyaml.Marshal(serverFile) c.Assert(err, jc.ErrorIsNil) err = ioutil.WriteFile(serverFilePath, []byte(content), 0644) c.Assert(err, jc.ErrorIsNil) s.run(c, "login", "--server", serverFilePath, "just-a-controller") // Make sure that the saved server details are sufficient to connect // to the api server. api, err := juju.NewAPIFromName("just-a-controller", nil) c.Assert(err, jc.ErrorIsNil) api.Close() }
func (s *cmdControllerSuite) TestAddModel(c *gc.C) { // The JujuConnSuite doesn't set up an ssh key in the fake home dir, // so fake one on the command line. The dummy provider also expects // a config value for 'controller'. context := s.run(c, "add-model", "new-model", "authorized-keys=fake-key", "controller=false") c.Check(testing.Stdout(context), gc.Equals, "") c.Check(testing.Stderr(context), gc.Equals, "added model \"new-model\"\n") // Make sure that the saved server details are sufficient to connect // to the api server. accountDetails, err := s.ControllerStore.AccountByName("kontroll", "admin@local") c.Assert(err, jc.ErrorIsNil) modelDetails, err := s.ControllerStore.ModelByName("kontroll", "admin@local", "new-model") c.Assert(err, jc.ErrorIsNil) api, err := juju.NewAPIConnection(juju.NewAPIConnectionParams{ Store: s.ControllerStore, ControllerName: "kontroll", AccountDetails: accountDetails, ModelUUID: modelDetails.ModelUUID, BootstrapConfig: noBootstrapConfig, DialOpts: api.DefaultDialOpts(), }) c.Assert(err, jc.ErrorIsNil) api.Close() }
// RunPost sends credentials obtained during the call to RunPre to the controller. func (r *RegisterMeteredCharm) RunPost(state api.Connection, bakeryClient *httpbakery.Client, ctx *cmd.Context, deployInfo DeploymentInfo, prevErr error) error { if prevErr != nil { return nil } if r.credentials == nil { return nil } api, cerr := getMetricCredentialsAPI(state) if cerr != nil { logger.Infof("failed to get the metrics credentials setter: %v", cerr) return cerr } defer api.Close() err := api.SetMetricCredentials(deployInfo.ServiceName, r.credentials) if err != nil { logger.Warningf("failed to set metric credentials: %v", err) return errors.Trace(err) } err = r.AllocateBudget.RunPost(state, bakeryClient, ctx, deployInfo, prevErr) if err != nil { logger.Warningf("failed to allocate budget: %v", err) return errors.Trace(err) } return nil }
func registerMeteredCharm(registrationURL string, state *api.State, jar *cookiejar.Jar, charmURL string, serviceName, environmentUUID string) error { charmsClient := charms.NewClient(state) defer charmsClient.Close() metered, err := charmsClient.IsMetered(charmURL) if err != nil { return err } if metered { httpClient := httpbakery.NewHTTPClient() httpClient.Jar = jar credentials, err := registerMetrics(registrationURL, environmentUUID, charmURL, serviceName, httpClient, openWebBrowser) if err != nil { logger.Infof("failed to register metrics: %v", err) return err } api, cerr := getMetricCredentialsAPI(state) if cerr != nil { logger.Infof("failed to get the metrics credentials setter: %v", cerr) } err = api.SetMetricCredentials(serviceName, credentials) if err != nil { logger.Infof("failed to set metric credentials: %v", err) return err } api.Close() } return nil }
func (s *cmdControllerSuite) testAddModel(c *gc.C, args ...string) { // The JujuConnSuite doesn't set up an ssh key in the fake home dir, // so fake one on the command line. The dummy provider also expects // a config value for 'controller'. args = append([]string{"add-model", "new-model"}, args...) args = append(args, "--config", "authorized-keys=fake-key", "--config", "controller=false", ) context := s.run(c, args...) c.Check(testing.Stdout(context), gc.Equals, "") c.Check(testing.Stderr(context), gc.Equals, ` Added 'new-model' model on dummy/dummy-region with credential 'cred' for user 'admin' `[1:]) // Make sure that the saved server details are sufficient to connect // to the api server. accountDetails, err := s.ControllerStore.AccountDetails("kontroll") c.Assert(err, jc.ErrorIsNil) modelDetails, err := s.ControllerStore.ModelByName("kontroll", "admin@local/new-model") c.Assert(err, jc.ErrorIsNil) api, err := juju.NewAPIConnection(juju.NewAPIConnectionParams{ Store: s.ControllerStore, ControllerName: "kontroll", AccountDetails: accountDetails, ModelUUID: modelDetails.ModelUUID, DialOpts: api.DefaultDialOpts(), OpenAPI: api.Open, }) c.Assert(err, jc.ErrorIsNil) api.Close() }
func (c *SpaceCommandBase) RunWithAPI(ctx *cmd.Context, toRun RunOnAPI) error { api, err := c.NewAPI() if err != nil { return errors.Annotate(err, "cannot connect to the API server") } defer api.Close() return toRun(api, ctx) }
func (c *registerCommand) listModels(store jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) { api, err := c.NewAPIRoot(store, controllerName, "") if err != nil { return nil, errors.Trace(err) } defer api.Close() mm := modelmanager.NewClient(api) return mm.ListModels(userName) }
func (s *cmdRegistrationSuite) TestAddUserAndRegister(c *gc.C) { // First, add user "bob", and record the "juju register" command // that is printed out. context := s.run(c, nil, "add-user", "bob", "Bob Dobbs") c.Check(testing.Stderr(context), gc.Equals, "") stdout := testing.Stdout(context) c.Check(stdout, gc.Matches, ` User "Bob Dobbs \(bob\)" added Please send this command to bob: juju register .* "Bob Dobbs \(bob\)" has not been granted access to any models(.|\n)* `[1:]) jujuRegisterCommand := strings.Fields(strings.TrimSpace( strings.SplitN(stdout[strings.Index(stdout, "juju register"):], "\n", 2)[0], )) c.Logf("%q", jujuRegisterCommand) // Now run the "juju register" command. We need to pass the // controller name and password to set. stdin := strings.NewReader("bob-controller\nhunter2\nhunter2\n") args := jujuRegisterCommand[1:] // drop the "juju" context = s.run(c, stdin, args...) c.Check(testing.Stdout(context), gc.Equals, "") c.Check(testing.Stderr(context), gc.Equals, ` Please set a name for this controller: Enter password: Confirm password: Welcome, bob. You are now logged into "bob-controller". There are no models available. You can create models with "juju create-model", or you can ask an administrator or owner of a model to grant access to that model with "juju grant". `[1:]) // Make sure that the saved server details are sufficient to connect // to the api server. accountDetails, err := s.ControllerStore.AccountByName("bob-controller", "bob@local") c.Assert(err, jc.ErrorIsNil) api, err := juju.NewAPIConnection(juju.NewAPIConnectionParams{ Store: s.ControllerStore, ControllerName: "bob-controller", AccountDetails: accountDetails, BootstrapConfig: noBootstrapConfig, DialOpts: api.DefaultDialOpts(), }) c.Assert(err, jc.ErrorIsNil) c.Assert(api.Close(), jc.ErrorIsNil) }
// killSystemViaClient attempts to kill the system using the client // endpoint for older juju systems which do not implement systemmanager.DestroySystem func (c *killCommand) killSystemViaClient(ctx *cmd.Context, info configstore.EnvironInfo, systemEnviron environs.Environ, store configstore.Storage) error { api, err := c.getClientAPI() if err != nil { defer api.Close() } if api != nil { err = api.DestroyEnvironment() if err != nil { ctx.Infof("Unable to destroy system through the API: %s. Destroying through provider.", err) } } return environs.Destroy(systemEnviron, store) }
func (c *listCommand) listForModel(ctx *cmd.Context) (err error) { api, err := c.apiFunc(c) if err != nil { return errors.Trace(err) } defer api.Close() result, err := api.List() if err != nil { return errors.Trace(err) } if len(result) == 0 && c.out.Name() == "tabular" { ctx.Infof(noBlocks) return nil } return c.out.Write(ctx, formatBlockInfo(result)) }
func (s *cmdControllerSuite) TestCreateEnvironment(c *gc.C) { c.Assert(envcmd.WriteCurrentController("dummyenv"), jc.ErrorIsNil) // The JujuConnSuite doesn't set up an ssh key in the fake home dir, // so fake one on the command line. The dummy provider also expects // a config value for 'state-server'. context := s.run(c, "create-environment", "new-env", "authorized-keys=fake-key", "state-server=false") c.Check(testing.Stdout(context), gc.Equals, "") c.Check(testing.Stderr(context), gc.Equals, ` created environment "new-env" dummyenv (controller) -> new-env `[1:]) // Make sure that the saved server details are sufficient to connect // to the api server. api, err := juju.NewAPIFromName("new-env", nil) c.Assert(err, jc.ErrorIsNil) api.Close() }
func (c *migrateCommand) getTargetControllerMacaroons() ([]macaroon.Slice, error) { apiContext, err := c.APIContext() if err != nil { return nil, errors.Trace(err) } // Connect to the target controller, ensuring up-to-date macaroons, // and return the macaroons in the cookie jar for the controller. // // TODO(axw,mjs) add a controller API that returns a macaroon that // may be used for the sole purpose of migration. api, err := c.newAPIRoot(c.ClientStore(), c.targetController, "") if err != nil { return nil, errors.Annotate(err, "connecting to target controller") } defer api.Close() return httpbakery.MacaroonsForURL(apiContext.Jar, api.CookieURL()), nil }
func (t *timeoutOpener) Open(store jujuclient.ClientStore, controllerName, modelName string) (api.Connection, error) { // Make the channels buffered so the created goroutine is guaranteed // not go get blocked trying to send down the channel. apic := make(chan api.Connection, 1) errc := make(chan error, 1) go func() { api, dialErr := t.opener.Open(store, controllerName, modelName) if dialErr != nil { errc <- dialErr return } select { case apic <- api: // sent fine default: // couldn't send, was blocked by the dummy value, must have timed out. api.Close() } }() var apiRoot api.Connection select { case err := <-errc: return nil, err case apiRoot = <-apic: case <-t.clock.After(t.timeout): select { case apic <- nil: // Fill up the buffer on the apic to indicate to the other goroutine // that we have timed out. case apiRoot = <-apic: // We hit that weird edge case where we have both timed out and // returned a viable apiRoot at exactly the same time, and the other // goroutine managed to send back the apiRoot before we pushed the // dummy value. If this is the case, then we are good, return the // apiRoot return apiRoot, nil } return nil, ErrConnTimedOut } return apiRoot, nil }
// RunPost sends credentials obtained during the call to RunPre to the controller. func (r *RegisterMeteredCharm) RunPost(state api.Connection, client *http.Client, deployInfo DeploymentInfo) error { if r.credentials == nil { return nil } api, cerr := getMetricCredentialsAPI(state) if cerr != nil { logger.Infof("failed to get the metrics credentials setter: %v", cerr) return cerr } defer api.Close() err := api.SetMetricCredentials(deployInfo.ServiceName, r.credentials) if err != nil { logger.Infof("failed to set metric credentials: %v", err) return err } return nil }
func (c *changePasswordCommand) recordMacaroon(user, password string) error { accountDetails := &jujuclient.AccountDetails{User: user} args, err := c.NewAPIConnectionParams( c.ClientStore(), c.ControllerName(), "", accountDetails, ) if err != nil { return errors.Trace(err) } args.DialOpts.BakeryClient.WebPageVisitor = httpbakery.NewMultiVisitor( authentication.NewVisitor(accountDetails.User, func(string) (string, error) { return password, nil }), args.DialOpts.BakeryClient.WebPageVisitor, ) api, err := c.newAPIConnection(args) if err != nil { return errors.Annotate(err, "connecting to API") } return api.Close() }
func (c *listCommand) listForController(ctx *cmd.Context) (err error) { api, err := c.controllerAPIFunc(c) if err != nil { return errors.Trace(err) } defer api.Close() result, err := api.ListBlockedModels() if err != nil { return errors.Trace(err) } if len(result) == 0 && c.out.Name() == "tabular" { ctx.Infof(noBlocks) return nil } info, err := FormatModelBlockInfo(result) if err != nil { return errors.Trace(err) } return c.out.Write(ctx, info) }
// RunPost sends credentials obtained during the call to RunPre to the controller. func (r *RegisterMeteredCharm) RunPost(state api.Connection, client *http.Client, deployInfo DeploymentInfo) error { if r.credentials == nil { return nil } api, cerr := getMetricCredentialsAPI(state) if cerr != nil { logger.Infof("failed to get the metrics credentials setter: %v", cerr) return cerr } defer api.Close() err := api.SetMetricCredentials(deployInfo.ServiceName, r.credentials) if params.IsCodeNotImplemented(err) { // The state server is too old to support metering. Warn // the user, but don't return an error. logger.Warningf("current state server version does not support charm metering") return nil } else if err != nil { logger.Infof("failed to set metric credentials: %v", err) return err } return nil }
credentials, err := registerMetrics(registrationURL, environmentUUID, charmURL, serviceName, httpClient, openWebBrowser) if err != nil { logger.Infof("failed to register metrics: %v", err) return err } api, cerr := getMetricCredentialsAPI(state) if cerr != nil { logger.Infof("failed to get the metrics credentials setter: %v", cerr) } err = api.SetMetricCredentials(serviceName, credentials) if err != nil { logger.Infof("failed to set metric credentials: %v", err) return err } api.Close() } return nil } func registerMetrics(registrationURL, environmentUUID, charmURL, serviceName string, client *http.Client, visitWebPage func(*url.URL) error) ([]byte, error) { if registrationURL == "" { return nil, errors.Errorf("no metric registration url is specified") } registerURL, err := url.Parse(registrationURL) if err != nil { return nil, errors.Trace(err) } registrationPost := metricRegistrationPost{ EnvironmentUUID: environmentUUID,
func (s *cmdRegistrationSuite) TestAddUserAndRegister(c *gc.C) { // First, add user "bob", and record the "juju register" command // that is printed out. context := run(c, nil, "add-user", "bob", "Bob Dobbs") c.Check(testing.Stderr(context), gc.Equals, "") stdout := testing.Stdout(context) expectPat := ` User "Bob Dobbs \(bob\)" added Please send this command to bob: juju register (.+) "Bob Dobbs \(bob\)" has not been granted access to any models(.|\n)* `[1:] c.Assert(stdout, gc.Matches, expectPat) arg := regexp.MustCompile("^" + expectPat + "$").FindStringSubmatch(stdout)[1] c.Logf("juju register %q", arg) // Now run the "juju register" command. We need to pass the // controller name and password to set, and we need a different // file store to mimic a different local OS user. s.CreateUserHome(c, &jujutesting.UserHomeParams{ Username: "******", }) // The expected prompt does not include a warning about the controller // name, as this new local user does not have a controller named // "kontroll" registered. prompter := cmdtesting.NewSeqPrompter(c, "»", ` Enter a new password: »hunter2 Confirm password: »hunter2 Initial password successfully set for bob. Enter a name for this controller \[kontroll\]: »bob-controller Welcome, bob. You are now logged into "bob-controller". There are no models available. (.|\n)* `[1:]) context = run(c, prompter, "register", arg) prompter.AssertDone() // Make sure that the saved server details are sufficient to connect // to the api server. jar, err := cookiejar.New(&cookiejar.Options{ Filename: cookiejar.DefaultCookieFile(), }) c.Assert(err, jc.ErrorIsNil) dialOpts := api.DefaultDialOpts() dialOpts.BakeryClient = httpbakery.NewClient() dialOpts.BakeryClient.Jar = jar accountDetails, err := s.ControllerStore.AccountDetails("bob-controller") c.Assert(err, jc.ErrorIsNil) api, err := juju.NewAPIConnection(juju.NewAPIConnectionParams{ Store: s.ControllerStore, ControllerName: "bob-controller", AccountDetails: accountDetails, DialOpts: dialOpts, OpenAPI: api.Open, }) c.Assert(err, jc.ErrorIsNil) c.Assert(api.Close(), jc.ErrorIsNil) }
func (s *cmdRegistrationSuite) TestAddUserAndRegister(c *gc.C) { // First, add user "bob", and record the "juju register" command // that is printed out. context := s.run(c, nil, "add-user", "bob", "Bob Dobbs") c.Check(testing.Stderr(context), gc.Equals, "") stdout := testing.Stdout(context) c.Check(stdout, gc.Matches, ` User "Bob Dobbs \(bob\)" added Please send this command to bob: juju register .* "Bob Dobbs \(bob\)" has not been granted access to any models(.|\n)* `[1:]) jujuRegisterCommand := strings.Fields(strings.TrimSpace( strings.SplitN(stdout[strings.Index(stdout, "juju register"):], "\n", 2)[0], )) c.Logf("%q", jujuRegisterCommand) // Now run the "juju register" command. We need to pass the // controller name and password to set, and we need a different // file store to mimic a different local OS user. userHomeParams := jujutesting.UserHomeParams{Username: "******"} s.CreateUserHome(c, &userHomeParams) stdin := strings.NewReader("bob-controller\nhunter2\nhunter2\n") args := jujuRegisterCommand[1:] // drop the "juju" // The expected prompt does not include a warning about the controller // name, as this new local user does not have a controller named // "kontroll" registered. expectedPrompt := ` Enter a name for this controller [kontroll]: Enter a new password: Confirm password: Welcome, bob. You are now logged into "bob-controller". There are no models available. You can add models with "juju add-model", or you can ask an administrator or owner of a model to grant access to that model with "juju grant". `[1:] context = s.run(c, stdin, args...) c.Check(testing.Stdout(context), gc.Equals, "") c.Check(testing.Stderr(context), gc.Equals, expectedPrompt) // Make sure that the saved server details are sufficient to connect // to the api server. jar, err := cookiejar.New(&cookiejar.Options{ Filename: cookiejar.DefaultCookieFile(), }) c.Assert(err, jc.ErrorIsNil) dialOpts := api.DefaultDialOpts() dialOpts.BakeryClient = httpbakery.NewClient() dialOpts.BakeryClient.Jar = jar accountDetails, err := s.ControllerStore.AccountDetails("bob-controller") c.Assert(err, jc.ErrorIsNil) api, err := juju.NewAPIConnection(juju.NewAPIConnectionParams{ Store: s.ControllerStore, ControllerName: "bob-controller", AccountDetails: accountDetails, DialOpts: dialOpts, OpenAPI: api.Open, }) c.Assert(err, jc.ErrorIsNil) c.Assert(api.Close(), jc.ErrorIsNil) }
// Run implements Command.Run func (c *killCommand) Run(ctx *cmd.Context) error { store, err := configstore.Default() if err != nil { return errors.Annotate(err, "cannot open controller info storage") } cfgInfo, err := store.ReadInfo(c.ModelName()) if err != nil { return errors.Annotate(err, "cannot read controller info") } // Verify that we're destroying a controller apiEndpoint := cfgInfo.APIEndpoint() if apiEndpoint.ServerUUID != "" && apiEndpoint.ModelUUID != apiEndpoint.ServerUUID { return errors.Errorf("%q is not a controller; use juju model destroy to destroy it", c.ModelName()) } if !c.assumeYes { if err = confirmDestruction(ctx, c.ModelName()); err != nil { return err } } // Attempt to connect to the API. api, err := c.getControllerAPI() switch { case err == nil: defer api.Close() case errors.Cause(err) == common.ErrPerm: return errors.Annotate(err, "cannot destroy controller") default: if errors.Cause(err) != modelcmd.ErrConnTimedOut { logger.Debugf("unable to open api: %s", err) } ctx.Infof("Unable to open API: %s\n", err) api = nil } // Obtain bootstrap / controller environ information controllerEnviron, err := c.getControllerEnviron(cfgInfo, api) if err != nil { return errors.Annotate(err, "cannot obtain bootstrap information") } // If we were unable to connect to the API, just destroy the controller through // the environs interface. if api == nil { ctx.Infof("Unable to connect to the API server. Destroying through provider.") return environs.Destroy(controllerEnviron, store) } // Attempt to destroy the controller and all environments. err = api.DestroyController(true) if err != nil { ctx.Infof("Unable to destroy controller through the API: %s. Destroying through provider.", err) return environs.Destroy(controllerEnviron, store) } ctx.Infof("Destroying controller %q\nWaiting for resources to be reclaimed", c.ModelName()) updateStatus := newTimedStatusUpdater(ctx, api, apiEndpoint.ModelUUID) for ctrStatus, envsStatus := updateStatus(0); hasUnDeadEnvirons(envsStatus); ctrStatus, envsStatus = updateStatus(2 * time.Second) { ctx.Infof(fmtCtrStatus(ctrStatus)) for _, envStatus := range envsStatus { ctx.Verbosef(fmtEnvStatus(envStatus)) } } ctx.Infof("All hosted models reclaimed, cleaning up controller machines") return environs.Destroy(controllerEnviron, store) }
// TODO(axw) define an error for "no answer" func (s *jujuNameServer) answer(q dns.Question) (dns.RR, error) { if q.Qtype != dns.TypeA { return nil, nil } var envName string entityName := strings.ToLower(strings.TrimSuffix(q.Name, "."+zone)) if i := strings.IndexRune(entityName, '.'); i >= 0 { envName = entityName[i+1:] entityName = entityName[:i] } else { var err error envName, err = envcmd.GetDefaultEnvironment() if err != nil { return nil, err } } // TODO(axw) cache API connection api, err := s.openAPI(envName) if err != nil { return nil, err } defer api.Close() client := api.Client() // If the entity name parses as a tag, extract the ID. This enables // us to address "unit-mysql-0", where we couldn't otherwise, since // slashes are not allowed in domain names. Similarly for container // machines (e.g. to address "0/lxc/0", pass "machine-0-lxc-0"). if tag, err := names.ParseTag(entityName); err == nil { entityName = tag.Id() } var addr string if names.IsValidService(entityName) { status, err := client.Status([]string{entityName}) if err != nil { return nil, err } service := status.Services[entityName] addresses := make([]string, 0, len(service.Units)) for _, unit := range service.Units { if unit.PublicAddress == "" { continue } if includeLost || unit.UnitAgent.Status != "lost" { addresses = append(addresses, unit.PublicAddress) } } // Might be nice to have additional info in TXT? if len(addresses) == 0 { return nil, nil } addr = addresses[rand.Intn(len(addresses))] } else { // Assume it's a machine or unit name. addr, err = client.PublicAddress(entityName) if err != nil { return nil, err } } ip := net.ParseIP(addr) if ip != nil { rr := new(dns.A) rr.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0, } rr.A = ip return rr, nil } else { rr := new(dns.CNAME) rr.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 0, } rr.Target = addr + "." return rr, nil } }
func (c *addModelCommand) Run(ctx *cmd.Context) error { api, err := c.newApiRoot() if err != nil { return errors.Annotate(err, "opening API connection") } defer api.Close() store := c.ClientStore() controllerName := c.ControllerName() accountDetails, err := store.AccountDetails(controllerName) if err != nil { return errors.Trace(err) } modelOwner := accountDetails.User if c.Owner != "" { if !names.IsValidUser(c.Owner) { return errors.Errorf("%q is not a valid user name", c.Owner) } modelOwner = names.NewUserTag(c.Owner).Canonical() } forUserSuffix := fmt.Sprintf(" for user '%s'", names.NewUserTag(modelOwner).Name()) attrs, err := c.getConfigValues(ctx) if err != nil { return errors.Trace(err) } cloudClient := c.newCloudAPI(api) var cloudTag names.CloudTag var cloud jujucloud.Cloud var cloudRegion string if c.CloudRegion != "" { cloudTag, cloud, cloudRegion, err = c.getCloudRegion(cloudClient) if err != nil { return errors.Trace(err) } } // If the user has specified a credential, then we will upload it if // it doesn't already exist in the controller, and it exists locally. var credentialTag names.CloudCredentialTag if c.CredentialName != "" { var err error if c.CloudRegion == "" { if cloudTag, cloud, err = defaultCloud(cloudClient); err != nil { return errors.Trace(err) } } credentialTag, err = c.maybeUploadCredential(ctx, cloudClient, cloudTag, cloudRegion, cloud, modelOwner) if err != nil { return errors.Trace(err) } } addModelClient := c.newAddModelAPI(api) model, err := addModelClient.CreateModel(c.Name, modelOwner, cloudTag.Id(), cloudRegion, credentialTag, attrs) if err != nil { return errors.Trace(err) } messageFormat := "Added '%s' model" messageArgs := []interface{}{c.Name} if modelOwner == accountDetails.User { controllerName := c.ControllerName() if err := store.UpdateModel(controllerName, c.Name, jujuclient.ModelDetails{ model.UUID, }); err != nil { return errors.Trace(err) } if err := store.SetCurrentModel(controllerName, c.Name); err != nil { return errors.Trace(err) } } if c.CloudRegion != "" || model.CloudRegion != "" { // The user explicitly requested a cloud/region, // or the cloud supports multiple regions. Whichever // the case, tell the user which cloud/region the // model was deployed to. cloudRegion := model.Cloud if model.CloudRegion != "" { cloudRegion += "/" + model.CloudRegion } messageFormat += " on %s" messageArgs = append(messageArgs, cloudRegion) } if model.CloudCredential != "" { tag := names.NewCloudCredentialTag(model.CloudCredential) credentialName := tag.Name() if tag.Owner().Canonical() != modelOwner { credentialName = fmt.Sprintf("%s/%s", tag.Owner().Canonical(), credentialName) } messageFormat += " with credential '%s'" messageArgs = append(messageArgs, credentialName) } messageFormat += forUserSuffix // "Added '<model>' model [on <cloud>/<region>] [with credential '<credential>'] for user '<user namePart>'" ctx.Infof(messageFormat, messageArgs...) if _, ok := attrs[config.AuthorizedKeysKey]; !ok { // It is not an error to have no authorized-keys when adding a // model, though this should never happen since we generate // juju-specific SSH keys. ctx.Infof(` No SSH authorized-keys were found. You must use "juju add-ssh-key" before "juju ssh", "juju scp", or "juju debug-hooks" will work.`) } return nil }
// Run implements Command.Run func (c *killCommand) Run(ctx *cmd.Context) error { if c.apiDialerFunc == nil { c.apiDialerFunc = c.NewAPIRoot } store, err := configstore.Default() if err != nil { return errors.Annotate(err, "cannot open system info storage") } cfgInfo, err := store.ReadInfo(c.systemName) if err != nil { return errors.Annotate(err, "cannot read system info") } // Verify that we're destroying a system apiEndpoint := cfgInfo.APIEndpoint() if apiEndpoint.ServerUUID != "" && apiEndpoint.EnvironUUID != apiEndpoint.ServerUUID { return errors.Errorf("%q is not a system; use juju environment destroy to destroy it", c.systemName) } if !c.assumeYes { if err = confirmDestruction(ctx, c.systemName); err != nil { return err } } // Attempt to connect to the API. api, err := c.getSystemAPI(cfgInfo) switch { case err == nil: defer api.Close() case errors.Cause(err) == common.ErrPerm: return errors.Annotate(err, "cannot destroy system") default: if err != ErrConnTimedOut { logger.Debugf("unable to open api: %s", err) } ctx.Infof("Unable to open API: %s\n", err) api = nil } // Obtain bootstrap / system environ information systemEnviron, err := c.getSystemEnviron(cfgInfo, api) if err != nil { return errors.Annotate(err, "cannot obtain bootstrap information") } // If we were unable to connect to the API, just destroy the system through // the environs interface. if api == nil { return environs.Destroy(systemEnviron, store) } // Attempt to destroy the system with destroyEnvs and ignoreBlocks = true err = api.DestroySystem(true, true) if params.IsCodeNotImplemented(err) { // Fall back to using the client endpoint to destroy the system, // sending the info we were already able to collect. return c.killSystemViaClient(ctx, cfgInfo, systemEnviron, store) } if err != nil { ctx.Infof("Unable to destroy system through the API: %s. Destroying through provider.", err) } return environs.Destroy(systemEnviron, store) }