diff --git a/builtin/logical/aws/backend.go b/builtin/logical/aws/backend.go index 850a8036e8..b206c1beb0 100644 --- a/builtin/logical/aws/backend.go +++ b/builtin/logical/aws/backend.go @@ -1,6 +1,7 @@ package aws import ( + "strings" "time" "github.com/hashicorp/vault/logical" @@ -14,6 +15,8 @@ func Factory(map[string]string) (logical.Backend, error) { func Backend() *framework.Backend { var b backend b.Backend = &framework.Backend{ + Help: strings.TrimSpace(backendHelp), + PathsSpecial: &logical.Paths{ Root: []string{ "root", @@ -41,3 +44,9 @@ func Backend() *framework.Backend { type backend struct { *framework.Backend } + +const backendHelp = ` +The AWS backend dynamically generates AWS access keys for a set of +IAM policies. The AWS access keys have a configurable lease set and +are automatically revoked at the end of the lease. +` diff --git a/logical/framework/backend.go b/logical/framework/backend.go index 9bcdd6f811..90311bb307 100644 --- a/logical/framework/backend.go +++ b/logical/framework/backend.go @@ -16,6 +16,11 @@ import ( // // This is recommended over implementing logical.Backend directly. type Backend struct { + // Help is the help text that is shown when a help request is made + // on the root of this resource. The root help is special since we + // show all the paths that can be requested. + Help string + // Paths are the various routes that the backend responds to. // This cannot be modified after construction (i.e. dynamically changing // paths, including adding or removing, is not allowed once the @@ -54,6 +59,8 @@ type RollbackFunc func(*logical.Request, string, interface{}) error // logical.Backend impl. func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) { + b.once.Do(b.init) + // Check for special cased global operations. These don't route // to a specific Path. switch req.Operation { @@ -65,6 +72,11 @@ func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) return b.handleRollback(req) } + // If the path is empty and it is a help operation, handle that. + if req.Path == "" && req.Operation == logical.HelpOperation { + return b.handleRootHelp() + } + // Find the matching route path, captures := b.route(req.Path) if path == nil { @@ -171,6 +183,23 @@ func (b *Backend) route(path string) (*Path, map[string]string) { return nil, nil } +func (b *Backend) handleRootHelp() (*logical.Response, error) { + paths := make([]string, 0, len(b.Paths)) + for _, p := range b.pathsRe { + paths = append(paths, p.String()) + } + + help, err := executeTemplate(rootHelpTemplate, &rootHelpTemplateData{ + Help: b.Help, + Paths: paths, + }) + if err != nil { + return nil, err + } + + return logical.HelpResponse(help, nil), nil +} + func (b *Backend) handleRevokeRenew( req *logical.Request) (*logical.Response, error) { if req.Secret == nil { @@ -295,3 +324,24 @@ func (t FieldType) Zero() interface{} { panic("unknown type: " + t.String()) } } + +type rootHelpTemplateData struct { + Help string + Paths []string +} + +const rootHelpTemplate = ` +## DESCRIPTION + +{{.Help}} + +## PATHS + +The following paths are supported by this backend. To view help for +any of the paths below, use the help command with any route matching +the path pattern. + +{{range .Paths}} {{.}} +{{end}} + +` diff --git a/logical/framework/backend_test.go b/logical/framework/backend_test.go index 291292ee67..7af03ee52e 100644 --- a/logical/framework/backend_test.go +++ b/logical/framework/backend_test.go @@ -137,6 +137,23 @@ func TestBackendHandleRequest_help(t *testing.T) { } } +func TestBackendHandleRequest_helpRoot(t *testing.T) { + b := &Backend{ + Help: "42", + } + + resp, err := b.HandleRequest(&logical.Request{ + Operation: logical.HelpOperation, + Path: "", + }) + if err != nil { + t.Fatalf("err: %s", err) + } + if resp.Data["help"] == nil { + t.Fatalf("bad: %#v", resp) + } +} + func TestBackendHandleRequest_renew(t *testing.T) { var called uint32 callback := func(*logical.Request, *FieldData) (*logical.Response, error) { diff --git a/logical/framework/template.go b/logical/framework/template.go new file mode 100644 index 0000000000..b60e5129cc --- /dev/null +++ b/logical/framework/template.go @@ -0,0 +1,24 @@ +package framework + +import ( + "bytes" + "fmt" + "strings" + "text/template" +) + +func executeTemplate(tpl string, data interface{}) (string, error) { + // Parse the help template + t, err := template.New("root").Parse(tpl) + if err != nil { + return "", fmt.Errorf("error parsing template: %s", err) + } + + // Execute the template and store the output + var buf bytes.Buffer + if err := t.Execute(&buf, data); err != nil { + return "", fmt.Errorf("error executing template: %s", err) + } + + return strings.TrimSpace(buf.String()), nil +}