diff --git a/.github/workflows/samples-terraform.yaml b/.github/workflows/samples-terraform.yaml
new file mode 100644
index 000000000000..04504f8f7c16
--- /dev/null
+++ b/.github/workflows/samples-terraform.yaml
@@ -0,0 +1,46 @@
+name: Samples Terraform
+
+on:
+ push:
+ paths:
+ - 'samples/client/petstore/terraform/**'
+ - 'samples/client/petstore/terraform-addpet/**'
+ - 'samples/client/petstore/terraform-server/**'
+ - 'samples/client/others/terraform/**'
+ pull_request:
+ paths:
+ - 'samples/client/petstore/terraform/**'
+ - 'samples/client/petstore/terraform-addpet/**'
+ - 'samples/client/petstore/terraform-server/**'
+ - 'samples/client/others/terraform/**'
+
+jobs:
+ build:
+ name: Build Terraform Provider
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sample:
+ - samples/client/petstore/terraform/
+ - samples/client/petstore/terraform-addpet/
+ - samples/client/petstore/terraform-server/
+ - samples/client/others/terraform/allof-discriminator/
+ - samples/client/others/terraform/oneof-anyof-required/
+ - samples/client/others/terraform/oneof-discriminator-lookup/
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions/setup-go@v6
+ with:
+ go-version: "stable"
+ - run: go version
+ - name: Install Dependencies
+ working-directory: ${{ matrix.sample }}
+ run: |
+ go mod tidy
+ - name: Build provider
+ working-directory: ${{ matrix.sample }}
+ run: go build -v ./...
+ - name: Run tests
+ working-directory: ${{ matrix.sample }}
+ run: go test ./... -v -timeout 120m
diff --git a/bin/configs/terraform-provider-allof-discriminator.yaml b/bin/configs/terraform-provider-allof-discriminator.yaml
new file mode 100644
index 000000000000..e1b81ffb1def
--- /dev/null
+++ b/bin/configs/terraform-provider-allof-discriminator.yaml
@@ -0,0 +1,11 @@
+generatorName: terraform-provider
+outputDir: samples/client/others/terraform/allof-discriminator
+inputSpec: modules/openapi-generator/src/test/resources/3_0/go/allof_multiple_ref_and_discriminator.yaml
+templateDir: modules/openapi-generator/src/main/resources/terraform-provider
+gitHost: github.com
+gitUserId: example
+gitRepoId: terraform-provider-allof
+additionalProperties:
+ providerName: "allof"
+ providerAddress: "registry.terraform.io/example/allof"
+ hideGenerationTimestamp: "true"
diff --git a/bin/configs/terraform-provider-oneof-anyof-required.yaml b/bin/configs/terraform-provider-oneof-anyof-required.yaml
new file mode 100644
index 000000000000..da6bf834c24b
--- /dev/null
+++ b/bin/configs/terraform-provider-oneof-anyof-required.yaml
@@ -0,0 +1,11 @@
+generatorName: terraform-provider
+outputDir: samples/client/others/terraform/oneof-anyof-required
+inputSpec: modules/openapi-generator/src/test/resources/3_0/go/spec-with-oneof-anyof-required.yaml
+templateDir: modules/openapi-generator/src/main/resources/terraform-provider
+gitHost: github.com
+gitUserId: example
+gitRepoId: terraform-provider-oneof-anyof
+additionalProperties:
+ providerName: "oneof"
+ providerAddress: "registry.terraform.io/example/oneof-anyof"
+ hideGenerationTimestamp: "true"
diff --git a/bin/configs/terraform-provider-oneof-discriminator-lookup.yaml b/bin/configs/terraform-provider-oneof-discriminator-lookup.yaml
new file mode 100644
index 000000000000..0f2a5034dac1
--- /dev/null
+++ b/bin/configs/terraform-provider-oneof-discriminator-lookup.yaml
@@ -0,0 +1,11 @@
+generatorName: terraform-provider
+outputDir: samples/client/others/terraform/oneof-discriminator-lookup
+inputSpec: modules/openapi-generator/src/test/resources/3_0/go/spec-with-oneof-discriminator.yaml
+templateDir: modules/openapi-generator/src/main/resources/terraform-provider
+gitHost: github.com
+gitUserId: example
+gitRepoId: terraform-provider-oneof-disc
+additionalProperties:
+ providerName: "oneof"
+ providerAddress: "registry.terraform.io/example/oneof-disc"
+ hideGenerationTimestamp: "true"
diff --git a/bin/configs/terraform-provider-petstore-addpet.yaml b/bin/configs/terraform-provider-petstore-addpet.yaml
new file mode 100644
index 000000000000..def0ff729b72
--- /dev/null
+++ b/bin/configs/terraform-provider-petstore-addpet.yaml
@@ -0,0 +1,11 @@
+generatorName: terraform-provider
+outputDir: samples/client/petstore/terraform-addpet
+inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-addpet-only.yaml
+templateDir: modules/openapi-generator/src/main/resources/terraform-provider
+gitHost: github.com
+gitUserId: example
+gitRepoId: terraform-provider-petstore-addpet
+additionalProperties:
+ providerName: "petstore"
+ providerAddress: "registry.terraform.io/example/petstore-addpet"
+ hideGenerationTimestamp: "true"
diff --git a/bin/configs/terraform-provider-petstore-new.yaml b/bin/configs/terraform-provider-petstore-new.yaml
new file mode 100644
index 000000000000..d0be301e0d19
--- /dev/null
+++ b/bin/configs/terraform-provider-petstore-new.yaml
@@ -0,0 +1,11 @@
+generatorName: terraform-provider
+outputDir: samples/client/petstore/terraform
+inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
+templateDir: modules/openapi-generator/src/main/resources/terraform-provider
+gitHost: github.com
+gitUserId: example
+gitRepoId: terraform-provider-petstore
+additionalProperties:
+ providerName: "petstore"
+ providerAddress: "registry.terraform.io/example/petstore"
+ hideGenerationTimestamp: "true"
diff --git a/bin/configs/terraform-provider-petstore-server.yaml b/bin/configs/terraform-provider-petstore-server.yaml
new file mode 100644
index 000000000000..9d6322fefe35
--- /dev/null
+++ b/bin/configs/terraform-provider-petstore-server.yaml
@@ -0,0 +1,11 @@
+generatorName: terraform-provider
+outputDir: samples/client/petstore/terraform-server
+inputSpec: modules/openapi-generator/src/test/resources/3_0/go-server/petstore.yaml
+templateDir: modules/openapi-generator/src/main/resources/terraform-provider
+gitHost: github.com
+gitUserId: example
+gitRepoId: terraform-provider-petstore-server
+additionalProperties:
+ providerName: "petstore"
+ providerAddress: "registry.terraform.io/example/petstore-server"
+ hideGenerationTimestamp: "true"
diff --git a/docs/generators.md b/docs/generators.md
index bfa97dd22e54..930bdbd80304 100644
--- a/docs/generators.md
+++ b/docs/generators.md
@@ -70,6 +70,7 @@ The following generators are available:
* [swift-combine](generators/swift-combine.md)
* [swift5 (deprecated)](generators/swift5.md)
* [swift6](generators/swift6.md)
+* [terraform-provider (experimental)](generators/terraform-provider.md)
* [typescript (experimental)](generators/typescript.md)
* [typescript-angular](generators/typescript-angular.md)
* [typescript-aurelia](generators/typescript-aurelia.md)
diff --git a/docs/generators/terraform-provider.md b/docs/generators/terraform-provider.md
new file mode 100644
index 000000000000..8f14dff0c1c9
--- /dev/null
+++ b/docs/generators/terraform-provider.md
@@ -0,0 +1,229 @@
+---
+title: Documentation for the terraform-provider Generator
+---
+
+## METADATA
+
+| Property | Value | Notes |
+| -------- | ----- | ----- |
+| generator name | terraform-provider | pass this to the generate command after -g |
+| generator stability | EXPERIMENTAL | |
+| generator type | CLIENT | |
+| generator language | Go | |
+| generator default templating engine | mustache | |
+| helpTxt | Generates a Terraform provider (Go, using HashiCorp Plugin Framework). | |
+
+## CONFIG OPTIONS
+These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
+
+| Option | Description | Values | Default |
+| ------ | ----------- | ------ | ------- |
+|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
+|packageName|Go package name (convention: lowercase).| |openapi|
+|packageVersion|Go package version.| |1.0.0|
+|providerAddress|Terraform provider registry address| |registry.terraform.io/example/example|
+|providerName|Terraform provider name (e.g. 'petstore')| |example|
+|providerVersion|Terraform provider version| |0.1.0|
+
+## IMPORT MAPPING
+
+| Type/Alias | Imports |
+| ---------- | ------- |
+
+
+## INSTANTIATION TYPES
+
+| Type/Alias | Instantiated By |
+| ---------- | --------------- |
+
+
+## LANGUAGE PRIMITIVES
+
+
+- bool
+- byte
+- complex128
+- complex64
+- float32
+- float64
+- int
+- int32
+- int64
+- interface{}
+- map[string]interface{}
+- rune
+- string
+- uint
+- uint32
+- uint64
+
+
+## RESERVED WORDS
+
+
+- bool
+- break
+- byte
+- case
+- chan
+- complex128
+- complex64
+- const
+- continue
+- default
+- defer
+- else
+- error
+- fallthrough
+- float32
+- float64
+- for
+- func
+- go
+- goto
+- if
+- import
+- int
+- int16
+- int32
+- int64
+- int8
+- interface
+- map
+- nil
+- package
+- range
+- return
+- rune
+- select
+- string
+- struct
+- switch
+- type
+- uint
+- uint16
+- uint32
+- uint64
+- uint8
+- uintptr
+- var
+
+
+## FEATURE SET
+
+
+### Client Modification Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasePath|✗|ToolingExtension
+|Authorizations|✗|ToolingExtension
+|UserAgent|✗|ToolingExtension
+|MockServer|✗|ToolingExtension
+
+### Data Type Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Custom|✗|OAS2,OAS3
+|Int32|✓|OAS2,OAS3
+|Int64|✓|OAS2,OAS3
+|Float|✓|OAS2,OAS3
+|Double|✓|OAS2,OAS3
+|Decimal|✓|ToolingExtension
+|String|✓|OAS2,OAS3
+|Byte|✓|OAS2,OAS3
+|Binary|✓|OAS2,OAS3
+|Boolean|✓|OAS2,OAS3
+|Date|✓|OAS2,OAS3
+|DateTime|✓|OAS2,OAS3
+|Password|✓|OAS2,OAS3
+|File|✓|OAS2
+|Uuid|✗|
+|Array|✓|OAS2,OAS3
+|Null|✗|OAS3
+|AnyType|✗|OAS2,OAS3
+|Object|✓|OAS2,OAS3
+|Maps|✓|ToolingExtension
+|CollectionFormat|✓|OAS2
+|CollectionFormatMulti|✓|OAS2
+|Enum|✓|OAS2,OAS3
+|ArrayOfEnum|✓|ToolingExtension
+|ArrayOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
+|ArrayOfCollectionOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfEnum|✓|ToolingExtension
+|MapOfEnum|✓|ToolingExtension
+|MapOfModel|✓|ToolingExtension
+|MapOfCollectionOfPrimitives|✓|ToolingExtension
+|MapOfCollectionOfModel|✓|ToolingExtension
+|MapOfCollectionOfEnum|✓|ToolingExtension
+
+### Documentation Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Readme|✓|ToolingExtension
+|Model|✓|ToolingExtension
+|Api|✓|ToolingExtension
+
+### Global Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Host|✓|OAS2,OAS3
+|BasePath|✓|OAS2,OAS3
+|Info|✓|OAS2,OAS3
+|Schemes|✗|OAS2,OAS3
+|PartialSchemes|✓|OAS2,OAS3
+|Consumes|✓|OAS2
+|Produces|✓|OAS2
+|ExternalDocumentation|✓|OAS2,OAS3
+|Examples|✓|OAS2,OAS3
+|XMLStructureDefinitions|✗|OAS2,OAS3
+|MultiServer|✗|OAS3
+|ParameterizedServer|✗|OAS3
+|ParameterStyling|✗|OAS3
+|Callbacks|✗|OAS3
+|LinkObjects|✗|OAS3
+
+### Parameter Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Path|✓|OAS2,OAS3
+|Query|✓|OAS2,OAS3
+|Header|✓|OAS2,OAS3
+|Body|✓|OAS2
+|FormUnencoded|✓|OAS2
+|FormMultipart|✓|OAS2
+|Cookie|✓|OAS3
+
+### Schema Support Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Simple|✓|OAS2,OAS3
+|Composite|✓|OAS2,OAS3
+|Polymorphism|✗|OAS2,OAS3
+|Union|✗|OAS3
+|allOf|✗|OAS2,OAS3
+|anyOf|✗|OAS3
+|oneOf|✗|OAS3
+|not|✗|OAS3
+
+### Security Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasicAuth|✓|OAS2,OAS3
+|ApiKey|✓|OAS2,OAS3
+|OpenIDConnect|✗|OAS3
+|BearerToken|✓|OAS3
+|OAuth2_Implicit|✗|OAS2,OAS3
+|OAuth2_Password|✗|OAS2,OAS3
+|OAuth2_ClientCredentials|✗|OAS2,OAS3
+|OAuth2_AuthorizationCode|✗|OAS2,OAS3
+|SignatureAuth|✗|OAS3
+|AWSV4Signature|✗|ToolingExtension
+
+### Wire Format Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|JSON|✓|OAS2,OAS3
+|XML|✗|OAS2,OAS3
+|PROTOBUF|✗|ToolingExtension
+|Custom|✗|OAS2,OAS3
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TerraformProviderCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TerraformProviderCodegen.java
new file mode 100644
index 000000000000..7cc6bcfbcf98
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TerraformProviderCodegen.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openapitools.codegen.languages;
+
+import org.openapitools.codegen.*;
+import org.openapitools.codegen.meta.GeneratorMetadata;
+import org.openapitools.codegen.meta.Stability;
+import org.openapitools.codegen.meta.features.*;
+import org.openapitools.codegen.model.ModelMap;
+import org.openapitools.codegen.model.ModelsMap;
+import org.openapitools.codegen.model.OperationMap;
+import org.openapitools.codegen.model.OperationsMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.*;
+
+import static org.openapitools.codegen.utils.StringUtils.camelize;
+import static org.openapitools.codegen.utils.StringUtils.underscore;
+
+public class TerraformProviderCodegen extends AbstractGoCodegen {
+
+ private final Logger LOGGER = LoggerFactory.getLogger(TerraformProviderCodegen.class);
+
+ public static final String PROVIDER_NAME = "providerName";
+ public static final String PROVIDER_ADDRESS = "providerAddress";
+ public static final String PROVIDER_VERSION = "providerVersion";
+
+ protected String providerName = "example";
+ protected String providerAddress = "registry.terraform.io/example/example";
+ protected String providerVersion = "0.1.0";
+
+ @Override
+ public CodegenType getTag() {
+ return CodegenType.CLIENT;
+ }
+
+ @Override
+ public String getName() {
+ return "terraform-provider";
+ }
+
+ @Override
+ public String getHelp() {
+ return "Generates a Terraform provider (Go, using HashiCorp Plugin Framework).";
+ }
+
+ public TerraformProviderCodegen() {
+ super();
+
+ generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
+ .stability(Stability.EXPERIMENTAL)
+ .build();
+
+ modifyFeatureSet(features -> features
+ .includeDocumentationFeatures(DocumentationFeature.Readme)
+ .wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON))
+ .securityFeatures(EnumSet.of(
+ SecurityFeature.BasicAuth,
+ SecurityFeature.BearerToken,
+ SecurityFeature.ApiKey
+ ))
+ .excludeGlobalFeatures(
+ GlobalFeature.XMLStructureDefinitions,
+ GlobalFeature.Callbacks,
+ GlobalFeature.LinkObjects,
+ GlobalFeature.ParameterStyling
+ )
+ .excludeSchemaSupportFeatures(
+ SchemaSupportFeature.Polymorphism
+ )
+ );
+
+ outputFolder = "generated-code/terraform-provider";
+ embeddedTemplateDir = templateDir = "terraform-provider";
+
+ // API templates: generate per-tag resource, data source, and model files
+ apiTemplateFiles.put("resource.mustache", "_resource.go");
+ apiTemplateFiles.put("data_source.mustache", "_data_source.go");
+ apiTemplateFiles.put("resource_model.mustache", "_model.go");
+
+ // Model templates: generate per-schema Go structs for the client package
+ modelTemplateFiles.put("model.mustache", ".go");
+
+ // No doc templates
+ apiDocTemplateFiles.clear();
+ modelDocTemplateFiles.clear();
+
+ hideGenerationTimestamp = Boolean.TRUE;
+
+ // Override type mappings for Terraform (no time.Time or *os.File)
+ typeMapping.put("DateTime", "string");
+ typeMapping.put("date", "string");
+ typeMapping.put("File", "string");
+ typeMapping.put("file", "string");
+ typeMapping.put("binary", "string");
+
+ cliOptions.add(new CliOption(PROVIDER_NAME, "Terraform provider name (e.g. 'petstore')")
+ .defaultValue(providerName));
+ cliOptions.add(new CliOption(PROVIDER_ADDRESS, "Terraform provider registry address")
+ .defaultValue(providerAddress));
+ cliOptions.add(new CliOption(PROVIDER_VERSION, "Terraform provider version")
+ .defaultValue(providerVersion));
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ if (additionalProperties.containsKey(PROVIDER_NAME)) {
+ providerName = additionalProperties.get(PROVIDER_NAME).toString();
+ }
+ additionalProperties.put(PROVIDER_NAME, providerName);
+
+ if (additionalProperties.containsKey(PROVIDER_ADDRESS)) {
+ providerAddress = additionalProperties.get(PROVIDER_ADDRESS).toString();
+ }
+ additionalProperties.put(PROVIDER_ADDRESS, providerAddress);
+
+ if (additionalProperties.containsKey(PROVIDER_VERSION)) {
+ providerVersion = additionalProperties.get(PROVIDER_VERSION).toString();
+ }
+ additionalProperties.put(PROVIDER_VERSION, providerVersion);
+
+ if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
+ setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
+ } else {
+ setPackageName("provider");
+ }
+ additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
+
+ apiPackage = "internal" + File.separator + "provider";
+ modelPackage = "internal" + File.separator + "client";
+
+ // Supporting files
+ supportingFiles.add(new SupportingFile("main.mustache", "", "main.go"));
+ supportingFiles.add(new SupportingFile("provider.mustache", "internal" + File.separator + "provider", "provider.go"));
+ supportingFiles.add(new SupportingFile("client.mustache", "internal" + File.separator + "client", "client.go"));
+ supportingFiles.add(new SupportingFile("go.mod.mustache", "", "go.mod"));
+ supportingFiles.add(new SupportingFile("GNUmakefile.mustache", "", "GNUmakefile"));
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+ supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
+ supportingFiles.add(new SupportingFile("provider_example.mustache", "examples" + File.separator + "provider", "provider.tf"));
+ }
+
+ @Override
+ public String apiFileFolder() {
+ return outputFolder + File.separator + "internal" + File.separator + "provider";
+ }
+
+ @Override
+ public String modelFileFolder() {
+ return outputFolder + File.separator + "internal" + File.separator + "client";
+ }
+
+ @Override
+ public String toApiFilename(String name) {
+ return underscore(name);
+ }
+
+ @Override
+ public String toModelFilename(String name) {
+ return "model_" + underscore(name);
+ }
+
+ @Override
+ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) {
+ OperationMap objectMap = objs.getOperations();
+ List operations = objectMap.getOperation();
+
+ // Store original uppercase HTTP method and build fmt.Sprintf-ready paths
+ for (CodegenOperation operation : operations) {
+ // Store uppercase method for net/http (e.g. "POST", "GET")
+ operation.vendorExtensions.put("x-terraform-http-method", operation.httpMethod.toUpperCase(Locale.ROOT));
+
+ // Convert path params to fmt.Sprintf format: /pet/{petId} -> /pet/%v
+ if (operation.path != null && operation.pathParams != null && !operation.pathParams.isEmpty()) {
+ String fmtPath = operation.path;
+ for (CodegenParameter param : operation.pathParams) {
+ fmtPath = fmtPath.replace("{" + param.baseName + "}", "%v");
+ }
+ operation.vendorExtensions.put("x-terraform-path-fmt", fmtPath);
+ }
+
+ // http method verb conversion (e.g. PUT => Put) as parent does
+ operation.httpMethod = camelize(operation.httpMethod.toLowerCase(Locale.ROOT));
+ }
+
+ // CRUD detection per tag
+ CodegenOperation createOp = null;
+ CodegenOperation readOp = null;
+ CodegenOperation updateOp = null;
+ CodegenOperation deleteOp = null;
+ CodegenOperation listOp = null;
+
+ // First pass: check for explicit vendor extensions
+ for (CodegenOperation op : operations) {
+ if (getBooleanVendorExtension(op, "x-terraform-exclude")) {
+ continue;
+ }
+ if (getBooleanVendorExtension(op, "x-terraform-is-create") && createOp == null) {
+ createOp = op;
+ }
+ if (getBooleanVendorExtension(op, "x-terraform-is-read") && readOp == null) {
+ readOp = op;
+ }
+ if (getBooleanVendorExtension(op, "x-terraform-is-update") && updateOp == null) {
+ updateOp = op;
+ }
+ if (getBooleanVendorExtension(op, "x-terraform-is-delete") && deleteOp == null) {
+ deleteOp = op;
+ }
+ if (getBooleanVendorExtension(op, "x-terraform-is-list") && listOp == null) {
+ listOp = op;
+ }
+ }
+
+ // Second pass: auto-detect using REST pattern methods
+ for (CodegenOperation op : operations) {
+ if (getBooleanVendorExtension(op, "x-terraform-exclude")) {
+ continue;
+ }
+ if (createOp == null && op.isRestfulCreate()) {
+ createOp = op;
+ } else if (readOp == null && op.isRestfulShow()) {
+ readOp = op;
+ } else if (updateOp == null && op.isRestfulUpdate()) {
+ updateOp = op;
+ } else if (deleteOp == null && op.isRestfulDestroy()) {
+ deleteOp = op;
+ } else if (listOp == null && op.isRestfulIndex()) {
+ listOp = op;
+ }
+ }
+
+ // Mark the selected operations with vendor extensions
+ if (createOp != null) {
+ createOp.vendorExtensions.put("x-terraform-is-create", true);
+ }
+ if (readOp != null) {
+ readOp.vendorExtensions.put("x-terraform-is-read", true);
+ }
+ if (updateOp != null) {
+ updateOp.vendorExtensions.put("x-terraform-is-update", true);
+ }
+ if (deleteOp != null) {
+ deleteOp.vendorExtensions.put("x-terraform-is-delete", true);
+ }
+ if (listOp != null) {
+ listOp.vendorExtensions.put("x-terraform-is-list", true);
+ }
+
+ // Tag-level flags and CRUD details for template rendering
+ objectMap.put("hasCreate", createOp != null);
+ objectMap.put("hasRead", readOp != null);
+ objectMap.put("hasUpdate", updateOp != null);
+ objectMap.put("hasDelete", deleteOp != null);
+ objectMap.put("hasList", listOp != null);
+
+ // Store CRUD operation details at tag level for simplified template access
+ if (createOp != null) {
+ objectMap.put("createMethod", createOp.vendorExtensions.get("x-terraform-http-method"));
+ objectMap.put("createPath", createOp.path);
+ }
+ if (readOp != null) {
+ objectMap.put("readMethod", readOp.vendorExtensions.get("x-terraform-http-method"));
+ objectMap.put("readPath", readOp.vendorExtensions.getOrDefault("x-terraform-path-fmt", readOp.path));
+ objectMap.put("readHasPathParams", readOp.pathParams != null && !readOp.pathParams.isEmpty());
+ }
+ if (updateOp != null) {
+ objectMap.put("updateMethod", updateOp.vendorExtensions.get("x-terraform-http-method"));
+ objectMap.put("updatePath", updateOp.vendorExtensions.getOrDefault("x-terraform-path-fmt", updateOp.path));
+ objectMap.put("updateHasPathParams", updateOp.pathParams != null && !updateOp.pathParams.isEmpty());
+ }
+ if (deleteOp != null) {
+ objectMap.put("deleteMethod", deleteOp.vendorExtensions.get("x-terraform-http-method"));
+ objectMap.put("deletePath", deleteOp.vendorExtensions.getOrDefault("x-terraform-path-fmt", deleteOp.path));
+ objectMap.put("deleteHasPathParams", deleteOp.pathParams != null && !deleteOp.pathParams.isEmpty());
+ }
+
+ // Determine resource name from tag
+ String tag = objectMap.getClassname();
+ // Strip common suffixes like "Api", "API" from the tag name
+ String cleanTag = tag.replaceAll("(?i)api$", "");
+ if (cleanTag.isEmpty()) {
+ cleanTag = tag;
+ }
+ String resourceName = underscore(cleanTag).toLowerCase(Locale.ROOT);
+ // Allow override via x-terraform-resource-name on any operation
+ for (CodegenOperation op : operations) {
+ Object nameOverride = op.vendorExtensions.get("x-terraform-resource-name");
+ if (nameOverride != null) {
+ resourceName = nameOverride.toString();
+ break;
+ }
+ }
+ objectMap.put("resourceName", resourceName);
+ objectMap.put("resourceClassName", camelize(resourceName));
+
+ // Detect ID field from read operation path params
+ String idField = "id";
+ if (readOp != null && readOp.pathParams != null && !readOp.pathParams.isEmpty()) {
+ idField = readOp.pathParams.get(readOp.pathParams.size() - 1).paramName;
+ }
+ objectMap.put("idField", idField);
+
+ // Build model info for the resource schema
+ // Use the response type from readOp (or createOp) as the resource model
+ String responseModel = null;
+ if (readOp != null && readOp.returnType != null) {
+ responseModel = readOp.returnType;
+ } else if (createOp != null && createOp.returnType != null) {
+ responseModel = createOp.returnType;
+ }
+ objectMap.put("responseModel", responseModel);
+
+ // Build request model from createOp body params
+ String requestModel = null;
+ if (createOp != null && createOp.bodyParam != null && createOp.bodyParam.dataType != null) {
+ requestModel = createOp.bodyParam.dataType;
+ }
+ objectMap.put("requestModel", requestModel);
+
+ // Collect model properties for schema generation and resolve ID field
+ String idModelGoName = camelize(idField);
+ String idModelGoType = "string";
+
+ if (responseModel != null) {
+ for (ModelMap modelMap : allModels) {
+ CodegenModel model = modelMap.getModel();
+ if (model.classname.equals(responseModel)) {
+ List