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