Provider Functions

Provider functions allow you to expose custom functions that can be called from Terraform configurations. Functions are useful for data transformation, validation, and other operations that don’t fit into the resource or data source paradigms.

Overview

Functions in Terraform providers:

  • Accept zero or more typed parameters

  • Return a single typed value

  • Can report errors through diagnostics

  • Support variadic parameters (optional)

  • Are stateless and side-effect free

Creating a Function

To create a provider function, implement the Function protocol:

from tf.function import Function, FunctionSignature, Parameter, Return, CallContext
from tf.types import String, Number, Bool
from typing import Any, List

class UppercaseFunction(Function):
    def __init__(self, provider):
        self.provider = provider

    @classmethod
    def get_name(cls) -> str:
        """Return the function name as it will appear in Terraform"""
        return "uppercase"

    @classmethod
    def get_signature(cls) -> FunctionSignature:
        """Define the function's parameters and return type"""
        return FunctionSignature(
            parameters=[
                Parameter(
                    name="input",
                    type=String(),
                    description="The string to convert to uppercase",
                    allow_null_value=False,
                    allow_unknown_values=False,
                )
            ],
            return_type=Return(type=String()),
            summary="Convert a string to uppercase",
            description="This function converts the input string to uppercase letters",
        )

    def call(self, ctx: CallContext, arguments: List[Any]) -> Any:
        """Execute the function with the given arguments"""
        input_string = arguments[0]
        return input_string.upper()

Function Components

Parameters

Parameters define the inputs to your function:

Parameter(
    name="param_name",
    type=String(),  # Can be any TfType
    description="Human-readable description",
    description_kind=TextFormat.Markdown,  # Optional, defaults to Markdown
    allow_null_value=False,  # Whether null values are allowed
    allow_unknown_values=False,  # Whether unknown values are allowed
)

Return Type

The return type specifies what type of value the function returns:

Return(type=String())  # Can be any TfType

Function Signature

The signature combines parameters, return type, and metadata:

FunctionSignature(
    parameters=[...],  # List of Parameter objects
    return_type=Return(...),  # Return type
    variadic_parameter=Parameter(...),  # Optional variadic parameter
    summary="Short description",
    description="Detailed description",
    description_kind=TextFormat.Markdown,
    deprecation_message="Message if deprecated",
)

Variadic Functions

Functions can accept a variable number of arguments using a variadic parameter:

class ConcatFunction(Function):
    @classmethod
    def get_signature(cls) -> FunctionSignature:
        return FunctionSignature(
            parameters=[
                Parameter(name="separator", type=String()),
            ],
            variadic_parameter=Parameter(
                name="values",
                type=String(),
                description="Values to concatenate",
            ),
            return_type=Return(type=String()),
            summary="Concatenate strings with a separator",
        )

    def call(self, ctx: CallContext, arguments: List[Any]) -> Any:
        separator = arguments[0]
        values = arguments[1:]  # All remaining arguments
        return separator.join(values)

Error Handling

Functions can report errors through the diagnostics system:

def call(self, ctx: CallContext, arguments: List[Any]) -> Any:
    value = arguments[0]

    if value < 0:
        ctx.diagnostics.add_error(
            "Invalid input",
            "Value must be non-negative"
        )
        return 0  # Return a safe default

    return value * 2

If a function adds an error diagnostic, Terraform will treat the function call as failed.

Complex Example

Here’s a more complex example that validates and formats a phone number:

import re

class FormatPhoneFunction(Function):
    def __init__(self, provider):
        self.provider = provider

    @classmethod
    def get_name(cls) -> str:
        return "format_phone"

    @classmethod
    def get_signature(cls) -> FunctionSignature:
        return FunctionSignature(
            parameters=[
                Parameter(
                    name="phone",
                    type=String(),
                    description="Phone number to format",
                ),
                Parameter(
                    name="country_code",
                    type=String(),
                    description="Country code (e.g., 'US', 'UK')",
                ),
            ],
            return_type=Return(type=String()),
            summary="Format a phone number",
            description="Formats a phone number according to the specified country's conventions",
        )

    def call(self, ctx: CallContext, arguments: List[Any]) -> Any:
        phone = arguments[0]
        country_code = arguments[1]

        # Remove all non-digit characters
        digits = re.sub(r'\D', '', phone)

        if country_code == "US":
            if len(digits) != 10:
                ctx.diagnostics.add_error(
                    "Invalid phone number",
                    f"US phone numbers must have 10 digits, got {len(digits)}"
                )
                return phone  # Return original on error

            # Format as (XXX) XXX-XXXX
            return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"

        elif country_code == "UK":
            if len(digits) != 11 or not digits.startswith("0"):
                ctx.diagnostics.add_error(
                    "Invalid phone number",
                    "UK phone numbers must have 11 digits and start with 0"
                )
                return phone

            # Format as 0XXXX XXXXXX
            return f"{digits[:5]} {digits[5:]}"

        else:
            ctx.diagnostics.add_warning(
                "Unknown country code",
                f"Country code '{country_code}' is not supported, returning unformatted"
            )
            return phone

Registering Functions with Your Provider

Add your functions to the provider’s get_functions() method:

from tf.iface import Provider
from typing import Type, List

class MyProvider(Provider):
    # ... other provider methods ...

    def get_functions(self) -> List[Type[Function]]:
        """Return all function types supported by this provider"""
        return [
            UppercaseFunction,
            ConcatFunction,
            FormatPhoneFunction,
        ]

Using Functions in Terraform

Once implemented, your functions can be used in Terraform configurations:

# Using a simple function
output "uppercase_name" {
  value = provider::myprovider::uppercase("hello world")
}

# Using a variadic function
output "joined_values" {
  value = provider::myprovider::concat(", ", "apple", "banana", "cherry")
}

# Using a function with error handling
output "formatted_phone" {
  value = provider::myprovider::format_phone("5551234567", "US")
  # Returns: (555) 123-4567
}

Best Practices

  1. Keep functions pure: Functions should not have side effects or modify external state

  2. Validate inputs: Check parameter values and provide clear error messages

  3. Handle edge cases: Consider null values, empty strings, and boundary conditions

  4. Document thoroughly: Provide clear descriptions for the function and all parameters

  5. Use appropriate types: Choose the most specific type for parameters and return values

  6. Test comprehensively: Write unit tests covering normal cases, edge cases, and error conditions

Type Compatibility

Functions work with all Terraform types:

  • Primitives: String(), Number(), Bool()

  • Collections: List(), Map(), Set()

  • Complex types: Object() with nested attributes

  • Special types: DynamicPseudoType() for any-type parameters

See the types documentation for more details on available types.