Terraform on Naver Cloud - Creating a Custom Provider

After starting without any knowledge of Go, I encountered numerous challenges. However, writing Terraform code proved to be even more difficult. To solidify my understanding, I will document the development process and the flow I followed while working on this project.

Basic Setup

var (
	_ resource.Resource              = &cafeResource{}
	_ resource.ResourceWithConfigure = &cafeResource{}
)

func NewCafeResource() resource.Resource {
	return &cafeResource{}
}

type cafeResource struct {
	client *hashicups.Client
}

The code above follows a conventional Go approach to verifying interface implementation. It ensures that cafeResource implements both resource.Resource and resource.ResourceWithConfigure.

resource.Resource Interface

type Resource interface {
	Metadata(context.Context, MetadataRequest, *MetadataResponse)
	Schema(context.Context, SchemaRequest, *SchemaResponse)
	Create(context.Context, CreateRequest, *CreateResponse)
	Read(context.Context, ReadRequest, *ReadResponse)
	Update(context.Context, UpdateRequest, *UpdateResponse)
	Delete(context.Context, DeleteRequest, *DeleteResponse)
}

This interface defines the required functions that must be implemented: Create, Read, Update, and Delete. The method names must be consistent with the interface specifications.

resource.ResourceWithConfigure Interface

type ResourceWithConfigure interface {
	Resource
	Configure(context.Context, ConfigureRequest, *ConfigureResponse)
}

The Configure method allows the resource to receive provider-level settings and data. By implementing this interface in cafeResource, the provider’s configured client and other settings can be received and initialized.

Defining cafeResource

Creating NewCafeResource

func NewCafeResource() resource.Resource {
	return &cafeResource{}
}

This function is used to register the resource in provider.go:

func (p *hashicupsProvider) Resources(_ context.Context) []func() resource.Resource {
	return []func() resource.Resource{
		NewOrderResource,
		NewCafeResource,
	}
}

Make sure to add NewCafeResource to the list. If a DataSource has also been created, it should be added there as well.

Defining cafeResource

type cafeResource struct {
	client *hashicups.Client
}

This struct contains the definition for the resource that interacts with the HashiCups API. The client field stores the client instance used to communicate with the API. The usage of this client will be explored in more detail later.


Model Definition

type cafeResourceModel struct {
	ID          types.String `tfsdk:"id"`
	Name        types.String `tfsdk:"name"`
	Address     types.String `tfsdk:"address"`
	Description types.String `tfsdk:"description"`
	Image       types.String `tfsdk:"image"`
}

This model is defined to receive data from the API. The types were assigned based on existing models.

Defining Metadata and Schema

Metadata

func (r *cafeResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
	resp.TypeName = req.ProviderTypeName + "_cafe"
}

This function defines the metadata for the resource. When writing Terraform configuration files, _cafe is used to identify the cafe resource.

Schema

func (r *cafeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"id": schema.StringAttribute{
				Computed: true,
				PlanModifiers: []planmodifier.String{
					stringplanmodifier.UseStateForUnknown(),
				},
			},
			"name": schema.StringAttribute{
				Optional: true,
			},
			"address": schema.StringAttribute{
				Optional: true,
			},
			"description": schema.StringAttribute{
				Optional: true,
			},
			"image": schema.StringAttribute{
				Optional: true,
			},
		},
	}
}

The schema defines the structure of the resource and describes its attributes. This allows Terraform to validate user input, store resource state, and manage updates.

Roles of the Schema

  • Defining and Validating Attributes:
    • Specifies attribute names, types, requirements (required, optional), default values, and other properties.
    • Terraform uses this to validate user input and prevent invalid configurations.
  • State Management:
    • Terraform maintains the resource’s state based on the schema, ensuring consistency.
  • Planning and Change Management:
    • The schema helps Terraform determine which attributes have changed between configurations.

Key Schema Components

  • Attributes: Define the structure of the resource.
  • Computed: Attributes that Terraform determines automatically.
  • Required/Optional: Specifies whether attributes must be provided.
  • Default: Defines a default value if none is provided.
  • Description: Provides documentation for attributes.
  • PlanModifiers: Used to modify attribute behavior during the planning phase.

    Create Method

    func (r *cafeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
      var plan cafeResourceModel
      diags := req.Plan.Get(ctx, &plan)
      resp.Diagnostics.Append(diags...)
      if resp.Diagnostics.HasError() {
          return
      }
    

    The Create function extracts data from the CreateRequest. Although the request does not contain a JSON body, gRPC technology is used to structure it.

Structure of CreateRequest

type CreateRequest struct {
	Config       tfsdk.Config
	Plan         tfsdk.Plan
	ProviderMeta tfsdk.Config
}

By calling req.Plan.Get(ctx, &plan), the request data is automatically mapped to the cafeResourceModel.

	cafe := hashicups.Cafe{
		Name:        plan.Name.ValueString(),
		Address:     plan.Address.ValueString(),
		Description: plan.Description.ValueString(),
		Image:       plan.Image.ValueString(),
	}

	createdCafe, err := r.client.CreateCafe([]hashicups.Cafe{cafe})
	if err != nil {
		resp.Diagnostics.AddError(
			"Error creating cafe",
			"Could not create cafe, unexpected error: "+err.Error(),
		)
		return
	}

A hashicups.Cafe object is created using the values from plan, retrieved with ValueString(). This object is then passed to the API to create a new cafe resource.


This document provides an overview of developing a custom Terraform provider using Go, explaining the flow from basic setup to schema definition and resource creation.

Categories:

Updated:

Leave a comment