c349e97168
/cc @armon - This is a reasonably major refactor that I think cleans up a lot of the logic with secrets in responses. The reason for the refactor is that while implementing Renew/Revoke in logical/framework I found the existing API to be really awkward to work with. Primarily, we needed a way to send down internal data for Vault core to store since not all the data you need to revoke a key is always sent down to the user (for example the user than AWS key belongs to). At first, I was doing this manually in logical/framework with req.Storage, but this is going to be such a common event that I think its something core should assist with. Additionally, I think the added context for secrets will be useful in the future when we have a Vault API for returning orphaned out keys: we can also return the internal data that might help an operator. So this leads me to this refactor. I've removed most of the fields in `logical.Response` and replaced it with a single `*Secret` pointer. If this is non-nil, then the response represents a secret. The Secret struct encapsulates all the lease info and such. It also has some fields on it that are only populated at _request_ time for Revoke/Renew operations. There is precedent for this sort of behavior in the Go stdlib where http.Request/http.Response have fields that differ based on client/server. I copied this style. All core unit tests pass. The APIs fail for obvious reasons but I'll fix that up in the next commit.
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
package framework
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
"text/template"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
"github.com/mitchellh/go-wordwrap"
|
|
)
|
|
|
|
// Path is a single path that the backend responds to.
|
|
type Path struct {
|
|
// Pattern is the pattern of the URL that matches this path.
|
|
//
|
|
// This should be a valid regular expression. Named captures will be
|
|
// exposed as fields that should map to a schema in Fields. If a named
|
|
// capture is not a field in the Fields map, then it will be ignored.
|
|
Pattern string
|
|
|
|
// Fields is the mapping of data fields to a schema describing that
|
|
// field. Named captures in the Pattern also map to fields. If a named
|
|
// capture name matches a PUT body name, the named capture takes
|
|
// priority.
|
|
//
|
|
// Note that only named capture fields are available in every operation,
|
|
// whereas all fields are avaiable in the Write operation.
|
|
Fields map[string]*FieldSchema
|
|
|
|
// Callbacks are the set of callbacks that are called for a given
|
|
// operation. If a callback for a specific operation is not present,
|
|
// then logical.ErrUnsupportedOperation is automatically generated.
|
|
//
|
|
// The help operation is the only operation that the Path will
|
|
// automatically handle if the Help field is set. If both the Help
|
|
// field is set and there is a callback registered here, then the
|
|
// callback will be called.
|
|
Callbacks map[logical.Operation]OperationFunc
|
|
|
|
// Help is text describing how to use this path. This will be used
|
|
// to auto-generate the help operation. The Path will automatically
|
|
// generate a parameter listing and URL structure based on the
|
|
// regular expression, so the help text should just contain a description
|
|
// of what happens.
|
|
//
|
|
// HelpSynopsis is a one-sentence description of the path. This will
|
|
// be automatically line-wrapped at 80 characters.
|
|
//
|
|
// HelpDescription is a long-form description of the path. This will
|
|
// be automatically line-wrapped at 80 characters.
|
|
HelpSynopsis string
|
|
HelpDescription string
|
|
}
|
|
|
|
func (p *Path) helpCallback(
|
|
req *logical.Request, data *FieldData) (*logical.Response, error) {
|
|
var tplData pathTemplateData
|
|
tplData.Request = req.Path
|
|
tplData.RoutePattern = p.Pattern
|
|
tplData.Synopsis = wordwrap.WrapString(p.HelpSynopsis, 80)
|
|
tplData.Description = wordwrap.WrapString(p.HelpDescription, 80)
|
|
|
|
// Alphabetize the fields
|
|
fieldKeys := make([]string, 0, len(p.Fields))
|
|
for k, _ := range p.Fields {
|
|
fieldKeys = append(fieldKeys, k)
|
|
}
|
|
sort.Strings(fieldKeys)
|
|
|
|
// Build the field help
|
|
tplData.Fields = make([]pathTemplateFieldData, len(fieldKeys))
|
|
for i, k := range fieldKeys {
|
|
schema := p.Fields[k]
|
|
description := wordwrap.WrapString(schema.Description, 60)
|
|
if description == "" {
|
|
description = "<no description>"
|
|
}
|
|
|
|
tplData.Fields[i] = pathTemplateFieldData{
|
|
Key: k,
|
|
Type: schema.Type.String(),
|
|
Description: description,
|
|
}
|
|
}
|
|
|
|
// Parse the help template
|
|
tpl, err := template.New("root").Parse(pathHelpTemplate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing template: %s", err)
|
|
}
|
|
|
|
// Execute the template and store the output
|
|
var buf bytes.Buffer
|
|
if err := tpl.Execute(&buf, &tplData); err != nil {
|
|
return nil, fmt.Errorf("error executing template: %s", err)
|
|
}
|
|
|
|
return logical.HelpResponse(buf.String(), nil), nil
|
|
}
|
|
|
|
type pathTemplateData struct {
|
|
Request string
|
|
RoutePattern string
|
|
Synopsis string
|
|
Description string
|
|
Fields []pathTemplateFieldData
|
|
}
|
|
|
|
type pathTemplateFieldData struct {
|
|
Key string
|
|
Type string
|
|
Description string
|
|
URL bool
|
|
}
|
|
|
|
const pathHelpTemplate = `
|
|
Request: {{.Request}}
|
|
Matching Route: {{.RoutePattern}}
|
|
|
|
{{.Synopsis}}
|
|
|
|
## Parameters
|
|
|
|
{{range .Fields}}
|
|
### {{.Key}} (type: {{.Type}})
|
|
|
|
{{.Description}}
|
|
|
|
{{end}}
|
|
## Description
|
|
|
|
{{.Description}}
|
|
`
|