Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions bin/configs/terraform-provider-petstore-new.yaml
Original file line number Diff line number Diff line change
@@ -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"

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ org.openapitools.codegen.languages.StaticHtml2Generator
org.openapitools.codegen.languages.Swift5ClientCodegen
org.openapitools.codegen.languages.Swift6ClientCodegen
org.openapitools.codegen.languages.SwiftCombineClientCodegen
org.openapitools.codegen.languages.TerraformProviderCodegen
org.openapitools.codegen.languages.TypeScriptClientCodegen
org.openapitools.codegen.languages.TypeScriptAngularClientCodegen
org.openapitools.codegen.languages.TypeScriptAureliaClientCodegen
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
default: testacc

# Run acceptance tests
.PHONY: testacc
testacc:
TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m

# Build provider
.PHONY: build
build:
go build -o terraform-provider-{{providerName}}

# Install provider locally
.PHONY: install
install: build
mkdir -p ~/.terraform.d/plugins/{{providerAddress}}/{{providerVersion}}/$(shell go env GOOS)_$(shell go env GOARCH)
mv terraform-provider-{{providerName}} ~/.terraform.d/plugins/{{providerAddress}}/{{providerVersion}}/$(shell go env GOOS)_$(shell go env GOARCH)/

# Generate documentation
.PHONY: docs
docs:
go generate ./...

# Run linter
.PHONY: lint
lint:
golangci-lint run ./...
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Terraform Provider {{providerName}}
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

