The x-speakeasy-entity Annotation

Add the x-speakeasy-entity annotation to objects in your OpenAPI spec to include them as entities in the Terraform provider.

As a component:


components:
schemas:
Order:
description: An order helps you make coffee
x-speakeasy-entity: Order
properties:
id:
type: integer
description: Numeric identifier of the order.
name:
type: string
description: Product name of the coffee.
price:
type: number
description: Suggested cost of the coffee.
required:
- name
- price
type: object

Or inline in a path:


paths:
/order:
post:
tags:
- Order
summary: Create a coffee order
x-speakeasy-entity-operation: Order#create
requestBody:
content:
application/json:
schema:
x-speakeasy-entity: Order
properties:
id:
type: integer
description: Numeric identifier of the order.
name:
type: string
description: Product name of the coffee.
price:
type: number
description: Suggested cost of the coffee.
required:
- name
- price
type: object


resource "yourprovider_order" "example" {
name = "Filter Blend"
price = 11.5
}

Where you place the x-speakeasy-entity annotation affects the Terraform provider structure.

  • At the top level: Properties are nested objects.
  • At a lower Level: Properties above the annotation are flattened.

Top Level


Pet:
x-speakeasy-entity: Pet
...


resource "yourprovider_pet" "example" {
data = { name = "Filter Blend" }
}

Lower Level


Pet:
properties:
data:
x-speakeasy-entity: Pet
...


resource "yourprovider_pet" "example" {
name = "Filter Blend"
}

Warning Icon

Warning

Properties above the x-speakeasy-entity annotation are flattened, which could cause conflicts. Apply the annotation carefully to align the structure of the Terraform provider with your API's intended user interaction.

The x-speakeasy-entity-operation Annotation

The x-speakeasy-entity-operation annotation specifies CRUD (create, read, update, and delete) operations associated with each endpoint in your OpenAPI spec for a Terraform entity.

Use Case

Use the x-speakeasy-entity-operation annotation to precisely map API capabilities to Terraform resource management and ensure each API endpoint is accurately represented in Terraform operations for consistent behavior with the API.

Format

The value is structured as Entity#operation,operation,...#order:

  • Entity represents the name of the entity.
  • operation can be one or more of create, get, update, and delete, concatenated with commas.
  • order is optional and can be used to define a second API call that should be invoked for a given CRUD invocation.

Behavior of Operations

  • Entity:create makes the entity a Terraform resource.
  • Entity:read ensures consistency with Terraform state and updates attributes.
  • Entity:update provides update support for the resource. Without it, attributes are ForceNew.
  • Entity:delete enables deletion of the resource. Without it, no action is taken on deletion.
  • Entity:create,update (Idempotent Operations) indicates the API is idempotent. Combine these operations to allow for the same API call to both create new objects and update existing ones, depending on attribute changes.

paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
x-speakeasy-entity-operation: Pet#create
/pet/{petId}:
get:
tags:
- pet
summary: Info for a specific pet
x-speakeasy-entity-operation: Pet#read
update:
tags:
- pet
summary: Update the pet
x-speakeasy-entity-operation: Pet#update
delete:
tags:
- pet
summary: Delete the pet
x-speakeasy-entity-operation: Pet#delete

One API Operation, Multiple Resources

You can optionally annotate one operation with multiple entity annotations. This is useful, for example, when a single API call is used across multiple resources, or annotates or adds additional information to operations.


parameters:
- in: query
name: id
required: false
schema:
type: string
operationId: GetMetadataForResourceOrGroup
x-speakeasy-entity-operation:
- Resource#read#2
- Group#read#2

The x-speakeasy-match Annotation

The x-speakeasy-match annotation adjusts the API parameter name to align with a Terraform state property. If mismatches occur, a generation error will highlight appropriate root-level properties for accurate mapping.

Use Case

Use x-speakeasy-match when an API parameter doesn't match a root-level object property.


paths:
/pet/{petId}:
delete:
parameters:
- name: petId
x-speakeasy-match: id
x-speakeasy-entity-operation: Pet#delete

The x-speakeasy-param-readonly Extension

The x-speakeasy-param-readonly extension marks a property as read-only. Any user attempt to modify it in Terraform will result in a runtime error.

Use Case

Use this extension to prevent unintended changes to critical properties in Terraform configurations.


components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-readonly: true

The x-speakeasy-param-optional Extension

Apply x-speakeasy-param-optional to any property to designate it as optional. This extension takes precedence over the required attribute in the JSON Schema specification.

Use case

Provides flexibility in Terraform configurations by allowing optional settings for certain properties.


components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-optional: true

The x-speakeasy-param-force-new Extension

Properties marked with this extension will cause the associated Terraform resource to be destroyed and recreated whenever the property value changes. This setting ensures that any alteration to the property triggers a complete recreation of the object.

Use Case

Use the x-speakeasy-param-force-new extension to mark properties that, when altered, require the creation of a new resource instance for proper application.


