clientcache commands now can output curl strings (#4042)

pull/4202/head
Todd 2 years ago committed by Johan Brandhorst-Satzkorn
parent b71d134e06
commit 9aab26e91b

@ -19,10 +19,18 @@ var LastOutputStringError *OutputStringError
type OutputStringError struct {
*retryablehttp.Request
unixSocket string
parsingError error
parsedCurlString string
}
func NewOutputDomainSocketCurlStringError(req *retryablehttp.Request, socketAddr string) *OutputStringError {
return &OutputStringError{
Request: req,
unixSocket: socketAddr,
}
}
func (d *OutputStringError) Error() string {
if d.parsedCurlString == "" {
d.parseRequest()
@ -46,6 +54,9 @@ func (d *OutputStringError) parseRequest() {
if d.Request.Method != "GET" {
d.parsedCurlString = fmt.Sprintf("%s -X %s", d.parsedCurlString, d.Request.Method)
}
if d.unixSocket != "" {
d.parsedCurlString = fmt.Sprintf("%s --unix-socket %s", d.parsedCurlString, d.unixSocket)
}
for k, v := range d.Request.Header {
for _, h := range v {
if strings.ToLower(k) == "authorization" {

@ -109,7 +109,11 @@ func (c *AddTokenCommand) Add(ctx context.Context, apiClient *api.Client, keyrin
} else {
return nil, nil, errors.New("The found auth token is not in the proper format.")
}
pa.AuthToken = token
if c.FlagOutputCurlString {
pa.AuthToken = "/*token*/"
} else {
pa.AuthToken = token
}
default:
at := c.ReadTokenFromKeyring(keyringType, tokenName)
if at == nil {
@ -121,15 +125,19 @@ func (c *AddTokenCommand) Add(ctx context.Context, apiClient *api.Client, keyrin
}
pa.AuthTokenId = at.Id
}
var opts []client.Option
if c.FlagOutputCurlString {
opts = append(opts, client.WithOutputCurlString())
}
dotPath, err := DefaultDotDirectory(ctx)
if err != nil {
return nil, nil, err
}
return addToken(ctx, dotPath, &pa)
return addToken(ctx, dotPath, &pa, opts...)
}
func addToken(ctx context.Context, daemonPath string, p *daemon.UpsertTokenRequest) (*api.Response, *api.Error, error) {
func addToken(ctx context.Context, daemonPath string, p *daemon.UpsertTokenRequest, opt ...client.Option) (*api.Response, *api.Error, error) {
addr, err := daemon.SocketAddress(daemonPath)
if err != nil {
return nil, nil, fmt.Errorf("Error when retrieving the socket address: %w", err)
@ -143,7 +151,7 @@ func addToken(ctx context.Context, daemonPath string, p *daemon.UpsertTokenReque
if err != nil {
return nil, nil, fmt.Errorf("Error when making a new client: %w.", err)
}
resp, err := c.Post(ctx, "/v1/tokens", p)
resp, err := c.Post(ctx, "/v1/tokens", p, opt...)
if err != nil {
return nil, nil, fmt.Errorf("Error when sending request to the daemon: %w.", err)
}

@ -96,11 +96,15 @@ func (c *StatusCommand) Status(ctx context.Context) (*api.Response, *daemon.Stat
if err != nil {
return nil, nil, nil, err
}
var opts []client.Option
if c.FlagOutputCurlString {
opts = append(opts, client.WithOutputCurlString())
}
return status(ctx, dotPath)
return status(ctx, dotPath, opts...)
}
func status(ctx context.Context, daemonPath string) (*api.Response, *daemon.StatusResult, *api.Error, error) {
func status(ctx context.Context, daemonPath string, opt ...client.Option) (*api.Response, *daemon.StatusResult, *api.Error, error) {
const op = "daemon.status"
addr, err := daemon.SocketAddress(daemonPath)
if err != nil {
@ -115,7 +119,7 @@ func status(ctx context.Context, daemonPath string) (*api.Response, *daemon.Stat
return nil, nil, nil, err
}
resp, err := c.Get(ctx, "/v1/status", nil)
resp, err := c.Get(ctx, "/v1/status", nil, opt...)
if err != nil {
return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("client do failed"))
}

@ -132,11 +132,11 @@ func (c *SearchCommand) Run(args []string) int {
}
func (c *SearchCommand) Search(ctx context.Context) (*api.Response, *daemon.SearchResult, *api.Error, error) {
client, err := c.Client()
cl, err := c.Client()
if err != nil {
return nil, nil, nil, err
}
t := client.Token()
t := cl.Token()
if t == "" {
return nil, nil, nil, fmt.Errorf("Auth Token selected for searching is empty.")
}
@ -150,15 +150,19 @@ func (c *SearchCommand) Search(ctx context.Context) (*api.Response, *daemon.Sear
resource: c.flagResource,
authTokenId: strings.Join(tSlice[:2], "_"),
}
var opts []client.Option
if c.FlagOutputCurlString {
opts = append(opts, client.WithOutputCurlString())
}
dotPath, err := daemoncmd.DefaultDotDirectory(ctx)
if err != nil {
return nil, nil, nil, err
}
return search(ctx, dotPath, tf)
return search(ctx, dotPath, tf, opts...)
}
func search(ctx context.Context, daemonPath string, fb filterBy) (*api.Response, *daemon.SearchResult, *api.Error, error) {
func search(ctx context.Context, daemonPath string, fb filterBy, opt ...client.Option) (*api.Response, *daemon.SearchResult, *api.Error, error) {
addr, err := daemon.SocketAddress(daemonPath)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error when retrieving the socket address: %w", err)
@ -175,7 +179,7 @@ func search(ctx context.Context, daemonPath string, fb filterBy) (*api.Response,
q.Add("auth_token_id", fb.authTokenId)
q.Add("resource", fb.resource)
q.Add("query", fb.flagQuery)
resp, err := c.Get(ctx, "/v1/search", q)
resp, err := c.Get(ctx, "/v1/search", q, opt...)
if err != nil {
return nil, nil, nil, fmt.Errorf("Error when sending request to the daemon: %w.", err)
}

@ -17,7 +17,7 @@ import (
"github.com/hashicorp/go-retryablehttp"
)
const hostHeader = "api.boundary.localhost"
const hostHeader = "clientcache.boundary.localhost"
type Client struct {
client *retryablehttp.Client
@ -56,11 +56,19 @@ func New(ctx context.Context, address *url.URL) (*Client, error) {
// Get sends a GET http request to the provided path. The vals provided are
// encoded and attached to the request if present.
func (c *Client) Get(ctx context.Context, path string, vals *url.Values) (*api.Response, error) {
func (c *Client) Get(ctx context.Context, path string, vals *url.Values, opt ...Option) (*api.Response, error) {
req := request(ctx, "GET", path)
if vals != nil {
req.URL.RawQuery = vals.Encode()
}
opts, err := getOpts(opt...)
if err != nil {
return nil, err
}
if opts.withOutputCurlString {
api.LastOutputStringError = api.NewOutputDomainSocketCurlStringError(req, c.domainSocketPath)
return nil, api.LastOutputStringError
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
@ -70,7 +78,7 @@ func (c *Client) Get(ctx context.Context, path string, vals *url.Values) (*api.R
// Post sends a POST http request to the provided path. The body is marshaled
// to json and added to the request body.
func (c *Client) Post(ctx context.Context, path string, body any) (*api.Response, error) {
func (c *Client) Post(ctx context.Context, path string, body any, opt ...Option) (*api.Response, error) {
req := request(ctx, "POST", path)
if body != nil {
b, err := json.Marshal(body)
@ -79,6 +87,14 @@ func (c *Client) Post(ctx context.Context, path string, body any) (*api.Response
}
req.SetBody(b)
}
opts, err := getOpts(opt...)
if err != nil {
return nil, err
}
if opts.withOutputCurlString {
api.LastOutputStringError = api.NewOutputDomainSocketCurlStringError(req, c.domainSocketPath)
return nil, api.LastOutputStringError
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err

@ -0,0 +1,39 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package client
// GetOpts - iterate the inbound Options and return a struct.
func getOpts(opt ...Option) (options, error) {
opts := getDefaultOptions()
for _, o := range opt {
if o != nil {
err := o(&opts)
if err != nil {
return opts, err
}
}
}
return opts, nil
}
// Option - how Options are passed as arguments.
type Option func(*options) error
// options - how options are represented.
type options struct {
withOutputCurlString bool
}
func getDefaultOptions() options {
return options{}
}
// WithOutputCurlString specifies that the client should return an
// OutputStringError that prints out the curl string for the request being generated.
func WithOutputCurlString() Option {
return func(o *options) error {
o.withOutputCurlString = true
return nil
}
}

@ -0,0 +1,28 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package client
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_GetOpts(t *testing.T) {
t.Parallel()
t.Run("default", func(t *testing.T) {
opts, err := getOpts()
assert.NoError(t, err)
testOpts := options{}
assert.Equal(t, opts, testOpts)
})
t.Run("WithOutputCurlString", func(t *testing.T) {
opts, err := getOpts(WithOutputCurlString())
assert.NoError(t, err)
testOpts := getDefaultOptions()
testOpts.withOutputCurlString = true
assert.Equal(t, opts, testOpts)
})
}

@ -24,23 +24,23 @@ type refresher interface {
// KeyringToken has keyring held auth token information.
type KeyringToken struct {
// The keyring type used by boundary to access the auth token
KeyringType string `json:"keyring_type"`
KeyringType string `json:"keyring_type,omitempty"`
// The token identifier for the provided keyring type that holds the auth token
TokenName string `json:"token_name"`
TokenName string `json:"token_name,omitempty"`
}
// userTokenToAdd is the request body to this handler.
type UpsertTokenRequest struct {
// BoundaryAddr is a required field for all requests
BoundaryAddr string `json:"boundary_addr"`
BoundaryAddr string `json:"boundary_addr,omitempty"`
// The id of the auth token asserted to be attempted to be added
AuthTokenId string `json:"auth_token_id"`
AuthTokenId string `json:"auth_token_id,omitempty"`
// The raw auth token for this user. Either this field or the Keyring field
// must be set but not both.
AuthToken string `json:"auth_token"`
AuthToken string `json:"auth_token,omitempty"`
// Keyring is the keyring info used when adding an auth token held in
// keyring to the daemon.
Keyring *KeyringToken `json:"keyring"`
Keyring *KeyringToken `json:"keyring,omitempty"`
}
func newTokenHandlerFunc(ctx context.Context, repo *cache.Repository, refresher refresher) (http.HandlerFunc, error) {

Loading…
Cancel
Save