summaryrefslogtreecommitdiffstats
path: root/client/go
diff options
context:
space:
mode:
authorEirik Nygaard <eirik.nygaard@yahooinc.com>2022-03-11 14:38:06 +0100
committerEirik Nygaard <eirik.nygaard@yahooinc.com>2022-03-11 14:55:50 +0100
commit05ec8177b4fa81d9c5bdab16d01b346709e31fd5 (patch)
treef2b0e46deebb2d0733e47dbf3916e46e1f73bf5f /client/go
parente7aeb65690f6cba7f97ccced5f14fde32530aa64 (diff)
Move spinner configuration to CLI struct
Do not use spinner if quiet flag is set. Missing access token or changed scopes now require a manual re-authentication. This matches better with failed refresh flow.
Diffstat (limited to 'client/go')
-rw-r--r--client/go/auth/auth0/auth0.go79
-rw-r--r--client/go/cmd/clone.go3
-rw-r--r--client/go/cmd/deploy.go5
-rw-r--r--client/go/cmd/login.go63
-rw-r--r--client/go/cmd/root.go15
-rw-r--r--client/go/util/spinner.go28
6 files changed, 87 insertions, 106 deletions
diff --git a/client/go/auth/auth0/auth0.go b/client/go/auth/auth0/auth0.go
index 52ba3f085a4..593489600d1 100644
--- a/client/go/auth/auth0/auth0.go
+++ b/client/go/auth/auth0/auth0.go
@@ -18,9 +18,7 @@ import (
"time"
"github.com/lestrrat-go/jwx/jwt"
- "github.com/pkg/browser"
"github.com/vespa-engine/vespa/client/go/auth"
- "github.com/vespa-engine/vespa/client/go/util"
)
const accessTokenExpThreshold = 5 * time.Minute
@@ -138,9 +136,7 @@ func (a *Auth0) IsLoggedIn() bool {
}
// PrepareSystem loads the System, refreshing its token if necessary.
-// The System access token needs a refresh if:
-// 1. the System scopes are different from the currently required scopes - (auth0 changes).
-// 2. the access token is expired.
+// The System access token needs a refresh if the access token has expired.
func (a *Auth0) PrepareSystem(ctx context.Context) (*System, error) {
if err := a.init(); err != nil {
return nil, err
@@ -150,11 +146,10 @@ func (a *Auth0) PrepareSystem(ctx context.Context) (*System, error) {
return nil, err
}
- if s.AccessToken == "" || scopesChanged(s) {
- s, err = RunLogin(ctx, a, true)
- if err != nil {
- return nil, err
- }
+ if s.AccessToken == "" {
+ return nil, fmt.Errorf("access token missing: re-authenticate with 'vespa auth login'")
+ } else if scopesChanged(s) {
+ return nil, fmt.Errorf("authentication scopes cahnges: re-authenticate with 'vespa auth login'")
} else if isExpired(s.ExpiresAt, accessTokenExpThreshold) {
// check if the stored access token is expired:
// use the refresh token to get a new access token:
@@ -351,70 +346,6 @@ func (a *Auth0) initContext() (err error) {
return nil
}
-// RunLogin runs the login flow guiding the user through the process
-// by showing the login instructions, opening the browser.
-// Use `expired` to run the login from other commands setup:
-// this will only affect the messages.
-func RunLogin(ctx context.Context, a *Auth0, expired bool) (*System, error) {
- if expired {
- fmt.Println("Please sign in to re-authorize the CLI.")
- }
-
- state, err := a.Authenticator.Start(ctx)
- if err != nil {
- return nil, fmt.Errorf("could not start the authentication process: %w", err)
- }
-
- fmt.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode)
-
- fmt.Println("If you prefer, you can open the URL directly for verification")
- fmt.Printf("Your Verification URL: %s\n\n", state.VerificationURI)
-
- fmt.Println("Press Enter to open the browser to log in or ^C to quit...")
- fmt.Scanln()
-
- err = browser.OpenURL(state.VerificationURI)
-
- if err != nil {
- fmt.Printf("Couldn't open the URL, please do it manually: %s.", state.VerificationURI)
- }
-
- var res auth.Result
- err = util.Spinner(os.Stderr, "Waiting for login to complete in browser ...", func() error {
- res, err = a.Authenticator.Wait(ctx, state)
- return err
- })
-
- if err != nil {
- return nil, fmt.Errorf("login error: %w", err)
- }
-
- fmt.Print("\n")
- fmt.Println("Successfully logged in.")
- fmt.Print("\n")
-
- // store the refresh token
- secretsStore := &auth.Keyring{}
- err = secretsStore.Set(auth.SecretsNamespace, a.system, res.RefreshToken)
- if err != nil {
- // log the error but move on
- fmt.Println("Could not store the refresh token locally, please expect to login again once your access token expired.")
- }
-
- s := System{
- Name: a.system,
- AccessToken: res.AccessToken,
- ExpiresAt: time.Now().Add(time.Duration(res.ExpiresIn) * time.Second),
- Scopes: auth.RequiredScopes(),
- }
- err = a.AddSystem(&s)
- if err != nil {
- return nil, fmt.Errorf("could not add system to config: %w", err)
- }
-
- return &s, nil
-}
-
func RunLogout(a *Auth0) error {
s, err := a.getSystem()
if err != nil {
diff --git a/client/go/cmd/clone.go b/client/go/cmd/clone.go
index 180ec18debf..0829e064af9 100644
--- a/client/go/cmd/clone.go
+++ b/client/go/cmd/clone.go
@@ -19,7 +19,6 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
- "github.com/vespa-engine/vespa/client/go/util"
)
func newCloneCmd(cli *CLI) *cobra.Command {
@@ -116,7 +115,7 @@ func fetchSampleAppsZip(destination string, cli *CLI) error {
return fmt.Errorf("could not create temporary file: %w", err)
}
defer f.Close()
- return util.Spinner(cli.Stderr, color.YellowString("Downloading sample apps ..."), func() error {
+ return cli.spinner(cli.Stderr, color.YellowString("Downloading sample apps ..."), func() error {
request, err := http.NewRequest("GET", "https://github.com/vespa-engine/sample-apps/archive/refs/heads/master.zip", nil)
if err != nil {
return fmt.Errorf("invalid url: %w", err)
diff --git a/client/go/cmd/deploy.go b/client/go/cmd/deploy.go
index cf2176b0c69..fbecd4e19de 100644
--- a/client/go/cmd/deploy.go
+++ b/client/go/cmd/deploy.go
@@ -12,7 +12,6 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
- "github.com/vespa-engine/vespa/client/go/util"
"github.com/vespa-engine/vespa/client/go/vespa"
)
@@ -54,7 +53,7 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
opts := cli.createDeploymentOptions(pkg, target)
var result vespa.PrepareResult
- err = util.Spinner(cli.Stderr, "Uploading application package ...", func() error {
+ err = cli.spinner(cli.Stderr, "Uploading application package ...", func() error {
result, err = vespa.Deploy(opts)
return err
})
@@ -103,7 +102,7 @@ func newPrepareCmd(cli *CLI) *cobra.Command {
}
opts := cli.createDeploymentOptions(pkg, target)
var result vespa.PrepareResult
- err = util.Spinner(cli.Stderr, "Uploading application package ...", func() error {
+ err = cli.spinner(cli.Stderr, "Uploading application package ...", func() error {
result, err = vespa.Prepare(opts)
return err
})
diff --git a/client/go/cmd/login.go b/client/go/cmd/login.go
index 3750037be88..5cf471ed8db 100644
--- a/client/go/cmd/login.go
+++ b/client/go/cmd/login.go
@@ -1,10 +1,21 @@
package cmd
import (
+ "fmt"
+ "log"
+ "os"
+ "time"
+
+ "github.com/pkg/browser"
"github.com/spf13/cobra"
+ "github.com/vespa-engine/vespa/client/go/auth"
"github.com/vespa-engine/vespa/client/go/auth/auth0"
)
+// newLoginCmd runs the login flow guiding the user through the process
+// by showing the login instructions, opening the browser.
+// Use `expired` to run the login from other commands setup:
+// this will only affect the messages.
func newLoginCmd(cli *CLI) *cobra.Command {
return &cobra.Command{
Use: "login",
@@ -27,7 +38,57 @@ func newLoginCmd(cli *CLI) *cobra.Command {
if err != nil {
return err
}
- _, err = auth0.RunLogin(ctx, a, false)
+ state, err := a.Authenticator.Start(ctx)
+ if err != nil {
+ return fmt.Errorf("could not start the authentication process: %w", err)
+ }
+
+ log.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode)
+
+ log.Println("If you prefer, you can open the URL directly for verification")
+ log.Printf("Your Verification URL: %s\n\n", state.VerificationURI)
+
+ log.Println("Press Enter to open the browser to log in or ^C to quit...")
+ fmt.Scanln()
+
+ err = browser.OpenURL(state.VerificationURI)
+
+ if err != nil {
+ log.Printf("Couldn't open the URL, please do it manually: %s.", state.VerificationURI)
+ }
+
+ var res auth.Result
+ err = cli.spinner(os.Stderr, "Waiting for login to complete in browser ...", func() error {
+ res, err = a.Authenticator.Wait(ctx, state)
+ return err
+ })
+
+ if err != nil {
+ return fmt.Errorf("login error: %w", err)
+ }
+
+ log.Print("\n")
+ log.Println("Successfully logged in.")
+ log.Print("\n")
+
+ // store the refresh token
+ secretsStore := &auth.Keyring{}
+ err = secretsStore.Set(auth.SecretsNamespace, system.Name, res.RefreshToken)
+ if err != nil {
+ // log the error but move on
+ log.Println("Could not store the refresh token locally, please expect to login again once your access token expired.")
+ }
+
+ s := auth0.System{
+ Name: system.Name,
+ AccessToken: res.AccessToken,
+ ExpiresAt: time.Now().Add(time.Duration(res.ExpiresIn) * time.Second),
+ Scopes: auth.RequiredScopes(),
+ }
+ err = a.AddSystem(&s)
+ if err != nil {
+ return fmt.Errorf("could not add system to config: %w", err)
+ }
return err
},
}
diff --git a/client/go/cmd/root.go b/client/go/cmd/root.go
index d352bd2e822..49cab75be53 100644
--- a/client/go/cmd/root.go
+++ b/client/go/cmd/root.go
@@ -52,6 +52,7 @@ type CLI struct {
httpClient util.HTTPClient
exec executor
isTerminal func() bool
+ spinner func(w io.Writer, message string, fn func() error) error
}
// Flags holds the global Flags of Vespa CLI.
@@ -141,6 +142,7 @@ Vespa documentation: https://docs.vespa.ai`,
if err := cli.loadConfig(); err != nil {
return nil, err
}
+ cli.configureSpinner()
cli.configureCommands()
cmd.PersistentPreRunE = cli.configureOutput
return &cli, nil
@@ -203,6 +205,19 @@ func (c *CLI) configureFlags() {
c.flags = &flags
}
+func (c *CLI) configureSpinner() {
+ // Explicitly disable spinner for Screwdriver. It emulates a tty but
+ // \r result in a newline, and output gets truncated.
+ _, screwdriver := c.Environment["SCREWDRIVER"]
+ if c.flags.quiet || !c.isTerminal() || screwdriver {
+ c.spinner = func(w io.Writer, message string, fn func() error) error {
+ return fn()
+ }
+ } else {
+ c.spinner = util.Spinner
+ }
+}
+
func (c *CLI) configureCommands() {
rootCmd := c.cmd
authCmd := newAuthCmd()
diff --git a/client/go/util/spinner.go b/client/go/util/spinner.go
index 52ea133f6f7..39b00352c32 100644
--- a/client/go/util/spinner.go
+++ b/client/go/util/spinner.go
@@ -4,12 +4,10 @@ package util
import (
"io"
- "os"
"strings"
"time"
"github.com/briandowns/spinner"
- "github.com/mattn/go-isatty"
)
// Spinner writes message to writer w and executes function fn. While fn is running a spinning animation will be
@@ -24,33 +22,11 @@ func Spinner(w io.Writer, message string, fn func() error) error {
}
s.Prefix = message
s.FinalMSG = "\r" + message + "done\n"
- useSpinner := useSpinner(w)
- if useSpinner { // spinner package does this check too, but it's hardcoded to check os.Stdout :(
- s.Start()
- }
+ s.Start()
err := fn()
if err != nil {
s.FinalMSG = "\r" + message + "failed\n"
}
- if useSpinner {
- s.Stop()
- }
+ s.Stop()
return err
}
-
-func useSpinner(w io.Writer) bool {
- if !isTerminal(w) {
- return false
- }
- // Explicitly disable spinner for Screwdriver. It emulates a tty but
- // \r result in a newline, and output gets truncated.
- if _, screwdriver := os.LookupEnv("SCREWDRIVER"); screwdriver {
- return false
- }
- return true
-}
-
-func isTerminal(w io.Writer) bool {
- f, ok := w.(*os.File)
- return ok && isatty.IsTerminal(f.Fd())
-}