| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package provider |
| |
| import ( |
| "context" |
| "fmt" |
| "regexp" |
| "strings" |
| "time" |
| |
| "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" |
| "github.com/hashicorp/terraform-plugin-framework/attr" |
| "github.com/hashicorp/terraform-plugin-framework/path" |
| "github.com/hashicorp/terraform-plugin-framework/resource" |
| "github.com/hashicorp/terraform-plugin-framework/resource/schema" |
| "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/hashicorp/terraform-plugin-framework-validators/stringvalidator" |
| ) |
| |
| var ( |
| _ resource.Resource = (*timeSleepResource)(nil) |
| _ resource.ResourceWithImportState = (*timeSleepResource)(nil) |
| ) |
| |
| func NewTimeSleepResource() resource.Resource { |
| return &timeSleepResource{} |
| } |
| |
| type timeSleepResource struct{} |
| |
| func (t timeSleepResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { |
| resp.TypeName = req.ProviderTypeName + "_sleep" |
| } |
| |
| func (t timeSleepResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { |
| resp.Schema = schema.Schema{ |
| Description: "Manages a resource that delays creation and/or destruction, typically for further resources. " + |
| "This prevents cross-platform compatibility and destroy-time issues with using " + |
| "the [`local-exec` provisioner](https://www.terraform.io/docs/provisioners/local-exec.html).", |
| Attributes: map[string]schema.Attribute{ |
| "create_duration": schema.StringAttribute{ |
| Description: "[Time duration](https://golang.org/pkg/time/#ParseDuration) to delay resource creation. " + |
| "For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay.", |
| Optional: true, |
| Validators: []validator.String{ |
| stringvalidator.AtLeastOneOf(path.MatchRoot("destroy_duration")), |
| stringvalidator.RegexMatches(regexp.MustCompile(`^[0-9]+(\.[0-9]+)?(ms|s|m|h)$`), |
| "must be a number immediately followed by ms (milliseconds), s (seconds), m (minutes), or h (hours). For example, \"30s\" for 30 seconds."), |
| }, |
| }, |
| "destroy_duration": schema.StringAttribute{ |
| Description: "[Time duration](https://golang.org/pkg/time/#ParseDuration) to delay resource destroy. " + |
| "For example, `30s` for 30 seconds or `5m` for 5 minutes. Updating this value by itself will not trigger a delay. " + |
| "This value or any updates to it must be successfully applied into the Terraform state before destroying this resource to take effect.", |
| Optional: true, |
| Validators: []validator.String{ |
| stringvalidator.AtLeastOneOf(path.MatchRoot("create_duration")), |
| stringvalidator.RegexMatches(regexp.MustCompile(`^[0-9]+(\.[0-9]+)?(ms|s|m|h)$`), |
| "must be a number immediately followed by ms (milliseconds), s (seconds), m (minutes), or h (hours). For example, \"30s\" for 30 seconds."), |
| }, |
| }, |
| "triggers": schema.MapAttribute{ |
| Description: "(Optional) Arbitrary map of values that, when changed, will run any creation or destroy delays again. " + |
| "See [the main provider documentation](../index.md) for more information.", |
| ElementType: types.StringType, |
| Optional: true, |
| PlanModifiers: []planmodifier.Map{ |
| mapplanmodifier.RequiresReplace(), |
| }, |
| }, |
| "id": schema.StringAttribute{ |
| CustomType: timetypes.RFC3339Type{}, |
| Description: "RFC3339 format of the offset timestamp, e.g. `2020-02-12T06:36:13Z`.", |
| Computed: true, |
| PlanModifiers: []planmodifier.String{ |
| stringplanmodifier.UseStateForUnknown(), |
| }, |
| }, |
| }, |
| } |
| } |
| |
| func (t timeSleepResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { |
| id := req.ID |
| |
| idParts := strings.Split(id, ",") |
| |
| if len(idParts) != 2 || (idParts[0] == "" && idParts[1] == "") { |
| resp.Diagnostics.AddError( |
| "Unexpected Format of ID", |
| fmt.Sprintf("Unexpected format of ID (%q), expected CREATEDURATION,DESTROYDURATION where at least one value is non-empty", id)) |
| |
| return |
| } |
| |
| state := timeSleepModelV0{ |
| CreateDuration: types.StringNull(), |
| DestroyDuration: types.StringNull(), |
| ID: timetypes.NewRFC3339TimeValue(time.Now().UTC()), |
| } |
| |
| if idParts[0] != "" { |
| _, err := time.ParseDuration(idParts[0]) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Import time sleep error", |
| "The create_duration cannot be parsed\n\n+"+ |
| fmt.Sprintf("Original Error: %s", err), |
| ) |
| return |
| } |
| state.CreateDuration = types.StringValue(idParts[0]) |
| } |
| |
| if idParts[1] != "" { |
| _, err := time.ParseDuration(idParts[1]) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Import time sleep error", |
| "The create_duration cannot be parsed\n\n+"+ |
| fmt.Sprintf("Original Error: %s", err), |
| ) |
| return |
| } |
| state.DestroyDuration = types.StringValue(idParts[1]) |
| } |
| |
| state.Triggers = types.MapValueMust(types.StringType, map[string]attr.Value{}) |
| |
| diags := resp.State.Set(ctx, state) |
| resp.Diagnostics.Append(diags...) |
| } |
| |
| func (t timeSleepResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { |
| var plan timeSleepModelV0 |
| |
| diags := req.Plan.Get(ctx, &plan) |
| resp.Diagnostics.Append(diags...) |
| if resp.Diagnostics.HasError() { |
| return |
| } |
| |
| if plan.CreateDuration.ValueString() != "" { |
| duration, err := time.ParseDuration(plan.CreateDuration.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Create time sleep error", |
| "The create_duration cannot be parsed\n\n+"+ |
| fmt.Sprintf("Original Error: %s", err), |
| ) |
| return |
| } |
| |
| select { |
| case <-ctx.Done(): |
| resp.Diagnostics.AddError( |
| "Create time sleep error", |
| fmt.Sprintf("Original Error: %s", ctx.Err()), |
| ) |
| return |
| case <-time.After(duration): |
| } |
| } |
| |
| state := timeSleepModelV0{ |
| CreateDuration: plan.CreateDuration, |
| DestroyDuration: plan.DestroyDuration, |
| Triggers: plan.Triggers, |
| ID: timetypes.NewRFC3339TimeValue(time.Now().UTC()), |
| } |
| diags = resp.State.Set(ctx, state) |
| resp.Diagnostics.Append(diags...) |
| } |
| |
| func (t timeSleepResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { |
| |
| } |
| |
| func (t timeSleepResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { |
| var data timeSleepModelV0 |
| |
| // Read Terraform plan data into the model |
| resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) |
| |
| // Save updated data into Terraform state |
| resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) |
| } |
| |
| func (t timeSleepResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { |
| var state timeSleepModelV0 |
| |
| diags := req.State.Get(ctx, &state) |
| resp.Diagnostics.Append(diags...) |
| if resp.Diagnostics.HasError() { |
| return |
| } |
| |
| if state.DestroyDuration.ValueString() != "" { |
| duration, err := time.ParseDuration(state.DestroyDuration.ValueString()) |
| if err != nil { |
| resp.Diagnostics.AddError( |
| "Delete time sleep error", |
| "The create_duration cannot be parsed\n\n+"+ |
| fmt.Sprintf("Original Error: %s", err), |
| ) |
| return |
| } |
| |
| select { |
| case <-ctx.Done(): |
| resp.Diagnostics.AddError( |
| "Delete time sleep error", |
| fmt.Sprintf("Original Error: %s", ctx.Err()), |
| ) |
| return |
| case <-time.After(duration): |
| } |
| } |
| } |
| |
| type timeSleepModelV0 struct { |
| CreateDuration types.String `tfsdk:"create_duration"` |
| DestroyDuration types.String `tfsdk:"destroy_duration"` |
| Triggers types.Map `tfsdk:"triggers"` |
| ID timetypes.RFC3339 `tfsdk:"id"` |
| } |