Page cover

2 3 0 Custom Terraform Provider: Resource Skeleton (Go)

In this episode, Andrew defines validateUUID() and providerConfigure() functions, and sets up the skeleton of resource().

Code Notes

  • schema.Resource: Resource() represents a thing (entity) in Terraform that has a set of configurable attributes and a lifecycle (create, read, update and delete).

package main

import (
	"context"
	"log"
	"fmt"
	"github.com/google/uuid"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
)

func main() {
	plugin.Serve(&plugin.ServeOpts{
		ProviderFunc: Provider,
	})
	fmt.Println("Hello, world!")
}

type Config struct {
	Endpoint string
	Token string
	UserUuid string
}

func Provider() *schema.Provider {
	var p *schema.Provider
	p = &schema.Provider{
		ResourcesMap:  map[string]*schema.Resource{
			"terratowns_home": Resource(),
		},
		DataSourcesMap:  map[string]*schema.Resource{

		},
		Schema: map[string]*schema.Schema{
			"endpoint": {
				Type: schema.TypeString,
				Required: true,
				Description: "The endpoint for hte external service",
			},
			"token": {
				Type: schema.TypeString,
				Sensitive: true, // make the token as sensitive to hide it the logs
				Required: true,
				Description: "Bearer token for authorization",
			},
			"user_uuid": {
				Type: schema.TypeString,
				Required: true,
				Description: "UUID for configuration",
				ValidateFunc: validateUUID,
			},
		},
	}
	p.ConfigureContextFunc = providerConfigure(p)
	return p
}

func validateUUID(v interface{}, k string) (ws []string, errors []error) {
	log.Print("validateUUID:start")
	value := v.(string)
	if _, err := uuid.Parse(value); err != nil {
		errors = append(errors, fmt.Errorf("invalid UUID format"))
	}
	log.Print("validateUUID:end")
	return
}

func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
	return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics ) {
		log.Print("providerConfigure:start")
		config := Config{
			Endpoint: d.Get("endpoint").(string),
			Token: d.Get("token").(string),
			UserUuid: d.Get("user_uuid").(string),
		}
		log.Print("providerConfigure:end")
		return &config, nil
	}
}

func Resource() *schema.Resource {
	log.Print("Resource:start")
	resource := &schema.Resource{
		CreateContext: resourceHouseCreate,
		ReadContext: resourceHouseRead,
		UpdateContext: resourceHouseUpdate,
		DeleteContext: resourceHouseDelete,
	}
	log.Print("Resource:start")
	return resource
}

func resourceHouseCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	var diags diag.Diagnostics
	return diags
}

func resourceHouseRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	var diags diag.Diagnostics
	return diags
}

func resourceHouseUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	var diags diag.Diagnostics
	return diags
}

func resourceHouseDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	var diags diag.Diagnostics
	return diags
}

validateUUID()

func validateUUID(v interface{}, k string) (ws []string, errors []error) {
	value := v.(string)
	if _, err := uuid.Parse(value); err != nil {
		errors = append(errors, fmt.Errorf("invalid UUID format"))
	}
	return
}

Line 1, function signature

  • Parameters

    • The parameter v is of type interface{}, which can accept values of any data type.

    • The parameter k should be a string.

  • Return value

    • This function will return two slices - a slice of strings (ws) and another of errors (errors).

Line 2 value

  • The string from the v (interface{})'s argument will be assigned to the variable value.

  • := is Go's way of declaring a variable.

  • .(string) is Go's way of casting a type to a value.

    • a bit deeper: technically speaking, this is called "type assertion" in go and it is a bit different from casting in other languages.

  • Lines 3-4 if _, err

    • First, assign the two return values from the function uuid.Parse(value) to the variables _ and err. Note that the function uuid is coming from Google's uuid package for Go (see the import statement in line 7 in the source code at the top).

    • If the variable err's data type is not nil (err != nil), it means that there is an error. In that case, append the error values to the errors slice which will be later returned.

  • Line 6 return statement

    • Note that what will be returned is not declared after the return keyword. This is characteristic of Go, where it is not necessary to specify one as the return values are already declared in the function signature (which is the func statement, line 1).

configureProvider()

func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
	return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics ) {
		config := Config{
			Endpoint: d.Get("endpoint").(string),
			Token: d.Get("token").(string),
			UserUuid: d.Get("user_uuid").(string),
		}
		return &config, nil
	}
}

Function signature, line 1

  • Parameters

    • p is an object of schema.Provider type, which is a Terraform provider.

  • Return values

    • Interestingly, this function returns another function, which is a schema.ConfigureContextFunc.

      • Parameters: this function to be returned takes a context object from the context package, and a ResourceData object from the schema package.

        • context: is a package used to manage and control the lifecycle of processes and requests in Go applications.

      • Lines 3-7: The function internally constructs an object called config which has three string values. This will be later used for authenticating our http requests and connecting to the cloud service (TerraTowns).

        • Return values: this function returns a pointer to the config struct and a nil. nil indicates that the configuration was successful.

Resources

Last updated