Skip to content

Commit

Permalink
Restrict cache results to 250 entries by default (#5049)
Browse files Browse the repository at this point in the history
A query parameter `max_result_set_size` can be specified to set a
per-request limit, with `-1` meaning all values. If there were more
entries than the max size used for the request, an `incomplete` boolean
is set in the response.
  • Loading branch information
jefferai authored and tmessi committed Sep 23, 2024
1 parent 331c946 commit 3cdd078
Show file tree
Hide file tree
Showing 14 changed files with 703 additions and 158 deletions.
43 changes: 35 additions & 8 deletions internal/clientcache/cmd/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
stderrors "errors"
"fmt"
"math"
"net/url"
"os"
"strings"
Expand Down Expand Up @@ -40,9 +41,10 @@ var (

type SearchCommand struct {
*base.Command
flagQuery string
flagResource string
flagForceRefresh bool
flagQuery string
flagResource string
flagForceRefresh bool
flagMaxResultSetSize int64
}

func (c *SearchCommand) Synopsis() string {
Expand Down Expand Up @@ -112,6 +114,12 @@ func (c *SearchCommand) Flags() *base.FlagSets {
Usage: `Specifies the resource type to search over`,
Completion: complete.PredictSet(supportedResourceTypes...),
})
f.Int64Var(&base.Int64Var{
Name: "max-result-set-size",
Target: &c.flagMaxResultSetSize,
Usage: `Specifies an override to the default maximum result set size. Set to -1 to disable the limit. 0 will use the default.`,
Completion: complete.PredictNothing,
})
f.BoolVar(&base.BoolVar{
Name: "force-refresh",
Target: &c.flagForceRefresh,
Expand Down Expand Up @@ -148,6 +156,15 @@ func (c *SearchCommand) Run(args []string) int {
return base.CommandUserError
}

switch {
case c.flagMaxResultSetSize < -1:
c.PrintCliError(stderrors.New("Max result set size must be greater than or equal to -1"))
return base.CommandUserError
case c.flagMaxResultSetSize > math.MaxInt:
c.PrintCliError(stderrors.New(fmt.Sprintf("Max result set size must be less than or equal to the %v", math.MaxInt)))
return base.CommandUserError
}

resp, result, apiErr, err := c.Search(ctx)
if err != nil {
c.PrintCliError(err)
Expand All @@ -164,6 +181,9 @@ func (c *SearchCommand) Run(args []string) int {
return base.CommandCliError
}
default:
if result.Incomplete {
c.UI.Warn("The maximum result set size was reached and the search results are incomplete. Please narrow your search or adjust the -max-result-set-size parameter.")
}
switch {
case len(result.ResolvableAliases) > 0:
c.UI.Output(printAliasListTable(result.ResolvableAliases))
Expand Down Expand Up @@ -199,6 +219,9 @@ func (c *SearchCommand) Search(ctx context.Context) (*api.Response, *daemon.Sear
authTokenId: strings.Join(tSlice[:2], "_"),
forceRefresh: c.flagForceRefresh,
}
if c.flagMaxResultSetSize != 0 {
tf.maxResultSetSize = int(c.flagMaxResultSetSize)
}
var opts []client.Option
if c.FlagOutputCurlString {
opts = append(opts, client.WithOutputCurlString())
Expand Down Expand Up @@ -230,6 +253,9 @@ func search(ctx context.Context, daemonPath string, fb filterBy, opt ...client.O
if fb.forceRefresh {
q.Add("force_refresh", "true")
}
if fb.maxResultSetSize != 0 {
q.Add("max_result_set_size", fmt.Sprintf("%d", fb.maxResultSetSize))
}
resp, err := c.Get(ctx, "/v1/search", q, opt...)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error when sending request to the cache: %w.", err)
Expand Down Expand Up @@ -424,9 +450,10 @@ func printSessionListTable(items []*sessions.Session) string {
}

type filterBy struct {
flagFilter string
flagQuery string
authTokenId string
resource string
forceRefresh bool
flagFilter string
flagQuery string
authTokenId string
resource string
forceRefresh bool
maxResultSetSize int
}
17 changes: 17 additions & 0 deletions internal/clientcache/internal/cache/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

/*
Package cache contains the domain logic for the client cache.
*/
package cache

const (
// defaultLimitedResultSetSize is the default number of results to
// return when limiting
defaultLimitedResultSetSize = 250

// unlimitedMaxResultSetSize is the value to use when we want to return all
// results
unlimitedMaxResultSetSize = -1
)
21 changes: 20 additions & 1 deletion internal/clientcache/internal/cache/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package cache

import (
stderrors "errors"

"github.com/hashicorp/go-dbw"
)

Expand All @@ -16,14 +18,16 @@ type options struct {
withTargetRetrievalFunc TargetRetrievalFunc
withSessionRetrievalFunc SessionRetrievalFunc
withIgnoreSearchStaleness bool
withMaxResultSetSize int
}

// Option - how options are passed as args
type Option func(*options) error

func getDefaultOptions() options {
return options{
withDbType: dbw.Sqlite,
withDbType: dbw.Sqlite,
withMaxResultSetSize: defaultLimitedResultSetSize,
}
}

Expand Down Expand Up @@ -94,3 +98,18 @@ func WithIgnoreSearchStaleness(b bool) Option {
return nil
}
}

// WithMaxResultSetSize provides an option for limiting the result set, e.g.
// when no filter is provided on a list. A 0 does nothing (keeps the default).
func WithMaxResultSetSize(with int) Option {
return func(o *options) error {
switch {
case with == 0:
return nil
case with < -1:
return stderrors.New("max result set size must be -1 or greater")
}
o.withMaxResultSetSize = with
return nil
}
}
15 changes: 14 additions & 1 deletion internal/clientcache/internal/cache/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func Test_GetOpts(t *testing.T) {
opts, err := getOpts()
require.NoError(t, err)
testOpts := options{
withDbType: dbw.Sqlite,
withDbType: dbw.Sqlite,
withMaxResultSetSize: defaultLimitedResultSetSize,
}
assert.Equal(t, opts, testOpts)
})
Expand Down Expand Up @@ -93,4 +94,16 @@ func Test_GetOpts(t *testing.T) {
testOpts.withIgnoreSearchStaleness = true
assert.Equal(t, opts, testOpts)
})
t.Run("withMaxResultSetSize", func(t *testing.T) {
opts, err := getOpts(WithMaxResultSetSize(defaultLimitedResultSetSize))
require.NoError(t, err)
testOpts := getDefaultOptions()
testOpts.withMaxResultSetSize = defaultLimitedResultSetSize
assert.Equal(t, opts, testOpts)
opts, err = getOpts(WithMaxResultSetSize(0))
require.Nil(t, err)
assert.Equal(t, opts, testOpts)
_, err = getOpts(WithMaxResultSetSize(-2))
require.Error(t, err)
})
}
Loading

0 comments on commit 3cdd078

Please sign in to comment.