Stable API¶
tf’s API consists of:
Low-level protocol classes that library consumers are expected to implement (such as
Provider,Resource, andDataSource).Mid-level “glue” for schema definitions, such as
AttributeandSchema.A handful of utility functions and classes, such as
run_provider()andinstall_provider.
As a library consumer and provider author, your primary interaction will be writing classes that satisfy Provider, Resource, and DataSource.
If your provider has a large number of elements, you will likely want to implement your own higher-level abstractions to stamp out boilerplate class definitions.
Protocols give you the flexibility to do this in a clean way.
However, it’s perfectly fine (if somewhat tedious) to implement each element against it’s protocol individually.
Execution Interface¶
The execution interface provides program entrypoints for OpenTofu to run your provider.
You provider should have a Console Script entrypoint (named terraform-provider-myprovider) pointing to your main function.
Your main function should create an instance of your provider and call run_provider().
- tf.runner.run_provider(provider: Provider, argv: list[str] | None = None)¶
Run the given provider with the given arguments.
- Parameters:
provider – Provider instance to run
argv – Optional arguments to run the provider with
An installation utility is provided to install your provider into the plugins directory.
Warning
Running install_provider(). is only required if you want to install your provider in development mode into your plugin directory.
There are easier ways to test your provider during development, such as setting the TF_CLI_CONFIG_FILE.
- tf.runner.install_provider(host: str, namespace: str, project: str, version: str, plugin_dir: Path, provider_script: Path)¶
Installs the given (host, namespace, project, version) provider into the plugin directory. The provider_script should be the terraform-provider-<project> executable. If the plugin directory does not exist, it will be created.
- Parameters:
host – Host of the provider
namespace – Namespace of the provider
project – Project of the provider
version – Version of the provider
plugin_dir – Directory to install the provider into
provider_script – Path to the provider executable (typically installed as a pip entrypoint)
Provider Interface¶
To implement a provider, you must subclass Provider and implement the methods.
Essentially a Provider answers some questions about itself, allows configuration of itself,
and provides a list of DataSource and Resource classes it supports.
- class tf.iface.Provider(*args, **kwargs)¶
- abstract configure_provider(diags: Diagnostics, config: dict)¶
Called when the provider is configured. This is a good place to set up any global state
- abstract full_name() str¶
Get the full provider name eg terraform.example.com/ex/ex
- abstract get_data_sources() list[Type[DataSource]]¶
Get all the data source types that this provider supports
- get_functions() list[Type[Function]]¶
Get all the function types that this provider supports
- abstract get_model_prefix() str¶
Get the model prefix for all loaded resources
- abstract get_resources() list[Type[Resource]]¶
Get all the resource types that this provider supports
- abstract validate_config(diags: Diagnostics, config: dict)¶
Validate the provider configuration
The AbstractResource represents an element: a data source or a resource.
Both types of elements must be named and have a schema.
- class tf.iface.AbstractResource(*args, **kwargs)¶
- abstract classmethod get_name() str¶
Get the type name for this resource type
Data Sources¶
A DataSource is the simplest element to implement – it’s stateless and only provides data.
A DataSource must implement one method, read(),
and may optionally implement validate_config().
- class tf.iface.DataSource(*args, **kwargs)¶
Bases:
AbstractResource,Protocol- abstract read(ctx: ReadDataContext, config: dict) dict | None¶
Read the data source
- validate(diags: Diagnostics, type_name: str, config: dict)¶
Validate the data source configuration
Resources¶
A Resource is a more complex element to implement – it’s stateful and provides data and actions.
You must implement:
get_name() and, get_schema()
as well as
create(), read(), update(),
and delete() to control the lifecycle of the resource.
You may also optionally implement import_() and plan(),
upgrade(), and validate() to further hook into the lifecycle.
- class tf.iface.Resource(*args, **kwargs)¶
Bases:
AbstractResource,Protocol- abstract create(ctx: CreateContext, planned: dict) dict | None¶
Create the resource, returning the actual state after creation.
This is called when a user runs opentofu apply and the resource needs to be initially created.
- Parameters:
ctx – CreateContext
planned – The planned state of the resource
- abstract delete(ctx: DeleteContext, current: dict)¶
Delete the resource, returning None generally
- import_(ctx: ImportContext, id: str) dict | None¶
Import a resource
This is called when a user runs opentofu import and provides a resource ID to import.
- Parameters:
ctx – ImportContext
id – The resource ID to import
- plan(ctx: PlanContext, current: dict | None, planned: dict) dict | None¶
Modify the resource change plan
- abstract read(ctx: ReadContext, current: dict) dict | None¶
Read the current state of the resource
- abstract update(ctx: UpdateContext, current: dict, planned: dict) dict | None¶
Update the resource to the planned state, returning the actual state after the update
- upgrade(ctx: UpgradeContext, version: int, old: dict) dict | None¶
Upgrade an old resource state to the newest schema version
- validate(diags: Diagnostics, type_name: str, config: dict)¶
Validate the resource configuration
This is called before any other operation to validate the configuration of the resource. You should run parameter validation here. Generate errors and warnings through the diags object.
- Parameters:
diags – Diagnostics
type_name – The type name of the resource
config – The configuration to validate
State¶
State is a snapshot of your resource at a point in time. It is a dictionary of field names to values.
- tf.iface.State¶
State is the current state of a resource. It is a dictionary where field names are mapped to Python values (or None, or Unknown). Resource operations are mostly just pushing around, mutating, and returning State.
Config¶
Config is used instead of State during validation.
- tf.iface.Config¶
Config is like State, except its used in configuration validation and the values are null when they are not bound to a value. This is because the configuration is not yet bound to a resource. This is merely for validating that set of input parameters or values are correct.
Schemas¶
Every resource must be specified with a schema. A schema consists of a version, a set of fields, and a set of block types.
- class tf.schema.Schema(attributes: list[Attribute] | None = None, version: int | None = None, block_types: list[NestedBlock] | None = None, description: str | None = None, description_kind: TextFormat | None = None, deprecated: bool | None = None)¶
A schema is a description of the data model for a resource/data source/provider.
- Parameters:
attributes – List of attributes
version – Version of the schema
block_types – List of nested block types
Example:
from tf.schema import Schema, Attribute from tf.types import Number schema = schema.Schema( version=2, attributes=[ schema.Attribute("a", types.Number(), required=True), schema.Attribute("b", types.Number(), required=True, requires_replace=True), schema.Attribute("sum", types.Number(), computed=True), ], )
Attributes¶
An attribute is a field in a schema. It ties a field name to a type and a set of behaviors.
- class tf.schema.Attribute(name: str, type: TfType, description: str | None = None, required: bool | None = False, optional: bool | None = False, computed: bool | None = False, sensitive: bool | None = False, description_kind: TextFormat | None = None, deprecated: bool | None = None, requires_replace: bool | None = None, read_only: bool | None = False, default: Any = Unknown)¶
An attribute is a single field in a schema.
- Parameters:
name – Name of the attribute
type – Type of the attribute
description – Description of the attribute
required – Required?
optional – Optional?
computed – Computed?
sensitive – Sensitive?
description_kind – Description kind (defaults to Markdown)
deprecated – Deprecated?
requires_replace – Should a change of this value require a replace of the resource?
default – If this value is computed but not set, this will be the default value in the change plan
Blocks¶
Blocks are a way to group fields together in a schema. They are akin to sub-resources. Blocks define their own attributes and may have nested blocks.
Types¶
All types must implement the TfType interface.
- class tf.types.TfType(*args, **kwargs)¶
- abstract decode(value: Any) Any¶
Decode the tf-serializable representation into the python representation
- abstract encode(value: Any) Any¶
Encode the python representation into the tf-serializable
- semantically_equal(a_decoded, b_decoded) bool¶
Check if two Python-types (represented by the implementing type) are semantically equal. For Integers, ints will be passed in, and so on.
- abstract tf_type() bytes¶
Return the TF type pattern
A handful of types are provided for you:
- class tf.types.Bool(*args, **kwargs)¶
True or False. Maps to Python bool.
- class tf.types.Number(*args, **kwargs)¶
Numbers are numeric values. They can be integers or floats. Maps to Python int or float.
Usually this is fine, but if you need to distinguish between the two you must do it in your Resource CRUD implementation.
- class tf.types.String(*args, **kwargs)¶
Strings are sequences of characters. Maps to Python str.
- class tf.types.List(element_type: TfType)¶
Lists are ordered collections of homogeneously-typed values. Maps to Python list.
- Parameters:
element_type – The type of the elements in the list.
- class tf.types.Map(element_type: TfType)¶
Maps are collections of string-keyed, homogeneously-typed values. Maps to Python dict.
- Parameters:
element_type – The type of the elements in the map.
- class tf.types.Set(element_type: TfType)¶
Sets are collections of homogeneously-typed values. Sets are represented as lists in Python because TF Sets can have object values, which Python doesn’t like. Maps to Python list.
OK in TF, Bad in Python: set(({“a”: 123},))
Result: TypeError: unhashable type: ‘dict’
- Parameters:
element_type – The type of the elements in the set.
Several utility types are also provided:
Unknown¶
Unknown is a special value that represents a value that is not known at plan time.
Unknown is a fundamental part of TF’s design and is used extensively in the planning phase.
TF makes a distinction between null and unknown. tf represents them with None and tf.types.Unknown respectively.
- tf.types.Unknown¶
Unknown is a sentinel value that represents a value that is not yet known. You will find these in a state plan.