Configuration
Terraform Provider generation works by extracting a set of resources from your OpenAPI spec, the properties that make up that resource, annotations to those properties, and the methods used to Create, Read, Update and Destroy those resources.
This graph is then compiled into a Terraform Provider.
A set of required annotations exist to help guide the terraform provider generation process, and a set of "override" extensions exist for when you want to induce certain behaviours in terraform.
x-speakeasy-entity
The x-speakeasy-entity
annotation is used to specify which objects in your OpenAPI spec represent a Terraform entity
. This annotation is required for every object that you want to include in your Terraform provider.
This annotation is only applicable to object
types.
Every property in the x-speakeasy-entity
annotated object will become available at the root of the terraform resource.
Example
The annotation, as applied to an Order
object below, will become available like so:
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 Properties
Speakeasy will infer the following annotations from your annotated JSON schema, without additional effort:
- If a property is marked as
required
, it will be marked asOptional: false
in the Terraform schema (and vice versa). I.e. if it is not specified, it will be a run-time error when invoking the provider - If a property is marked as
readonly
, it will be marked asComputed: true
in the Terraform schema. I.e. it will be read-only in Terraform, and will not allow a user to specify it. Alternatively, don't define it in a Request Type, but do in a Response Type for the same effect.
Effect of varying the "depth" of the annotation
The x-speakeasy-entity
annotation is an "informative" extension. Where it is used varies the way that a terraform provider is generated. For instance, if it is used on a top-level object (e.g. a Create response body), then all properties under it will be available as nested objects. If it is used deeper down, every property defined "above" the extension will be flattened into the object.
For example, in this case 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 instead apply the x-speakeasy-entity annotation lower down, we inline the object, maintaining any objects "above" the annotation 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.
I.e.
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
Would be usable like:
resource "yourprovider_pet" "example" {
id = 123123
name = "Filter Blend"
}
And the following would unify 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"
}
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
: i.e. any change to them will force a full Destruction / Recreation 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"
}
x-speakeasy-entity-operation
The x-speakeasy-entity-operation
annotation is used 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
Behaviour when some operations are missing
- If an
Entity:create
operation exists, the entity will be made available as a terraform "resource". - If an
Entity:get
operation exists, theEntity:get
operation will be used regularly to ensure the resource is consistent with terraform state, and to enhance the terraform state with attributes if the most recent entity state is not returned from aEntity:create
orEntity:update
invocation. - If an
Entity:update
operation exists, the entity will be made into a terraform "resource" withupdate
support. I.e. if it does not exist, then all attributes areForceNew
. - If an
Entity:delete
operation exists, the entity will be made into a terraform "resource" withdelete
support. I.e. if it does not exist, then no effect will happen when a user deletes the. 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. There will be a generation error informing you of available root level properties. This will rename the API parameters such that 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
x-speakeasy-param-readonly
If this extension is set on any type, it will be marked as Readonly. I.e. 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
x-speakeasy-param-optional.
If this extension is set on any type, it will be marked as Optional. I.e. 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
x-speakeasy-param-force-new
If this extension is set on any type, it will be marked as ForceNew
. I.e. 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
x-speakeasy-param-sensitive
If this extension is set on any type, it will be marked as Sensitive
. I.e. 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