Skip to content

Commit

Permalink
Migrating id 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 27a71d7 commit d4502ea
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 17 deletions.
1 change: 0 additions & 1 deletion internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ func New() *schema.Provider {
Schema: map[string]*schema.Schema{},

ResourcesMap: map[string]*schema.Resource{
"random_id": resourceId(),
"random_shuffle": resourceShuffle(),
"random_pet": resourcePet(),
"random_string": resourceString(),
Expand Down
13 changes: 12 additions & 1 deletion internal/provider_fm/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@ package provider_fm

import "github.com/hashicorp/terraform-plugin-framework/types"

type ID struct {
ID types.String `tfsdk:"id"`
Keepers types.Map `tfsdk:"keepers"`
ByteLength types.Int64 `tfsdk:"byte_length"`
Prefix types.String `tfsdk:"prefix"`
B64URL types.String `tfsdk:"b64_url"`
B64Std types.String `tfsdk:"b64_std"`
Hex types.String `tfsdk:"hex"`
Dec types.String `tfsdk:"dec"`
}

type UUID struct {
ID types.String `tfsdk:"id"`
Result types.String `tfsdk:"result"`
Keepers types.Map `tfsdk:"keepers""`
Keepers types.Map `tfsdk:"keepers"`
}
1 change: 1 addition & 0 deletions internal/provider_fm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ 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{},
}, nil
}
Expand Down
219 changes: 219 additions & 0 deletions internal/provider_fm/resource_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package provider_fm

import (
"context"
"crypto/rand"
"encoding/base64"
hex2 "encoding/hex"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"math/big"
"strings"
)

type resourceIDType struct{}

func (r resourceIDType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Description: `
The resource ` + "`random_id`" + ` generates random numbers that are intended to be
used as unique identifiers for other resources.
This resource *does* use a cryptographic random number generator in order
to minimize the chance of collisions, making the results of this resource
when a 16-byte identifier is requested of equivalent uniqueness to a
type-4 UUID.
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()},
},
"byte_length": {
Description: "The number of random bytes to produce. The minimum value is 1, which produces " +
"eight bits of randomness.",
Type: types.Int64Type,
Required: true,
PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()},
},
"prefix": {
Description: "Arbitrary string to prefix the output value with. This string is supplied as-is, " +
"meaning it is not guaranteed to be URL-safe or base64 encoded.",
Type: types.StringType,
Optional: true,
PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()},
},
"b64_url": {
Description: "The generated id presented in base64, using the URL-friendly character set: " +
"case-sensitive letters, digits and the characters `_` and `-`.",
Type: types.StringType,
Computed: true,
},
"b64_std": {
Description: "The generated id presented in base64 without additional transformations.",
Type: types.StringType,
Computed: true,
},
"hex": {
Description: "The generated id presented in padded hexadecimal digits. This result will " +
"always be twice as long as the requested byte length.",
Type: types.StringType,
Computed: true,
},
"dec": {
Description: "The generated id presented in non-padded decimal digits.",
Type: types.StringType,
Computed: true,
},
"id": {
Description: "The generated id presented in base64 without additional transformations or prefix.",
Type: types.StringType,
Computed: true,
},
},
}, nil
}

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

type resourceID struct {
p provider
}

func (r resourceID) 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 ID

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

byteLength := plan.ByteLength.Value
bytes := make([]byte, byteLength)

n, err := rand.Reader.Read(bytes)
if int64(n) != byteLength {
resp.Diagnostics.AddError(
"generated insufficient random bytes: %s",
fmt.Sprintf("generated insufficient random bytes: %s", err),
)
return
}
if err != nil {
resp.Diagnostics.AddError(
"error generating random bytes",
fmt.Sprintf("error generating random bytes: %s", err),
)
return
}

id := base64.RawURLEncoding.EncodeToString(bytes)
prefix := plan.Prefix.Value
b64Std := base64.StdEncoding.EncodeToString(bytes)
hex := hex2.EncodeToString(bytes)

bigInt := big.Int{}
bigInt.SetBytes(bytes)
dec := bigInt.String()

i := ID{
ID: types.String{Value: id},
Keepers: plan.Keepers,
ByteLength: types.Int64{Value: plan.ByteLength.Value},
Prefix: plan.Prefix,
B64URL: types.String{Value: prefix + id},
B64Std: types.String{Value: prefix + b64Std},
Hex: types.String{Value: prefix + hex},
Dec: types.String{Value: prefix + dec},
}

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

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

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

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

func (r resourceID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
id := req.ID
var prefix string

sep := strings.LastIndex(id, ",")
if sep != -1 {
prefix = id[:sep]
id = id[sep+1:]
}

bytes, err := base64.RawURLEncoding.DecodeString(id)
if err != nil {
resp.Diagnostics.AddError(
"error decoding ID",
fmt.Sprintf("error decoding ID: %s", err))
return
}

b64Std := base64.StdEncoding.EncodeToString(bytes)
hex := hex2.EncodeToString(bytes)

bigInt := big.Int{}
bigInt.SetBytes(bytes)
dec := bigInt.String()

var state ID

state.ID.Value = id
state.ByteLength.Value = int64(len(bytes))
state.Keepers.ElemType = types.StringType
state.B64Std.Value = prefix + b64Std
state.B64URL.Value = prefix + id
state.Hex.Value = prefix + hex
state.Dec.Value = prefix + dec

if prefix == "" {
state.Prefix.Null = true
} else {
state.Prefix.Value = prefix
}

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 @@ -16,8 +16,8 @@ type idLens struct {

func TestAccResourceID(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccResourceIDConfig,
Expand All @@ -40,8 +40,8 @@ func TestAccResourceID(t *testing.T) {

func TestAccResourceID_importWithPrefix(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccResourceIDConfigWithPrefix,
Expand Down
11 changes: 1 addition & 10 deletions internal/provider_fm/resource_uuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

type resourceUUIDType struct{}
Expand Down Expand Up @@ -94,22 +93,14 @@ func (r resourceUUID) Read(ctx context.Context, req tfsdk.ReadResourceRequest, r
}

func (r resourceUUID) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) {
var plan UUID
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

// Intentionally left blank.
}

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

func (r resourceUUID) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
tfsdk.ResourceImportStatePassthroughID(ctx, tftypes.NewAttributePath().WithAttributeName("id"), req, resp)

bytes, err := uuid.ParseUUID(req.ID)
if err != nil {
resp.Diagnostics.AddError(
Expand Down

0 comments on commit d4502ea

Please sign in to comment.