Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/juju/user: consult external directory server to find out host names for public controllers #7087

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 47 additions & 13 deletions cmd/juju/user/login.go
Expand Up @@ -5,13 +5,14 @@ package user

import (
"fmt"
"net/http"
"os"
"strings"

"github.com/juju/cmd"
"github.com/juju/errors"
"github.com/juju/gnuflag"
"github.com/juju/utils/keyvalues"
"github.com/juju/httprequest"
"github.com/juju/utils/set"
"gopkg.in/juju/names.v2"

Expand Down Expand Up @@ -112,7 +113,7 @@ func (c *loginCommand) SetFlags(fset *gnuflag.FlagSet) {
c.ControllerCommandBase.SetFlags(fset)
fset.StringVar(&c.controllerName, "c", "", "Controller to operate in")
fset.StringVar(&c.controllerName, "controller", "", "")
fset.BoolVar(&c.forceHost, "host", false, "force the domain argument to be treated as the host name of a controller")
fset.BoolVar(&c.forceHost, "host", false, "force the domain argument to be treated as the host name of a controller rather than a user name")
}

// Init implements Command.Init.
Expand Down Expand Up @@ -229,12 +230,11 @@ Run "juju logout" first before attempting to log in as a different user.`)
}

func (c *loginCommand) controllerLogin(ctx *cmd.Context, store jujuclient.ClientStore) error {
knownDomains, err := c.getKnownControllerDomains()
if err != nil {
// TODO(rogpeppe) perhaps this shouldn't be fatal.
return errors.Trace(err)
// TODO(rog) provide a way of avoiding this external network access.
controllerHost, err := c.getKnownControllerDomain(c.userOrDomain)
if err != nil && !errors.IsNotFound(err) {
logger.Warningf("could not determine controller domain: %v", err)
}
controllerHost := knownDomains[c.userOrDomain]
if controllerHost == "" {
if !c.forceHost {
return errNotControllerLogin
Expand Down Expand Up @@ -419,15 +419,49 @@ one of them:
return nil
}

// getKnownControllerDomains returns the list of known
type controllerDomainResponse struct {
Host string `json:"host"`
}

const defaultJujuDirectory = "https://api.jujucharms.com/directory"

// getKnownControllerDomain returns the list of known
// controller domain aliases.
func (c *loginCommand) getKnownControllerDomains() (map[string]string, error) {
controllers := os.Getenv("JUJU_PUBLIC_CONTROLLERS")
m, err := keyvalues.Parse(strings.Fields(controllers), false)
func (c *loginCommand) getKnownControllerDomain(name string) (string, error) {
if strings.Contains(name, ".") || strings.Contains(name, ":") {
return "", errors.NotFoundf("controller %q", name)
}
baseURL := defaultJujuDirectory
if u := os.Getenv("JUJU_DIRECTORY"); u != "" {
baseURL = u
}
client, err := c.BakeryClient()
if err != nil {
return nil, errors.Annotatef(err, "bad value for JUJU_PUBLIC_CONTROLLERS")
return "", errors.Trace(err)
}
req, err := http.NewRequest("GET", baseURL+"/v1/controller/"+name, nil)
if err != nil {
return "", errors.Trace(err)
}
httpResp, err := client.Do(req)
if err != nil {
return "", errors.Trace(err)
}
defer httpResp.Body.Close()
if httpResp.StatusCode != http.StatusOK {
if httpResp.StatusCode == http.StatusNotFound {
return "", errors.NotFoundf("controller %q", name)
}
return "", errors.Errorf("unexpected HTTP response %q", httpResp.Status)
}
var resp controllerDomainResponse
if err := httprequest.UnmarshalJSONResponse(httpResp, &resp); err != nil {
return "", errors.Trace(err)
}
if resp.Host == "" {
return "", errors.Errorf("no host field found in response")
}
return m, nil
return resp.Host, nil
}

func friendlyUserName(user string) string {
Expand Down
24 changes: 22 additions & 2 deletions cmd/juju/user/logincontroller_test.go
Expand Up @@ -4,6 +4,7 @@ package user_test

import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
Expand Down Expand Up @@ -61,6 +62,7 @@ func (s *LoginControllerSuite) SetUpTest(c *gc.C) {
return s.apiConnection, nil
})
s.PatchValue(user.LoginClientStore, s.store)
s.PatchEnvironment("JUJU_DIRECTORY", "http://0.1.2.3/directory")
}

func (s *LoginControllerSuite) TearDownTest(c *gc.C) {
Expand All @@ -69,7 +71,11 @@ func (s *LoginControllerSuite) TearDownTest(c *gc.C) {
}

func (s *LoginControllerSuite) TestRegisterPublic(c *gc.C) {
os.Setenv("JUJU_PUBLIC_CONTROLLERS", "bighost=bighost.jujucharms.com")
dirSrv := serveDirectory(map[string]string{
"bighost": "bighost.jujucharms.com:443",
})
defer dirSrv.Close()
os.Setenv("JUJU_DIRECTORY", dirSrv.URL)
s.apiConnection.authTag = names.NewUserTag("bob@external")
s.apiConnection.controllerAccess = "login"
stdout, stderr, code := s.run(c, "bighost")
Expand Down Expand Up @@ -156,7 +162,9 @@ Welcome, bob@external. You are now logged into "foo".
}

func (s *LoginControllerSuite) TestRegisterPublicAPIOpenError(c *gc.C) {
os.Setenv("JUJU_PUBLIC_CONTROLLERS", "bighost=bighost.jujucharms.com")
srv := serveDirectory(map[string]string{"bighost": "https://0.1.2.3/directory"})
defer srv.Close()
os.Setenv("JUJU_DIRECTORY", srv.URL)
*user.APIOpen = func(c *modelcmd.JujuCommandBase, info *api.Info, opts api.DialOpts) (api.Connection, error) {
return nil, errors.New("open failed")
}
Expand Down Expand Up @@ -216,3 +224,15 @@ func (m *loginMockAPI) ControllerAccess() string {
}

const mockControllerUUID = "df136476-12e9-11e4-8a70-b2227cce2b54"

func serveDirectory(dir map[string]string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
name := strings.TrimPrefix(req.URL.Path, "/v1/controller/")
if name == req.URL.Path || dir[name] == "" {
http.NotFound(w, req)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"host":%q}`, dir[name])
}))
}
1 change: 1 addition & 0 deletions featuretests/cmd_juju_login_test.go
Expand Up @@ -63,6 +63,7 @@ func (s *cmdLoginSuite) TestLoginCommand(c *gc.C) {
context := s.run(c, strings.NewReader("hunter2\nhunter2\n"), "login", "test")
c.Assert(testing.Stdout(context), gc.Equals, "")
c.Assert(testing.Stderr(context), gc.Equals, `
WARNING could not determine controller domain: Get https://api.jujucharms.com/directory/v1/controller/test: access to address "api.jujucharms.com:443" not allowed
please enter password for test on kontroll:
You are now logged in to "kontroll" as "test".
`[1:])
Expand Down