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