func (s *AccountsSuite) TestUpdateAccountIgnoresEmptyAccess(c *gc.C) { testAccountDetails := jujuclient.AccountDetails{ User: "******", Password: "******", } err := s.store.UpdateAccount("ctrl", testAccountDetails) c.Assert(err, jc.ErrorIsNil) details, err := s.store.AccountDetails("ctrl") c.Assert(err, jc.ErrorIsNil) testAccountDetails.LastKnownAccess = ctrlAdminAccountDetails.LastKnownAccess c.Assert(testAccountDetails.LastKnownAccess, gc.Equals, "superuser") c.Assert(*details, jc.DeepEquals, testAccountDetails) }
// NewAPIConnection returns an api.Connection to the specified Juju controller, // with specified account credentials, optionally scoped to the specified model // name. func NewAPIConnection(args NewAPIConnectionParams) (api.Connection, error) { apiInfo, controller, err := connectionInfo(args) if err != nil { return nil, errors.Annotatef(err, "cannot work out how to connect") } if len(apiInfo.Addrs) == 0 { return nil, errors.New("no API addresses") } logger.Infof("connecting to API addresses: %v", apiInfo.Addrs) st, err := args.OpenAPI(apiInfo, args.DialOpts) if err != nil { redirErr, ok := errors.Cause(err).(*api.RedirectError) if !ok { return nil, errors.Trace(err) } // We've been told to connect to a different API server, // so do so. Note that we don't copy the account details // because the account on the redirected server may well // be different - we'll use macaroon authentication // directly without sending account details. // Copy the API info because it's possible that the // apiConfigConnect is still using it concurrently. apiInfo = &api.Info{ ModelTag: apiInfo.ModelTag, Addrs: network.HostPortsToStrings(usableHostPorts(redirErr.Servers)), CACert: redirErr.CACert, } st, err = args.OpenAPI(apiInfo, args.DialOpts) if err != nil { return nil, errors.Annotatef(err, "cannot connect to redirected address") } // TODO(rog) update cached model addresses. // TODO(rog) should we do something with the logged-in username? return st, nil } addrConnectedTo, err := serverAddress(st.Addr()) if err != nil { return nil, errors.Trace(err) } // Update API addresses if they've changed. Error is non-fatal. // Note that in the redirection case, we won't update the addresses // of the controller we first connected to. This shouldn't be // a problem in practice because the intended scenario for // controllers that redirect involves them having well known // public addresses that won't change over time. hostPorts := st.APIHostPorts() agentVersion := "" if v, ok := st.ServerVersion(); ok { agentVersion = v.String() } params := UpdateControllerParams{ AgentVersion: agentVersion, AddrConnectedTo: []network.HostPort{addrConnectedTo}, CurrentHostPorts: hostPorts, } err = updateControllerDetailsFromLogin(args.Store, args.ControllerName, controller, params) if err != nil { logger.Errorf("cannot cache API addresses: %v", err) } // Process the account details obtained from login. var accountDetails *jujuclient.AccountDetails user, ok := st.AuthTag().(names.UserTag) if !apiInfo.SkipLogin { if ok { if accountDetails, err = args.Store.AccountDetails(args.ControllerName); err != nil { if !errors.IsNotFound(err) { logger.Errorf("cannot load local account information: %v", err) } } else { accountDetails.LastKnownAccess = st.ControllerAccess() } } if ok && !user.IsLocal() && apiInfo.Tag == nil { // We used macaroon auth to login; save the username // that we've logged in as. accountDetails = &jujuclient.AccountDetails{ User: user.Canonical(), LastKnownAccess: st.ControllerAccess(), } } else if apiInfo.Tag == nil { logger.Errorf("unexpected logged-in username %v", st.AuthTag()) } } if accountDetails != nil { if err := args.Store.UpdateAccount(args.ControllerName, *accountDetails); err != nil { logger.Errorf("cannot update account information: %v", err) } } return st, nil }