mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
OpenAPI: Define default response structure for ListOperations (#21934)
* OpenAPI: Define default response structure for ListOperations Almost all Vault ListOperation responses have an identical response schema. Update the OpenAPI generator to know this, and remove a few instances where that standard response schema had been manually copy/pasted into place in individual endpoints. * changelog * Only render StandardListResponse schema, if an operation uses it * Teach the response schema validation test helper about the default list schema too --------- Co-authored-by: Anton Averchenkov <84287187+averche@users.noreply.github.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/vault/helper/parseip"
|
"github.com/hashicorp/vault/helper/parseip"
|
||||||
"github.com/hashicorp/vault/sdk/framework"
|
"github.com/hashicorp/vault/sdk/framework"
|
||||||
"github.com/hashicorp/vault/sdk/helper/cidrutil"
|
"github.com/hashicorp/vault/sdk/helper/cidrutil"
|
||||||
@@ -311,17 +311,6 @@ can only be set during role creation and once set, it can't be reset later.`,
|
|||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
logical.ListOperation: &framework.PathOperation{
|
logical.ListOperation: &framework.PathOperation{
|
||||||
Callback: b.pathRoleList,
|
Callback: b.pathRoleList,
|
||||||
Responses: map[int][]framework.Response{
|
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HelpSynopsis: strings.TrimSpace(roleHelp["role-list"][0]),
|
HelpSynopsis: strings.TrimSpace(roleHelp["role-list"][0]),
|
||||||
@@ -984,17 +973,6 @@ Overrides secret_id_ttl role option when supplied. May not be longer than role's
|
|||||||
DisplayAttrs: &framework.DisplayAttributes{
|
DisplayAttrs: &framework.DisplayAttributes{
|
||||||
OperationSuffix: "secret-ids",
|
OperationSuffix: "secret-ids",
|
||||||
},
|
},
|
||||||
Responses: map[int][]framework.Response{
|
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Required: true,
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HelpSynopsis: strings.TrimSpace(roleHelp["role-secret-id"][0]),
|
HelpSynopsis: strings.TrimSpace(roleHelp["role-secret-id"][0]),
|
||||||
|
|||||||
@@ -239,18 +239,6 @@ func pathFetchListCerts(b *backend) *framework.Path {
|
|||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
logical.ListOperation: &framework.PathOperation{
|
logical.ListOperation: &framework.PathOperation{
|
||||||
Callback: b.pathFetchCertList,
|
Callback: b.pathFetchCertList,
|
||||||
Responses: map[int][]framework.Response{
|
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
Description: `A list of keys`,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -37,18 +37,6 @@ func pathListCertsRevoked(b *backend) *framework.Path {
|
|||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
logical.ListOperation: &framework.PathOperation{
|
logical.ListOperation: &framework.PathOperation{
|
||||||
Callback: b.pathListRevokedCertsHandler,
|
Callback: b.pathListRevokedCertsHandler,
|
||||||
Responses: map[int][]framework.Response{
|
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
Description: `List of Keys`,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -32,18 +32,6 @@ func pathListRoles(b *backend) *framework.Path {
|
|||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
logical.ListOperation: &framework.PathOperation{
|
logical.ListOperation: &framework.PathOperation{
|
||||||
Callback: b.pathRoleList,
|
Callback: b.pathRoleList,
|
||||||
Responses: map[int][]framework.Response{
|
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
Description: "List of roles",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
3
changelog/21934.txt
Normal file
3
changelog/21934.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
openapi: Fix response definitions for list operations
|
||||||
|
```
|
||||||
@@ -197,6 +197,29 @@ var OASStdRespNoContent = &OASResponse{
|
|||||||
Description: "empty body",
|
Description: "empty body",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var OASStdRespListOK = &OASResponse{
|
||||||
|
Description: "OK",
|
||||||
|
Content: OASContent{
|
||||||
|
"application/json": &OASMediaTypeObject{
|
||||||
|
Schema: &OASSchema{
|
||||||
|
Ref: "#/components/schemas/StandardListResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var OASStdSchemaStandardListResponse = &OASSchema{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]*OASSchema{
|
||||||
|
"keys": {
|
||||||
|
Type: "array",
|
||||||
|
Items: &OASSchema{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Regex for handling fields in paths, and string cleanup.
|
// Regex for handling fields in paths, and string cleanup.
|
||||||
// Predefined here to avoid substantial recompilation.
|
// Predefined here to avoid substantial recompilation.
|
||||||
|
|
||||||
@@ -456,6 +479,9 @@ func documentPath(p *Path, backend *Backend, requestResponsePrefix string, doc *
|
|||||||
if len(props.Responses) == 0 {
|
if len(props.Responses) == 0 {
|
||||||
if opType == logical.DeleteOperation {
|
if opType == logical.DeleteOperation {
|
||||||
op.Responses[204] = OASStdRespNoContent
|
op.Responses[204] = OASStdRespNoContent
|
||||||
|
} else if opType == logical.ListOperation {
|
||||||
|
op.Responses[200] = OASStdRespListOK
|
||||||
|
doc.Components.Schemas["StandardListResponse"] = OASStdSchemaStandardListResponse
|
||||||
} else {
|
} else {
|
||||||
op.Responses[200] = OASStdRespOK
|
op.Responses[200] = OASStdRespOK
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,18 @@ func GetResponseSchema(t *testing.T, path *framework.Path, operation logical.Ope
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(schemaResponses) == 0 {
|
if len(schemaResponses) == 0 {
|
||||||
|
// ListOperations have a default response schema that is implicit unless overridden
|
||||||
|
if operation == logical.ListOperation {
|
||||||
|
return &framework.Response{
|
||||||
|
Description: "OK",
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"keys": {
|
||||||
|
Type: framework.TypeStringSlice,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"could not find response schema: %s: %q operation: no responses found",
|
"could not find response schema: %s: %q operation: no responses found",
|
||||||
path.Pattern,
|
path.Pattern,
|
||||||
|
|||||||
@@ -377,17 +377,6 @@ func rawPaths(prefix string, r *RawBackend) []*framework.Path {
|
|||||||
OperationPrefix: "raw",
|
OperationPrefix: "raw",
|
||||||
OperationVerb: "list",
|
OperationVerb: "list",
|
||||||
},
|
},
|
||||||
Responses: map[int][]framework.Response{
|
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
Summary: "Return a list keys for a given path prefix.",
|
Summary: "Return a list keys for a given path prefix.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4679,6 +4679,14 @@ func (b *SystemBackend) pathInternalOpenAPI(ctx context.Context, req *logical.Re
|
|||||||
|
|
||||||
doc.CreateOperationIDs(context)
|
doc.CreateOperationIDs(context)
|
||||||
|
|
||||||
|
// Every backend that includes a ListOperation that uses the default response schema will have supplied its own
|
||||||
|
// version of that schema, on a last writer wins basis. To ensure an external plugin doesn't end up being the last
|
||||||
|
// writer, we now override with the version within the code of the hosting Vault instance, if the document now
|
||||||
|
// being generated contains any version of this:
|
||||||
|
if _, ok := doc.Components.Schemas["StandardListResponse"]; ok {
|
||||||
|
doc.Components.Schemas["StandardListResponse"] = framework.OASStdSchemaStandardListResponse
|
||||||
|
}
|
||||||
|
|
||||||
buf, err := json.Marshal(doc)
|
buf, err := json.Marshal(doc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -3894,18 +3894,7 @@ func (b *SystemBackend) policyPaths() []*framework.Path {
|
|||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
logical.ListOperation: &framework.PathOperation{
|
logical.ListOperation: &framework.PathOperation{
|
||||||
Callback: b.handlePoliciesPasswordList,
|
Callback: b.handlePoliciesPasswordList,
|
||||||
Responses: map[int][]framework.Response{
|
Summary: "List the existing password policies.",
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
Summary: "List the existing password policies.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -92,17 +92,6 @@ func (b *SystemBackend) quotasPaths() []*framework.Path {
|
|||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
logical.ListOperation: &framework.PathOperation{
|
logical.ListOperation: &framework.PathOperation{
|
||||||
Callback: b.handleRateLimitQuotasList(),
|
Callback: b.handleRateLimitQuotasList(),
|
||||||
Responses: map[int][]framework.Response{
|
|
||||||
http.StatusOK: {{
|
|
||||||
Description: "OK",
|
|
||||||
Fields: map[string]*framework.FieldSchema{
|
|
||||||
"keys": {
|
|
||||||
Type: framework.TypeStringSlice,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
HelpSynopsis: strings.TrimSpace(quotasHelp["rate-limit-list"][0]),
|
HelpSynopsis: strings.TrimSpace(quotasHelp["rate-limit-list"][0]),
|
||||||
|
|||||||
Reference in New Issue
Block a user