Terraform Provider for SIGNL4

This article describes how to create a Terraform provider for SIGNL4.

Terraform is an infrastructure as code tool for safely and efficiently building, changing, and versioning infrastructure—from low-level components like compute and networking to high-level features like DNS and SaaS.

Category Example

In this example we create a Terraform provider for SIGNL4 that supports:

  • Creating a category (via POST)
  • Deleting a category (via DELETE)

You can create any additional resource configuration you need by referring to the SIGNL4 REST API.

You’ll need to implement a custom Terraform provider using the Terraform Plugin Framework (Go SDK).

Project

The Terraform project looks like follows.

📦 Project Structure

terraform-provider-signl4/
├── main.go
├── go.mod
└── internal/
    └── provider/
        └── category_resource.go

🔧 main.go

package main

import (
	"github.com/hashicorp/terraform-plugin-framework/providerserver"
	"terraform-provider-signl4/internal/provider"
)

func main() {
	providerserver.Serve("signl4", provider.New, providerserver.ServeOpts{
		Address: "registry.terraform.io/example/signl4",
	})
}

📁 internal/provider/category_resource.go

package provider

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/resource"
	"github.com/hashicorp/terraform-plugin-framework/types"
)

type signl4Provider struct{}

func New() provider.Provider {
	return &signl4Provider{}
}

func (p *signl4Provider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
	resp.TypeName = "signl4"
}

func (p *signl4Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
	// No global provider schema needed
}

func (p *signl4Provider) Resources(_ context.Context) []func() resource.Resource {
	return []func() resource.Resource{
		NewCategoryResource,
	}
}

func (p *signl4Provider) DataSources(_ context.Context) []func() datasource.DataSource {
	return nil
}

🔨 Category Resource

type categoryResource struct{}

func NewCategoryResource() resource.Resource {
	return &categoryResource{}
}

type categoryModel struct {
	ID            types.String `tfsdk:"id"`
	TeamID        types.String `tfsdk:"team_id"`
	APIKey        types.String `tfsdk:"api_key"`
	Name          types.String `tfsdk:"name"`
	Color         types.String `tfsdk:"color"`
	ImageName     types.String `tfsdk:"image_name"`
	Keywords      types.List   `tfsdk:"keywords"`
	KeywordMatch  types.String `tfsdk:"keyword_matching"`
}

func (r *categoryResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
	resp.TypeName = "signl4_category"
}

func (r *categoryResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = map[string]types.Attribute{
		"id":               types.StringType,
		"team_id":          types.StringType,
		"api_key":          types.StringType,
		"name":             types.StringType,
		"color":            types.StringType,
		"image_name":       types.StringType,
		"keywords":         types.ListType{ElemType: types.StringType},
		"keyword_matching": types.StringType,
	}
}

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

	url := fmt.Sprintf("https://connect.signl4.com/api/v2/categories/%s", plan.TeamID.ValueString())

	payload := map[string]interface{}{
		"name":             plan.Name.ValueString(),
		"color":            plan.Color.ValueString(),
		"imageName":        plan.ImageName.ValueString(),
		"keywordMatching":  plan.KeywordMatch.ValueString(),
		"keywords":         plan.Keywords.Elements(),
		"augmentations": []map[string]interface{}{
			{"enabled": false, "name": "", "type": "Subject", "value": ""},
			{"enabled": false, "name": "", "type": "Location", "value": ""},
			{"enabled": false, "name": "", "type": "Text", "value": ""},
		},
	}

	body, _ := json.Marshal(payload)
	reqBody := strings.NewReader(string(body))

	request, _ := http.NewRequest("POST", url, reqBody)
	request.Header.Set("X-S4-Api-Key", plan.APIKey.ValueString())
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("Accept", "application/json")

	client := &http.Client{}
	res, err := client.Do(request)
	if err != nil {
		resp.Diagnostics.AddError("Error creating category", err.Error())
		return
	}
	defer res.Body.Close()

	// You should parse response for actual ID, here assumed manually
	plan.ID = types.StringValue("generated-or-returned-category-id")

	diags = resp.State.Set(ctx, plan)
	resp.Diagnostics.Append(diags...)
}

func (r *categoryResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
	var state categoryModel
	req.State.Get(ctx, &state)

	url := fmt.Sprintf("https://connect.signl4.com/api/v2/categories/%s/%s", state.TeamID.ValueString(), state.ID.ValueString())

	request, _ := http.NewRequest("DELETE", url, nil)
	request.Header.Set("X-S4-Api-Key", state.APIKey.ValueString())

	client := &http.Client{}
	_, err := client.Do(request)
	if err != nil {
		resp.Diagnostics.AddError("Error deleting category", err.Error())
		return
	}
}

🧪 Example Terraform Usage

provider "signl4" {}

resource "signl4_category" "my_category" {
  team_id          = "{SIGNL4-TEAM-ID}"
  api_key          = "{SIGNL4-API-Key}"
  name             = "MyCategory"
  color            = "#30AFE5"
  image_name       = "bug.svg"
  keyword_matching = "Any"
  keywords         = ["keyword1", "keyword2"]
}

Instructions to Build and Use

Prerequisites

  • Go 1.21+
  • Terraform CLI installed

Build the Provider

You can build the profider using the following commands.

cd terraform-provider-signl4
go mod tidy
go build -o terraform-provider-signl4

Move the binary to your Terraform plugin directory:

mkdir -p ~/.terraform.d/plugins/registry.terraform.io/example/signl4/1.0.0/linux_amd64
mv terraform-provider-signl4 ~/.terraform.d/plugins/registry.terraform.io/example/signl4/1.0.0/linux_amd64/

Replace linux_amd64 with darwin_amd64 or windows_amd64 depending on your system.

Create Example Terraform Config

Create a file called main.tf:

terraform {
  required_providers {
    signl4 = {
      source  = "registry.terraform.io/example/signl4"
      version = "1.0.0"
    }
  }
}

provider "signl4" {}

resource "signl4_category" "example" {
  team_id          = "{SIGNL4-TEAM-ID}"
  api_key          = "{SIGNL4-API-Key}"
  name             = "MyCategory"
  color            = "#30AFE5"
  image_name       = "bug.svg"
  keyword_matching = "Any"
  keywords         = ["keyword1", "keyword2"]
}

Initialize and Apply

terraform init
terraform apply

Terraform will create the SIGNL4 category via API. You can also run terraform destroy to trigger the DELETE.

GitHub

You can find the sample project including the above mentioned files on GitHub.


Learn more at signl4.com. Last update: 2025-05-05 13:56.