diff --git a/.gitignore b/.gitignore index 6366b3c11a..5e0f0deefb 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ vendor/ *.winfile eol=crlf /.vs node_modules + +#used for schema code generation but is not commited to avoid constant updates +tools/codegen/open-api-spec.yml diff --git a/GNUmakefile b/GNUmakefile index 5680dad48a..1a0bf5b154 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -155,5 +155,5 @@ jira-release-version: go run ./tools/jira-release-version/*.go .PHONY: generate-schema -generate-schema: +generate-schema: # resource_name is optional, if not provided all configured resources will be generated @go run ./tools/codegen/main.go $(resource_name) diff --git a/tools/codegen/codespec/api_to_provider_spec_mapper.go b/tools/codegen/codespec/api_to_provider_spec_mapper.go index 8c780163fb..79af19737f 100644 --- a/tools/codegen/codespec/api_to_provider_spec_mapper.go +++ b/tools/codegen/codespec/api_to_provider_spec_mapper.go @@ -15,30 +15,40 @@ import ( "github.com/mongodb/terraform-provider-mongodbatlas/tools/codegen/openapi" ) -func ToCodeSpecModel(atlasAdminAPISpecFilePath, configPath string, resourceName string) (*Model, error) { +func ToCodeSpecModel(atlasAdminAPISpecFilePath, configPath string, resourceName *string) (*Model, error) { apiSpec, err := openapi.ParseAtlasAdminAPI(atlasAdminAPISpecFilePath) if err != nil { return nil, fmt.Errorf("unable to parse Atlas Admin API: %v", err) } - config, err := config.ParseGenConfigYAML(configPath) + configModel, err := config.ParseGenConfigYAML(configPath) if err != nil { return nil, fmt.Errorf("unable to parse config file: %v", err) } - // find resource operations, schemas, etc from OAS - oasResource, err := getAPISpecResource(apiSpec.Model, config.Resources[resourceName], resourceName) - if err != nil { - return nil, fmt.Errorf("unable to get APISpecResource schema: %v", err) + resourceConfigsToIterate := configModel.Resources + if resourceName != nil { // only generate a specific resource + resourceConfigsToIterate = map[string]config.Resource{ + *resourceName: configModel.Resources[*resourceName], + } } - // map OAS resource model to CodeSpecModel - codeSpecResource := apiSpecResourceToCodeSpecModel(oasResource, config.Resources[resourceName], resourceName) + var results []Resource + for name, resourceConfig := range resourceConfigsToIterate { + log.Printf("Generating resource: %s", name) + // find resource operations, schemas, etc from OAS + oasResource, err := getAPISpecResource(apiSpec.Model, resourceConfig, SnakeCaseString(name)) + if err != nil { + return nil, fmt.Errorf("unable to get APISpecResource schema: %v", err) + } + // map OAS resource model to CodeSpecModel + results = append(results, *apiSpecResourceToCodeSpecModel(oasResource, resourceConfig, SnakeCaseString(name))) + } - return &Model{Resources: []Resource{*codeSpecResource}}, nil + return &Model{Resources: results}, nil } -func apiSpecResourceToCodeSpecModel(oasResource APISpecResource, resourceConfig config.Resource, name string) *Resource { +func apiSpecResourceToCodeSpecModel(oasResource APISpecResource, resourceConfig config.Resource, name SnakeCaseString) *Resource { createOp := oasResource.CreateOp readOp := oasResource.ReadOp @@ -126,7 +136,7 @@ func opResponseToAttributes(op *high.Operation) Attributes { return responseAttributes } -func getAPISpecResource(spec high.Document, resourceConfig config.Resource, name string) (APISpecResource, error) { +func getAPISpecResource(spec high.Document, resourceConfig config.Resource, name SnakeCaseString) (APISpecResource, error) { var errResult error var resourceDeprecationMsg *string diff --git a/tools/codegen/codespec/api_to_provider_spec_mapper_test.go b/tools/codegen/codespec/api_to_provider_spec_mapper_test.go index 3626b5ada2..c9839763ef 100644 --- a/tools/codegen/codespec/api_to_provider_spec_mapper_test.go +++ b/tools/codegen/codespec/api_to_provider_spec_mapper_test.go @@ -98,7 +98,7 @@ func TestConvertToProviderSpec(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - result, err := codespec.ToCodeSpecModel(tc.inputOpenAPISpecPath, tc.inputConfigPath, tc.inputResourceName) + result, err := codespec.ToCodeSpecModel(tc.inputOpenAPISpecPath, tc.inputConfigPath, &tc.inputResourceName) require.NoError(t, err) assert.Equal(t, tc.expectedResult, result, "Expected result to match the specified structure") }) @@ -269,7 +269,7 @@ func TestConvertToProviderSpec_nested(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - result, err := codespec.ToCodeSpecModel(tc.inputOpenAPISpecPath, tc.inputConfigPath, tc.inputResourceName) + result, err := codespec.ToCodeSpecModel(tc.inputOpenAPISpecPath, tc.inputConfigPath, &tc.inputResourceName) require.NoError(t, err) assert.Equal(t, tc.expectedResult, result, "Expected result to match the specified structure") }) @@ -348,7 +348,7 @@ func TestConvertToProviderSpec_nested_schemaOverrides(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - result, err := codespec.ToCodeSpecModel(tc.inputOpenAPISpecPath, tc.inputConfigPath, tc.inputResourceName) + result, err := codespec.ToCodeSpecModel(tc.inputOpenAPISpecPath, tc.inputConfigPath, &tc.inputResourceName) require.NoError(t, err) assert.Equal(t, tc.expectedResult, result, "Expected result to match the specified structure") }) diff --git a/tools/codegen/codespec/config.go b/tools/codegen/codespec/config.go index 0ebff82d01..b63b789b45 100644 --- a/tools/codegen/codespec/config.go +++ b/tools/codegen/codespec/config.go @@ -65,7 +65,7 @@ func applyAlias(attr *Attribute, attrPathName *string, schemaOptions config.Sche parts[i] = newName if i == len(parts)-1 { - attr.Name = AttributeName(newName) + attr.Name = SnakeCaseString(newName) } } } diff --git a/tools/codegen/codespec/model.go b/tools/codegen/codespec/model.go index fe2c4d57ca..ddeab098ac 100644 --- a/tools/codegen/codespec/model.go +++ b/tools/codegen/codespec/model.go @@ -1,7 +1,5 @@ package codespec -import "strings" - type ElemType int const ( @@ -19,7 +17,7 @@ type Model struct { type Resource struct { Schema *Schema - Name string + Name SnakeCaseString } type Schema struct { @@ -48,29 +46,12 @@ type Attribute struct { SingleNested *SingleNestedAttribute Description *string - Name AttributeName + Name SnakeCaseString DeprecationMessage *string Sensitive *bool ComputedOptionalRequired ComputedOptionalRequired } -type AttributeName string // stored in snake case - -func (snake AttributeName) SnakeCase() string { - return string(snake) -} - -func (snake AttributeName) PascalCase() string { - words := strings.Split(string(snake), "_") - var pascalCase string - for i := range words { - if words[i] != "" { - pascalCase += strings.ToUpper(string(words[i][0])) + strings.ToLower(words[i][1:]) - } - } - return pascalCase -} - type BoolAttribute struct { Default *bool } diff --git a/tools/codegen/codespec/string_case.go b/tools/codegen/codespec/string_case.go new file mode 100644 index 0000000000..d079e2d485 --- /dev/null +++ b/tools/codegen/codespec/string_case.go @@ -0,0 +1,21 @@ +package codespec + +import ( + "strings" + + "github.com/huandu/xstrings" +) + +type SnakeCaseString string + +func (snake SnakeCaseString) SnakeCase() string { + return string(snake) +} + +func (snake SnakeCaseString) PascalCase() string { + return xstrings.ToCamelCase(string(snake)) // in xstrings v1.15.0 we can switch to using ToPascalCase for same functionality +} + +func (snake SnakeCaseString) LowerCaseNoUnderscore() string { + return strings.ReplaceAll(string(snake), "_", "") +} diff --git a/tools/codegen/codespec/terraform_helper.go b/tools/codegen/codespec/terraform_helper.go index a3e9baa93d..84e764f82c 100644 --- a/tools/codegen/codespec/terraform_helper.go +++ b/tools/codegen/codespec/terraform_helper.go @@ -11,9 +11,9 @@ var ( unsupportedCharacters = regexp.MustCompile(`[^a-zA-Z0-9_]+`) ) -func terraformAttrName(attrName string) AttributeName { +func terraformAttrName(attrName string) SnakeCaseString { if attrName == "" { - return AttributeName(attrName) + return SnakeCaseString(attrName) } removedUnsupported := unsupportedCharacters.ReplaceAllString(attrName, "") @@ -24,5 +24,5 @@ func terraformAttrName(attrName string) AttributeName { return fmt.Sprintf("%c_%s", firstChar, strings.ToLower(restOfString)) }) - return AttributeName(strings.ToLower(insertedUnderscores)) + return SnakeCaseString(strings.ToLower(insertedUnderscores)) } diff --git a/tools/codegen/config.yml b/tools/codegen/config.yml index 9bdd78947a..2f2e500cd3 100644 --- a/tools/codegen/config.yml +++ b/tools/codegen/config.yml @@ -26,10 +26,23 @@ resources: ], definition: "stringvalidator.ConflictsWith(path.MatchRoot(\"name\"))" }] - + prefix_path: computability: optional: true computed: true - timeouts: ["create", "read", "delete"] \ No newline at end of file + timeouts: ["create", "read", "delete"] + + search_deployment: + read: + path: /api/atlas/v2/groups/{groupId}/clusters/{clusterName}/search/deployment + method: GET + create: + path: /api/atlas/v2/groups/{groupId}/clusters/{clusterName}/search/deployment + method: POST + schema: + aliases: + group_id: project_id + ignores: ["links"] + timeouts: ["create", "read", "delete"] diff --git a/tools/codegen/main.go b/tools/codegen/main.go index 4c58fd8e7e..f45e2e3c98 100644 --- a/tools/codegen/main.go +++ b/tools/codegen/main.go @@ -1,34 +1,40 @@ package main import ( + "fmt" "log" "os" "github.com/mongodb/terraform-provider-mongodbatlas/tools/codegen/codespec" "github.com/mongodb/terraform-provider-mongodbatlas/tools/codegen/openapi" + "github.com/mongodb/terraform-provider-mongodbatlas/tools/codegen/schema" ) const ( atlasAdminAPISpecURL = "https://raw.githubusercontent.com/mongodb/atlas-sdk-go/main/openapi/atlas-api-transformed.yaml" - configPath = "schema-generation/config.yml" - specFilePath = "open-api-spec.yml" + configPath = "tools/codegen/config.yml" + specFilePath = "tools/codegen/open-api-spec.yml" ) func main() { resourceName := getOsArg() - if resourceName == nil { - log.Fatal("No resource name provided") - } - log.Printf("Resource name: %s\n", *resourceName) if err := openapi.DownloadOpenAPISpec(atlasAdminAPISpecURL, specFilePath); err != nil { log.Fatalf("an error occurred when downloading Atlas Admin API spec: %v", err) } - _, err := codespec.ToCodeSpecModel(specFilePath, configPath, *resourceName) + model, err := codespec.ToCodeSpecModel(specFilePath, configPath, resourceName) if err != nil { log.Fatalf("an error occurred while generating codespec.Model: %v", err) } + + for i := range model.Resources { + resourceModel := model.Resources[i] + schemaCode := schema.GenerateGoCode(resourceModel) + if err := writeToFile(fmt.Sprintf("internal/service/%s/resource_schema.go", resourceModel.Name.LowerCaseNoUnderscore()), schemaCode); err != nil { + log.Fatalf("an error occurred when writing content to file: %v", err) + } + } } func getOsArg() *string { @@ -37,3 +43,12 @@ func getOsArg() *string { } return &os.Args[1] } + +func writeToFile(fileName, content string) error { + // will override content if file exists + err := os.WriteFile(fileName, []byte(content), 0o600) + if err != nil { + return err + } + return nil +} diff --git a/tools/codegen/schema/schema_file.go b/tools/codegen/schema/schema_file.go index 0b4ca650ee..880e0e9014 100644 --- a/tools/codegen/schema/schema_file.go +++ b/tools/codegen/schema/schema_file.go @@ -3,16 +3,16 @@ package schema import ( "go/format" - genconfigmapper "github.com/mongodb/terraform-provider-mongodbatlas/tools/codegen/codespec" + "github.com/mongodb/terraform-provider-mongodbatlas/tools/codegen/codespec" "github.com/mongodb/terraform-provider-mongodbatlas/tools/codegen/schema/codetemplate" ) -func GenerateGoCode(input genconfigmapper.Resource) string { +func GenerateGoCode(input codespec.Resource) string { schemaAttrs := GenerateSchemaAttributes(input.Schema.Attributes) models := GenerateTypedModels(input.Schema.Attributes) tmplInputs := codetemplate.SchemaFileInputs{ - PackageName: input.Name, + PackageName: input.Name.LowerCaseNoUnderscore(), Imports: append(schemaAttrs.Imports, models.Imports...), SchemaAttributes: schemaAttrs.Code, Models: models.Code, diff --git a/tools/codegen/schema/testdata/nested-attributes.golden.go b/tools/codegen/schema/testdata/nested-attributes.golden.go index 1db7965f8d..b0915d9593 100644 --- a/tools/codegen/schema/testdata/nested-attributes.golden.go +++ b/tools/codegen/schema/testdata/nested-attributes.golden.go @@ -1,4 +1,4 @@ -package test_name +package testname import ( "context" diff --git a/tools/codegen/schema/testdata/primitive-attributes.golden.go b/tools/codegen/schema/testdata/primitive-attributes.golden.go index 22ec350705..1025675c4b 100644 --- a/tools/codegen/schema/testdata/primitive-attributes.golden.go +++ b/tools/codegen/schema/testdata/primitive-attributes.golden.go @@ -1,4 +1,4 @@ -package test_name +package testname import ( "context"