diff options
Diffstat (limited to 'client/go/internal/cli/cmd/document.go')
-rw-r--r-- | client/go/internal/cli/cmd/document.go | 190 |
1 files changed, 145 insertions, 45 deletions
diff --git a/client/go/internal/cli/cmd/document.go b/client/go/internal/cli/cmd/document.go index b5b63fd32df..07a98d2e626 100644 --- a/client/go/internal/cli/cmd/document.go +++ b/client/go/internal/cli/cmd/document.go @@ -5,15 +5,22 @@ package cmd import ( + "bytes" + "errors" "fmt" "io" + "net/http" + "os" + "strconv" "strings" "time" "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" ) func addDocumentFlags(cmd *cobra.Command, printCurl *bool, timeoutSecs *int) { @@ -21,6 +28,128 @@ func addDocumentFlags(cmd *cobra.Command, printCurl *bool, timeoutSecs *int) { cmd.PersistentFlags().IntVarP(timeoutSecs, "timeout", "T", 60, "Timeout for the document request in seconds") } +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 int, printCurl bool) (*document.Client, *serviceWithCurl, error) { + docService, err := documentService(cli) + if err != nil { + return nil, nil, err + } + service := &serviceWithCurl{curlCmdWriter: io.Discard, service: docService} + if printCurl { + service.curlCmdWriter = 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}) + if err != nil { + return nil, nil, err + } + return client, service, nil +} + +func sendOperation(op document.Operation, args []string, timeoutSecs int, printCurl bool, cli *CLI) error { + client, service, err := documentClient(cli, timeoutSecs, printCurl) + if err != nil { + return err + } + id := "" + filename := args[0] + if len(args) > 1 { + id = args[0] + filename = args[1] + } + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + doc, err := document.NewDecoder(f).Decode() + if errors.Is(err, document.ErrMissingId) { + if id == "" { + return fmt.Errorf("no document id given neither as argument or as a 'put', 'update' or 'remove' key in the JSON file") + } + } else if err != nil { + return err + } + if id != "" { + docId, err := document.ParseId(id) + if err != nil { + return err + } + doc.Id = docId + } + if op > -1 { + if id == "" && op != doc.Operation { + return fmt.Errorf("wanted document operation is %s, but JSON file specifies %s", op, doc.Operation) + } + doc.Operation = op + } + if doc.Body != nil { + service.bodyFile = f.Name() + } + result := client.Send(doc) + return printResult(cli, operationResult(false, doc, service.service, result), false) +} + +func readDocument(id string, timeoutSecs int, printCurl bool, cli *CLI) error { + client, service, err := documentClient(cli, timeoutSecs, printCurl) + if err != nil { + return err + } + docId, err := document.ParseId(id) + if err != nil { + return err + } + result := client.Get(docId) + return printResult(cli, operationResult(true, document.Document{Id: docId}, service.service, result), true) +} + +func operationResult(read bool, doc document.Document, service *vespa.Service, result document.Result) util.OperationResult { + bodyReader := bytes.NewReader(result.Body) + if result.HTTPStatus == 200 { + if read { + return util.SuccessWithPayload("Read "+doc.Id.String(), util.ReaderToJSON(bodyReader)) + } else { + return util.Success(doc.Operation.String() + " " + doc.Id.String()) + } + } + if result.HTTPStatus/100 == 4 { + return util.FailureWithPayload("Invalid document operation: Status "+strconv.Itoa(result.HTTPStatus), util.ReaderToJSON(bodyReader)) + } + return util.FailureWithPayload(service.Description()+" at "+service.BaseURL+": Status "+strconv.Itoa(result.HTTPStatus), util.ReaderToJSON(bodyReader)) +} + func newDocumentCmd(cli *CLI) *cobra.Command { var ( printCurl bool @@ -44,11 +173,7 @@ should be used instead of this.`, SilenceUsage: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - service, err := documentService(cli) - if err != nil { - return err - } - return printResult(cli, vespa.Send(args[0], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), false) + return sendOperation(-1, args, timeoutSecs, printCurl, cli) }, } addDocumentFlags(cmd, &printCurl, &timeoutSecs) @@ -72,15 +197,7 @@ $ vespa document put id:mynamespace:music::a-head-full-of-dreams src/test/resour DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - service, err := documentService(cli) - if err != nil { - return err - } - if len(args) == 1 { - return printResult(cli, vespa.Put("", args[0], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), false) - } else { - return printResult(cli, vespa.Put(args[0], args[1], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), false) - } + return sendOperation(document.OperationPut, args, timeoutSecs, printCurl, cli) }, } addDocumentFlags(cmd, &printCurl, &timeoutSecs) @@ -103,15 +220,7 @@ $ vespa document update id:mynamespace:music::a-head-full-of-dreams src/test/res DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - service, err := documentService(cli) - if err != nil { - return err - } - if len(args) == 1 { - return printResult(cli, vespa.Update("", args[0], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), false) - } else { - return printResult(cli, vespa.Update(args[0], args[1], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), false) - } + return sendOperation(document.OperationUpdate, args, timeoutSecs, printCurl, cli) }, } addDocumentFlags(cmd, &printCurl, &timeoutSecs) @@ -134,14 +243,20 @@ $ vespa document remove id:mynamespace:music::a-head-full-of-dreams`, DisableAutoGenTag: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - service, err := documentService(cli) - if err != nil { - return err - } if strings.HasPrefix(args[0], "id:") { - return printResult(cli, vespa.RemoveId(args[0], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), false) + client, service, err := documentClient(cli, timeoutSecs, printCurl) + if err != nil { + return err + } + id, err := document.ParseId(args[0]) + if err != nil { + return err + } + doc := document.Document{Id: id, Operation: document.OperationRemove} + result := client.Send(doc) + return printResult(cli, operationResult(false, doc, service.service, result), false) } else { - return printResult(cli, vespa.RemoveOperation(args[0], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), false) + return sendOperation(document.OperationRemove, args, timeoutSecs, printCurl, cli) } }, } @@ -162,11 +277,7 @@ func newDocumentGetCmd(cli *CLI) *cobra.Command { SilenceUsage: true, Example: `$ vespa document get id:mynamespace:music::a-head-full-of-dreams`, RunE: func(cmd *cobra.Command, args []string) error { - service, err := documentService(cli) - if err != nil { - return err - } - return printResult(cli, vespa.Get(args[0], service, operationOptions(cli.Stderr, printCurl, timeoutSecs)), true) + return readDocument(args[0], timeoutSecs, printCurl, cli) }, } addDocumentFlags(cmd, &printCurl, &timeoutSecs) @@ -181,17 +292,6 @@ func documentService(cli *CLI) (*vespa.Service, error) { return cli.service(target, vespa.DocumentService, 0, cli.config.cluster()) } -func operationOptions(stderr io.Writer, printCurl bool, timeoutSecs int) vespa.OperationOptions { - curlOutput := io.Discard - if printCurl { - curlOutput = stderr - } - return vespa.OperationOptions{ - CurlOutput: curlOutput, - Timeout: time.Second * time.Duration(timeoutSecs), - } -} - func printResult(cli *CLI, result util.OperationResult, payloadOnlyOnSuccess bool) error { out := cli.Stdout if !result.Success { |