Create Your Terraform Provider From OpenAPI / Swagger
Introduction
Terraform is an infrastructure-as-code tool that uses providers to manage cloud infrastructure via API calls. Creating and maintaining these providers, typically written in Go, can be challenging due to the need for specialized skills and continuous updates with API changes.
Speakeasy offers a streamlined solution by generating Terraform providers directly from OpenAPI specifications. This approach minimizes the need for Go expertise, ensures providers are consistently updated, and simplifies the development and maintenance of Terraform providers for complex cloud environments.
What Does Speakeasy Generate?
Using the Terraform Plugin Framework (opens in a new tab), Speakeasy generates a fully functional Terraform provider from an annotated OpenAPI spec.
Provider Components
Provider Components | Speakeasy Generation |
---|---|
Resource Schemas | Generated by adding the x-speakeasy-entity-operation: MyEntity#create to any operation in the OpenAPI spec. Hoists and merges JSON schemas associated with all create, read, update, and delete operations appropriately. |
Data Source Schemas | Generated by adding the x-speakeasy-entity-operation: MyEntity#read to any operation in the OpenAPI spec. Hoists and merges JSON schemas associated with all read operations into a single data source. |
Create Methods | Generated by adding the x-speakeasy-entity-operation: MyEntity#create to any operation in the OpenAPI spec. Speakeasy generates platform connector logic to invoke all API requests and save responses to the Terraform state. |
Read Methods | Generated by adding the x-speakeasy-entity-operation: MyEntity#read to any operation in the OpenAPI spec. Speakeasy generates platform connector logic to invoke all API requests and save responses to the Terraform state. These operations are also invoked and connected to state in the CREATE and UPDATE methods should some data attributes only be available by making an additional API call. By defining these, drift detection and import are enabled on your Terraform provider for all associated API response attributes. |
Update Methods | Generated by adding the x-speakeasy-entity-operation: MyEntity#update to any operation in the OpenAPI spec. Speakeasy generates platform connector logic to invoke all API requests and save responses to the Terraform state. All attributes available in a create request but unavailable in an update request are associated with a plan modifier that forces a resource recreation when changed. |
Delete Methods | Generated by adding the x-speakeasy-entity-operation: MyEntity#delete to any operation with all non-optional API request properties available in the OpenAPI spec. Speakeasy generates platform connector logic to invoke all API requests and save responses to the Terraform state. |
Plan Validators | Generated when using restricted OpenAPI data types such as JSON fields, date fields, date-time fields, and OpenAPI validations. Also generated for some specific Speakeasy extensions when a customer requests it (for example, x-speakeasy-conflicts-with ). |
Plan Modifiers | Generated when needed to ensure API and the terraform state and terraform plan commands have the appropriate semantics, for instance, to work around diff-detection issues. |
Resource Imports | Generated by adding the x-speakeasy-entity-operation: MyEntity#read to an operation with a single ID field in the OpenAPI spec. |
What Does Speakeasy Support?
Speakeasy follows OpenAPI semantics when generating a provider. In most simple CRUD APIs, very few annotations are required on your OpenAPI specifications, with all of the following inferred from the specification.
Supported OpenAPI Semantics
OpenAPI Semantics | Speakeasy Support / Comment | |
---|---|---|
Resource Schemas (const) | ✅ | When an attribute in an OpenAPI specification is specified as const , it is removed from a Terraform schema and always sent in the request or assumed in the response. |
Resource Schemas (default) | ✅ | When an attribute specifies a default value, this value will be used whenever that attribute is not explicitly set (for example, if it is unset in the Terraform configuration), ensuring consistency and reliability, especially important as users often use older provider versions and server developers may adjust defaults. |
Server Configuration | ✅ | A server_url variable is available in the provider to enable it to be invoked against any API server, defaulted to the first entry in the servers field of your OpenAPI specification. |
Global Authentication | ✅ | Every authentication mechanism that relies on static authorization is supported with its values automatically available to be configured in the provider configuration. For OAuth, a custom hook containing your authentication flow logic needs to be written. |
Query Parameter Serialization | ✅️ | Full support. All query parameter attributes will be available as resource or data source attributes in a Terraform-native form. Might require remapping through x-speakeasy-match . |
Request Headers | ✅️ | Full support. All request attributes will be available as resource or data source attributes in a Terraform-native form. |
Multiple API Requests in One CRUD Step | ✅ | Full support. For example, create requires two API calls. |
JSON Schema type: string | ✅ | Full support |
JSON Schema type: number | ✅ | Full support |
JSON Schema type: integer | ✅ | Full support |
JSON Schema type: boolean | ✅ | Full support |
JSON Schema type: object | ✅ | Full support |
JSON Schema type: null | ✅ | Full support |
JSON Schema required: [requiredPropertyA, ...] | ✅ | Full support. Combined into Required or Optional Terraform attribute modifiers. |
JSON Schema enum: [...values...] | ✅ | Full support. A plan validator is added to assert that only one of the predefined values is set. |
JSON Schema type: array | ✅ | Full support. |
JSON Schema type: array, minItems: N | ✅ | Full support. A plan validator is added to ensure the Terraform ListAttribute has N items set. |
JSON Schema type: array, maxItems: J | ✅ | Full support. A plan validator is added to ensure the Terraform ListAttribute has J items set. |
JSON Schema type: array, uniqueItems: true | ✅ | Full support. A plan validator is added to ensure the Terraform ListAttribute has all items as unique values. |
JSON Schema type: number, format: float | ✅ | Full support. |
JSON Schema type: number, format: double | ✅ | Full support. |
JSON Schema type: integer, format: int32 | ✅ | Full support. |
JSON Schema type: integer, format: int64 | ✅ | Full support. |
JSON Schema type: string, format: date | ✅ | Full support. A plan validator is added to ensure that this string value is set to a date in the YYYY-MM-DD format, for example, "2022-01-30" . |
JSON Schema type: string, format: date-time | ✅ | Full support. A plan validator is added to ensure that this String value is RFC 3339-compatible, for example, "2019-10-12T07:20:50.52Z" . |
JSON Schema format: binary | ✅ | Full support. Accessible as a string attribute |
JSON Schema nullable: true | ✅ | Full support. Combined into Required or Optional Terraform attribute modifiers. |
JSON Schema additionalProperties: true | ✅ | Full support. Note that a free-form object without additionalProperties: true is treated as an empty object. |
JSON Schema additionalProperties: ${JSON Schema} | ✅ | Full support. |
JSON Schema oneOf: [${JSON Schema}, ...] | ✅ | Full support. Represented as a nested object with one child attribute for each oneOf subschema. A plan validator is added that asserts only one child attribute can be set. |
JSON Schema anyOf: [${JSON Schema}, ...] | ✅ | Full support. Considered the same as oneOf . |
JSON Schema allOf: [${JSON Schema}, ...] | ✅ | Full support. Constructs an "uber-type" by merging the superset of all subschemas. |
JSON Schema "Any" Type | ✅ | Full support. Requires the x-speakeasy-type-override: any annotation. Used as an escape hatch. |
OpenAPI readOnly: true | ✅ | Full support. |
OpenAPI writeOnly: true | ✅ | Full support. |
Example Generation | ✅ | Full support. Propagates example and examples into generated Terraform resource examples. Uses a type-appropriate value for other cases. |
Massaging of Divergent API and Schema Types | ⚠️ | Partial support. Speakeasy supports many different mechanisms to configure the reconciliation of divergent Terraform types and the merging of multiple API requests into a single resource schema, even when there's type divergence across APIs. Reach out to us on Slack support for more information or look at these advanced examples (opens in a new tab) that describe some common scenarios. |
What Are the Limitations of Speakeasy Terraform Provider Generation?
Speakeasy covers a wide range of OpenAPI features and we're constantly working to support more. The following outlines the current limitations of our Terraform provider generation product:
Limitations
OpenAPI Semantics | Speakeasy Support / Comment | |
---|---|---|
Operation-Specific Authentication | ⚠️ | Speakeasy supports all OpenAPI global authentication mechanisms but currently doesn't support overriding global authentication on certain specific operations without advanced techniques like monkey patching. Ensure that all API operations are authenticated through global security configuration. |
label / matrix Path Parameter Serialization | ⚠️ | No support for label or matrix path parameter styles. Might require remapping through x-speakeasy-match when path parameter names for Read , Update , or Delete do not match Create attribute names. |
XML Request Body Serialization | ⚠️ | Full support for all JSON data types, multipart encoding, binary, form data, file uploads, URL-encoded form methods, plain text, and raw byte entries. No support for XML or other methods. All request attributes will be available as resource or data source attributes in a Terraform-native form. |
XML Response Body Deserialization | ⚠️ | Full support for all JSON data types, plain text, and raw bytes. No support for other media types. All response attributes will be made available in the Terraform state. |
Circular References | ⚠️ | Partial support through x-speakeasy-type-override: any to enable an attribute to be set with jsonencode(...arbitrary data...) . However, the Terraform type schema doesn't support circular dependencies. |
Lists of Lists of Primitives | ⚠️ | Partial support through x-speakeasy-type-override: any to enable an attribute to be set with jsonencode(...arbitrary data...) . However, the Terraform ListAttribute can only contain primitive item types. To support lists of lists of attributes, use an escape hatch like x-speakeasy-ignore or x-speakeasy-type-override: any . |
Note: Circular References
The Terraform Type
schema must be a directed acyclic graph. OpenAPI's use of JSON Schema allows for circular references to be defined. When Speakeasy detects a circular reference, it will output an error and instructions for a few workarounds.
To prevent errors due to circular references, apply x-speakeasy-type-override: any
to the attribute causing the circular reference, and the attribute will be inferred as a string that satisfies JSON. A Terraform user can then use the jsonencode
function to pass arbitrary data into the attribute.
Terraform Framework Types From JSON Schema Types
Speakeasy generates the following Terraform Framework types (opens in a new tab) based on JSON Schema type
and format
:
JSON Schema Type | JSON Schema Additions | Terraform Framework Type | Notes |
---|---|---|---|
type: string | - | schema.StringAttribute | |
type: number | - | schema.NumberAttribute | |
type: integer | - | schema.Int64Attribute | |
type: boolean | - | schema.BoolAttribute | |
type: array | - | schema.ListAttribute or Schema.ListNestedAttribute | A ListAttribute is used when the items has primitive type . |
type: array | format: set | schema.SetAttribute or Schema.SetNestedAttribute | A SetAttribute is used when the items has primitive type . |
type: object | properties: {...} | schema.SingleNestedAttribute | |
type: object | additionalProperties: {...} | schema.MapAttribute or schema.MapNestedAttribute | A MapAttribute is used when the additionalProperties has primitive type . |
type: null | N/A | Element ignored |
Speakeasy handles JSON Schema composition (opens in a new tab) keywords with the following behaviors:
JSON Schema Subschema Type | Handling |
---|---|
oneOf | A schema.SingleNestedAttribute is created with one key for each oneOf child. If a discriminator is defined, the discriminator is used to determine the name of the subkey. A plan validator is defined to validate that only one subattribute is used at a time. |
anyOf | Considered the same as oneOf . Speakeasy does not strictly enforce this subschema type, as it has not been observed being used correctly in production environments. |
allOf | Merges all subschemas. When the subschemas are objects, it creates a composite object incorporating all properties from the child schemas. |
1. Prerequisites
To get started with creating the Speakeasy Terraform provider, you need:
- Speakeasy CLI
- An API spec in a supported format:
Spec Format | Supported |
---|---|
OpenAPI 3.0 | ✅ |
OpenAPI 3.1 | ✅ |
JSON Schema | ✅ |
Postman Collection | 🔜 |
TIP
If you are using an unsupported spec format, use these tools to help you convert to a supported format:
2. Add Annotations
Annotate objects representing Terraform entities with x-speakeasy-entity
to determine their inclusion in the Terraform provider.
paths: /pet: post: ... x-speakeasy-entity-operation: Pet#create ...Pet: x-speakeasy-entity: Pet ...
Terraform Usage:
resource "petstore_pet" "myPet" { ...}
Speakeasy infers Terraform types from your JSON schema, focusing on the semantics of the CREATE
and UPDATE
requests and responses. You don't need to define any specific Terraform types in your OpenAPI spec.
- Required vs. Optional: If a property is required in the
CREATE
request body, it's marked asRequired: true
; otherwise, it'sOptional: true
. - Computed Properties: Properties that appear in a response body but are absent from the
CREATE
request are marked asComputed: true
. This indicates that Terraform will compute the properties' values. - The
ForceNew
Property: If a property exists in theCREATE
request but is not present in theUPDATE
request, it's labeledForceNew
. - Enum Validation: When an attribute is defined as an enum, Speakeasy configures a
Validator
for runtime type checks. This ensures that all request properties precisely match one of the enumerated values. READ
,UPDATE
, andDELETE
Dependencies: Every parameter essential forREAD
,UPDATE
, orDELETE
operations must either be part of theCREATE
API response body or be consistently required in theCREATE
API request. This ensures that all necessary parameters are available for these operations.
TIP
Use additional x-speakeasy
annotations to customize your provider as necessary.
4. Enhance Generated Documentation
Speakeasy helps you autogenerate documentation using the HashiCorp terraform-plugin-docs
tools and packages. For best results, we recommend:
- Include Descriptions: Ensure your OpenAPI spec contains detailed descriptions of resources, attributes, and operations. Clear and concise descriptions help users understand the purpose and use of each component.
- Provide Examples: Use examples in your OpenAPI spec to illustrate how resources and attributes should be configured. Speakeasy leverages these examples to generate usage snippets that users can refer to when starting with your provider.
The Swagger Pet Store generates a usage snippet for the pet resource like the following:
5. Generate Terraform
- Run the Speakeasy
quickstart
command:
speakeasy quickstart
- Follow the interactive guide and provide the necessary information when prompted, including the path to spec. Make sure to choose
terraform
as the language of choice. Once you've finished the quickstart, you can regenerate the Terraform provider at any point by runningspeakeasy run
.
Frequently Asked Questions
Do the generated Terraform providers support the ability to import resources?
Yes, generated Terraform providers do support the ability to import resources. However, there are specific prerequisites and considerations to keep in mind:
Prerequisites
-
API Specification: It is essential to have an annotated and type-complete API operation defined for reading each resource in the OpenAPI specification. Tag the operation with
x-speakeasy-entity-operation: MyEntity#read
. -
Comprehensive
READ
Operation: If any attribute of a resource is not defined in theREAD
API, Terraform will set that attribute tonull
during the import process.
Simple Keys
A simple key is a single required ID field and is directly exposed to terraform import
operations. For example, if the pet
resource has a single id
field, the import command will look like this: terraform import petstore_pet.my_pet my_pet_id
.
Handling Composite Keys
Speakeasy natively supports direct import for resources with multiple ID fields. Speakeasy generates code that supports import syntax through a user providing a JSON-encoded object with all required parameters defined. Documentation, and appropriate error messages if this syntax isn't followed will also be generated.
Import Composite Keys by block
An import block is a way to import a resource into the Terraform state by generating a Terraform specification. Using a composite key, the import block will look like this:
terraform plan -generate-config-out=generated.tf
Import composite keys by CLI
To import a resource with composite keys using the Terraform CLI, you can use the terraform import
command:
terraform import my_test_resource.my_example '{ "primary_key_one": "9cedad30-2a8a-40f7-9d65-4fabb04e54ff", "primary_key_two": "e20c40a0-40e8-49ac-b5d0-6e2f41f9e66f" }'