This Terraform provider was generated using [OpenAPI Generator](https://openapi-generator.tech).

## Requirements

- [Terraform](https://www.terraform.io/downloads.html) >= 1.0
- [Go](https://golang.org/doc/install) >= 1.21

## Building The Provider

1. Clone the repository
2. Enter the repository directory
3. Build the provider using the Go `install` command:

```shell
go install
```

## Using the provider

```hcl
terraform {
required_providers {
{{providerName}} = {
source = "{{providerAddress}}"
}
}
}

provider "{{providerName}}" {
endpoint = "{{basePath}}"
}
```

## Developing the Provider

If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (see [Requirements](#requirements) above).

To compile the provider, run `go install`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory.

To generate or update documentation, run `go generate`.

In order to run the full suite of Acceptance tests, run `make testacc`.

```shell
make testacc
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{{>partial_header}}
package client

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)

// Client is the API client used to interact with the API.
type Client struct {
BaseURL string
HTTPClient *http.Client
ApiKey string
Token string
Username string
Password string
}

// NewClient creates a new API client with the given base URL.
func NewClient(baseURL string) *Client {
return &Client{
BaseURL: baseURL,
HTTPClient: &http.Client{},
}
}

// DoRequest executes an HTTP request and returns the response body.
func (c *Client) DoRequest(ctx context.Context, method, path string, body interface{}) ([]byte, error) {
var reqBody io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("error marshaling request body: %w", err)
}
reqBody = bytes.NewBuffer(jsonBody)
}

req, err := http.NewRequestWithContext(ctx, method, c.BaseURL+path, reqBody)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}

if body != nil {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Accept", "application/json")

// Authentication
if c.Token != "" {
req.Header.Set("Authorization", "Bearer "+c.Token)
} else if c.ApiKey != "" {
req.Header.Set("Authorization", c.ApiKey)
} else if c.Username != "" {
req.SetBasicAuth(c.Username, c.Password)
}

resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
}

return respBody, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{{#operations}}
{{>partial_header}}
package provider

import (
"context"
"fmt"
{{#hasRead}}
"encoding/json"
{{/hasRead}}

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
{{#hasRead}}
"github.com/hashicorp/terraform-plugin-log/tflog"
{{/hasRead}}

"{{gitHost}}/{{gitUserId}}/{{gitRepoId}}/internal/client"
)

var _ datasource.DataSource = &{{resourceClassName}}DataSource{}

func New{{resourceClassName}}DataSource() datasource.DataSource {
return &{{resourceClassName}}DataSource{}
}

type {{resourceClassName}}DataSource struct {
client *client.Client
}

func (d *{{resourceClassName}}DataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_{{resourceName}}"
}

func (d *{{resourceClassName}}DataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Fetches a {{resourceName}} data source.",
Attributes: map[string]schema.Attribute{
{{#tfAttributes}}
"{{terraformName}}": schema.{{#isString}}String{{/isString}}{{#isInt64}}Int64{{/isInt64}}{{#isFloat64}}Float64{{/isFloat64}}{{#isBool}}Bool{{/isBool}}{{^isString}}{{^isInt64}}{{^isFloat64}}{{^isBool}}String{{/isBool}}{{/isFloat64}}{{/isInt64}}{{/isString}}Attribute{
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Data source schema treats every OpenAPI isRequired field as a Required input, and all others as Computed. OpenAPI required fields describe response shape, not user configuration; this forces users to provide response fields and breaks data source lookups. Data sources should only mark lookup inputs as Required/Optional and mark response fields as Computed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/terraform-provider/data_source.mustache, line 40:

<comment>Data source schema treats every OpenAPI `isRequired` field as a Required input, and all others as Computed. OpenAPI required fields describe response shape, not user configuration; this forces users to provide response fields and breaks data source lookups. Data sources should only mark lookup inputs as Required/Optional and mark response fields as Computed.</comment>

<file context>
@@ -0,0 +1,108 @@
+		Description: "Fetches a {{resourceName}} data source.",
+		Attributes: map[string]schema.Attribute{
+{{#tfAttributes}}
+			"{{terraformName}}": schema.{{#isString}}String{{/isString}}{{#isInt64}}Int64{{/isInt64}}{{#isFloat64}}Float64{{/isFloat64}}{{#isBool}}Bool{{/isBool}}{{^isString}}{{^isInt64}}{{^isFloat64}}{{^isBool}}String{{/isBool}}{{/isFloat64}}{{/isInt64}}{{/isString}}Attribute{
+{{#isRequired}}
+				Required:    true,
</file context>
Fix with Cubic

{{#isRequired}}
Required: true,
{{/isRequired}}
{{^isRequired}}
Computed: true,
{{/isRequired}}
Description: "{{description}}",
},
{{/tfAttributes}}
},
}
}

func (d *{{resourceClassName}}DataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

c, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *client.Client, got: %T.", req.ProviderData),
)
return
}

d.client = c
}

{{#hasRead}}
func (d *{{resourceClassName}}DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var config {{resourceClassName}}Model

resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}

{{#readHasPathParams}}
respBody, err := d.client.DoRequest(ctx, "{{readMethod}}", fmt.Sprintf("{{readPath}}", config.{{idFieldExported}}{{idFieldValueAccessor}}), nil)
{{/readHasPathParams}}
{{^readHasPathParams}}
respBody, err := d.client.DoRequest(ctx, "{{readMethod}}", "{{readPath}}", nil)
{{/readHasPathParams}}
if err != nil {
resp.Diagnostics.AddError("Error reading {{resourceName}}", err.Error())
return
}

var result client.{{responseModel}}
if err := json.Unmarshal(respBody, &result); err != nil {
resp.Diagnostics.AddError("Error parsing response", err.Error())
return
}

config.FromClientModel(&result)

tflog.Trace(ctx, "read {{resourceName}} data source")
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
}
{{/hasRead}}
{{^hasRead}}
func (d *{{resourceClassName}}DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
resp.Diagnostics.AddError("Not Supported", "Read is not supported for {{resourceName}}")
}
{{/hasRead}}
{{/operations}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
*.dll
*.exe
*.exe~
*.dylib
*.so
*.test
*.out
*.tfstate
*.tfstate.*
.terraform/
terraform.tfvars
terraform-provider-{{providerName}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module {{gitHost}}/{{gitUserId}}/{{gitRepoId}}

go 1.22

toolchain go1.24.4

require (
github.com/hashicorp/terraform-plugin-framework v1.17.0
github.com/hashicorp/terraform-plugin-log v0.10.0
github.com/hashicorp/terraform-plugin-testing v1.14.0
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{{>partial_header}}
package main

import (
"context"
"flag"
"log"

"github.com/hashicorp/terraform-plugin-framework/providerserver"

"{{gitHost}}/{{gitUserId}}/{{gitRepoId}}/internal/provider"
)

var (
version string = "{{providerVersion}}"
)

func main() {
var debug bool

flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()

opts := providerserver.ServeOpts{
Address: "{{providerAddress}}",
Debug: debug,
}

err := providerserver.Serve(context.Background(), provider.New(version), opts)

if err != nil {
log.Fatal(err.Error())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{>partial_header}}
package client
{{#models}}
{{#model}}

// {{classname}} - {{{description}}}{{^description}}{{classname}} struct{{/description}}
type {{classname}} struct {
{{#vars}}
{{name}} {{#isArray}}[]{{#items}}{{dataType}}{{/items}}{{/isArray}}{{^isArray}}{{#isMap}}map[string]{{#items}}{{dataType}}{{/items}}{{/isMap}}{{^isMap}}{{dataType}}{{/isMap}}{{/isArray}}{{{vendorExtensions.x-go-datatag}}}
{{/vars}}
}
{{/model}}
{{/models}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
Loading
Loading