blob: c143eaf05211d973c3bc1d1bd8f4d0ea4f134e5d [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package provider
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"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/mapplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/terraform-providers/terraform-provider-random/internal/diagnostics"
)
var (
_ resource.Resource = (*bytesResource)(nil)
_ resource.ResourceWithImportState = (*bytesResource)(nil)
)
func NewBytesResource() resource.Resource {
return &bytesResource{}
}
type bytesResource struct {
}
func (r *bytesResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_bytes"
}
func (r *bytesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = bytesSchemaV0()
}
func (r *bytesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan bytesModelV0
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
bytes := make([]byte, plan.Length.ValueInt64())
_, err := rand.Read(bytes)
if err != nil {
resp.Diagnostics.AddError(
"Create Random bytes error",
"There was an error during random generation.\n\n"+
diagnostics.RetryMsg+
fmt.Sprintf("Original Error: %s", err),
)
return
}
u := &bytesModelV0{
Length: plan.Length,
Base64: types.StringValue(base64.StdEncoding.EncodeToString(bytes)),
Hex: types.StringValue(hex.EncodeToString(bytes)),
Keepers: plan.Keepers,
}
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 *bytesResource) Read(context.Context, resource.ReadRequest, *resource.ReadResponse) {
}
func (r *bytesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var model bytesModelV0
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 *bytesResource) Delete(context.Context, resource.DeleteRequest, *resource.DeleteResponse) {
}
func (r *bytesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
bytes, err := base64.StdEncoding.DecodeString(req.ID)
if err != nil {
resp.Diagnostics.AddError(
"Import Random bytes Error",
"There was an error during the parsing of the base64 string.\n\n"+
diagnostics.RetryMsg+
fmt.Sprintf("Original Error: %s", err),
)
return
}
var state bytesModelV0
state.Length = types.Int64Value(int64(len(bytes)))
state.Base64 = types.StringValue(req.ID)
state.Hex = types.StringValue(hex.EncodeToString(bytes))
state.Keepers = types.MapNull(types.StringType)
diags := resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
type bytesModelV0 struct {
Length types.Int64 `tfsdk:"length"`
Keepers types.Map `tfsdk:"keepers"`
Base64 types.String `tfsdk:"base64"`
Hex types.String `tfsdk:"hex"`
}
func bytesSchemaV0() schema.Schema {
return schema.Schema{
Version: 0,
Description: "The resource `random_bytes` generates random bytes that are intended to be " +
"used as a secret, or key. Use this in preference to `random_id` when the output is " +
"considered sensitive, and should not be displayed in the CLI.",
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{
mapplanmodifier.RequiresReplace(),
},
},
"length": schema.Int64Attribute{
Description: "The number of bytes requested. The minimum value for length is 1.",
Required: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
},
Validators: []validator.Int64{
int64validator.AtLeast(1),
},
},
"base64": schema.StringAttribute{
Description: "The generated bytes presented in base64 string format.",
Computed: true,
Sensitive: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"hex": schema.StringAttribute{
Description: "The generated bytes presented in hex string format.",
Computed: true,
Sensitive: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}