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:

  1. If a property is marked as required in the CREATE request body, it will be marked as Required: true in the Terraform schema. If a property is not marked required, it will be marked as Optional: true. This is how the property will be reported to an end user invoking the provider using terraform plan.
  2. If a property is returned in a response body but is not available in the CREATE request, it will be marked as Computed: true.
  3. 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 a PlanModifier.
  4. 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.
  5. 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 an Entity:create or Entity:update invocation.
  • If an Entity:update operation exists, the entity will be made into a Terraform resource with update support. If Entity:update does not exist, all attributes are ForceNew.
  • If an Entity:delete operation exists, the entity will be made into a Terraform resource with delete support. If Entity: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:

  1. 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.
  2. 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

The x-speakeasy-conflicts-with Extension

This extension is used to mark a property as conflicting with another property. This is useful when you have a property that is only valid when another property is not set.

This property can be applied to either (or both) of the attributes that conflict, with a value of the relative path to the other attribute.

If multiple values are set, all of the paths are checked for conflicts.

Example


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