diff --git a/api/logical.go b/api/logical.go index 747b9bc12c..d2e5bb5e5e 100644 --- a/api/logical.go +++ b/api/logical.go @@ -65,23 +65,7 @@ func (c *Logical) ReadWithDataWithContext(ctx context.Context, path string, data ctx, cancelFunc := c.c.withConfiguredTimeout(ctx) defer cancelFunc() - r := c.c.NewRequest(http.MethodGet, "/v1/"+path) - - var values url.Values - for k, v := range data { - if values == nil { - values = make(url.Values) - } - for _, val := range v { - values.Add(k, val) - } - } - - if values != nil { - r.Params = values - } - - resp, err := c.c.rawRequestWithContext(ctx, r) + resp, err := c.readRawWithDataWithContext(ctx, path, data) if resp != nil { defer resp.Body.Close() } @@ -106,6 +90,41 @@ func (c *Logical) ReadWithDataWithContext(ctx context.Context, path string, data return ParseSecret(resp.Body) } +func (c *Logical) ReadRaw(path string) (*Response, error) { + return c.ReadRawWithData(path, nil) +} + +func (c *Logical) ReadRawWithData(path string, data map[string][]string) (*Response, error) { + return c.ReadRawWithDataWithContext(context.Background(), path, data) +} + +func (c *Logical) ReadRawWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*Response, error) { + ctx, cancelFunc := c.c.withConfiguredTimeout(ctx) + defer cancelFunc() + + return c.readRawWithDataWithContext(ctx, path, data) +} + +func (c *Logical) readRawWithDataWithContext(ctx context.Context, path string, data map[string][]string) (*Response, error) { + r := c.c.NewRequest(http.MethodGet, "/v1/"+path) + + var values url.Values + for k, v := range data { + if values == nil { + values = make(url.Values) + } + for _, val := range v { + values.Add(k, val) + } + } + + if values != nil { + r.Params = values + } + + return c.c.RawRequestWithContext(ctx, r) +} + func (c *Logical) List(path string) (*Secret, error) { return c.ListWithContext(context.Background(), path) } diff --git a/changelog/14945.txt b/changelog/14945.txt new file mode 100644 index 0000000000..50dea7159f --- /dev/null +++ b/changelog/14945.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Support the -format=raw option, to read non-JSON Vault endpoints and original response bodies. +``` diff --git a/command/format.go b/command/format.go index 4dffed6463..accc41405a 100644 --- a/command/format.go +++ b/command/format.go @@ -70,6 +70,7 @@ var Formatters = map[string]Formatter{ "yaml": YamlFormatter{}, "yml": YamlFormatter{}, "pretty": PrettyFormatter{}, + "raw": RawFormatter{}, } func Format(ui cli.Ui) string { @@ -125,6 +126,27 @@ func (j JsonFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) e return nil } +// An output formatter for raw output of the original request object +type RawFormatter struct{} + +func (r RawFormatter) Format(data interface{}) ([]byte, error) { + byte_data, ok := data.([]byte) + if !ok { + return nil, fmt.Errorf("unable to type assert to []byte: %T; please share the command run", data) + } + + return byte_data, nil +} + +func (r RawFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) error { + b, err := r.Format(data) + if err != nil { + return err + } + ui.Output(string(b)) + return nil +} + // An output formatter for yaml output format of an object type YamlFormatter struct{} diff --git a/command/read.go b/command/read.go index b12eb3f60a..9faf2f4022 100644 --- a/command/read.go +++ b/command/read.go @@ -91,19 +91,40 @@ func (c *ReadCommand) Run(args []string) int { return 1 } - secret, err := client.Logical().ReadWithData(path, data) + if Format(c.UI) != "raw" { + secret, err := client.Logical().ReadWithData(path, data) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err)) + return 2 + } + if secret == nil { + c.UI.Error(fmt.Sprintf("No value found at %s", path)) + return 2 + } + + if c.flagField != "" { + return PrintRawField(c.UI, secret, c.flagField) + } + + return OutputSecret(c.UI, secret) + } + + resp, err := client.Logical().ReadRawWithData(path, data) if err != nil { - c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err)) + c.UI.Error(fmt.Sprintf("Error reading: %s: %s", path, err)) return 2 } - if secret == nil { + if resp == nil || resp.Body == nil { c.UI.Error(fmt.Sprintf("No value found at %s", path)) return 2 } + defer resp.Body.Close() - if c.flagField != "" { - return PrintRawField(c.UI, secret, c.flagField) + contents, err := io.ReadAll(resp.Body) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading: %s: %s", path, err)) + return 2 } - return OutputSecret(c.UI, secret) + return OutputData(c.UI, contents) }