The second part of the story about Terraform Provider internals. This part will be more focused on different Provider’s
mechanisms and objects implementation in Go.
Generic Provider structure
Terraform has created terraform-provider-hashicups repo where
some example Provider is defined. There is a minimal codebase required for having a working Provider.
Below I want to run through the Terraform Providers’ (for both AWS and Hashicups) source files and explain the main parts required for the Provider to work.
Let’s go through the files in the repo and try to understand what is going on here.
The Provider’s entry point is defined within a main.go file:
The provider package might have another name depending on your provider name (and therefore package name). Usually, the provider package is a separate directory within the whole Provider’s repo.
provider.Provider is a name of a function within the provider package and it MUST return a pointer to schema.Provider object in order to conform to Terraform protocol.
In general, a Provider should be defined in the following way:
That means, we can define our provider in Terraform code in the following way:
In addition, you SHOULD specify all resources and datasources that are supported by your provider. From the code above, we can make an assumption that using this provider we’ll be able to manage the next resources:
myprovider_resource_name
myprovider_data_source_name
For example, let’s take a look at AWS Provider internal/provider/provider.go file where Provider object is defined.
So, you need to define the following values:
define Provider Schema that describes all attributes that can be used by provider itself for initialization and configuration
list all data sources that will be available within your provider
list all resources that will be available within your provider
Both data sources and resources MUST be defined as a map of strings mapped to the functions that return resource schemas.
Resource and Data Source generic structure
At this step, we need to define all data sources and resources which are available in our Provider. In our previous example,
we had the following resources list within the provider definition:
We need to define resourceFunction() somewhere within the package in order to make it work. And make sure that this function returns a pointer to schema.Resource. Very similar to the Provider mechanism, right? 😀
Let’s take a look at some basic resource function structure:
CRUD functions
We need to implement CRUD functions specified for CreateContext, ReadContext, UpdateContext and DeleteContext.
In most cases, they are following the same logic:
get values from schema
convert them into a data structure which is required by Client lib
pass the data structure into the client call (create, modify or delete)
process client response and errors if any
convert values from client response object into schema values
CRUD functions MUST accept the following parameters in order to be compliant with Terraform protocols:
ctx context.Context - Terraform’s context, you can ignore this parameter as it’s not directly related to resource management;
d *schema.ResourceData - ResourceData object that holds all of the resource attributes defined in .tf files;
m interface{} - object, that is returned by providerConfigure function from the Provider definition.
Here is an example of Create function:
Here is an example of Read function:
Here is an Update function:
Here is a Delete function:
Please note that Terraform will not use our plugin and throw an error until we define at least Create, Read and Delete functions as they are basic ones for the resource management lifecycle.
How to do initial tests of your provider or resource
Once we defined CRUD functions for our resource, we can start testing our provider and resource itself.
Firstly, you need to build your provider into an executable binary. For doing so, please run go build . in the root directory of your provider repository.
Go then will try to compile and build everything you’ve already coded. The process will fail if there are any errors in your code (syntactical, types mismatch, and so on). Please fix all errors and rerun the command.
Then please run go install. It will copy your provider’s binary into the GOBIN directory.
To start using your Provider locally it’s needed to say Terraform where to look for your Provider’s binary. As I said in the previous part of the story, Terraform will look for providers locally in your workdir first (a directory with your .tf files), then it will check ~/.terraform.d directory and then will try to find a Provider within Terraform Registry. That’s obvious, that your Provider binary is not in either place as it’s stored in GOBIN directory.
GOBIN by default is set to ${GOPATH}/bin, you can get these values by running go env | grep 'GOPATH\|GOBIN'.
Well, we’ve found where our Terraform Provider binary is located. How to say Terraform look for our Provider within this location?
We can do it via creating a so-called terraform.rc file and specify where to locate custom and “in-development” providers.
Please create a .terraformrcfile within your $HOME directory and populate it with the following lines:
The only thing to do left - is to create .tf file with your resource/provider definitions and try to run terraform plan.
Test .tf file for myprovider might look like the following:
At the moment, you are ready to play with it and develop your own basic provider. The Provider’s complexity is just limited by your goals and Go Lang skills 🙂.
Leave a comment