Developing Terraform Custom Provider for Terraform v0.12
Tags: terraform, tutorial, showdev
Published: 2020-10-31 19:00:30


# Table of Content
  - [Introduction](#introduction)
  - [Bit of intro into Terraform](#bit-of-intro-into-terraform)
  - [Terraform Provider](#terraform-provider)
  - [Terraform Custom Provider](#terraform-custom-provider)
  - [What is required to develop Custom Provider](#what-is-required-to-develop-custom-provider)
  - [Install and Configure Terraform](#install-and-configure-terraform)
  - [How to develop provider code with Go](#how-to-develop-provider-code-with-go)
      - [Step 1 Setting up Go develop environment](#step-1-setting-up-go-develop-environment) 
      - [Step 2 Custom Provider Source Code Details](#step-2-custom-provider-source-code-details)
      - [Step 3 Build go code and create tf provider executable](#step-3-build-go-code-and-create-tf-provider-executable)
      - [Step 4 Create terraform file](#step-4-create-terraform-file)
      - [Step 5 Testing Provider Create and Destroy resource](#step-5-testing-provider-create-and-destroy-resource)
  - [Bibliography and Reference](#bibliography-and-reference)

## *Introduction*
We assume that the readers of this article has prior understanding and working knowledge in Terraform and has created resource provisioning TF files for various Cloud providers. But still....

## *Bit of intro into Terraform*
- Terraform is a Cloud agnostic Infra as code technology and used to create immutable infrastructure. 
- Terraform is used to create, manage, and update infrastructure resources such as physical machines, VMs, network switches, containers, and more. Almost any infrastructure type can be represented as a resource in Terraform.
- Deliver infrastructure as code with Terraform using declarative tf configuration files
- Plan and predict changes: Terraform provides an elegant user experience for operators to safely and predictably make changes to infrastructure
- Create reproducible infrastructure: Terraform makes it easy to re-use configurations for similar infrastructure, helping you avoid mistakes and save time.

## *Terraform Provider*
A provider is responsible for understanding API interactions and exposing resources. Most providers configure a specific infrastructure platform (either cloud or self-hosted). Providers can also offer local utilities for tasks like generating random numbers for unique resource names.

## *Terraform Custom Provider*
According to Terraform documentation,

There are a few possible reasons for authoring a custom Terraform provider, such as:

- An internal private cloud whose functionality is either proprietary or would not benefit the open source community.

- A "work in progress" provider being tested locally before contributing back.

- Extensions of an existing provider

Refer the Hashicorp Documentation on [writing custom providers](https://www.terraform.io/docs/extend/writing-custom-providers.html)

## *What is required to develop Custom Provider*
1. Just small enough Go development knowledge 
2. Resource creation supported by API calls from the respective provider

## *Install and Configure Terraform*
- Refer here [for installing terraform](https://www.terraform.io/downloads.html)
- Windows
  - Download and extract the terraform executable
  - Add terraform executable path to ENV PATH variable
- In Linux flavours, Copy the terraform executable in /usr/bin path to execute it from any path.

## *How to develop provider code with Go*
### *Step 1 Setting up Go develop environment* 
[Refer my Dev community article](https://dev.to/chefgs/getting-started-with-go-in-ubuntu-16-04-3b59)

[Read my article on getting started with Go Hello World](https://dev.to/chefgs/go-hello-world-program-17pk)

### *Step 2 Custom Provider Source Code Details*
Please check out the provider source files from [GitHub Repo Here](https://github.com/chefgs/terraform_repo/tree/main/custom_provider/tf_custom_provider)
- Required source files for custom provider are, 
  - `main.go`
  - `provider.go` 
  - `resource_server.go`
```
The code layout looks like this:
.
├── main.go
├── provider.go
├── resource_server.go
```

- Go entry point function is `main.go`.
```
// main.go
package main

import (
        "github.com/hashicorp/terraform-plugin-sdk/plugin"
        "github.com/hashicorp/terraform-plugin-sdk/terraform"
)

func main() {
        plugin.Serve(&plugin.ServeOpts{
                ProviderFunc: func() terraform.ResourceProvider {
                        return Provider()
                },
        })
}
```

- `provider.go` will have the resource server function calls.
```
// provider.go
package main

import (
        "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func Provider() *schema.Provider {
        return &schema.Provider{
                ResourcesMap: map[string]*schema.Resource{
                     "customprovider_server": resourceServer(),
                },
        }
}
```

- All the resource creation has to be coded in `resource_server.go`. This file will have the resource function declaration and definition like create, delete etc, it also gets the input params required to create resources. 
```
// resource_server.go
package main

import (
        "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceServer() *schema.Resource {
        return &schema.Resource{
                Create: resourceServerCreate,
                Read:   resourceServerRead,
                Update: resourceServerUpdate,
                Delete: resourceServerDelete,

                Schema: map[string]*schema.Schema{
                        "num_sys": &schema.Schema{
                                Type:     schema.TypeString,
                                Required: true,
                        },
                },
        }
}

func resourceServerCreate(d *schema.ResourceData, m interface{}) error {
        num_sys := d.Get("num_sys").(string)

        d.SetId(num_sys)

/* 
API call to be added for respective provider. In case of developing provider for on-prem cloud or public cloud, that is not supported by terraform, add the API call to corresponding Resource creation API 
*/
        return resourceServerRead(d, m)
}

func resourceServerRead(d *schema.ResourceData, m interface{}) error {
        return nil
}

func resourceServerUpdate(d *schema.ResourceData, m interface{}) error {
        return resourceServerRead(d, m)
}

func resourceServerDelete(d *schema.ResourceData, m interface{}) error {
        return nil
}
```
- Details regarding the custom provider code in `resource_server.go`: 
  - Our code repo example implemented with mock resource creation for the provider called 'customprovider'. 
  - In real-time case, it has to be changed for the provider name of respective cloud or on-premises server. 
  - Most of providers have API calls to be consumed for resource operation like create/update/delete etc.. So We need to define the logic of resource operations like create and delete using the custom provider api calls, to apply the terraform template.
  - Please add the API call implementation in the `/* commented out */` section in `resource_server.go` 

### *Step 3 Build go code and create tf provider executable*
```
cd tf_custom_provider/
go mod init
go mod tidy
go build -o terraform-provider-customprovider
```

Please note,
```
Third-party plugins (both providers and provisioners) can be manually installed into the user plugins directory
Located at %APPDATA%\terraform.d\plugins on Windows and ~/.terraform.d/plugins on other systems.

Copy the custom provider executable created in the step above to Terraform plugin directory
```

- After adding the logic for resource operations in `resource_server.go`, our custom provider is ready to get tested

### *Step 4 Create terraform file* 
Test the provider by creating `main.tf`, by providing the resource inputs. In our sample just the number of server count added as an input parameter for demo purpose.
Create or Edit `main.tf` file with code to create custom provider resource
```
resource "customprovider_server" "my-server-name" {
	num_sys = "2"
}
```

### *Step 5 Testing Provider Create and Destroy resource*
- The custom provider executable should be placed inside the "~/.terraform.d/plugins" (in Linux server) path to enable the access to the custom provider functionality
- Execute the following Terraform commands to verify the custom provider functionalities we have added,
```
terraform init
terraform plan
terraform apply
terraform destroy
```

## *Bibliography and Reference*
[Terraform Docs](https://www.terraform.io/docs/index.html)
[Custom Provider Lab Tutorial](https://learn.hashicorp.com/collections/terraform/providers) 
[Custom Provider Easy Tutorial](https://www.hashicorp.com/blog/writing-custom-terraform-providers)