diff options
32 files changed, 961 insertions, 118 deletions
diff --git a/client/go/auth/auth.go b/client/go/auth/auth.go new file mode 100644 index 00000000000..397e410924d --- /dev/null +++ b/client/go/auth/auth.go @@ -0,0 +1,175 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package auth + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + "time" +) + +const ( + audiencePath = "/api/v2/" + waitThresholdInSeconds = 3 + // SecretsNamespace namespace used to set/get values from the keychain + SecretsNamespace = "vespa-cli" +) + +var requiredScopes = []string{"openid", "offline_access"} + +type Authenticator struct { + Audience string + ClientID string + DeviceCodeEndpoint string + OauthTokenEndpoint string +} + +// SecretStore provides access to stored sensitive data. +type SecretStore interface { + // Get gets the secret + Get(namespace, key string) (string, error) + // Delete removes the secret + Delete(namespace, key string) error +} + +type Result struct { + Tenant string + Domain string + RefreshToken string + AccessToken string + ExpiresIn int64 +} + +type State struct { + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationURI string `json:"verification_uri_complete"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval"` +} + +// RequiredScopes returns the scopes used for login. +func RequiredScopes() []string { return requiredScopes } + +func (s *State) IntervalDuration() time.Duration { + return time.Duration(s.Interval+waitThresholdInSeconds) * time.Second +} + +// Start kicks-off the device authentication flow +// by requesting a device code from Auth0, +// The returned state contains the URI for the next step of the flow. +func (a *Authenticator) Start(ctx context.Context) (State, error) { + s, err := a.getDeviceCode(ctx) + if err != nil { + return State{}, fmt.Errorf("cannot get device code: %w", err) + } + return s, nil +} + +// Wait waits until the user is logged in on the browser. +func (a *Authenticator) Wait(ctx context.Context, state State) (Result, error) { + t := time.NewTicker(state.IntervalDuration()) + for { + select { + case <-ctx.Done(): + return Result{}, ctx.Err() + case <-t.C: + data := url.Values{ + "client_id": {a.ClientID}, + "grant_type": {"urn:ietf:params:oauth:grant-type:device_code"}, + "device_code": {state.DeviceCode}, + } + r, err := http.PostForm(a.OauthTokenEndpoint, data) + if err != nil { + return Result{}, fmt.Errorf("cannot get device code: %w", err) + } + defer r.Body.Close() + + var res struct { + AccessToken string `json:"access_token"` + IDToken string `json:"id_token"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + ExpiresIn int64 `json:"expires_in"` + TokenType string `json:"token_type"` + Error *string `json:"error,omitempty"` + ErrorDescription string `json:"error_description,omitempty"` + } + + err = json.NewDecoder(r.Body).Decode(&res) + if err != nil { + return Result{}, fmt.Errorf("cannot decode response: %w", err) + } + + if res.Error != nil { + if *res.Error == "authorization_pending" { + continue + } + return Result{}, errors.New(res.ErrorDescription) + } + + ten, domain, err := parseTenant(res.AccessToken) + if err != nil { + return Result{}, fmt.Errorf("cannot parse tenant from the given access token: %w", err) + } + + return Result{ + RefreshToken: res.RefreshToken, + AccessToken: res.AccessToken, + ExpiresIn: res.ExpiresIn, + Tenant: ten, + Domain: domain, + }, nil + } + } +} + +func (a *Authenticator) getDeviceCode(ctx context.Context) (State, error) { + data := url.Values{ + "client_id": {a.ClientID}, + "scope": {strings.Join(requiredScopes, " ")}, + "audience": {a.Audience}, + } + r, err := http.PostForm(a.DeviceCodeEndpoint, data) + if err != nil { + return State{}, fmt.Errorf("cannot get device code: %w", err) + } + defer r.Body.Close() + var res State + err = json.NewDecoder(r.Body).Decode(&res) + if err != nil { + return State{}, fmt.Errorf("cannot decode response: %w", err) + } + return res, nil +} + +func parseTenant(accessToken string) (tenant, domain string, err error) { + parts := strings.Split(accessToken, ".") + v, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return "", "", err + } + var payload struct { + AUDs []string `json:"aud"` + } + if err := json.Unmarshal(v, &payload); err != nil { + return "", "", err + } + for _, aud := range payload.AUDs { + u, err := url.Parse(aud) + if err != nil { + return "", "", err + } + if u.Path == audiencePath { + parts := strings.Split(u.Host, ".") + return parts[0], u.Host, nil + } + } + return "", "", fmt.Errorf("audience not found for %s", audiencePath) +} diff --git a/client/go/auth/secrets.go b/client/go/auth/secrets.go new file mode 100644 index 00000000000..e38d8c56595 --- /dev/null +++ b/client/go/auth/secrets.go @@ -0,0 +1,24 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package auth + +import ( + "github.com/zalando/go-keyring" +) + +type Keyring struct{} + +// Set sets the given key/value pair with the given namespace. +func (k *Keyring) Set(namespace, key, value string) error { + return keyring.Set(namespace, key, value) +} + +// Get gets a value for the given namespace and key. +func (k *Keyring) Get(namespace, key string) (string, error) { + return keyring.Get(namespace, key) +} + +// Delete deletes a value for the given namespace and key. +func (k *Keyring) Delete(namespace, key string) error { + return keyring.Delete(namespace, key) +} diff --git a/client/go/auth/token.go b/client/go/auth/token.go new file mode 100644 index 00000000000..e9b90b8994e --- /dev/null +++ b/client/go/auth/token.go @@ -0,0 +1,68 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package auth + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +type TokenResponse struct { + AccessToken string `json:"access_token"` + IDToken string `json:"id_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` +} + +type TokenRetriever struct { + Authenticator *Authenticator + Secrets SecretStore + Client *http.Client +} + +// Delete deletes the given tenant from the secrets' storage. +func (t *TokenRetriever) Delete(tenant string) error { + return t.Secrets.Delete(SecretsNamespace, tenant) +} + +// Refresh gets a new access token from the provided refresh token, +// The request is used the default client_id and endpoint for device authentication. +func (t *TokenRetriever) Refresh(ctx context.Context, tenant string) (TokenResponse, error) { + // get stored refresh token: + refreshToken, err := t.Secrets.Get(SecretsNamespace, tenant) + if err != nil { + return TokenResponse{}, fmt.Errorf("cannot get the stored refresh token: %w", err) + } + if refreshToken == "" { + return TokenResponse{}, errors.New("cannot use the stored refresh token: the token is empty") + } + // get access token: + r, err := t.Client.PostForm(t.Authenticator.OauthTokenEndpoint, url.Values{ + "grant_type": {"refresh_token"}, + "client_id": {t.Authenticator.ClientID}, + "refresh_token": {refreshToken}, + }) + if err != nil { + return TokenResponse{}, fmt.Errorf("cannot get a new access token from the refresh token: %w", err) + } + + defer r.Body.Close() + if r.StatusCode != http.StatusOK { + b, _ := ioutil.ReadAll(r.Body) + bodyStr := string(b) + return TokenResponse{}, fmt.Errorf("cannot get a new access token from the refresh token: %s", bodyStr) + } + + var res TokenResponse + err = json.NewDecoder(r.Body).Decode(&res) + if err != nil { + return TokenResponse{}, fmt.Errorf("cannot decode response: %w", err) + } + + return res, nil +} diff --git a/client/go/cli/cli.go b/client/go/cli/cli.go new file mode 100644 index 00000000000..e1dde387b89 --- /dev/null +++ b/client/go/cli/cli.go @@ -0,0 +1,355 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package cli + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/joeshaw/envdecode" + "github.com/pkg/browser" + "github.com/vespa-engine/vespa/client/go/util" + "io/ioutil" + "net/http" + "os" + "os/signal" + "path/filepath" + "sort" + "sync" + "time" + + "github.com/lestrrat-go/jwx/jwt" + "github.com/vespa-engine/vespa/client/go/auth" +) + +const accessTokenExpThreshold = 5 * time.Minute + +var errUnauthenticated = errors.New("not logged in. Try 'vespa login'") + +type config struct { + InstallID string `json:"install_id,omitempty"` + DefaultTenant string `json:"default_tenant"` + Tenants map[string]Tenant `json:"tenants"` +} + +// Tenant is an auth0 Tenant. +type Tenant struct { + Name string `json:"name"` + Domain string `json:"domain"` + AccessToken string `json:"access_token,omitempty"` + Scopes []string `json:"scopes,omitempty"` + ExpiresAt time.Time `json:"expires_at"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` +} + +type Cli struct { + Authenticator *auth.Authenticator + tenant string + initOnce sync.Once + errOnce error + Path string + config config +} + +// IsLoggedIn encodes the domain logic for determining whether we're +// logged in. This might check our config storage, or just in memory. +func (c *Cli) IsLoggedIn() bool { + // No need to check errors for initializing context. + _ = c.init() + + if c.tenant == "" { + return false + } + + // Parse the access token for the tenant. + t, err := jwt.ParseString(c.config.Tenants[c.tenant].AccessToken) + if err != nil { + return false + } + + // Check if token is valid. + if err = jwt.Validate(t, jwt.WithIssuer("https://vespa-cd.auth0.com/")); err != nil { + return false + } + + return true +} + +// default to vespa-cd.auth0.com +var ( + authCfg struct { + Audience string `env:"AUTH0_AUDIENCE,default=https://vespa-cd.auth0.com/api/v2/"` + ClientID string `env:"AUTH0_CLIENT_ID,default=4wYWA496zBP28SLiz0PuvCt8ltL11DZX"` + DeviceCodeEndpoint string `env:"AUTH0_DEVICE_CODE_ENDPOINT,default=https://vespa-cd.auth0.com/oauth/device/code"` + OauthTokenEndpoint string `env:"AUTH0_OAUTH_TOKEN_ENDPOINT,default=https://vespa-cd.auth0.com/oauth/token"` + } +) + +func ContextWithCancel() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + go func() { + <-ch + defer cancel() + os.Exit(0) + }() + return ctx +} + +// Setup will try to initialize the config context, as well as figure out if +// there's a readily available tenant. +func GetCli(configPath string) (*Cli, error) { + c := Cli{} + c.Path = configPath + if err := envdecode.StrictDecode(&authCfg); err != nil { + return nil, fmt.Errorf("could not decode env: %w", err) + } + c.Authenticator = &auth.Authenticator{ + Audience: authCfg.Audience, + ClientID: authCfg.ClientID, + DeviceCodeEndpoint: authCfg.DeviceCodeEndpoint, + OauthTokenEndpoint: authCfg.OauthTokenEndpoint, + } + return &c, nil +} + +// prepareTenant loads the Tenant, refreshing its token if necessary. +// The Tenant access token needs a refresh if: +// 1. the Tenant scopes are different from the currently required scopes. +// 2. the access token is expired. +func (c *Cli) PrepareTenant(ctx context.Context) (Tenant, error) { + if err := c.init(); err != nil { + return Tenant{}, err + } + t, err := c.getTenant() + if err != nil { + return Tenant{}, err + } + + if t.ClientID != "" && t.ClientSecret != "" { + return t, nil + } + + if t.AccessToken == "" || scopesChanged(t) { + t, err = RunLogin(ctx, c, true) + if err != nil { + return Tenant{}, err + } + } else if isExpired(t.ExpiresAt, accessTokenExpThreshold) { + // check if the stored access token is expired: + // use the refresh token to get a new access token: + tr := &auth.TokenRetriever{ + Authenticator: c.Authenticator, + Secrets: &auth.Keyring{}, + Client: http.DefaultClient, + } + + res, err := tr.Refresh(ctx, t.Domain) + if err != nil { + // ask and guide the user through the login process: + fmt.Println(fmt.Errorf("failed to renew access token, %s", err)) + t, err = RunLogin(ctx, c, true) + if err != nil { + return Tenant{}, err + } + } else { + // persist the updated tenant with renewed access token + t.AccessToken = res.AccessToken + t.ExpiresAt = time.Now().Add( + time.Duration(res.ExpiresIn) * time.Second, + ) + + err = c.AddTenant(t) + if err != nil { + return Tenant{}, err + } + } + } + + return t, nil +} + +// isExpired is true if now() + a threshold is after the given date +func isExpired(t time.Time, threshold time.Duration) bool { + return time.Now().Add(threshold).After(t) +} + +// scopesChanged compare the Tenant scopes +// with the currently required scopes. +func scopesChanged(t Tenant) bool { + want := auth.RequiredScopes() + got := t.Scopes + + sort.Strings(want) + sort.Strings(got) + + if (want == nil) != (got == nil) { + return true + } + + if len(want) != len(got) { + return true + } + + for i := range t.Scopes { + if want[i] != got[i] { + return true + } + } + + return false +} + +func (c *Cli) getTenant() (Tenant, error) { + if err := c.init(); err != nil { + return Tenant{}, err + } + + t, ok := c.config.Tenants[c.tenant] + if !ok { + return Tenant{}, fmt.Errorf("unable to find tenant: %s; run 'vespa login' to configure a new tenant", c.tenant) + } + + return t, nil +} + +// AddTenant assigns an existing, or new Tenant. This is expected to be called +// after a login has completed. +func (c *Cli) AddTenant(ten Tenant) error { + _ = c.init() + + if c.config.DefaultTenant == "" { + c.config.DefaultTenant = ten.Domain + } + + // If we're dealing with an empty file, we'll need to initialize this map. + if c.config.Tenants == nil { + c.config.Tenants = map[string]Tenant{} + } + + c.config.Tenants[ten.Domain] = ten + + if err := c.persistConfig(); err != nil { + return fmt.Errorf("unexpected error persisting config: %w", err) + } + + return nil +} + +func (c *Cli) persistConfig() error { + dir := filepath.Dir(c.Path) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + } + + buf, err := json.MarshalIndent(c.config, "", " ") + if err != nil { + return err + } + + if err := ioutil.WriteFile(c.Path, buf, 0600); err != nil { + return err + } + + return nil +} + +func (c *Cli) init() error { + c.initOnce.Do(func() { + if c.errOnce = c.initContext(); c.errOnce != nil { + return + } + }) + return c.errOnce +} + +func (c *Cli) initContext() (err error) { + if _, err := os.Stat(c.Path); os.IsNotExist(err) { + return errUnauthenticated + } + + var buf []byte + if buf, err = ioutil.ReadFile(c.Path); err != nil { + return err + } + + if err := json.Unmarshal(buf, &c.config); err != nil { + return err + } + + if c.tenant == "" && c.config.DefaultTenant == "" { + return errUnauthenticated + } + + if c.tenant == "" { + c.tenant = c.config.DefaultTenant + } + + 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, cli *Cli, expired bool) (Tenant, error) { + if expired { + fmt.Println("Please sign in to re-authorize the CLI.") + } + + state, err := cli.Authenticator.Start(ctx) + if err != nil { + return Tenant{}, fmt.Errorf("could not start the authentication process: %w", err) + } + + fmt.Printf("Your Device Confirmation code is: %s\n\n", state.UserCode) + 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("Waiting for login to complete in browser", func() error { + res, err = cli.Authenticator.Wait(ctx, state) + return err + }) + + if err != nil { + return Tenant{}, 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, res.Domain, 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.") + } + + t := Tenant{ + Name: res.Tenant, + Domain: res.Domain, + AccessToken: res.AccessToken, + ExpiresAt: time.Now().Add(time.Duration(res.ExpiresIn) * time.Second), + Scopes: auth.RequiredScopes(), + } + err = cli.AddTenant(t) + if err != nil { + return Tenant{}, fmt.Errorf("could not add tenant to config: %w", err) + } + + return t, nil +} diff --git a/client/go/cmd/config.go b/client/go/cmd/config.go index d10f66c83c6..0b08a2dc28d 100644 --- a/client/go/cmd/config.go +++ b/client/go/cmd/config.go @@ -148,6 +148,10 @@ func (c *Config) ReadAPIKey(tenantName string) ([]byte, error) { return ioutil.ReadFile(c.APIKeyPath(tenantName)) } +func (c *Config) AuthConfigPath() string { + return filepath.Join(c.Home, "auth", "config.json") +} + func (c *Config) ReadSessionID(app vespa.ApplicationID) (int64, error) { sessionPath, err := c.applicationFilePath(app, "session_id") if err != nil { diff --git a/client/go/cmd/helpers.go b/client/go/cmd/helpers.go index 4768290e33e..54d8798b71d 100644 --- a/client/go/cmd/helpers.go +++ b/client/go/cmd/helpers.go @@ -153,10 +153,17 @@ func getConsoleURL() string { } func getApiURL() string { - if getSystem() == "publiccd" { - return "https://api.vespa-external-cd.aws.oath.cloud:4443" + if vespa.Auth0AccessTokenEnabled() { + if getSystem() == "publiccd" { + return "https://api.vespa-external-cd.aws.oath.cloud:443" + } + return "https://api.vespa-external.aws.oath.cloud:443" + } else { + if getSystem() == "publiccd" { + return "https://api.vespa-external-cd.aws.oath.cloud:4443" + } + return "https://api.vespa-external.aws.oath.cloud:4443" } - return "https://api.vespa-external.aws.oath.cloud:4443" } func getTarget() vespa.Target { @@ -174,9 +181,12 @@ func getTarget() vespa.Target { fatalErr(err, "Could not load config") return nil } - apiKey, err := ioutil.ReadFile(cfg.APIKeyPath(deployment.Application.Tenant)) - if err != nil { - fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'") + var apiKey []byte = nil + if !vespa.Auth0AccessTokenEnabled() { + apiKey, err = ioutil.ReadFile(cfg.APIKeyPath(deployment.Application.Tenant)) + if err != nil { + fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'") + } } privateKeyFile, err := cfg.PrivateKeyPath(deployment.Application) if err != nil { @@ -201,7 +211,8 @@ func getTarget() vespa.Target { vespa.LogOptions{ Writer: stdout, Level: vespa.LogLevel(logLevelArg), - }) + }, + cfg.AuthConfigPath()) } fatalErrHint(fmt.Errorf("Invalid target: %s", targetType), "Valid targets are 'local', 'cloud' or an URL") return nil @@ -232,11 +243,13 @@ func getDeploymentOpts(cfg *Config, pkg vespa.ApplicationPackage, target vespa.T fatalErrHint(fmt.Errorf("Missing certificate in application package"), "Applications in Vespa Cloud require a certificate", "Try 'vespa cert'") return opts } - var err error - opts.APIKey, err = cfg.ReadAPIKey(deployment.Application.Tenant) - if err != nil { - fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'") - return opts + if !vespa.Auth0AccessTokenEnabled() { + var err error + opts.APIKey, err = cfg.ReadAPIKey(deployment.Application.Tenant) + if err != nil { + fatalErrHint(err, "Deployment to cloud requires an API key. Try 'vespa api-key'") + return opts + } } opts.Deployment = deployment } diff --git a/client/go/cmd/login.go b/client/go/cmd/login.go new file mode 100644 index 00000000000..767d462b0be --- /dev/null +++ b/client/go/cmd/login.go @@ -0,0 +1,34 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/vespa-engine/vespa/client/go/cli" + "github.com/vespa-engine/vespa/client/go/vespa" +) + +func init() { + if vespa.Auth0AccessTokenEnabled() { + rootCmd.AddCommand(loginCmd) + } +} + +var loginCmd = &cobra.Command{ + Use: "login", + Args: cobra.NoArgs, + Short: "Authenticate the Vespa CLI", + Example: "$ vespa login", + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + cfg, err := LoadConfig() + if err != nil { + return err + } + c, err := cli.GetCli(cfg.AuthConfigPath()) + if err != nil { + return err + } + _, err = cli.RunLogin(ctx, c, false) + return err + }, +} diff --git a/client/go/go.mod b/client/go/go.mod index 27faff3fd0b..70eea958933 100644 --- a/client/go/go.mod +++ b/client/go/go.mod @@ -3,12 +3,23 @@ module github.com/vespa-engine/vespa/client/go go 1.15 require ( + github.com/briandowns/spinner v1.16.0 + github.com/fatih/color v1.10.0 // indirect + github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 + github.com/lestrrat-go/jwx v1.2.9 github.com/logrusorgru/aurora/v3 v3.0.0 - github.com/mattn/go-colorable v0.0.9 - github.com/mattn/go-isatty v0.0.3 + github.com/mattn/go-colorable v0.1.8 + github.com/mattn/go-isatty v0.0.13 + github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 + github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.8.1 + github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.7.0 + github.com/zalando/go-keyring v0.1.1 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/client/go/go.sum b/client/go/go.sum index 0462d0575a1..59656af2b35 100644 --- a/client/go/go.sum +++ b/client/go/go.sum @@ -45,6 +45,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs= +github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -57,9 +59,14 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -68,12 +75,18 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec= +github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -165,6 +178,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd h1:nIzoSW6OhhppWLm4yqBwZsKJlAayUu5FGozhrF3ETSM= +github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd/go.mod h1:MEQrHur0g8VplbLOv5vXmDzacSaH9Z7XhcgsSh1xciU= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -180,14 +195,31 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.9 h1:kS8kLI4oaBYJJ6u6rpbPI0tDYVCqo0P5u8vv1zoQ49U= +github.com/lestrrat-go/jwx v1.2.9/go.mod h1:25DcLbNWArPA/Ew5CcBmewl32cJKxOk5cbepBsIJFzw= +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -204,7 +236,11 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -235,6 +271,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -249,6 +287,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zalando/go-keyring v0.1.1 h1:w2V9lcx/Uj4l+dzAf1m9s+DJ1O8ROkEHnynonHjTcYE= +github.com/zalando/go-keyring v0.1.1/go.mod h1:OIC+OZ28XbmwFxU/Rp9V7eKzZjamBJwRzC8UFJH9+L8= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -269,6 +309,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -367,6 +410,7 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -376,9 +420,11 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -403,8 +449,11 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -575,8 +624,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/client/go/util/spinner.go b/client/go/util/spinner.go new file mode 100644 index 00000000000..1deb4296d28 --- /dev/null +++ b/client/go/util/spinner.go @@ -0,0 +1,63 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package util + +import ( + "os" + "time" + + "github.com/briandowns/spinner" + "github.com/pkg/errors" +) + +const ( + spinnerTextEllipsis = "..." + spinnerTextDone = "done" + spinnerTextFailed = "failed" + spinnerColor = "blue" +) + +var messages = os.Stderr + +func Spinner(text string, fn func() error) error { + initialMsg := text + spinnerTextEllipsis + " " + doneMsg := initialMsg + spinnerTextDone + "\n" + failMsg := initialMsg + spinnerTextFailed + "\n" + + return loading(initialMsg, doneMsg, failMsg, fn) +} + +func loading(initialMsg, doneMsg, failMsg string, fn func() error) error { + done := make(chan struct{}) + errc := make(chan error) + go func() { + defer close(done) + + s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(messages)) + s.Prefix = initialMsg + s.FinalMSG = doneMsg + s.HideCursor = true + s.Writer = messages + + if err := s.Color(spinnerColor); err != nil { + panic(Error(err, "failed setting spinner color")) + } + + s.Start() + err := <-errc + if err != nil { + s.FinalMSG = failMsg + } + + s.Stop() + }() + + err := fn() + errc <- err + <-done + return err +} + +func Error(e error, message string) error { + return errors.Wrap(e, message) +} diff --git a/client/go/vespa/deploy.go b/client/go/vespa/deploy.go index 3718c7d813a..9c5fb3a12ac 100644 --- a/client/go/vespa/deploy.go +++ b/client/go/vespa/deploy.go @@ -327,12 +327,12 @@ func Submit(opts DeploymentOpts) error { Header: make(http.Header), } request.Header.Set("Content-Type", writer.FormDataContentType()) - signer := NewRequestSigner(opts.Deployment.Application.SerializedForm(), opts.APIKey) - if err := signer.SignRequest(request); err != nil { + serviceDescription := "Submit service" + sigKeyId := opts.Deployment.Application.SerializedForm() + if err := opts.Target.PrepareApiRequest(request, sigKeyId); err != nil { return err } - serviceDescription := "Submit service" - response, err := util.HttpDo(request, time.Minute*10, serviceDescription) + response, err := util.HttpDo(request, time.Minute*10, sigKeyId) if err != nil { return err } @@ -344,7 +344,7 @@ func checkDeploymentOpts(opts DeploymentOpts) error { if !opts.ApplicationPackage.HasCertificate() { return fmt.Errorf("%s: missing certificate in package", opts) } - if opts.APIKey == nil { + if !Auth0AccessTokenEnabled() && opts.APIKey == nil { return fmt.Errorf("%s: missing api key", opts.String()) } return nil @@ -363,13 +363,11 @@ func uploadApplicationPackage(url *url.URL, opts DeploymentOpts) (int64, error) Header: header, Body: ioutil.NopCloser(zipReader), } - if opts.APIKey != nil { - signer := NewRequestSigner(opts.Deployment.Application.SerializedForm(), opts.APIKey) - if err := signer.SignRequest(request); err != nil { - return 0, err - } - } serviceDescription := "Deploy service" + sigKeyId := opts.Deployment.Application.SerializedForm() + if err := opts.Target.PrepareApiRequest(request, sigKeyId); err != nil { + return 0, err + } response, err := util.HttpDo(request, time.Minute*10, serviceDescription) if err != nil { return 0, err diff --git a/client/go/vespa/target.go b/client/go/vespa/target.go index 8a09440f5cc..f497bf5b3cd 100644 --- a/client/go/vespa/target.go +++ b/client/go/vespa/target.go @@ -6,13 +6,16 @@ import ( "crypto/tls" "encoding/json" "fmt" + "github.com/vespa-engine/vespa/client/go/cli" "io" "io/ioutil" "math" "net/http" "net/url" + "os" "sort" "strconv" + "strings" "time" "github.com/vespa-engine/vespa/client/go/util" @@ -35,6 +38,7 @@ type Service struct { BaseURL string Name string TLSOptions TLSOptions + Target *Target } // Target represents a Vespa platform, running named Vespa services. @@ -47,6 +51,8 @@ type Target interface { // PrintLog writes the logs of this deployment using given options to control output. PrintLog(options LogOptions) error + + PrepareApiRequest(req *http.Request, sigKeyId string) error } // TLSOptions configures the certificate to use for service requests. @@ -66,11 +72,21 @@ type LogOptions struct { Level int } +func Auth0AccessTokenEnabled() bool { + v, present := os.LookupEnv("VESPA_CLI_OAUTH2_DEVICE_FLOW") + if !present { + return false + } + return strings.ToLower(v) == "true" || v == "1" || v == "" +} + type customTarget struct { targetType string baseURL string } +func (t *customTarget) PrepareApiRequest(req *http.Request, sigKeyId string) error { return nil } + // Do sends request to this service. Any required authentication happens automatically. func (s *Service) Do(request *http.Request, timeout time.Duration) (*http.Response, error) { if s.TLSOptions.KeyPair.Certificate != nil { @@ -192,8 +208,9 @@ type cloudTarget struct { tlsOptions TLSOptions logOptions LogOptions - queryURL string - documentURL string + queryURL string + documentURL string + authConfigPath string } func (t *cloudTarget) Type() string { return t.targetType } @@ -221,6 +238,30 @@ func (t *cloudTarget) Service(name string, timeout time.Duration, runID int64) ( return nil, fmt.Errorf("unknown service: %s", name) } +func (t *cloudTarget) PrepareApiRequest(req *http.Request, sigKeyId string) error { + if Auth0AccessTokenEnabled() { + if err := t.addAuth0AccessToken(req); err != nil { + return err + } + } else if t.apiKey != nil { + signer := NewRequestSigner(sigKeyId, t.apiKey) + if err := signer.SignRequest(req); err != nil { + return err + } + } + return nil +} + +func (t *cloudTarget) addAuth0AccessToken(request *http.Request) error { + c, err := cli.GetCli(t.authConfigPath) + tenant, err := c.PrepareTenant(cli.ContextWithCancel()) + if err != nil { + return err + } + request.Header.Set("Authorization", "Bearer "+tenant.AccessToken) + return nil +} + func (t *cloudTarget) logsURL() string { return fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/environment/%s/region/%s/logs", t.apiURL, @@ -233,7 +274,6 @@ func (t *cloudTarget) PrintLog(options LogOptions) error { if err != nil { return err } - signer := NewRequestSigner(t.deployment.Application.SerializedForm(), t.apiKey) lastFrom := options.From requestFunc := func() *http.Request { fromMillis := lastFrom.Unix() * 1000 @@ -244,9 +284,7 @@ func (t *cloudTarget) PrintLog(options LogOptions) error { q.Set("to", strconv.FormatInt(toMillis, 10)) } req.URL.RawQuery = q.Encode() - if err := signer.SignRequest(req); err != nil { - panic(err) - } + t.PrepareApiRequest(req, t.deployment.Application.SerializedForm()) return req } logFunc := func(status int, response []byte) (bool, error) { @@ -280,16 +318,15 @@ func (t *cloudTarget) PrintLog(options LogOptions) error { } func (t *cloudTarget) waitForEndpoints(timeout time.Duration, runID int64) error { - signer := NewRequestSigner(t.deployment.Application.SerializedForm(), t.apiKey) if runID > 0 { - if err := t.waitForRun(signer, runID, timeout); err != nil { + if err := t.waitForRun(runID, timeout); err != nil { return err } } - return t.discoverEndpoints(signer, timeout) + return t.discoverEndpoints(timeout) } -func (t *cloudTarget) waitForRun(signer *RequestSigner, runID int64, timeout time.Duration) error { +func (t *cloudTarget) waitForRun(runID int64, timeout time.Duration) error { runURL := fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/job/%s-%s/run/%d", t.apiURL, t.deployment.Application.Tenant, t.deployment.Application.Application, t.deployment.Application.Instance, @@ -303,7 +340,7 @@ func (t *cloudTarget) waitForRun(signer *RequestSigner, runID int64, timeout tim q := req.URL.Query() q.Set("after", strconv.FormatInt(lastID, 10)) req.URL.RawQuery = q.Encode() - if err := signer.SignRequest(req); err != nil { + if err := t.PrepareApiRequest(req, t.deployment.Application.SerializedForm()); err != nil { panic(err) } return req @@ -353,7 +390,7 @@ func (t *cloudTarget) printLog(response jobResponse, last int64) int64 { return response.LastID } -func (t *cloudTarget) discoverEndpoints(signer *RequestSigner, timeout time.Duration) error { +func (t *cloudTarget) discoverEndpoints(timeout time.Duration) error { deploymentURL := fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/environment/%s/region/%s", t.apiURL, t.deployment.Application.Tenant, t.deployment.Application.Application, t.deployment.Application.Instance, @@ -362,7 +399,7 @@ func (t *cloudTarget) discoverEndpoints(signer *RequestSigner, timeout time.Dura if err != nil { return err } - if err := signer.SignRequest(req); err != nil { + if err := t.PrepareApiRequest(req, t.deployment.Application.SerializedForm()); err != nil { return err } var endpointURL string @@ -409,14 +446,16 @@ func CustomTarget(baseURL string) Target { } // CloudTarget creates a Target for the Vespa Cloud platform. -func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions) Target { +func CloudTarget(apiURL string, deployment Deployment, apiKey []byte, tlsOptions TLSOptions, logOptions LogOptions, + authConfigPath string) Target { return &cloudTarget{ - apiURL: apiURL, - targetType: cloudTargetType, - deployment: deployment, - apiKey: apiKey, - tlsOptions: tlsOptions, - logOptions: logOptions, + apiURL: apiURL, + targetType: cloudTargetType, + deployment: deployment, + apiKey: apiKey, + tlsOptions: tlsOptions, + logOptions: logOptions, + authConfigPath: authConfigPath, } } diff --git a/client/go/vespa/target_test.go b/client/go/vespa/target_test.go index ed924059297..d4d23901513 100644 --- a/client/go/vespa/target_test.go +++ b/client/go/vespa/target_test.go @@ -143,7 +143,10 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target { x509KeyPair, err := tls.X509KeyPair(kp.Certificate, kp.PrivateKey) assert.Nil(t, err) - apiKey, err := CreateAPIKey() + var apiKey []byte = nil + if !Auth0AccessTokenEnabled() { + apiKey, err = CreateAPIKey() + } assert.Nil(t, err) target := CloudTarget( @@ -154,7 +157,7 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target { }, apiKey, TLSOptions{KeyPair: x509KeyPair}, - LogOptions{Writer: logWriter}) + LogOptions{Writer: logWriter}, "") if ct, ok := target.(*cloudTarget); ok { ct.apiURL = url } else { diff --git a/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp b/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp index 139288f6b6f..1fadd3993ff 100644 --- a/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp +++ b/searchcore/src/tests/proton/matching/partial_result/partial_result_test.cpp @@ -23,7 +23,7 @@ void checkMerge(const std::vector<double> &a, const std::vector<double> &b, EXPECT_EQUAL(a.size() + b.size(), res_a.totalHits()); ASSERT_EQUAL(expect.size(), res_a.size()); for (size_t i = 0; i < expect.size(); ++i) { - EXPECT_EQUAL(expect[i], res_a.hit(i)._rankValue); + EXPECT_EQUAL(expect[i], res_a.hit(i).getRank()); } } @@ -70,10 +70,10 @@ TEST("require that partial results can be created without sort data") { res.totalHits(1000); EXPECT_EQUAL(1000u, res.totalHits()); ASSERT_EQUAL(2u, res.size()); - EXPECT_EQUAL(1u, res.hit(0)._docId); - EXPECT_EQUAL(10.0, res.hit(0)._rankValue); - EXPECT_EQUAL(2u, res.hit(1)._docId); - EXPECT_EQUAL(5.0, res.hit(1)._rankValue); + EXPECT_EQUAL(1u, res.hit(0).getDocId()); + EXPECT_EQUAL(10.0, res.hit(0).getRank()); + EXPECT_EQUAL(2u, res.hit(1).getDocId()); + EXPECT_EQUAL(5.0, res.hit(1).getRank()); } TEST("require that partial results can be created with sort data") { @@ -90,12 +90,12 @@ TEST("require that partial results can be created with sort data") { res.totalHits(1000); EXPECT_EQUAL(1000u, res.totalHits()); ASSERT_EQUAL(2u, res.size()); - EXPECT_EQUAL(1u, res.hit(0)._docId); - EXPECT_EQUAL(10.0, res.hit(0)._rankValue); + EXPECT_EQUAL(1u, res.hit(0).getDocId()); + EXPECT_EQUAL(10.0, res.hit(0).getRank()); EXPECT_EQUAL(str1.data(), res.sortData(0).first); EXPECT_EQUAL(str1.size(), res.sortData(0).second); - EXPECT_EQUAL(2u, res.hit(1)._docId); - EXPECT_EQUAL(5.0, res.hit(1)._rankValue); + EXPECT_EQUAL(2u, res.hit(1).getDocId()); + EXPECT_EQUAL(5.0, res.hit(1).getRank()); EXPECT_EQUAL(str2.data(), res.sortData(1).first); EXPECT_EQUAL(str2.size(), res.sortData(1).second); } @@ -133,10 +133,10 @@ TEST("require that lower docid is preferred when sorting on rank") { res_c.add(search::RankedHit(1, 1.0)); res_a.merge(res_b); ASSERT_EQUAL(1u, res_a.size()); - EXPECT_EQUAL(2u, res_a.hit(0)._docId); + EXPECT_EQUAL(2u, res_a.hit(0).getDocId()); res_a.merge(res_c); ASSERT_EQUAL(1u, res_a.size()); - EXPECT_EQUAL(1u, res_a.hit(0)._docId); + EXPECT_EQUAL(1u, res_a.hit(0).getDocId()); } TEST("require that lower docid is preferred when using sortspec") { @@ -149,10 +149,10 @@ TEST("require that lower docid is preferred when using sortspec") { res_c.add(search::RankedHit(1, 1.0), PartialResult::SortRef(foo.data(), foo.size())); res_a.merge(res_b); ASSERT_EQUAL(1u, res_a.size()); - EXPECT_EQUAL(2u, res_a.hit(0)._docId); + EXPECT_EQUAL(2u, res_a.hit(0).getDocId()); res_a.merge(res_c); ASSERT_EQUAL(1u, res_a.size()); - EXPECT_EQUAL(1u, res_a.hit(0)._docId); + EXPECT_EQUAL(1u, res_a.hit(0).getDocId()); } TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h b/searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h index aeca09295c8..1ab1adb1add 100644 --- a/searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h +++ b/searchcore/src/vespa/searchcore/proton/bucketdb/remove_batch_entry.h @@ -9,6 +9,10 @@ namespace proton::bucketdb { +/* + * Class containing meta data for a single document being removed from + * bucket db. + */ class RemoveBatchEntry { document::GlobalId _gid; document::BucketId _bucket_id; diff --git a/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp b/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp index 6ae97a125ad..432752d69d0 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/partial_result.cpp @@ -7,10 +7,10 @@ namespace proton::matching { namespace { bool before(const search::RankedHit &a, const search::RankedHit &b) { - if (a._rankValue != b._rankValue) { - return (a._rankValue > b._rankValue); + if (a.getRank() != b.getRank()) { + return (a.getRank() > b.getRank()); } - return (a._docId < b._docId); + return (a.getDocId() < b.getDocId()); } void mergeHits(size_t maxHits, diff --git a/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp b/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp index 341bd3bb855..f332ca5ec26 100644 --- a/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp +++ b/searchcore/src/vespa/searchcore/proton/matching/result_processor.cpp @@ -108,7 +108,7 @@ ResultProcessor::extract_docid_ordering(const PartialResult &result) const std::vector<std::pair<uint32_t,uint32_t>> list; list.reserve(est_size); for (size_t i = _offset; i < result.size(); ++i) { - list.emplace_back(result.hit(i)._docId, list.size()); + list.emplace_back(result.hit(i).getDocId(), list.size()); } std::sort(list.begin(), list.end(), [](const auto &a, const auto &b){ return (a.first < b.first); }); return list; @@ -142,11 +142,11 @@ ResultProcessor::makeReply(PartialResultUP full_result) for (size_t i = 0; i < hitcnt; ++i) { search::engine::SearchReply::Hit &dst = r.hits[i]; const search::RankedHit &src = result.hit(hitOffset + i); - uint32_t docId = src._docId; + uint32_t docId = src.getDocId(); if (metaStore.getGidEvenIfMoved(docId, gid)) { dst.gid = gid; } - dst.metric = src._rankValue; + dst.metric = src.getRank(); LOG(debug, "convertLidToGid: hit[%zu]: lid(%u) -> gid(%s)", i, docId, dst.gid.toString().c_str()); } if (result.hasSortData() && (hitcnt > 0)) { diff --git a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp index 4076194542a..de54386e4af 100644 --- a/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp +++ b/searchlib/src/tests/attribute/searchcontext/searchcontext_test.cpp @@ -487,7 +487,7 @@ SearchContextTest::checkResultSet(const ResultSet & rs, const DocSet & expected, ASSERT_TRUE(array != nullptr); uint32_t i = 0; for (auto iter = expected.begin(); iter != expected.end(); ++iter, ++i) { - EXPECT_TRUE(array[i]._docId == *iter); + EXPECT_TRUE(array[i].getDocId() == *iter); } } } @@ -1517,10 +1517,10 @@ SearchContextTest::requireThatSearchIsWorkingAfterClearDoc(const vespalib::strin EXPECT_EQUAL(4u, rs->getNumHits()); ASSERT_TRUE(4u == rs->getNumHits()); const RankedHit * array = rs->getArray(); - EXPECT_EQUAL(1u, array[0]._docId); - EXPECT_EQUAL(2u, array[1]._docId); - EXPECT_EQUAL(3u, array[2]._docId); - EXPECT_EQUAL(4u, array[3]._docId); + EXPECT_EQUAL(1u, array[0].getDocId()); + EXPECT_EQUAL(2u, array[1].getDocId()); + EXPECT_EQUAL(3u, array[2].getDocId()); + EXPECT_EQUAL(4u, array[3].getDocId()); } a->clearDoc(1); a->clearDoc(3); @@ -1529,8 +1529,8 @@ SearchContextTest::requireThatSearchIsWorkingAfterClearDoc(const vespalib::strin ResultSetPtr rs = performSearch(v, term); EXPECT_EQUAL(2u, rs->getNumHits()); const RankedHit * array = rs->getArray(); - EXPECT_EQUAL(2u, array[0]._docId); - EXPECT_EQUAL(4u, array[1]._docId); + EXPECT_EQUAL(2u, array[0].getDocId()); + EXPECT_EQUAL(4u, array[1].getDocId()); } } @@ -1578,9 +1578,9 @@ SearchContextTest::requireThatSearchIsWorkingAfterLoadAndClearDoc(const vespalib const RankedHit * array = rs->getArray(); for (uint32_t i = 0; i < 14; ++i) { if (i < 5) { - EXPECT_EQUAL(i + 1, array[i]._docId); + EXPECT_EQUAL(i + 1, array[i].getDocId()); } else - EXPECT_EQUAL(i + 2, array[i]._docId); + EXPECT_EQUAL(i + 2, array[i].getDocId()); } } ValueType buf; @@ -1682,15 +1682,15 @@ SearchContextTest::requireThatFlagAttributeIsWorkingWhenNewDocsAreAdded() { ResultSetPtr rs = performSearch(fa, "<24"); EXPECT_EQUAL(2u, rs->getNumHits()); - EXPECT_EQUAL(1u, rs->getArray()[0]._docId); - EXPECT_EQUAL(2u, rs->getArray()[1]._docId); + EXPECT_EQUAL(1u, rs->getArray()[0].getDocId()); + EXPECT_EQUAL(2u, rs->getArray()[1].getDocId()); } { ResultSetPtr rs = performSearch(fa, "24"); EXPECT_EQUAL(3u, rs->getNumHits()); - EXPECT_EQUAL(1u, rs->getArray()[0]._docId); - EXPECT_EQUAL(2u, rs->getArray()[1]._docId); - EXPECT_EQUAL(4u, rs->getArray()[2]._docId); + EXPECT_EQUAL(1u, rs->getArray()[0].getDocId()); + EXPECT_EQUAL(2u, rs->getArray()[1].getDocId()); + EXPECT_EQUAL(4u, rs->getArray()[2].getDocId()); } } { @@ -1717,15 +1717,15 @@ SearchContextTest::requireThatFlagAttributeIsWorkingWhenNewDocsAreAdded() EXPECT_EQUAL(exp50.size(), rs1->getNumHits()); EXPECT_EQUAL(exp50.size(), rs2->getNumHits()); for (size_t j = 0; j < exp50.size(); ++j) { - EXPECT_EQUAL(exp50[j], rs1->getArray()[j]._docId); - EXPECT_EQUAL(exp50[j], rs2->getArray()[j]._docId); + EXPECT_EQUAL(exp50[j], rs1->getArray()[j].getDocId()); + EXPECT_EQUAL(exp50[j], rs2->getArray()[j].getDocId()); } } { ResultSetPtr rs = performSearch(fa, "60"); EXPECT_EQUAL(exp60.size(), rs->getNumHits()); for (size_t j = 0; j < exp60.size(); ++j) { - EXPECT_EQUAL(exp60[j], rs->getArray()[j]._docId); + EXPECT_EQUAL(exp60[j], rs->getArray()[j].getDocId()); } } } diff --git a/searchlib/src/tests/grouping/grouping_test.cpp b/searchlib/src/tests/grouping/grouping_test.cpp index ef4930de8ce..2eab66cb3b7 100644 --- a/searchlib/src/tests/grouping/grouping_test.cpp +++ b/searchlib/src/tests/grouping/grouping_test.cpp @@ -105,7 +105,7 @@ public: hit._rankValue = rank; _hits.push_back(hit); for (uint32_t pos = (_hits.size() - 1); - pos > 0 && (_hits[pos]._rankValue > _hits[pos - 1]._rankValue); + pos > 0 && (_hits[pos].getRank() > _hits[pos - 1].getRank()); --pos) { std::swap(_hits[pos], _hits[pos - 1]); diff --git a/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp b/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp index 66fa359f1a3..e82079073e7 100644 --- a/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp +++ b/searchlib/src/tests/groupingengine/groupingengine_benchmark.cpp @@ -88,7 +88,7 @@ public: hit._rankValue = rank; _hits.push_back(hit); for (uint32_t pos = (_hits.size() - 1); - pos > 0 && (_hits[pos]._rankValue > _hits[pos - 1]._rankValue); + pos > 0 && (_hits[pos].getRank() > _hits[pos - 1].getRank()); --pos) { std::swap(_hits[pos], _hits[pos - 1]); diff --git a/searchlib/src/tests/groupingengine/groupingengine_test.cpp b/searchlib/src/tests/groupingengine/groupingengine_test.cpp index a0179c36c23..d54b68388e4 100644 --- a/searchlib/src/tests/groupingengine/groupingengine_test.cpp +++ b/searchlib/src/tests/groupingengine/groupingengine_test.cpp @@ -87,7 +87,7 @@ public: hit._rankValue = rank; _hits.push_back(hit); for (uint32_t pos = (_hits.size() - 1); - pos > 0 && (_hits[pos]._rankValue > _hits[pos - 1]._rankValue); + pos > 0 && (_hits[pos].getRank() > _hits[pos - 1].getRank()); --pos) { std::swap(_hits[pos], _hits[pos - 1]); diff --git a/searchlib/src/tests/hitcollector/hitcollector_test.cpp b/searchlib/src/tests/hitcollector/hitcollector_test.cpp index 617e0e85824..ed68c47ea23 100644 --- a/searchlib/src/tests/hitcollector/hitcollector_test.cpp +++ b/searchlib/src/tests/hitcollector/hitcollector_test.cpp @@ -70,8 +70,8 @@ void checkResult(const ResultSet & rs, const std::vector<RankedHit> & exp) ASSERT_EQUAL(rs.getArrayUsed(), exp.size()); for (uint32_t i = 0; i < exp.size(); ++i) { - EXPECT_EQUAL(rh[i]._docId, exp[i]._docId); - EXPECT_EQUAL(rh[i]._rankValue + 1.0, exp[i]._rankValue + 1.0); + EXPECT_EQUAL(rh[i].getDocId(), exp[i].getDocId()); + EXPECT_EQUAL(rh[i].getRank() + 1.0, exp[i].getRank() + 1.0); } } else { ASSERT_TRUE(rs.getArray() == nullptr); diff --git a/searchlib/src/tests/sortresults/sorttest.cpp b/searchlib/src/tests/sortresults/sorttest.cpp index cd892800ca5..bbd6d0b72ce 100644 --- a/searchlib/src/tests/sortresults/sorttest.cpp +++ b/searchlib/src/tests/sortresults/sorttest.cpp @@ -41,17 +41,17 @@ test_sort(unsigned int caseNum, unsigned int n, unsigned int ntop) } FastS_SortResults(array, n, ntop); - minmax = array[ntop - 1]._rankValue; + minmax = array[ntop - 1].getRank(); for(i = 0; i < n; i++) { if (i < ntop && i > 0 - && array[i]._rankValue > array[i - 1]._rankValue) { + && array[i].getRank() > array[i - 1].getRank()) { printf("ERROR: rank(%d) > rank(%d)\n", i, i - 1); ok = false; break; } if (i >= ntop && - array[i]._rankValue > minmax) { + array[i].getRank() > minmax) { printf("ERROR: rank(%d) > rank(%d)\n", i, ntop - 1); ok = false; diff --git a/searchlib/src/tests/sortspec/multilevelsort.cpp b/searchlib/src/tests/sortspec/multilevelsort.cpp index f438fce0e7f..576e1d1336c 100644 --- a/searchlib/src/tests/sortspec/multilevelsort.cpp +++ b/searchlib/src/tests/sortspec/multilevelsort.cpp @@ -275,21 +275,21 @@ MultilevelSortTest::sortAndCheck(const std::vector<Spec> &spec, uint32_t num, for (uint32_t j = 0; j < spec.size(); ++j) { int cmp = 0; if (spec[j]._type == RANK) { - if (hits[i]._rankValue < hits[i+1]._rankValue) { + if (hits[i].getRank() < hits[i+1].getRank()) { cmp = -1; - } else if (hits[i]._rankValue > hits[i+1]._rankValue) { + } else if (hits[i].getRank() > hits[i+1].getRank()) { cmp = 1; } } else if (spec[j]._type == DOCID) { - if (hits[i]._docId < hits[i+1]._docId) { + if (hits[i].getDocId() < hits[i+1].getDocId()) { cmp = -1; - } else if (hits[i]._docId > hits[i+1]._docId) { + } else if (hits[i].getDocId() > hits[i+1].getDocId()) { cmp = 1; } } else { AttributeVector *av = vec[spec[j]._name].get(); cmp = compare(av, spec[j]._type, - hits[i]._docId, hits[i+1]._docId); + hits[i].getDocId(), hits[i+1].getDocId()); } if (spec[j]._asc) { EXPECT_TRUE(cmp <= 0); diff --git a/searchlib/src/vespa/searchlib/aggregation/grouping.cpp b/searchlib/src/vespa/searchlib/aggregation/grouping.cpp index 68098d6c35a..f373b5fc0b3 100644 --- a/searchlib/src/vespa/searchlib/aggregation/grouping.cpp +++ b/searchlib/src/vespa/searchlib/aggregation/grouping.cpp @@ -205,13 +205,13 @@ void Grouping::postProcess() void Grouping::aggregateWithoutClock(const RankedHit * rankedHit, unsigned int len) { for(unsigned int i(0); i < len; i++) { - aggregate(rankedHit[i]._docId, rankedHit[i]._rankValue); + aggregate(rankedHit[i].getDocId(), rankedHit[i].getRank()); } } void Grouping::aggregateWithClock(const RankedHit * rankedHit, unsigned int len) { for(unsigned int i(0); (i < len) && !hasExpired(); i++) { - aggregate(rankedHit[i]._docId, rankedHit[i]._rankValue); + aggregate(rankedHit[i].getDocId(), rankedHit[i].getRank()); } } diff --git a/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h b/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h index b31e726b103..fdf9ab624ad 100644 --- a/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h +++ b/searchlib/src/vespa/searchlib/attribute/loadedenumvalue.h @@ -29,7 +29,7 @@ public: uint64_t operator()(const LoadedEnumAttribute &v) { - return (static_cast<uint64_t>(v._enum) << 32) | v._docId; + return (static_cast<uint64_t>(v._enum) << 32) | v.getDocId(); } }; diff --git a/searchlib/src/vespa/searchlib/attribute/loadedvalue.h b/searchlib/src/vespa/searchlib/attribute/loadedvalue.h index 701ccdc902c..b8f938838d2 100644 --- a/searchlib/src/vespa/searchlib/attribute/loadedvalue.h +++ b/searchlib/src/vespa/searchlib/attribute/loadedvalue.h @@ -103,9 +103,10 @@ public: T _value; uint32_t _eidx; }; + uint32_t _docId; uint32_t _idx; - vespalib::datastore::EntryRef _pidx; + vespalib::datastore::EntryRef _pidx; private: int32_t _weight; Value _value; diff --git a/searchlib/src/vespa/searchlib/common/sortresults.cpp b/searchlib/src/vespa/searchlib/common/sortresults.cpp index 7a54de708d0..7510ae162ce 100644 --- a/searchlib/src/vespa/searchlib/common/sortresults.cpp +++ b/searchlib/src/vespa/searchlib/common/sortresults.cpp @@ -51,7 +51,7 @@ FastS_insertion_sort(RankedHit a[], uint32_t n) for (i=1; i<n ; i++) { swap = a[i]; j = i; - while (R(swap._rankValue) > R(a[j-1]._rankValue)) { + while (R(swap.getRank()) > R(a[j-1].getRank())) { a[j] = a[j-1]; if (!(--j)) break;; } @@ -74,13 +74,13 @@ FastS_radixsort(RankedHit a[], uint32_t n, uint32_t ntop) memset(cnt, 0, 256*sizeof(uint32_t)); // Count occurrences [NB: will fail with n < 3] for(i = 0; i < n - 3; i += 4) { - cnt[(R(a[i]._rankValue) >> SHIFT) & 0xFF]++; - cnt[(R(a[i + 1]._rankValue) >> SHIFT) & 0xFF]++; - cnt[(R(a[i + 2]._rankValue) >> SHIFT) & 0xFF]++; - cnt[(R(a[i + 3]._rankValue) >> SHIFT) & 0xFF]++; + cnt[(R(a[i].getRank()) >> SHIFT) & 0xFF]++; + cnt[(R(a[i + 1].getRank()) >> SHIFT) & 0xFF]++; + cnt[(R(a[i + 2].getRank()) >> SHIFT) & 0xFF]++; + cnt[(R(a[i + 3].getRank()) >> SHIFT) & 0xFF]++; } for(; i < n; i++) - cnt[(R(a[i]._rankValue) >> SHIFT) & 0xFF]++; + cnt[(R(a[i].getRank()) >> SHIFT) & 0xFF]++; // Accumulate cnt positions sorted = (cnt[0]==n); @@ -109,14 +109,14 @@ FastS_radixsort(RankedHit a[], uint32_t n, uint32_t ntop) // Grab first element to move j = ptr[i]; swap = a[j]; - k = (R(swap._rankValue) >> SHIFT) & 0xFF; + k = (R(swap.getRank()) >> SHIFT) & 0xFF; // Swap into correct class until cycle completed if (i!=k) { do { temp = a[ptr[k]]; a[ptr[k]++] = swap; - k = (R((swap = temp)._rankValue) >> SHIFT) & 0xFF; + k = (R((swap = temp).getRank()) >> SHIFT) & 0xFF; remain--; } while (i!=k); // Place last element in cycle @@ -265,11 +265,11 @@ FastS_SortSpec::initSortData(const RankedHit *hits, uint32_t n) written = sizeof(hits->_docId) + sizeof(_partitionId); break; case ASC_RANK: - serializeForSort<convertForSort<search::HitRank, true> >(hits[i]._rankValue, mySortData); + serializeForSort<convertForSort<search::HitRank, true> >(hits[i].getRank(), mySortData); written = sizeof(hits->_rankValue); break; case DESC_RANK: - serializeForSort<convertForSort<search::HitRank, false> >(hits[i]._rankValue, mySortData); + serializeForSort<convertForSort<search::HitRank, false> >(hits[i].getRank(), mySortData); written = sizeof(hits->_rankValue); break; case ASC_VECTOR: diff --git a/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp b/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp index 77c2aa3072a..ea278ebf607 100644 --- a/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp +++ b/searchlib/src/vespa/searchlib/fef/termfieldmatchdata.cpp @@ -18,7 +18,7 @@ TermFieldMatchData::TermFieldMatchData() : } TermFieldMatchData::TermFieldMatchData(const TermFieldMatchData & rhs) : - _docId(rhs._docId), + _docId(rhs.getDocId()), _fieldId(rhs._fieldId), _flags(rhs._flags), _sz(0), diff --git a/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h b/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h index f8328d15289..429eea038c9 100644 --- a/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h +++ b/searchlib/src/vespa/searchlib/memoryindex/field_index_remover.h @@ -21,6 +21,7 @@ private: struct WordFieldDocTuple { vespalib::datastore::EntryRef _wordRef; uint32_t _docId; + WordFieldDocTuple() noexcept : _wordRef(0), _docId(0) diff --git a/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp b/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp index b2b1d49bae9..3293019e538 100644 --- a/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/hitcollector.cpp @@ -195,7 +195,7 @@ mergeHitsIntoResultSet(const std::vector<HitCollector::Hit> &hits, ResultSet &re uint32_t rhCur(0); uint32_t rhEnd(result.getArrayUsed()); for (const auto &hit : hits) { - while (rhCur != rhEnd && result[rhCur]._docId != hit.first) { + while (rhCur != rhEnd && result[rhCur].getDocId() != hit.first) { // just set the iterators right ++rhCur; } diff --git a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h index a94f6087a0d..d0a75930ed5 100644 --- a/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h +++ b/searchlib/src/vespa/searchlib/test/fakedata/fakememtreeocc.h @@ -77,8 +77,8 @@ public: bool operator<(const PendingOp &rhs) const { if (_wordIdx != rhs._wordIdx) return _wordIdx < rhs._wordIdx; - if (_docId != rhs._docId) - return _docId < rhs._docId; + if (_docId != rhs.getDocId()) + return _docId < rhs.getDocId(); return _seq < rhs._seq; } }; |