Terraform Plugin Framework Utilities

Utilities for use with the HashiCorp Terraform Plugin Framework

Documentation

This project, much like the framework itself, is a work in progress. I will try to keep it as up to date with upstream changes as possible but, as always, community help is appreciated!

Installation

go get -u github.com/dcarbone/[email protected]

Type Conversion

Converting between types used internally by Terraform and typical Go types can be somewhat tricky and / or tedious.

To help with this, I have created a small suite of type conversion utilities designed to make converting to and from Terraform and Go easy and obvious.

You can see the complete list of available conversions here: terraform-plugin-framework-utils/conv

Attribute Validation

The Terraform Plugin Framework has a great validation interface that makes it easy to create custom validation rules for the various schemas you define in your provider.

Being in beta, the HashiCorp team has yet to provide a built-in set of providers. I have created a few that I have found useful when creating my own providers, and provided a small wrapper to make creating new providers as simple as defining a function.

Provided Validators

Required

Fails validation if the attribute is null or unknown

{
    Validators: []tfsdk.AttributeValidator{
        validation.Required()
    },
}

RegexpMatch

Fails validation if the attribute’s value that does not match the user-defined regular expression. This validator will attempt to convert the attribute to a string first.

{
    Validators: []tfsdk.AttributeValidator{
        validation.RegexpMatch("{{ your regex here }}")
    },
}

RegexpNotMatch

Fails validation if the attribute’s value matches the user-defined regular expression. This validator will attempt to convert the attribute to a string first.

{
    Validators: []tfsdk.AttributeValidator{
    	validation.RegexpNotMatch("{{ your regex here }}")
    },
}

Length

Fails validation if the attribute’s value’s length is not within the specified bounds.

{
    Validators: []tfsdk.AttributeValidator{
        // lower limit
        validation.Length(5, -1),

        // upper limit
        validation.Length(-1, 10),

        // lower and upper limit
        validation.Length(5, 10),
    },
}

Compare

Fails validation if the attribute’s value does not match the configured comparison operation.

See comparison.go for details on what comparison operations are available. You can add your own ComparisonFunc using SetComparisonFunc

{
    Validators: []tfsdk.AttributeValidator{
        // equal
        validation.Compare(validation.Equal, 5),

        // less than
        validation.Compare(validation.LessThan, 10),

        // less than or equal to
        validation.Compare(validation.LessThanOrEqualTo, 10),

        // greater than
        validation.Compare(validation.GreaterThan, 5),

        // greater than or equal to
        validation.Compare(validation.GreaterThanOrEqualTo, 5),

        // not equal
        validation.Compare(validation.NotEqual, 10).
    }
}

IsURL

Fails validation if the attribute’s value is not parseable by url.Parse

{
    Validators: []tfsdk.AttributeValidator{
        validation.IsUrl()
    }
}

IsDurationString

Fails validation if the attribute’s value is not parseable by time.ParseDuration

{
    Validators: []tfsdk.AttributeValidator{
        validation.IsDurationString()
    }
}

EnvVarValued

Fails validation if the environment variable name defined by the attribute’s value is, itself, not valued at runtime.

{
    Validators: []tfsdk.AttributeValidator{
        validation.EnvVarValued()
    }
}

FileIsReadable

Fails validation if the file at the path defined in the attribute’s value is not readable at runtime.

{
    Validators: []tfsdk.AttributeValidator{
        validation.FileIsReadable()
    }
}

MutuallyExclusiveSibling

Fails validation if the attribute is valued and the configured sibling attribute is also valued.

{
    Validators: []tfsdk.AttributeValidator{
        validation.MutuallyExclusiveSibling("{{ sibling field name }}")
    }
}

Example

# Example provider Terraform HCL
provider "whatever" {
  address = "http://example.com"
  address_env = "EXAMPLE_ADDR"
}

// Example validators list defined on the `address` attribute's schema
{
    Validators: []tfsdk.AttributeValidator{
        validation.MutuallyExclusiveSibling("address_env")
    }
}

Adding the above validator to the address attribute’s Validators list above will require that the address_env field must be empty when address is defined. You may also add same validator to the address_env attribute, this time pointing at the address field.

MutuallyInclusiveSibling

Requires that two sibling attributes either both be valued or not valued.

{
    Validators: []tfsdk.AttributeValidator{
        validation.MutuallyInclusiveSibling("{{ sibling field name }}")
    }
}

Example

# Example provider Terraform HCL
provider "whatever" {
  ssh_key_file = file("local/filepath/ssh.key")
  ssh_key_password = null
}

// Example validators list defined on the `ssh_key_password` attribute's schema
{
    Validators: []tfsdk.AttributeValidator{
        validation.MutuallyInclusiveSibling("ssh_key")
    }
}

Adding the above validator to the ssh_key_password attribute’s Validators list will require that, if the ssh_key_file attribute is defined so, too, must the ssh_key_password attribute be valued.

Test Utilities

The Terraform Provider Framework provides an excellent suite of test tools to use when creating unit and acceptance tests for provider.

For my uses, I wanted a way to construct hcl config blocks without having to define a heredoc string for each one.

So I created a few config utilities to assist with this.

Example

fieldMap := map[string]interface{}{
	"address": "http://example.com",
	"token": acctest.Literal("file(\"/location/on/disk/token\")"),
	"number_of_fish_in_the_sea": 3500000000000,
}
confHCL := acctest.CompileProviderConfig("my_provider", map[string]interface{}{})

provider "my_provider" {
  address = "http://example.com"
  token = file("/location/on/disk/token")
  number_of_fish_in_the_sea = 3500000000000
}

This can be used with the acctest.JoinConfigs func to bring together multiple reusable configuration blocks for different tests.

GitHub

View Github