Skip to content

Commit

Permalink
Migrate integer resource to framework (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
bendbennett committed May 5, 2022
1 parent a3d9560 commit 110d377
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 15 deletions.
1 change: 0 additions & 1 deletion internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ func New() *schema.Provider {
"random_pet": resourcePet(),
"random_string": resourceString(),
"random_password": resourcePassword(),
"random_integer": resourceInteger(),
},
}
}
Expand Down
11 changes: 10 additions & 1 deletion internal/provider_fm/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,17 @@ type ID struct {
Dec types.String `tfsdk:"dec"`
}

type Integer struct {
ID types.String `tfsdk:"id"`
Keepers types.Map `tfsdk:"keepers"`
Min types.Int64 `tfsdk:"min"`
Max types.Int64 `tfsdk:"max"`
Seed types.String `tfsdk:"seed"`
Result types.Int64 `tfsdk:"result"`
}

type UUID struct {
ID types.String `tfsdk:"id"`
Result types.String `tfsdk:"result"`
Keepers types.Map `tfsdk:"keepers"`
Result types.String `tfsdk:"result"`
}
5 changes: 3 additions & 2 deletions internal/provider_fm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderReq

func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) {
return map[string]tfsdk.ResourceType{
"random_id": resourceIDType{},
"random_uuid": resourceUUIDType{},
"random_id": resourceIDType{},
"random_integer": resourceIntegerType{},
"random_uuid": resourceUUIDType{},
}, nil
}

Expand Down
192 changes: 192 additions & 0 deletions internal/provider_fm/resource_integer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package provider_fm

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"strconv"
"strings"
)

type resourceIntegerType struct{}

func (r resourceIntegerType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Description: "The resource `random_integer` generates random values from a given range, described " +
"by the `min` and `max` attributes of a given resource.\n" +
"\n" +
"This resource can be used in conjunction with resources that have the `create_before_destroy` " +
"lifecycle flag set, to avoid conflicts with unique names during the brief period where both the " +
"old and new resources exist concurrently.",
Attributes: map[string]tfsdk.Attribute{
"keepers": {
Description: "Arbitrary map of values that, when changed, will trigger recreation of " +
"resource. See [the main provider documentation](../index.html) for more information.",
Type: types.MapType{
ElemType: types.StringType,
},
Optional: true,
PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()},
},
"min": {
Description: "The minimum inclusive value of the range.",
Type: types.Int64Type,
Required: true,
PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()},
},
"max": {
Description: "The maximum inclusive value of the range.",
Type: types.Int64Type,
Required: true,
PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()},
},
"seed": {
Description: "A custom seed to always produce the same value.",
Type: types.StringType,
Optional: true,
PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()},
},
"result": {
Description: "The random integer result.",
Type: types.Int64Type,
Computed: true,
},
"id": {
Description: "The generated uuid presented in string format.",
Type: types.StringType,
Computed: true,
},
},
}, nil
}

func (r resourceIntegerType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) {
return resourceInteger{
p: *(p.(*provider)),
}, nil
}

type resourceInteger struct {
p provider
}

func (r resourceInteger) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) {
if !r.p.configured {
resp.Diagnostics.AddError(
"provider not configured",
"provider not configured",
)
}

var plan Integer
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

max := int(plan.Max.Value)
min := int(plan.Min.Value)
seed := plan.Seed.Value

if max < min {
resp.Diagnostics.AddError(
"minimum value needs to be smaller than or equal to maximum value",
"minimum value needs to be smaller than or equal to maximum value",
)
return
}

rand := NewRand(seed)
number := rand.Intn((max+1)-min) + min

u := &Integer{
ID: types.String{Value: strconv.Itoa(number)},
Keepers: plan.Keepers,
Min: types.Int64{Value: int64(min)},
Max: types.Int64{Value: int64(max)},
Result: types.Int64{Value: int64(number)},
}

if seed != "" {
u.Seed.Value = seed
} else {
u.Seed.Null = true
}

diags = resp.State.Set(ctx, u)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

func (r resourceInteger) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) {
// Intentionally left blank.
}

func (r resourceInteger) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) {
// Intentionally left blank.
}

func (r resourceInteger) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) {
resp.State.RemoveResource(ctx)
}

func (r resourceInteger) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
parts := strings.Split(req.ID, ",")
if len(parts) != 3 && len(parts) != 4 {
resp.Diagnostics.AddError(
"Invalid import usage: expecting {result},{min},{max} or {result},{min},{max},{seed}",
"Invalid import usage: expecting {result},{min},{max} or {result},{min},{max},{seed}",
)
return
}

result, err := strconv.ParseInt(parts[0], 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"error parsing result",
fmt.Sprintf("error parsing result: %s", err),
)
return
}

min, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"error parsing min",
fmt.Sprintf("error parsing min: %s", err),
)
return
}

max, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"error parsing max",
fmt.Sprintf("error parsing max: %s", err),
)
return
}

var state Integer

state.ID.Value = parts[0]
state.Keepers.ElemType = types.StringType
state.Result.Value = int64(result)
state.Min.Value = int64(min)
state.Max.Value = int64(max)

if len(parts) == 4 {
state.Seed.Value = parts[3]
}

diags := resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package provider
package provider_fm

import (
"fmt"
Expand All @@ -11,8 +11,8 @@ import (
func TestAccResourceIntegerBasic(t *testing.T) {
t.Parallel()
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testRandomIntegerBasic,
Expand All @@ -33,8 +33,8 @@ func TestAccResourceIntegerBasic(t *testing.T) {
func TestAccResourceIntegerUpdate(t *testing.T) {
t.Parallel()
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testRandomIntegerBasic,
Expand All @@ -55,8 +55,8 @@ func TestAccResourceIntegerUpdate(t *testing.T) {
func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) {
t.Parallel()
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testRandomIntegerSeedless,
Expand All @@ -77,8 +77,8 @@ func TestAccResourceIntegerSeedless_to_seeded(t *testing.T) {
func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) {
t.Parallel()
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testRandomIntegerBasic,
Expand All @@ -99,8 +99,8 @@ func TestAccResourceIntegerSeeded_to_seedless(t *testing.T) {
func TestAccResourceIntegerBig(t *testing.T) {
t.Parallel()
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testRandomIntegerBig,
Expand Down
24 changes: 24 additions & 0 deletions internal/provider_fm/seed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package provider_fm

import (
"hash/crc64"
"math/rand"
"time"
)

// NewRand returns a seeded random number generator, using a seed derived
// from the provided string.
//
// If the seed string is empty, the current time is used as a seed.
func NewRand(seed string) *rand.Rand {
var seedInt int64
if seed != "" {
crcTable := crc64.MakeTable(crc64.ISO)
seedInt = int64(crc64.Checksum([]byte(seed), crcTable))
} else {
seedInt = time.Now().UnixNano()
}

randSource := rand.NewSource(seedInt)
return rand.New(randSource)
}

0 comments on commit 110d377

Please sign in to comment.