Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for common parameters for resources and data sources #114

Merged
merged 2 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20240116-102758.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: Added data source and resource support for query and path parameters specified
in the [OAS Path Item](https://spec.openapis.org/oas/v3.1.0#path-item-object)
time: 2024-01-16T10:27:58.255575839+01:00
custom:
Issue: "114"
2 changes: 2 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ In these OAS operations, the generator will search the `create` and `read` for s
- Will attempt to use `application/json` content-type first. If not found, will grab the first available content-type with a schema (alphabetical order)
4. `read` operation: [parameters](https://spec.openapis.org/oas/v3.1.0#parameterObject)
- The generator will merge all `query` and `path` parameters to the root of the schema.
- The generator will consider as parameters the ones in the [OAS Path Item](https://spec.openapis.org/oas/v3.1.0#path-item-object) and the ones in the [OAS Operation](https://spec.openapis.org/oas/v3.1.0#operation-object), merged based on the rules in the specification

All schemas found will be deep merged together, with the `requestBody` schema from the `create` operation being the **main schema** that the others will be merged on top. The deep merge has the following characteristics:

Expand All @@ -72,6 +73,7 @@ data_sources:
The generator uses the `read` operation to map to the provider code specification. Multiple schemas will have the [OAS types mapped to Provider Attributes](#oas-types-to-provider-attributes) and then be merged together; with the final result being the [Data Source](https://developer.hashicorp.com/terraform/plugin/code-generation/specification#data-source) `schema`. The schemas that will be merged together (in priority order):
1. `read` operation: [parameters](https://spec.openapis.org/oas/v3.1.0#parameterObject)
- The generator will merge all `query` and `path` parameters to the root of the schema.
- The generator will consider as parameters the ones in the [Path Item Object](https://spec.openapis.org/oas/v3.1.0#path-item-object) and the ones in the [Operation Object](https://spec.openapis.org/oas/v3.1.0#operation-object), merged based on the rules in the specification
2. `read` operation: response body in [responses](https://spec.openapis.org/oas/v3.1.0#responsesObject)
- The response body is the only schema **required** for data sources. If not found, the generator will skip the data source without mapping.
- Will attempt to use `200` or `201` response body. If not found, will grab the first available `2xx` response code with a schema (lexicographic order)
Expand Down
21 changes: 21 additions & 0 deletions internal/cmd/testdata/kubernetes/provider_code_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -9221,6 +9221,27 @@
],
"description": "Most recently observed status of the Deployment."
}
},
{
"name": "name",
"string": {
"computed_optional_required": "computed_optional",
"description": "name of the Deployment"
}
},
{
"name": "namespace",
"string": {
"computed_optional_required": "computed_optional",
"description": "object name and auth scope, such as for teams and projects"
}
},
{
"name": "pretty",
"string": {
"computed_optional_required": "computed_optional",
"description": "If 'true', then the output is pretty printed."
}
}
]
}
Expand Down
40 changes: 33 additions & 7 deletions internal/explorer/config_explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,19 @@ func (e configExplorer) FindResources() (map[string]Resource, error) {
continue
}

commonParameters, err := extractCommonParameters(e.spec.Paths, resourceConfig.Read.Path)
if err != nil {
errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s' common parameters: %w", name, err))
continue
}

resources[name] = Resource{
CreateOp: createOp,
ReadOp: readOp,
UpdateOp: updateOp,
DeleteOp: deleteOp,
SchemaOptions: extractSchemaOptions(resourceConfig.SchemaOptions),
CreateOp: createOp,
ReadOp: readOp,
UpdateOp: updateOp,
DeleteOp: deleteOp,
CommonParameters: commonParameters,
SchemaOptions: extractSchemaOptions(resourceConfig.SchemaOptions),
}
}

Expand All @@ -103,9 +110,17 @@ func (e configExplorer) FindDataSources() (map[string]DataSource, error) {
errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s.read': %w", name, err))
continue
}

commonParameters, err := extractCommonParameters(e.spec.Paths, dataSourceConfig.Read.Path)
if err != nil {
errResult = errors.Join(errResult, fmt.Errorf("failed to extract '%s' common parameters: %w", name, err))
continue
}

dataSources[name] = DataSource{
ReadOp: readOp,
SchemaOptions: extractSchemaOptions(dataSourceConfig.SchemaOptions),
ReadOp: readOp,
CommonParameters: commonParameters,
SchemaOptions: extractSchemaOptions(dataSourceConfig.SchemaOptions),
}
}
return dataSources, errResult
Expand Down Expand Up @@ -145,6 +160,17 @@ func extractOp(paths *high.Paths, oasLocation *config.OpenApiSpecLocation) (*hig
}
}

func extractCommonParameters(paths *high.Paths, path string) ([]*high.Parameter, error) {
// No need to search OAS if not defined
if paths.PathItems.GetOrZero(path) == nil {
return nil, fmt.Errorf("path '%s' not found in OpenAPI spec", path)
}

pathItem, _ := paths.PathItems.Get(path)

return pathItem.Parameters, nil
}

func extractSchemaProxy(document high.Document, componentRef string) (*highbase.SchemaProxy, error) {
// find the reference using the root document.Index
indexRef := document.Index.FindComponentInRoot(componentRef)
Expand Down
16 changes: 9 additions & 7 deletions internal/explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ type Explorer interface {

// Resource contains CRUD operations and schema options for configuration.
type Resource struct {
CreateOp *high.Operation
ReadOp *high.Operation
UpdateOp *high.Operation
DeleteOp *high.Operation
SchemaOptions SchemaOptions
CreateOp *high.Operation
ReadOp *high.Operation
UpdateOp *high.Operation
DeleteOp *high.Operation
CommonParameters []*high.Parameter
SchemaOptions SchemaOptions
}

// DataSource contains a Read operation and schema options for configuration.
type DataSource struct {
ReadOp *high.Operation
SchemaOptions SchemaOptions
ReadOp *high.Operation
CommonParameters []*high.Parameter
SchemaOptions SchemaOptions
}

// Provider contains a name and a schema.
Expand Down
35 changes: 35 additions & 0 deletions internal/explorer/explorer_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package explorer

import high "github.com/pb33f/libopenapi/datamodel/high/v3"

func mergeParameters(commonParameters []*high.Parameter, operation *high.Operation) []*high.Parameter {
mergedParameters := make([]*high.Parameter, len(commonParameters))
copy(mergedParameters, commonParameters)
if operation != nil {
for _, operationParameter := range operation.Parameters {
found := false
for i, mergedParameter := range mergedParameters {
if operationParameter.Name == mergedParameter.Name {
found = true
mergedParameters[i] = operationParameter
break
}
}
if !found {
mergedParameters = append(mergedParameters, operationParameter)
}
}
}
return mergedParameters
}

func (e *Resource) ReadOpParameters() []*high.Parameter {
return mergeParameters(e.CommonParameters, e.ReadOp)
}

func (e *DataSource) ReadOpParameters() []*high.Parameter {
return mergeParameters(e.CommonParameters, e.ReadOp)
}
Loading