Configuration
Speakeasy extracts resource information from your OpenAPI spec (including resource properties and annotations, and the methods to create, read, update, and destroy those resources) and compiles it into a Terraform provider.
To guide Speakeasy in generating Terraform providers, you need to include a set of required annotations in your OpenAPI spec. Speakeasy also provides a set of "override" extensions you can use to induce certain behaviors in Terraform.
The x-speakeasy-entity
Annotation
Use this required annotation for every object in your OpenAPI spec that represents a Terraform entity to specify that it should be included in the Terraform provider. The x-speakeasy-entity
annotation can only be used with object
types, and every property in the x-speakeasy-entity
annotated object will become available at the root of the Terraform resource.
Example
In the following example, the x-speakeasy-entity
annotation is applied to the Order
object to make it available in the Terraform provider:
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
resource "yourprovider_order" "example" {
name = "Filter Blend"
price = 11.5
}
Inferred Terraform Types
Speakeasy will attempt to automatically infer all Terraform type signatures in your annotated JSON schema from the semantics used to reference an object in the CREATE and UPDATE request and response bodies.
You should not need to define any specific Terraform types in your OpenAPI spec. You only need to provide Speakeasy with information about the semantics of your API.
Speakeasy applies the following rules recursively across an arbitrary JSON Schema type:
- If a property is marked as
required
in the CREATE request body, it will be marked asRequired: true
in the Terraform schema. If a property is not markedrequired
, it will be marked asOptional: true
. This is how the property will be reported to an end user invoking the provider usingterraform plan
. - If a property is returned in a response body but is not available in the CREATE request, it will be marked as
Computed: true
. - If a property is defined in a CREATE request body but not defined in an UPDATE request body, it is marked as
ForceNew
. This means that if the property is changed, the pre-existing resource is destroyed and recreated, rather than updated in place. This behavior is set using aPlanModifier
. - If you define an attribute as an enum, a runtime type check
Validator
is configured to ensure that all request properties must match one of those values exactly. - Every parameter that is required to make a READ, UPDATE, or DELETE call must be returned by the CREATE API call response body, or always specified as required in a CREATE API request body.
Effect of Varying the “Depth” of the Annotation
The x-speakeasy-entity
annotation is an "informative" extension. Its positioning can change how the Terraform provider is generated. For example, if you use the annotation on a top-level object (such as a CREATE response body), all properties under it will be available as nested objects. If you use it on a more deeply nested object, every property defined further up in the extension will be flattened into the object.
In the following example, x-speakeasy-entity: Pet
is applied to the "root" response body. This means that data
will be available as a nested object and name
will be available as a property under that.
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
x-speakeasy-entity-operation: Pet#create
description: Add a new pet to the store
operationId: addPet
requestBody:
content:
application/json:
schema:
type: object
x-speakeasy-entity: Pet
properties:
id:
type: integer
description: Numeric identifier of the Pet.
data:
type: object
properties:
name:
type: string
resource "yourprovider_pet" "example" {
id = 123123
data = {
name = "Filter Blend"
}
}
If we apply the x-speakeasy-entity
annotation lower down, we inline the object, and any objects "above" the annotation are flattened into the object:
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
x-speakeasy-entity-operation: Pet#create
description: Add a new pet to the store
operationId: addPet
requestBody:
content:
application/json:
schema:
type: object
properties:
id:
type: integer
description: Numeric identifier of the Pet.
data:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
resource "yourprovider_pet" "example" {
id = 123123
name = "Filter Blend"
}
Be warned: Any properties "above" the x-speakeasy-entity
are always flattened to their primitive type. This can cause conflicts. Always carefully apply the x-speakeasy-entity
by understanding exactly how you want your users to interact with your API.
To demonstrate, if you apply x-speakeasy-entity
like this:
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
x-speakeasy-entity-operation: Pet#create
description: Add a new pet to the store
operationId: addPet
requestBody:
content:
application/json:
schema:
type: object
properties:
id_label:
type: object
properties:
id:
type: string
data:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
The object would be usable like this:
resource "yourprovider_pet" "example" {
id = 123123
name = "Filter Blend"
}
The following unifies the two name
attributes, such that a user defines one attribute but it will be set twice in the object request.
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
x-speakeasy-entity-operation: Pet#create
description: Add a new pet to the store
operationId: addPet
requestBody:
content:
application/json:
schema:
type: object
properties:
id_label:
type: object
properties:
name:
type: string
data:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
resource "yourprovider_pet" "example" {
name = "Filter Blend"
}
This would invoke the create API call with the following:
{
"id_label": {
"name": "Filter Blend"
},
"data": {
"name": "Filter Blend"
}
}
Create Request Parameters
Similar to "parent" objects above the x-speakeasy-entity
annotation, request parameters are inlined into the Terraform resource, though these are always marked as ForceNew
when required
: Any change to these request parameters will force a full destroy-and-recreate cycle.
For instance, the following:
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
x-speakeasy-entity-operation: Pet#create
parameters:
- in: query
name: dryRun
schema:
type: boolean
required: true
description: Add a new pet to the store
operationId: addPet
requestBody:
content:
application/json:
schema:
x-speakeasy-entity: Pet
type: object
properties:
name:
type: string
Would enable the following resource interaction:
resource "yourprovider_pet" "example" {
dry_run = true
name = "Filter Blend"
}
The x-speakeasy-entity-operation
Annotation
Use the x-speakeasy-entity-operation
annotation to specify which endpoints in your OpenAPI spec are used to create, read, update, or delete a Terraform entity
.
The value of this annotation is a string in the format of Entity#operation,operation,...
, where Entity
is the name of the entity, and operation
is one of create
, get
, update
, or delete
, or multiple of these concatenated with commas.
Example
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
Behavior of Operations
- If an
Entity:create
operation exists, the entity will be made available as a Terraform resource. - If an
Entity:get
operation exists, it will be used regularly to ensure the resource is consistent with the Terraform state, and to enhance the Terraform state with attributes if the most recent entity state is not returned from anEntity:create
orEntity:update
invocation. - If an
Entity:update
operation exists, the entity will be made into a Terraform resource withupdate
support. IfEntity:update
does not exist, all attributes areForceNew
. - If an
Entity:delete
operation exists, the entity will be made into a Terraform resource withdelete
support. IfEntity:delete
does not exist, then nothing will happen when a user deletes the resource. This is often a bad practice but might be suitable for update-only resources. - If an
Entity:create,update
operation exists, the API is assumed to be idempotent, and this API will be called both when an attribute changes and if the object is new.
API Parameters
When an API parameter exactly matches an object property on the root level, no additional changes are required.
However, if an API parameter does not match an object property on the root level, the x-speakeasy-match
annotation must be used. A generation error will inform you of available root-level properties. The x-speakeasy-match
annotation will rename the API parameters and the API parameter will be set to an object available on the Terraform state.
Example
The following example would rewrite the petId
parameter to take it from id
on the Terraform state.
paths:
/pet/{petId}:
delete:
tags:
- pet
summary: Delete the pet
parameters:
- in: path
name: petId
schema:
type: integer
required: true
x-speakeasy-match: id
x-speakeasy-entity-operation: Pet#delete
The x-speakeasy-param-readonly
Extension
If this extension is set on any type, it will be marked as read-only. If a user attempts to specify it, a runtime error is raised.
Example
components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-readonly: true
The x-speakeasy-param-optional
Extension
Set this extension on any type to mark it as optional. This will override required
from the JSON Schema specification.
Example
components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-optional: true
The x-speakeasy-param-force-new
Extension
Set this extension on any type to mark it as ForceNew
. Any change will cause a full object recreation.
Example
components:
schemas:
Pet:
type: object
properties:
name:
type: string
id:
type: integer
x-speakeasy-param-force-new: true
The x-speakeasy-param-sensitive
Extension
Set this extension on any type to mark it as Sensitive
. It will be masked in the Terraform state and when printed on the console.
Example
components:
schemas:
Pet:
type: object
properties:
name:
type: string
secret:
type: string
x-speakeasy-param-sensitive: true
The x-speakeasy-terraform-ignore: true
Extension
Set this extension to true to remove this attribute and any interaction with it from the Terraform state.
Example
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
This extension can act as an escape hatch, replacing the attribute with a JSON string that can be provided inline. This is useful to improve the interface to attributes that are not well defined.
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
Usually, Terraform will appropriately detect changes in attributes and only make changes to attributes that have changed. However, in some cases, you might see spurious plan changes for computed attributes that are (known after apply)
.
If this is the case, you can add an aggressive plan modifier to those attributes using x-speakeasy-param-suppress-computed-diff: true
that will only mark the attribute as changing if:
- A GET request is available, and after making it to the API for the resource, the attribute in the plan differs from that in the API response.
- The initial creation API call is unavailable.
This extension is applied recursively downwards to any attributes within the tagged attribute.
Note: 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.
Example
components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
deep:
x-speakeasy-param-suppress-computed-diff: true
type: object
properties:
object:
type: object
properties:
in:
type: object
properties:
here:
type: string
name:
type: string
required:
- name
---
# Is the same as
components:
schemas:
Pet:
x-speakeasy-entity: Pet
type: object
properties:
deep:
x-speakeasy-param-suppress-computed-diff: true
type: object
properties:
object:
x-speakeasy-param-suppress-computed-diff: true
type: object
properties:
in:
x-speakeasy-param-suppress-computed-diff: true
type: object
properties:
here:
x-speakeasy-param-suppress-computed-diff: true
type: string
name:
type: string
required:
- name