blob: a70e53c10bab465ef5bc93f0f80573b6503b5f95 [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package provider
import (
"context"
"fmt"
"strconv"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
mapplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/map"
"github.com/terraform-providers/terraform-provider-random/internal/random"
)
var (
_ resource.Resource = (*integerResource)(nil)
_ resource.ResourceWithImportState = (*integerResource)(nil)
)
func NewIntegerResource() resource.Resource {
return &integerResource{}
}
type integerResource struct{}
func (r *integerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_integer"
}
func (r *integerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.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]schema.Attribute{
"keepers": schema.MapAttribute{
Description: "Arbitrary map of values that, when changed, will trigger recreation of " +
"resource. See [the main provider documentation](../index.html) for more information.",
ElementType: types.StringType,
Optional: true,
PlanModifiers: []planmodifier.Map{
mapplanmodifiers.RequiresReplaceIfValuesNotNull(),
},
},
"min": schema.Int64Attribute{
Description: "The minimum inclusive value of the range.",
Required: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
},
},
"max": schema.Int64Attribute{
Description: "The maximum inclusive value of the range.",
Required: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
},
},
"seed": schema.StringAttribute{
Description: "A custom seed to always produce the same value.",
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"result": schema.Int64Attribute{
Description: "The random integer result.",
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"id": schema.StringAttribute{
Description: "The string representation of the integer result.",
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}
func (r *integerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan integerModelV0
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
max := int(plan.Max.ValueInt64())
min := int(plan.Min.ValueInt64())
seed := plan.Seed.ValueString()
if max < min {
resp.Diagnostics.AddError(
"Create Random Integer Error",
"The minimum (min) value needs to be smaller than or equal to maximum (max) value.",
)
return
}
rand := random.NewRand(seed)
number := rand.Intn((max+1)-min) + min
u := &integerModelV0{
ID: types.StringValue(strconv.Itoa(number)),
Keepers: plan.Keepers,
Min: types.Int64Value(int64(min)),
Max: types.Int64Value(int64(max)),
Result: types.Int64Value(int64(number)),
}
if seed != "" {
u.Seed = types.StringValue(seed)
} else {
u.Seed = types.StringNull()
}
diags = resp.State.Set(ctx, u)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Read does not need to perform any operations as the state in ReadResourceResponse is already populated.
func (r *integerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
}
// Update ensures the plan value is copied to the state to complete the update.
func (r *integerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var model integerModelV0
resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, &model)...)
}
// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the
// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301).
func (r *integerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
}
func (r *integerResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
parts := strings.Split(req.ID, ",")
if len(parts) != 3 && len(parts) != 4 {
resp.Diagnostics.AddError(
"Import Random Integer Error",
"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(
"Import Random Integer Error",
"The value supplied could not be parsed as an integer.\n\n"+
fmt.Sprintf("Original Error: %s", err),
)
return
}
min, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Import Random Integer Error",
"The min value supplied could not be parsed as an integer.\n\n"+
fmt.Sprintf("Original Error: %s", err),
)
return
}
max, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Import Random Integer Error",
"The max value supplied could not be parsed as an integer.\n\n"+
fmt.Sprintf("Original Error: %s", err),
)
return
}
var state integerModelV0
state.ID = types.StringValue(parts[0])
// Using types.MapValueMust to ensure map is known.
state.Keepers = types.MapValueMust(types.StringType, nil)
state.Result = types.Int64Value(result)
state.Min = types.Int64Value(min)
state.Max = types.Int64Value(max)
if len(parts) == 4 {
state.Seed = types.StringValue(parts[3])
}
diags := resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
type integerModelV0 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"`
}