1package framework 2 3import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/hashicorp/vault/logical" 9) 10 11// Helper which returns a generic regex string for creating endpoint patterns 12// that are identified by the given name in the backends 13func GenericNameRegex(name string) string { 14 return fmt.Sprintf("(?P<%s>\\w[\\w-.]+\\w)", name) 15} 16 17// Helper which returns a regex string for optionally accepting the a field 18// from the API URL 19func OptionalParamRegex(name string) string { 20 return fmt.Sprintf("(/(?P<%s>.+))?", name) 21} 22 23// PathAppend is a helper for appending lists of paths into a single 24// list. 25func PathAppend(paths ...[]*Path) []*Path { 26 result := make([]*Path, 0, 10) 27 for _, ps := range paths { 28 result = append(result, ps...) 29 } 30 31 return result 32} 33 34// Path is a single path that the backend responds to. 35type Path struct { 36 // Pattern is the pattern of the URL that matches this path. 37 // 38 // This should be a valid regular expression. Named captures will be 39 // exposed as fields that should map to a schema in Fields. If a named 40 // capture is not a field in the Fields map, then it will be ignored. 41 Pattern string 42 43 // Fields is the mapping of data fields to a schema describing that 44 // field. Named captures in the Pattern also map to fields. If a named 45 // capture name matches a PUT body name, the named capture takes 46 // priority. 47 // 48 // Note that only named capture fields are available in every operation, 49 // whereas all fields are available in the Write operation. 50 Fields map[string]*FieldSchema 51 52 // Callbacks are the set of callbacks that are called for a given 53 // operation. If a callback for a specific operation is not present, 54 // then logical.ErrUnsupportedOperation is automatically generated. 55 // 56 // The help operation is the only operation that the Path will 57 // automatically handle if the Help field is set. If both the Help 58 // field is set and there is a callback registered here, then the 59 // callback will be called. 60 Callbacks map[logical.Operation]OperationFunc 61 62 // ExistenceCheck, if implemented, is used to query whether a given 63 // resource exists or not. This is used for ACL purposes: if an Update 64 // action is specified, and the existence check returns false, the action 65 // is not allowed since the resource must first be created. The reverse is 66 // also true. If not specified, the Update action is forced and the user 67 // must have UpdateCapability on the path. 68 ExistenceCheck func(*logical.Request, *FieldData) (bool, error) 69 70 // Help is text describing how to use this path. This will be used 71 // to auto-generate the help operation. The Path will automatically 72 // generate a parameter listing and URL structure based on the 73 // regular expression, so the help text should just contain a description 74 // of what happens. 75 // 76 // HelpSynopsis is a one-sentence description of the path. This will 77 // be automatically line-wrapped at 80 characters. 78 // 79 // HelpDescription is a long-form description of the path. This will 80 // be automatically line-wrapped at 80 characters. 81 HelpSynopsis string 82 HelpDescription string 83} 84 85func (p *Path) helpCallback( 86 req *logical.Request, data *FieldData) (*logical.Response, error) { 87 var tplData pathTemplateData 88 tplData.Request = req.Path 89 tplData.RoutePattern = p.Pattern 90 tplData.Synopsis = strings.TrimSpace(p.HelpSynopsis) 91 if tplData.Synopsis == "" { 92 tplData.Synopsis = "<no synopsis>" 93 } 94 tplData.Description = strings.TrimSpace(p.HelpDescription) 95 if tplData.Description == "" { 96 tplData.Description = "<no description>" 97 } 98 99 // Alphabetize the fields 100 fieldKeys := make([]string, 0, len(p.Fields)) 101 for k, _ := range p.Fields { 102 fieldKeys = append(fieldKeys, k) 103 } 104 sort.Strings(fieldKeys) 105 106 // Build the field help 107 tplData.Fields = make([]pathTemplateFieldData, len(fieldKeys)) 108 for i, k := range fieldKeys { 109 schema := p.Fields[k] 110 description := strings.TrimSpace(schema.Description) 111 if description == "" { 112 description = "<no description>" 113 } 114 115 tplData.Fields[i] = pathTemplateFieldData{ 116 Key: k, 117 Type: schema.Type.String(), 118 Description: description, 119 } 120 } 121 122 help, err := executeTemplate(pathHelpTemplate, &tplData) 123 if err != nil { 124 return nil, fmt.Errorf("error executing template: %s", err) 125 } 126 127 return logical.HelpResponse(help, nil), nil 128} 129 130type pathTemplateData struct { 131 Request string 132 RoutePattern string 133 Synopsis string 134 Description string 135 Fields []pathTemplateFieldData 136} 137 138type pathTemplateFieldData struct { 139 Key string 140 Type string 141 Description string 142 URL bool 143} 144 145const pathHelpTemplate = ` 146Request: {{.Request}} 147Matching Route: {{.RoutePattern}} 148 149{{.Synopsis}} 150 151{{ if .Fields -}} 152## PARAMETERS 153{{range .Fields}} 154{{indent 4 .Key}} ({{.Type}}) 155{{indent 8 .Description}} 156{{end}}{{end}} 157## DESCRIPTION 158 159{{.Description}} 160` 161