components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-force-new: true

The x-speakeasy-param-sensitive Extension

Properties marked as sensitive will be obscured in the Terraform state and concealed when displayed in the console. This ensures the confidentiality of sensitive data within Terraform operations.

Use Case

This extension is ideal for handling confidential properties like passwords, tokens, or personal data in Terraform by hiding sensitive information from logs and state files.


components:
schemas:
Pet:
type: object
properties:
name:
type: string
secret:
type: string
x-speakeasy-param-sensitive: true

The x-speakeasy-terraform-ignore: true Extension

When set to true, this extension ensures that the specified property and any interactions involving it are omitted from Terraform's state management.

Info Icon

Info

Please note that this extension is used for complete suppression of the property from terraform state. If you are looking to suppress from only the specific operation consider using x-speakeasy-ignore: true. This omits the operation only the annotated CRUD method. E.g. if a field is both in CREATE response body and READ response body, omitting it from READ response body just turns off diff detection for that field. It's still in CREATE response so it'll still be in terraform state

Use Case

This extension is useful for excluding irrelevant, unnecessary, or temporary properties from Terraform's state to keep it clean and focused.


components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
optionalMetadata:
x-speakeasy-terraform-ignore: true
type: string
name:
type: string
required:
- name


resource "petstore_pet" "mypet" {
name = "myPet"
# Attempting to set an ignored parameter results in an error
# optionalMetadata = true
}

The x-speakeasy-type-override: "any" Extension

Set the x-speakeasy-type-override extension to "any" to convert the associated attribute to a JSON string. This allows for inline specification of the attribute's value.

Use Case

Use this extension to accommodate attributes with variable or dynamic structures by allowing the provision of a JSON string inline.


components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
deep:
x-speakeasy-type-override: any
type: object
properties:
object:
type: object
additionalProperties: true
properties:
in:
type: object
properties:
here:
type: string
name:
type: string
required:
- name


resource "petstore_pet" "mypet" {
name = "myPet"
deep = jsonencode({
object = {
with = "anything"
defined = true
}
})
}

The x-speakeasy-param-suppress-computed-diff: true Extension

Setting the x-speakeasy-param-suppress-computed-diff to true suppresses unnecessary Terraform plan changes for computed attributes that are not definitively known until after application (marked as "known after apply"). An attribute is marked as changed only if a GET request confirms a difference between the plan and the API response or the initial creation API call is unavailable. Applies to all nested attributes within the tagged attribute.

Use Case

Useful in scenarios where computed attributes frequently cause spurious plan changes, leading to unnecessary updates in Terraform.


components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
status:
x-speakeasy-param-suppress-computed-diff: true
type: string

Warning Icon

Warning

Applying this modifier when x-speakeasy-entity-operation: my_resource#read is not defined may result in drift between the Terraform plan and remote state should updates to attributes happen outside of Terraform changes. Please only apply this when necessary.

The x-speakeasy-conflicts-with Extension

This extension indicates that a property conflicts with another, ensuring that certain combinations of properties are not set together. Accepts relative paths to conflicting attributes.

Use Case

Ideal for situations where certain attributes are mutually exclusive or where setting one attribute invalidates another.


components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
name_prefix:
type: string
x-speakeasy-conflicts-with: name
id:
type: string
generated_name_options:
type: object
properties:
prefix:
type: string
x-speakeasy-conflicts-with:
- ../name_prefix
- ../name
- ../id


resource "example_pet" "happy_pet" {
name = "Mrs Poppy"
name_prefix = "Mrs"
}


$ terraform plan
│ Error: Invalid Attribute Combination
│ with example_pet.happy_pet,
│ on provider.tf line 39, in resource "example_pet" "happy_pet":
│ 3: name_prefix = "test"
│ Attribute "name" cannot be specified when "name_prefix" is specified

The x-speakeasy-plan-validators Extension

Use the x-speakeasy-plan-validators extension to add custom validation logic (opens in a new tab) to Terraform plan operations and ensure configurations meet predefined criteria before execution. Apply this extension to a schema attribute to trigger the automatic generation of a plan validator compatible with the Terraform plugin framework, tailored to the attribute's type. This process enhances the provider's ability to enforce complex validation rules.

Validators created through this extension are organized in a structured directory path beginning with internal/validators/, ensuring a clean and manageable codebase.

Use Case

This extension is essential for scenarios requiring advanced validation logic that JSON Schema cannot accommodate. The extension allows you to implement intricate, context-specific validation strategies, ensuring your Terraform provider operates with enhanced accuracy and reliability.


components:
schemas:
Pet:
type: object
x-speakeasy-entity: Pet
properties:
name:
type: string
age:
type: integer
x-speakeasy-plan-validators: AgeValidator

In this scenario, when Speakeasy next generates the Terraform provider, it will bootstrap a custom validator file located at internal/validators/int64validators/age_validator.go, and import the schema configuration wherever x-speakeasy-plan-validators: AgeValidator is referenced. You can modify the validator file to contain your logic.

