github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/gui/gui.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gui
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/gnuflag"
    14  	"github.com/juju/httprequest"
    15  	"github.com/juju/version"
    16  	"github.com/juju/webbrowser"
    17  
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/controller"
    20  	"github.com/juju/juju/apiserver/params"
    21  	jujucmd "github.com/juju/juju/cmd"
    22  	"github.com/juju/juju/cmd/modelcmd"
    23  )
    24  
    25  // NewGUICommand creates and returns a new gui command.
    26  func NewGUICommand() cmd.Command {
    27  	return modelcmd.Wrap(&guiCommand{})
    28  }
    29  
    30  // guiCommand opens the Juju GUI in the default browser.
    31  type guiCommand struct {
    32  	modelcmd.ModelCommandBase
    33  
    34  	// Deprecated - used with --no-browser
    35  	noBrowser bool
    36  
    37  	// Deprecated - used with --show-credentials
    38  	showCreds bool
    39  
    40  	hideCreds bool
    41  	browser   bool
    42  
    43  	getGUIVersions func(connection api.Connection) ([]params.GUIArchiveVersion, error)
    44  }
    45  
    46  const guiDoc = `
    47  Print the Juju GUI URL and show admin credential to use to log into it:
    48  
    49  	juju gui
    50  
    51  Print the Juju GUI URL only:
    52  
    53  	juju gui --hide-credential
    54  
    55  Open the Juju GUI in the default browser and show admin credential to use to log into it:
    56  
    57  	juju gui --browser
    58  
    59  Open the Juju GUI in the default browser without printing the login credential:
    60  
    61  	juju gui --hide-credential --browser
    62  
    63  An error is returned if the Juju GUI is not available in the controller.
    64  `
    65  
    66  // Info implements the cmd.Command interface.
    67  func (c *guiCommand) Info() *cmd.Info {
    68  	return jujucmd.Info(&cmd.Info{
    69  		Name:    "gui",
    70  		Purpose: "Print the Juju GUI URL, or open the Juju GUI in the default browser.",
    71  		Doc:     guiDoc,
    72  	})
    73  }
    74  
    75  // SetFlags implements the cmd.Command interface.
    76  func (c *guiCommand) SetFlags(f *gnuflag.FlagSet) {
    77  	c.ModelCommandBase.SetFlags(f)
    78  	f.BoolVar(&c.hideCreds, "hide-credential", false, "Do not show admin credential to use for logging into the Juju GUI")
    79  	f.BoolVar(&c.showCreds, "show-credentials", true, "DEPRECATED. Show admin credential to use for logging into the Juju GUI")
    80  	f.BoolVar(&c.noBrowser, "no-browser", true, "DEPRECATED. --no-browser is now the default. Use --browser to open the web browser")
    81  	f.BoolVar(&c.browser, "browser", false, "Open the web browser, instead of just printing the Juju GUI URL")
    82  }
    83  
    84  func (c *guiCommand) guiVersions(conn api.Connection) ([]params.GUIArchiveVersion, error) {
    85  	if c.getGUIVersions == nil {
    86  		client := controller.NewClient(conn)
    87  		return client.GUIArchives()
    88  	}
    89  	return c.getGUIVersions(conn)
    90  }
    91  
    92  // Run implements the cmd.Command interface.
    93  func (c *guiCommand) Run(ctx *cmd.Context) error {
    94  	// Retrieve model details.
    95  	conn, err := c.NewControllerAPIRoot()
    96  	if err != nil {
    97  		return errors.Annotate(err, "cannot establish API connection")
    98  	}
    99  	defer conn.Close()
   100  
   101  	store, ok := c.ClientStore().(modelcmd.QualifyingClientStore)
   102  	if !ok {
   103  		store = modelcmd.QualifyingClientStore{
   104  			ClientStore: c.ClientStore(),
   105  		}
   106  	}
   107  	controllerName, err := c.ControllerName()
   108  	if err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  	modelName, details, err := c.ModelCommandBase.ModelDetails()
   112  	if err != nil {
   113  		return errors.Annotate(err, "cannot retrieve model details: please make sure you switched to a valid model")
   114  	}
   115  
   116  	// Make 2 URLs to try - the old and the new.
   117  	addr := guiAddr(conn)
   118  	rawURL := fmt.Sprintf("https://%s/gui/%s/", addr, details.ModelUUID)
   119  	qualifiedModelName, err := store.QualifiedModelName(controllerName, modelName)
   120  	if err != nil {
   121  		return errors.Annotate(err, "cannot construct model name")
   122  	}
   123  	// Do not include any possible "@external" fragment in the path.
   124  	qualifiedModelName = strings.Replace(qualifiedModelName, "@external/", "/", 1)
   125  	newRawURL := fmt.Sprintf("https://%s/gui/u/%s", addr, qualifiedModelName)
   126  
   127  	// Check that the Juju GUI is available.
   128  	var guiURL string
   129  	if guiURL, err = c.checkAvailable(rawURL, newRawURL, conn); err != nil {
   130  		return errors.Trace(err)
   131  	}
   132  
   133  	// Get the GUI version to print.
   134  	versions, err := c.guiVersions(conn)
   135  	if err != nil {
   136  		return errors.Trace(err)
   137  	}
   138  	var vers *version.Number
   139  	for _, v := range versions {
   140  		if v.Current {
   141  			vers = &v.Version
   142  			break
   143  		}
   144  	}
   145  
   146  	// Open the Juju GUI in the browser.
   147  	if err = c.openBrowser(ctx, guiURL, vers); err != nil {
   148  		return errors.Trace(err)
   149  	}
   150  
   151  	// Print login credentials if requested.
   152  	if err = c.showCredentials(ctx); err != nil {
   153  		return errors.Trace(err)
   154  	}
   155  	return nil
   156  }
   157  
   158  // guiAddr returns an address where the GUI is available.
   159  func guiAddr(conn api.Connection) string {
   160  	if dnsName := conn.PublicDNSName(); dnsName != "" {
   161  		return dnsName
   162  	}
   163  	return conn.Addr()
   164  }
   165  
   166  // checkAvailable ensures the Juju GUI is available on the controller at
   167  // one of the given URLs, returning the successful URL.
   168  func (c *guiCommand) checkAvailable(rawURL, newRawURL string, conn api.Connection) (string, error) {
   169  	client, err := conn.HTTPClient()
   170  	if err != nil {
   171  		return "", errors.Annotate(err, "cannot retrieve HTTP client")
   172  	}
   173  	if err = clientGet(client, newRawURL); err == nil {
   174  		return newRawURL, nil
   175  	}
   176  	if err = clientGet(client, rawURL); err != nil {
   177  		return "", errors.Annotate(err, "Juju GUI is not available")
   178  	}
   179  	return rawURL, nil
   180  }
   181  
   182  // openBrowser opens the Juju GUI at the given URL.
   183  func (c *guiCommand) openBrowser(ctx *cmd.Context, rawURL string, vers *version.Number) error {
   184  	u, err := url.Parse(rawURL)
   185  	if err != nil {
   186  		return errors.Annotate(err, "cannot parse Juju GUI URL")
   187  	}
   188  	if c.noBrowser && !c.browser {
   189  		versInfo := ""
   190  		if vers != nil {
   191  			versInfo = fmt.Sprintf("%v ", vers)
   192  		}
   193  		modelName, err := c.ModelName()
   194  		if err != nil {
   195  			return errors.Trace(err)
   196  		}
   197  		ctx.Infof("GUI %sfor model %q is enabled at:\n  %s", versInfo, modelName, u.String())
   198  		return nil
   199  	}
   200  	err = webbrowserOpen(u)
   201  	if err == nil {
   202  		ctx.Infof("Opening the Juju GUI in your browser.")
   203  		ctx.Infof("If it does not open, open this URL:\n%s", u)
   204  		return nil
   205  	}
   206  	if err == webbrowser.ErrNoBrowser {
   207  		ctx.Infof("Open this URL in your browser:\n%s", u)
   208  		return nil
   209  	}
   210  	return errors.Annotate(err, "cannot open web browser")
   211  }
   212  
   213  // showCredentials shows the admin username and password.
   214  func (c *guiCommand) showCredentials(ctx *cmd.Context) error {
   215  	if c.hideCreds || !c.showCreds {
   216  		return nil
   217  	}
   218  	// TODO(wallyworld) - what to do if we are using a macaroon.
   219  	accountDetails, err := c.CurrentAccountDetails()
   220  	if err != nil {
   221  		return errors.Annotate(err, "cannot retrieve credentials")
   222  	}
   223  	password := accountDetails.Password
   224  	if password == "" {
   225  		// TODO(wallyworld) - fix this
   226  		password = "<unknown> (password has been changed by the user)"
   227  	}
   228  	ctx.Infof("Your login credential is:\n  username: %s\n  password: %s", accountDetails.User, password)
   229  	return nil
   230  }
   231  
   232  // clientGet is defined for testing purposes.
   233  var clientGet = func(client *httprequest.Client, rawURL string) error {
   234  	return client.Get(rawURL, nil)
   235  }
   236  
   237  // webbrowserOpen is defined for testing purposes.
   238  var webbrowserOpen = webbrowser.Open