Repository: vespa-engine/vespa (language: java)

Issue:
Support verbose flag to all commands in Vespa CLI
**Is your feature request related to a problem? Please describe.**
Sometimes it is not clear how to use the Vespa APIs, particularly, what parameters to supply and what format the values  should be, specific to your environment, to get a task done. On such occasions, Vespa CLI's `verbose flag (-v)` is quite handy to get the cURL equivalent of the CLI command, provided that the task and flag is supported in CLI. However, the verbose flag is not exposed to all the commands of Vespa. For example, Vespa Visit (with `--selection` and other possible parameters). 

**Describe the solution you'd like**
I would like this verbose flag to be exposed for all the commands of Vespa CLI.

**Describe alternatives you've considered**

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion / hints:
Some CLI commands are too complicated to meaningfully be translated to a curl equivalent. I'll look into adding this for commands where it makes sense, such as `vespa visit`.

Fix patch:
diff --git a/client/go/internal/admin/deploy/curl.go b/client/go/internal/admin/deploy/curl.go
index ca044128e939..0ce1305226fd 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 ef04532314c8..133c9db0d3b6 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 9668e78bd1c2..6aa99a01902a 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/deploy.go b/client/go/internal/cli/cmd/deploy.go
index dd605237b5f0..c9331a540eb4 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 f4822330e05b..a7beff2e4b4e 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/document.go b/client/go/internal/cli/cmd/document.go
index a98e9867d34e..6892956880bd 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/log.go b/client/go/internal/cli/cmd/log.go
index e7178b74a56c..77ef7f681306 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 b380e627203c..3d63266afbce 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 6cfa6f5b8761..80c3d1408166 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 74e4b4c4a1c2..d90d89437f9f 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 004bdc038fec..864fe7d19611 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 2ca01764deba..f6e2f64e5344 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 0cfb3aa76d5d..d818359e61ca 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 3938938d2f35..e41f48840a29 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/vespa/deploy.go b/client/go/internal/vespa/deploy.go
index 4684e313291a..7d90930d5f92 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 f3a94f762ff5..3a76eac02929 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)
 }
 


Test patch:
diff --git a/client/go/internal/cli/cmd/config_test.go b/client/go/internal/cli/cmd/config_test.go
index 64d7d91fef1f..b13f8365f5fb 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/destroy_test.go b/client/go/internal/cli/cmd/destroy_test.go
index bbba593cc5d0..c62a54e59252 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_test.go b/client/go/internal/cli/cmd/document_test.go
index 636059acfde5..0b8d5a506156 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/curl/curl_test.go b/client/go/internal/curl/curl_test.go
index 61354c408d28..83bb7e72dbe8 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) {