Implementation Notes

  1. A plan validator is a type conformant to the terraform-plugin-framework expected interface. A unique plan validator will be bootstrapped in the appropriate subfolder for the Terraform type it is applied to: boolvalidators, float64validators, int64validators, listvalidators, mapvalidators, numbervalidators, objectvalidators, setvalidators, or stringvalidators. Speakeasy will always create and use a file as snake_case.go for a given x-speakeasy-plan-validators value.

  2. A plan validator operates on the raw (untyped) Terraform value types. However, you can convert a Terraform type to a value type Speakeasy manages (type_mytype.go) by using the included reflection utility. This is useful for applying validators to complex types like list, map, object, and set.

  3. While working with a plan validator, you have the ability to perform various tasks, including initiating network requests. However, it's important to ensure that plan validations do not result in any unintended side effects. Please refer to the HashiCorp guidance on plan validator development (opens in a new tab) or reach out in our Slack if you have questions.

  4. It is possible to have an array of plan validators, for example, x-speakeasy-plan-validators: [MinAgeValidator, MaxAgeValidator].

  5. A validator can only be applied to a resource attribute. Validators cannot be applied at the same level as the x-speakeasy-entity annotation because that becomes the "root" of the Terraform resource. However, validators can access or refer to any data in the entire resource (for an example, see the x-speakeasy-conflicts-with validator). The annotation will be ignored for data sources.

  6. Speakeasy regenerations do not delete user-written code. If the validator is no longer in use, it will be ignored (no longer referenced) but the source file will remain. You might want to delete such an orphaned validation file for repository hygiene.

The x-speakeasy-entity-version Extension

The x-speakeasy-entity-version extension specifies the version of a given resource. This is something that should only be applied if you need to write a state migrator: for instance if you are changing the type of a field.

Terraform resource versions are zero-indexed, and default to 0. So for your first breaking change which requires a state migrator, set x-speakeasy-entity-version: 1. Each state migrator function must migrate from the previous version of state.

If this is set, a boilerplate state upgrader will be written and hooked into internal/stateupgraders/your_resource_v1.go. Please refer to the terraform documentation (opens in a new tab) on how to write a state migrator.

Configuring environment values

Through the use of a gen.yaml configuration value, it is possible to set up a default value for provider variables to be pulled in from a user environment variable. This could be useful to (for instance) map a known environment value that will hold an API Key into the provider.


terraform:
environmentVariables:
- env: EXAMPLE_SERVER_URL_FROM_ENV_VAR
providerAttribute: server_url
- env: EXAMPLE_ACCESS_TOKEN
providerAttribute: access_token

environmentVariables is expected to be a list of objects with {env: string, providerAttribute: string} keys/values. These will create associations from environment variables (referenced as env) with provider attributes (referenced as providerAttribute).

Custom Resources or Data Sources

If you would like to include an existing resource that is outside of the speakeasy generated provider, reference it in gen.yaml like so.


terraform:
additionalResources:
- importAlias: custom
importLocation: github.com/custom/terraform-provider-example/src/custom_resource
resource: custom.NewCustomResource
additionalDataSources:
- importAlias: custom
importLocation: github.com/custom/terraform-provider-example/src/custom_datasource
datasource: custom.NewCustomDataSource

additionalResources is expected to be a list of { importLocation?: string, importAlias?: string, resource: string } objects. Each resource will be inserted into the provider resource list. If importLocation / importAlias is defined, we'll add that to the import list at the top of the provider file. resource is arbitrary text, and could contain a function invocation if desired.

additionalDataSources follows the same syntax, but with datasource as the text string to be inserted into the datasource list instead of resource.

To learn more about how to write a terraform resource yourself, please consult the official terraform documentation (opens in a new tab).

Other Keywords and Annotations

Success Icon

Tip

This section is not an exhaustive list of available keyword and annotation options. If you're unsure whether a keyword or annotation is supported, please reach out to our team at support@speakeasyapi.dev.

The anyOf Keyword

Terraform has limited support for the anyOf keyword due to its less flexible type system than JSON Schema. For instance, managing anyOf with multiple subtypes requires a large set of combined types, leading to practical and implementation challenges.

Consider replacing anyOf in your schema with oneOf or allOf. This adjustment aligns with Terraform's capabilities: oneOf for union types and allOf for intersection types.

For more guidance or to discuss schema adaptations, contact our support team at support@speakeasyapi.dev.

The oneOf Keyword

In Terraform, OneOf is defined as a SingleNestedAttribute where each potential child is represented by a unique key. To ensure compliance with oneOf semantics, an object plan validator is used to confirm that only one of these keys is active at any given time.

The allOf Keyword

For allOf, Speakeasy merges all sub-schemas into a single combined attribute, creating a unified schema component that encapsulates all specified properties.