summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2023-11-23 11:10:24 +0100
committerGitHub <noreply@github.com>2023-11-23 11:10:24 +0100
commit6f9b43ab5aeafa4bf52208a6ca606509d3edc076 (patch)
tree1eeacd63b87e734c249680b05b54ed141dae1e30 /client
parentafde670ae04cd7e4645a180816a61c697d29698c (diff)
parent7815564d007624847fc0da6ee8ae0d412dd8d7df (diff)
Merge pull request #29436 from vespa-engine/mpolden/verbose
Add verbose option to vespa visit
Diffstat (limited to 'client')
-rw-r--r--client/go/internal/admin/deploy/curl.go2
-rw-r--r--client/go/internal/cli/cmd/api_key.go2
-rw-r--r--client/go/internal/cli/cmd/cert.go4
-rw-r--r--client/go/internal/cli/cmd/config_test.go2
-rw-r--r--client/go/internal/cli/cmd/deploy.go4
-rw-r--r--client/go/internal/cli/cmd/destroy.go2
-rw-r--r--client/go/internal/cli/cmd/destroy_test.go2
-rw-r--r--client/go/internal/cli/cmd/document.go49
-rw-r--r--client/go/internal/cli/cmd/document_test.go2
-rw-r--r--client/go/internal/cli/cmd/log.go2
-rw-r--r--client/go/internal/cli/cmd/login.go2
-rw-r--r--client/go/internal/cli/cmd/logout.go2
-rw-r--r--client/go/internal/cli/cmd/prod.go4
-rw-r--r--client/go/internal/cli/cmd/root.go18
-rw-r--r--client/go/internal/cli/cmd/visit.go5
-rw-r--r--client/go/internal/cli/cmd/waiter.go2
-rw-r--r--client/go/internal/curl/curl.go13
-rw-r--r--client/go/internal/curl/curl_test.go4
-rw-r--r--client/go/internal/vespa/deploy.go6
-rw-r--r--client/go/internal/vespa/target.go36
20 files changed, 89 insertions, 74 deletions
diff --git a/client/go/internal/admin/deploy/curl.go b/client/go/internal/admin/deploy/curl.go
index ca044128e93..0ce1305226f 100644
--- a/client/go/internal/admin/deploy/curl.go
+++ b/client/go/internal/admin/deploy/curl.go
@@ -89,7 +89,7 @@ func runCurl(cmd *curl.Command, stdout io.Writer) error {
if err != nil {
if ee, ok := err.(*exec.ExitError); ok {
if ee.ProcessState.ExitCode() == 7 {
- return fmt.Errorf("HTTP request failed. Could not connect to %s", cmd.GetUrlPrefix())
+ return fmt.Errorf("HTTP request failed. Could not connect to %s", cmd.URLPrefix())
}
}
return fmt.Errorf("HTTP request failed with curl %s", err.Error())
diff --git a/client/go/internal/cli/cmd/api_key.go b/client/go/internal/cli/cmd/api_key.go
index ef04532314c..133c9db0d3b 100644
--- a/client/go/internal/cli/cmd/api_key.go
+++ b/client/go/internal/cli/cmd/api_key.go
@@ -58,7 +58,7 @@ Read more in https://cloud.vespa.ai/en/security/guide`,
}
func doApiKey(cli *CLI, overwriteKey bool, args []string) error {
- targetType, err := cli.targetType(true)
+ targetType, err := cli.targetType(cloudTargetOnly)
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/cert.go b/client/go/internal/cli/cmd/cert.go
index 9668e78bd1c..6aa99a01902 100644
--- a/client/go/internal/cli/cmd/cert.go
+++ b/client/go/internal/cli/cmd/cert.go
@@ -103,7 +103,7 @@ $ vespa auth cert add -a my-tenant.my-app.my-instance path/to/application/packag
}
func doCert(cli *CLI, overwriteCertificate, skipApplicationPackage bool, args []string) error {
- targetType, err := cli.targetType(true)
+ targetType, err := cli.targetType(cloudTargetOnly)
if err != nil {
return err
}
@@ -149,7 +149,7 @@ func doCert(cli *CLI, overwriteCertificate, skipApplicationPackage bool, args []
}
func doCertAdd(cli *CLI, overwriteCertificate bool, args []string) error {
- target, err := cli.target(targetOptions{cloudExclusive: true})
+ target, err := cli.target(targetOptions{supportedType: cloudTargetOnly})
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/config_test.go b/client/go/internal/cli/cmd/config_test.go
index 64d7d91fef1..b13f8365f5f 100644
--- a/client/go/internal/cli/cmd/config_test.go
+++ b/client/go/internal/cli/cmd/config_test.go
@@ -272,7 +272,7 @@ func TestConfigTargetResolving(t *testing.T) {
}
func assertTargetType(t *testing.T, expected string, cli *CLI) {
- targetType, err := cli.targetType(false)
+ targetType, err := cli.targetType(anyTarget)
require.Nil(t, err)
assert.Equal(t, expected, targetType.name)
}
diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go
index dd605237b5f..c9331a540eb 100644
--- a/client/go/internal/cli/cmd/deploy.go
+++ b/client/go/internal/cli/cmd/deploy.go
@@ -117,7 +117,7 @@ func newPrepareCmd(cli *CLI) *cobra.Command {
if err != nil {
return fmt.Errorf("could not find application package: %w", err)
}
- target, err := cli.target(targetOptions{})
+ target, err := cli.target(targetOptions{supportedType: localTargetOnly})
if err != nil {
return err
}
@@ -153,7 +153,7 @@ func newActivateCmd(cli *CLI) *cobra.Command {
if err != nil {
return fmt.Errorf("could not read session id: %w", err)
}
- target, err := cli.target(targetOptions{})
+ target, err := cli.target(targetOptions{supportedType: localTargetOnly})
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/destroy.go b/client/go/internal/cli/cmd/destroy.go
index f4822330e05..a7beff2e4b4 100644
--- a/client/go/internal/cli/cmd/destroy.go
+++ b/client/go/internal/cli/cmd/destroy.go
@@ -37,7 +37,7 @@ $ vespa destroy --force`,
DisableAutoGenTag: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
- target, err := cli.target(targetOptions{cloudExclusive: true})
+ target, err := cli.target(targetOptions{supportedType: cloudTargetOnly})
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/destroy_test.go b/client/go/internal/cli/cmd/destroy_test.go
index bbba593cc5d..c62a54e5925 100644
--- a/client/go/internal/cli/cmd/destroy_test.go
+++ b/client/go/internal/cli/cmd/destroy_test.go
@@ -57,5 +57,5 @@ func TestDestroy(t *testing.T) {
require.Nil(t, cli.Run("config", "set", "target", "local"))
require.Nil(t, cli.Run("config", "set", "application", "foo.bar.baz"))
require.NotNil(t, cli.Run("destroy", "-z", "prod.aws-us-east-1c"))
- assert.Equal(t, "Error: unsupported target local: this command only supports targets cloud and hosted\n", stderr.String())
+ assert.Equal(t, "Error: command does not support local target\n", stderr.String())
}
diff --git a/client/go/internal/cli/cmd/document.go b/client/go/internal/cli/cmd/document.go
index a98e9867d34..6892956880b 100644
--- a/client/go/internal/cli/cmd/document.go
+++ b/client/go/internal/cli/cmd/document.go
@@ -8,8 +8,6 @@ import (
"bytes"
"errors"
"fmt"
- "io"
- "net/http"
"os"
"strconv"
"strings"
@@ -17,7 +15,6 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
- "github.com/vespa-engine/vespa/client/go/internal/curl"
"github.com/vespa-engine/vespa/client/go/internal/util"
"github.com/vespa-engine/vespa/client/go/internal/vespa"
"github.com/vespa-engine/vespa/client/go/internal/vespa/document"
@@ -29,54 +26,24 @@ func addDocumentFlags(cli *CLI, cmd *cobra.Command, printCurl *bool, timeoutSecs
cli.bindWaitFlag(cmd, 0, waitSecs)
}
-type serviceWithCurl struct {
- curlCmdWriter io.Writer
- bodyFile string
- service *vespa.Service
-}
-
-func (s *serviceWithCurl) Do(request *http.Request, timeout time.Duration) (*http.Response, error) {
- cmd, err := curl.RawArgs(request.URL.String())
- if err != nil {
- return nil, err
- }
- cmd.Method = request.Method
- for k, vs := range request.Header {
- for _, v := range vs {
- cmd.Header(k, v)
- }
- }
- if s.bodyFile != "" {
- cmd.WithBodyFile(s.bodyFile)
- }
- cmd.Certificate = s.service.TLSOptions.CertificateFile
- cmd.PrivateKey = s.service.TLSOptions.PrivateKeyFile
- out := cmd.String() + "\n"
- if _, err := io.WriteString(s.curlCmdWriter, out); err != nil {
- return nil, err
- }
- return s.service.Do(request, timeout)
-}
-
-func documentClient(cli *CLI, timeoutSecs, waitSecs int, printCurl bool) (*document.Client, *serviceWithCurl, error) {
+func documentClient(cli *CLI, timeoutSecs, waitSecs int, printCurl bool) (*document.Client, *vespa.Service, error) {
docService, err := documentService(cli, waitSecs)
if err != nil {
return nil, nil, err
}
- service := &serviceWithCurl{curlCmdWriter: io.Discard, service: docService}
if printCurl {
- service.curlCmdWriter = cli.Stderr
+ docService.CurlWriter = vespa.CurlWriter{Writer: cli.Stderr}
}
client, err := document.NewClient(document.ClientOptions{
Compression: document.CompressionAuto,
Timeout: time.Duration(timeoutSecs) * time.Second,
BaseURL: docService.BaseURL,
NowFunc: time.Now,
- }, []util.HTTPClient{service})
+ }, []util.HTTPClient{docService})
if err != nil {
return nil, nil, err
}
- return client, service, nil
+ return client, docService, nil
}
func sendOperation(op document.Operation, args []string, timeoutSecs, waitSecs int, printCurl bool, cli *CLI) error {
@@ -117,10 +84,10 @@ func sendOperation(op document.Operation, args []string, timeoutSecs, waitSecs i
doc.Operation = op
}
if doc.Body != nil {
- service.bodyFile = f.Name()
+ service.CurlWriter.InputFile = f.Name()
}
result := client.Send(doc)
- return printResult(cli, operationResult(false, doc, service.service, result), false)
+ return printResult(cli, operationResult(false, doc, service, result), false)
}
func readDocument(id string, timeoutSecs, waitSecs int, printCurl bool, cli *CLI) error {
@@ -133,7 +100,7 @@ func readDocument(id string, timeoutSecs, waitSecs int, printCurl bool, cli *CLI
return err
}
result := client.Get(docId)
- return printResult(cli, operationResult(true, document.Document{Id: docId}, service.service, result), true)
+ return printResult(cli, operationResult(true, document.Document{Id: docId}, service, result), true)
}
func operationResult(read bool, doc document.Document, service *vespa.Service, result document.Result) util.OperationResult {
@@ -262,7 +229,7 @@ $ vespa document remove id:mynamespace:music::a-head-full-of-dreams`,
}
doc := document.Document{Id: id, Operation: document.OperationRemove}
result := client.Send(doc)
- return printResult(cli, operationResult(false, doc, service.service, result), false)
+ return printResult(cli, operationResult(false, doc, service, result), false)
} else {
return sendOperation(document.OperationRemove, args, timeoutSecs, waitSecs, printCurl, cli)
}
diff --git a/client/go/internal/cli/cmd/document_test.go b/client/go/internal/cli/cmd/document_test.go
index 636059acfde..0b8d5a50615 100644
--- a/client/go/internal/cli/cmd/document_test.go
+++ b/client/go/internal/cli/cmd/document_test.go
@@ -129,7 +129,7 @@ func assertDocumentSend(args []string, expectedOperation string, expectedMethod
}
}
if verbose {
- expectedCurl := "curl -X " + expectedMethod + " -H 'Content-Type: application/json; charset=utf-8'"
+ expectedCurl := "curl -X " + expectedMethod + " -m 66 -H 'Content-Type: application/json; charset=utf-8'"
if expectedPayloadFile != "" {
expectedCurl += " --data-binary @" + expectedPayloadFile
}
diff --git a/client/go/internal/cli/cmd/log.go b/client/go/internal/cli/cmd/log.go
index e7178b74a56..77ef7f68130 100644
--- a/client/go/internal/cli/cmd/log.go
+++ b/client/go/internal/cli/cmd/log.go
@@ -34,7 +34,7 @@ $ vespa log --follow`,
SilenceUsage: true,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
- target, err := cli.target(targetOptions{logLevel: levelArg, cloudExclusive: true})
+ target, err := cli.target(targetOptions{logLevel: levelArg, supportedType: cloudTargetOnly})
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/login.go b/client/go/internal/cli/cmd/login.go
index b380e627203..3d63266afbc 100644
--- a/client/go/internal/cli/cmd/login.go
+++ b/client/go/internal/cli/cmd/login.go
@@ -31,7 +31,7 @@ This command runs a browser-based authentication flow for the Vespa Cloud contro
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
- targetType, err := cli.targetType(true)
+ targetType, err := cli.targetType(cloudTargetOnly)
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/logout.go b/client/go/internal/cli/cmd/logout.go
index 6cfa6f5b876..80c3d140816 100644
--- a/client/go/internal/cli/cmd/logout.go
+++ b/client/go/internal/cli/cmd/logout.go
@@ -15,7 +15,7 @@ func newLogoutCmd(cli *CLI) *cobra.Command {
DisableAutoGenTag: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
- targetType, err := cli.targetType(true)
+ targetType, err := cli.targetType(cloudTargetOnly)
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/prod.go b/client/go/internal/cli/cmd/prod.go
index 74e4b4c4a1c..d90d89437f9 100644
--- a/client/go/internal/cli/cmd/prod.go
+++ b/client/go/internal/cli/cmd/prod.go
@@ -53,7 +53,7 @@ https://cloud.vespa.ai/en/reference/deployment`,
DisableAutoGenTag: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
- target, err := cli.target(targetOptions{noCertificate: true, cloudExclusive: true})
+ target, err := cli.target(targetOptions{noCertificate: true, supportedType: cloudTargetOnly})
if err != nil {
return err
}
@@ -135,7 +135,7 @@ https://cloud.vespa.ai/en/reference/vespa-cloud-api#submission-properties
Example: `$ mvn package # when adding custom Java components
$ vespa prod deploy`,
RunE: func(cmd *cobra.Command, args []string) error {
- target, err := cli.target(targetOptions{noCertificate: true, cloudExclusive: true})
+ target, err := cli.target(targetOptions{noCertificate: true, supportedType: cloudTargetOnly})
if err != nil {
return err
}
diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go
index 004bdc038fe..864fe7d1961 100644
--- a/client/go/internal/cli/cmd/root.go
+++ b/client/go/internal/cli/cmd/root.go
@@ -33,6 +33,10 @@ const (
targetFlag = "target"
colorFlag = "color"
quietFlag = "quiet"
+
+ anyTarget = iota
+ localTargetOnly
+ cloudTargetOnly
)
// CLI holds the Vespa CLI command tree, configuration and dependencies.
@@ -74,8 +78,8 @@ type targetOptions struct {
logLevel string
// noCertificate declares that no client certificate should be required when using this target.
noCertificate bool
- // cloudExclusive specifies whether to only allow Vespa Cloud and Hosted Vespa targets
- cloudExclusive bool
+ // supportedType specifies what type of target to allow.
+ supportedType int
}
type targetType struct {
@@ -350,7 +354,7 @@ func (c *CLI) waiter(timeout time.Duration) *Waiter { return &Waiter{Timeout: ti
// target creates a target according the configuration of this CLI and given opts.
func (c *CLI) target(opts targetOptions) (vespa.Target, error) {
- targetType, err := c.targetType(opts.cloudExclusive)
+ targetType, err := c.targetType(opts.supportedType)
if err != nil {
return nil, err
}
@@ -375,7 +379,7 @@ func (c *CLI) target(opts targetOptions) (vespa.Target, error) {
}
// targetType resolves the real target type and its custom URL (if any)
-func (c *CLI) targetType(cloud bool) (targetType, error) {
+func (c *CLI) targetType(targetTypeRestriction int) (targetType, error) {
v, err := c.config.targetOrURL()
if err != nil {
return targetType{}, err
@@ -388,8 +392,10 @@ func (c *CLI) targetType(cloud bool) (targetType, error) {
return targetType{}, err
}
}
- if cloud && tt.name != vespa.TargetCloud && tt.name != vespa.TargetHosted {
- return targetType{}, fmt.Errorf("unsupported target %s: this command only supports targets %s and %s", tt.name, vespa.TargetCloud, vespa.TargetHosted)
+ unsupported := (targetTypeRestriction == cloudTargetOnly && tt.name != vespa.TargetCloud && tt.name != vespa.TargetHosted) ||
+ (targetTypeRestriction == localTargetOnly && tt.name != vespa.TargetLocal && tt.name != vespa.TargetCustom)
+ if unsupported {
+ return targetType{}, fmt.Errorf("command does not support %s target", tt.name)
}
return tt, nil
}
diff --git a/client/go/internal/cli/cmd/visit.go b/client/go/internal/cli/cmd/visit.go
index 2ca01764deb..f6e2f64e534 100644
--- a/client/go/internal/cli/cmd/visit.go
+++ b/client/go/internal/cli/cmd/visit.go
@@ -36,6 +36,7 @@ type visitArgs struct {
bucketSpace string
bucketSpaces []string
waitSecs int
+ verbose bool
cli *CLI
}
@@ -112,6 +113,9 @@ $ vespa visit --field-set "[id]" # list document IDs
if err != nil {
return err
}
+ if vArgs.verbose {
+ service.CurlWriter = vespa.CurlWriter{Writer: cli.Stderr}
+ }
result = probeHandler(service, cli)
if result.Success {
result = visitClusters(&vArgs, service)
@@ -136,6 +140,7 @@ $ vespa visit --field-set "[id]" # list document IDs
cmd.Flags().IntVar(&vArgs.sliceId, "slice-id", -1, `The number of the slice this visit invocation should fetch`)
cmd.Flags().IntVar(&vArgs.slices, "slices", -1, `Split the document corpus into this number of independent slices`)
cmd.Flags().StringSliceVar(&vArgs.bucketSpaces, "bucket-space", []string{"global", "default"}, `The "default" or "global" bucket space`)
+ cmd.Flags().BoolVarP(&vArgs.verbose, "verbose", "v", false, `Print the equivalent curl command for the visit operation`)
cli.bindWaitFlag(cmd, 0, &vArgs.waitSecs)
return cmd
}
diff --git a/client/go/internal/cli/cmd/waiter.go b/client/go/internal/cli/cmd/waiter.go
index 0cfb3aa76d5..d818359e61c 100644
--- a/client/go/internal/cli/cmd/waiter.go
+++ b/client/go/internal/cli/cmd/waiter.go
@@ -31,7 +31,7 @@ func (w *Waiter) DeployService(target vespa.Target) (*vespa.Service, error) {
// Service returns the service identified by cluster ID, available on target.
func (w *Waiter) Service(target vespa.Target, cluster string) (*vespa.Service, error) {
- targetType, err := w.cli.targetType(false)
+ targetType, err := w.cli.targetType(anyTarget)
if err != nil {
return nil, err
}
diff --git a/client/go/internal/curl/curl.go b/client/go/internal/curl/curl.go
index 3938938d2f3..e41f48840a2 100644
--- a/client/go/internal/curl/curl.go
+++ b/client/go/internal/curl/curl.go
@@ -7,9 +7,10 @@ import (
"os/exec"
"runtime"
"sort"
+ "strconv"
+ "time"
"github.com/alessio/shellescape"
- "github.com/vespa-engine/vespa/client/go/internal/util"
)
type header struct {
@@ -23,6 +24,7 @@ type Command struct {
PrivateKey string
CaCertificate string
Certificate string
+ Timeout time.Duration
bodyFile string
bodyInput io.Reader
url *url.URL
@@ -30,20 +32,20 @@ type Command struct {
rawArgs []string
}
-func (c *Command) GetUrlPrefix() string {
+func (c *Command) URLPrefix() string {
return c.url.Scheme + "://" + c.url.Host
}
func (c *Command) WithBodyFile(fn string) {
if c.bodyInput != nil {
- util.JustExitMsg("cannot use both WithBodyFile and WithBodyInput")
+ panic("cannot use both WithBodyFile and WithBodyInput")
}
c.bodyFile = fn
}
func (c *Command) WithBodyInput(r io.Reader) {
if c.bodyFile != "" {
- util.JustExitMsg("cannot use both WithBodyFile and WithBodyInput")
+ panic("cannot use both WithBodyFile and WithBodyInput")
}
c.bodyInput = r
}
@@ -62,6 +64,9 @@ func (c *Command) Args() []string {
if c.Method != "" {
args = append(args, "-X", c.Method)
}
+ if c.Timeout > 0 {
+ args = append(args, "-m", strconv.FormatInt(int64(c.Timeout.Seconds()), 10))
+ }
sort.Slice(c.headers, func(i, j int) bool { return c.headers[i].key < c.headers[j].key })
for _, header := range c.headers {
args = append(args, "-H", header.key+": "+header.value)
diff --git a/client/go/internal/curl/curl_test.go b/client/go/internal/curl/curl_test.go
index 61354c408d2..83bb7e72dbe 100644
--- a/client/go/internal/curl/curl_test.go
+++ b/client/go/internal/curl/curl_test.go
@@ -3,6 +3,7 @@ package curl
import (
"testing"
+ "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -13,10 +14,11 @@ func TestPost(t *testing.T) {
require.Nil(t, err)
c.PrivateKey = "key.pem"
c.Certificate = "cert.pem"
+ c.Timeout = time.Minute
c.WithBodyFile("file.json")
c.Header("Content-Type", "application/json")
- assert.Equal(t, "curl --key key.pem --cert cert.pem -X POST -H 'Content-Type: application/json' --data-binary @file.json https://example.com", c.String())
+ assert.Equal(t, "curl --key key.pem --cert cert.pem -X POST -m 60 -H 'Content-Type: application/json' --data-binary @file.json https://example.com", c.String())
}
func TestGet(t *testing.T) {
diff --git a/client/go/internal/vespa/deploy.go b/client/go/internal/vespa/deploy.go
index 4684e313291..7d90930d5f9 100644
--- a/client/go/internal/vespa/deploy.go
+++ b/client/go/internal/vespa/deploy.go
@@ -242,9 +242,6 @@ func fetchFilesFromConfigServer(deployment DeploymentOptions, contentURL *url.UR
// Prepare deployment and return the session ID
func Prepare(deployment DeploymentOptions) (PrepareResult, error) {
- if deployment.Target.IsCloud() {
- return PrepareResult{}, fmt.Errorf("prepare is not supported with %s target", deployment.Target.Type())
- }
sessionURL, err := deployment.url("/application/v2/tenant/default/session")
if err != nil {
return PrepareResult{}, err
@@ -290,9 +287,6 @@ func Prepare(deployment DeploymentOptions) (PrepareResult, error) {
// Activate deployment with sessionID from a past prepare
func Activate(sessionID int64, deployment DeploymentOptions) error {
- if deployment.Target.IsCloud() {
- return fmt.Errorf("activate is not supported with %s target", deployment.Target.Type())
- }
u, err := deployment.url(fmt.Sprintf("/application/v2/tenant/default/session/%d/active", sessionID))
if err != nil {
return err
diff --git a/client/go/internal/vespa/target.go b/client/go/internal/vespa/target.go
index f3a94f762ff..3a76eac0292 100644
--- a/client/go/internal/vespa/target.go
+++ b/client/go/internal/vespa/target.go
@@ -11,6 +11,7 @@ import (
"strings"
"time"
+ "github.com/vespa-engine/vespa/client/go/internal/curl"
"github.com/vespa-engine/vespa/client/go/internal/util"
"github.com/vespa-engine/vespa/client/go/internal/version"
)
@@ -43,11 +44,43 @@ type Authenticator interface {
Authenticate(request *http.Request) error
}
+// CurlWriter configures printing of Curl-equivalent commands for HTTP requests passing through a Service.
+type CurlWriter struct {
+ Writer io.Writer
+ InputFile string
+}
+
+func (c *CurlWriter) print(request *http.Request, tlsOptions TLSOptions, timeout time.Duration) error {
+ if c.Writer == nil {
+ return nil
+ }
+ cmd, err := curl.RawArgs(request.URL.String())
+ if err != nil {
+ return err
+ }
+ cmd.Method = request.Method
+ for k, vs := range request.Header {
+ for _, v := range vs {
+ cmd.Header(k, v)
+ }
+ }
+ cmd.CaCertificate = tlsOptions.CACertificateFile
+ cmd.Certificate = tlsOptions.CertificateFile
+ cmd.PrivateKey = tlsOptions.PrivateKeyFile
+ cmd.Timeout = timeout
+ if c.InputFile != "" {
+ cmd.WithBodyFile(c.InputFile)
+ }
+ _, err = fmt.Fprintln(c.Writer, cmd.String())
+ return err
+}
+
// Service represents a Vespa service.
type Service struct {
BaseURL string
Name string
TLSOptions TLSOptions
+ CurlWriter CurlWriter
deployAPI bool
auth Authenticator
@@ -117,6 +150,9 @@ func (s *Service) Do(request *http.Request, timeout time.Duration) (*http.Respon
return nil, fmt.Errorf("%w: %s", errAuth, err)
}
}
+ if err := s.CurlWriter.print(request, s.TLSOptions, timeout); err != nil {
+ return nil, err
+ }
return s.httpClient.Do(request, timeout)
}