Skip to content

Commit 0294a7a

Browse files
authored
Merge pull request #113 from devops-rob/skip-bug
state upgrade functionality added to support users migrating from 1.2.2
2 parents 8b1a648 + 592b15e commit 0294a7a

File tree

4 files changed

+245
-4
lines changed

4 files changed

+245
-4
lines changed

GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = terracurl
22
organization = devops-rob
3-
version = 2.0.0-rc1
3+
version = 2.0.0
44
arch = darwin_amd64
55
#arch = linux_amd64
66

docs/resources/request.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ TerraCurl request resource
6060
- `request_parameters` (Map of String) Map of parameters to attach to the API call
6161
- `retry_interval` (Number) Interval between each attempt
6262
- `skip_destroy` (Boolean) Set this to true to skip issuing a request when the resource is being destroyed
63-
- `skip_read` (Boolean) Set to true to skip the read operation (no drift detection). Defaults to false.
63+
- `skip_read` (Boolean) Set to true to skip the read operation (no drift detection). Defaults to true.
6464
- `skip_tls_verify` (Boolean) Set this to true to disable verification of the server's TLS certificate
6565
- `timeout` (Number) Time in seconds before each request times out. Defaults to 10
6666

internal/provider/curl_resource.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import (
2323
"github.com/hashicorp/terraform-plugin-framework/types"
2424
)
2525

26+
const (
27+
resourceSchemaV0 = 0
28+
resourceSchemaV1 = 1
29+
)
30+
2631
// Ensure provider defined types fully satisfy framework interfaces.
2732
var _ resource.Resource = &CurlResource{}
2833
var _ resource.ResourceWithImportState = &CurlResource{}
@@ -97,7 +102,7 @@ func (r *CurlResource) Schema(ctx context.Context, req resource.SchemaRequest, r
97102
resp.Schema = schema.Schema{
98103
// This description is used by the documentation generator and the language server.
99104
MarkdownDescription: "TerraCurl request resource",
100-
105+
Version: resourceSchemaV1,
101106
Attributes: map[string]schema.Attribute{
102107
"name": schema.StringAttribute{
103108
MarkdownDescription: "Friendly name for this API call",
@@ -323,7 +328,7 @@ func (r *CurlResource) Schema(ctx context.Context, req resource.SchemaRequest, r
323328
"skip_read": schema.BoolAttribute{
324329
Optional: true,
325330
Computed: true,
326-
MarkdownDescription: "Set to true to skip the read operation (no drift detection). Defaults to false.",
331+
MarkdownDescription: "Set to true to skip the read operation (no drift detection). Defaults to true.",
327332
Default: booldefault.StaticBool(true),
328333
},
329334
"read_url": schema.StringAttribute{
@@ -428,6 +433,13 @@ func (r *CurlResource) Create(ctx context.Context, req resource.CreateRequest, r
428433
}
429434

430435
if !data.SkipRead.IsNull() && !data.SkipRead.ValueBool() {
436+
tflog.Debug(ctx, "skip_read validation triggered", map[string]interface{}{
437+
"skip_read_is_null": data.SkipRead.IsNull(),
438+
"skip_read_value": data.SkipRead.ValueBool(),
439+
"read_url_is_null": data.ReadUrl.IsNull(),
440+
"read_method_is_null": data.ReadMethod.IsNull(),
441+
"response_codes_null": data.ReadResponseCodes.IsNull(),
442+
})
431443
if data.ReadUrl.IsNull() || data.ReadMethod.IsNull() || data.ReadResponseCodes.IsNull() {
432444
resp.Diagnostics.AddError(
433445
"Invalid Configuration",
@@ -928,3 +940,39 @@ func (r CurlResource) ConfigValidators(ctx context.Context) []resource.ConfigVal
928940
),
929941
}
930942
}
943+
944+
func (r *CurlResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
945+
return map[int64]resource.StateUpgrader{
946+
resourceSchemaV0: {
947+
StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
948+
tflog.Debug(ctx, "Beginning state upgrade from v0 to v1")
949+
950+
var oldState CurlResourceModel
951+
diags := req.State.Get(ctx, &oldState)
952+
resp.Diagnostics.Append(diags...)
953+
if resp.Diagnostics.HasError() {
954+
return
955+
}
956+
957+
// Set skip_read to true and clear read-related fields
958+
oldState.SkipRead = types.BoolValue(true)
959+
oldState.ReadUrl = types.StringNull()
960+
oldState.ReadMethod = types.StringNull()
961+
oldState.ReadHeaders = types.MapNull(types.StringType)
962+
oldState.ReadParameters = types.MapNull(types.StringType)
963+
oldState.ReadRequestBody = types.StringNull()
964+
oldState.ReadCertFile = types.StringNull()
965+
oldState.ReadKeyFile = types.StringNull()
966+
oldState.ReadCaCertFile = types.StringNull()
967+
oldState.ReadCaCertDirectory = types.StringNull()
968+
oldState.ReadSkipTlsVerify = types.BoolNull()
969+
oldState.ReadResponseCodes = types.ListNull(types.StringType)
970+
971+
// Set the upgraded state
972+
diags = resp.State.Set(ctx, oldState)
973+
resp.Diagnostics.Append(diags...)
974+
tflog.Debug(ctx, "Completed state upgrade from v0 to v1")
975+
},
976+
},
977+
}
978+
}

internal/provider/curl_resource_test.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package provider
22

33
import (
4+
"context"
45
"fmt"
6+
"github.com/hashicorp/terraform-plugin-framework/attr"
7+
resource2 "github.com/hashicorp/terraform-plugin-framework/resource"
8+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
10+
"github.com/hashicorp/terraform-plugin-framework/types"
511
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
612
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
713
"github.com/hashicorp/terraform-plugin-testing/terraform"
@@ -359,6 +365,7 @@ EOF
359365

360366
func TestAccresourceCurlSkipRead(t *testing.T) {
361367
t.Setenv("TF_ACC", "true")
368+
t.Setenv("TF_LOG", "DEBUG")
362369
t.Setenv("USE_DEFAULT_CLIENT_FOR_TESTS", "true")
363370

364371
httpmock.Activate()
@@ -418,6 +425,52 @@ EOF
418425

419426
}
420427

428+
func TestAccresourceCurlSkipReadNoReadFields(t *testing.T) {
429+
t.Setenv("TF_ACC", "true")
430+
t.Setenv("USE_DEFAULT_CLIENT_FOR_TESTS", "true")
431+
432+
httpmock.Activate()
433+
defer httpmock.DeactivateAndReset()
434+
httpmock.RegisterResponder(
435+
"POST",
436+
"https://example.com/create",
437+
httpmock.NewStringResponder(200, `{"name": "devopsrob"}`),
438+
)
439+
440+
rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
441+
442+
resource.Test(t, resource.TestCase{
443+
PreCheck: func() { testAccPreCheck(t) },
444+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
445+
Steps: []resource.TestStep{
446+
{
447+
Config: testAccresourceCurlSkipReadNoReadFields(rName, RequestBody),
448+
},
449+
},
450+
})
451+
}
452+
453+
func testAccresourceCurlSkipReadNoReadFields(name string, body string) string {
454+
return fmt.Sprintf(`
455+
resource "terracurl_request" "test" {
456+
name = "%s"
457+
url = "https://example.com/create"
458+
response_codes = ["200"]
459+
460+
request_body = <<EOF
461+
%s
462+
EOF
463+
464+
retry_interval = 1
465+
max_retry = 1
466+
method = "POST"
467+
468+
skip_destroy = true
469+
skip_read = true
470+
}
471+
`, name, body)
472+
}
473+
421474
func TestAccresourceCurlRead(t *testing.T) {
422475
t.Setenv("TF_ACC", "true")
423476
t.Setenv("USE_DEFAULT_CLIENT_FOR_TESTS", "true")
@@ -731,3 +784,143 @@ func testMockEndpointRegister(endpoint string) resource.TestCheckFunc {
731784
return nil
732785
}
733786
}
787+
788+
func TestCurlResource_StateUpgrade(t *testing.T) {
789+
ctx := context.Background()
790+
r := &CurlResource{}
791+
792+
upgraders := r.UpgradeState(ctx)
793+
upgrader, ok := upgraders[0]
794+
if !ok {
795+
t.Fatal("No upgrader found for version 0")
796+
}
797+
798+
// Define the complete schema
799+
schemaVar := schema.Schema{
800+
Attributes: map[string]schema.Attribute{
801+
"id": schema.StringAttribute{Computed: true},
802+
"name": schema.StringAttribute{Optional: true},
803+
"url": schema.StringAttribute{Required: true},
804+
"method": schema.StringAttribute{Optional: true},
805+
"headers": schema.MapAttribute{ElementType: types.StringType, Optional: true},
806+
"request_parameters": schema.MapAttribute{ElementType: types.StringType, Optional: true},
807+
"request_body": schema.StringAttribute{Optional: true},
808+
"cert_file": schema.StringAttribute{Optional: true},
809+
"key_file": schema.StringAttribute{Optional: true},
810+
"ca_cert_file": schema.StringAttribute{Optional: true},
811+
"ca_cert_directory": schema.StringAttribute{Optional: true},
812+
"skip_tls_verify": schema.BoolAttribute{Optional: true},
813+
"timeout": schema.Int64Attribute{Optional: true},
814+
"response_codes": schema.ListAttribute{ElementType: types.StringType, Optional: true},
815+
"status_code": schema.StringAttribute{Computed: true},
816+
"response": schema.StringAttribute{Computed: true},
817+
"request_url_string": schema.StringAttribute{Computed: true},
818+
"max_retry": schema.Int64Attribute{Optional: true},
819+
"retry_interval": schema.Int64Attribute{Optional: true},
820+
"ignore_response_fields": schema.ListAttribute{ElementType: types.StringType, Optional: true},
821+
"drift_marker": schema.StringAttribute{Optional: true},
822+
823+
// Read-related fields
824+
"skip_read": schema.BoolAttribute{Optional: true},
825+
"read_url": schema.StringAttribute{Optional: true},
826+
"read_method": schema.StringAttribute{Optional: true},
827+
"read_headers": schema.MapAttribute{ElementType: types.StringType, Optional: true},
828+
"read_parameters": schema.MapAttribute{ElementType: types.StringType, Optional: true},
829+
"read_request_body": schema.StringAttribute{Optional: true},
830+
"read_cert_file": schema.StringAttribute{Optional: true},
831+
"read_key_file": schema.StringAttribute{Optional: true},
832+
"read_ca_cert_file": schema.StringAttribute{Optional: true},
833+
"read_ca_cert_directory": schema.StringAttribute{Optional: true},
834+
"read_skip_tls_verify": schema.BoolAttribute{Optional: true},
835+
"read_response_codes": schema.ListAttribute{ElementType: types.StringType, Optional: true},
836+
837+
// Destroy-related fields
838+
"skip_destroy": schema.BoolAttribute{Optional: true},
839+
"destroy_url": schema.StringAttribute{Optional: true},
840+
"destroy_method": schema.StringAttribute{Optional: true},
841+
"destroy_headers": schema.MapAttribute{ElementType: types.StringType, Optional: true},
842+
"destroy_request_parameters": schema.MapAttribute{ElementType: types.StringType, Optional: true},
843+
"destroy_request_body": schema.StringAttribute{Optional: true},
844+
"destroy_cert_file": schema.StringAttribute{Optional: true},
845+
"destroy_key_file": schema.StringAttribute{Optional: true},
846+
"destroy_ca_cert_file": schema.StringAttribute{Optional: true},
847+
"destroy_ca_cert_directory": schema.StringAttribute{Optional: true},
848+
"destroy_skip_tls_verify": schema.BoolAttribute{Optional: true},
849+
"destroy_response_codes": schema.ListAttribute{ElementType: types.StringType, Optional: true},
850+
"destroy_timeout": schema.Int64Attribute{Optional: true},
851+
"destroy_max_retry": schema.Int64Attribute{Optional: true},
852+
"destroy_retry_interval": schema.Int64Attribute{Optional: true},
853+
"destroy_request_url_string": schema.StringAttribute{Computed: true},
854+
},
855+
}
856+
857+
// Create initial state
858+
oldState := &CurlResourceModel{
859+
Id: types.StringValue("test-resource"),
860+
Name: types.StringValue("test"),
861+
Url: types.StringValue("https://api.example.com"),
862+
Method: types.StringValue("POST"),
863+
ReadUrl: types.StringValue("https://api.example.com/read"),
864+
Headers: types.MapValueMust(types.StringType, map[string]attr.Value{}),
865+
ReadHeaders: types.MapValueMust(types.StringType, map[string]attr.Value{}),
866+
ReadParameters: types.MapValueMust(types.StringType, map[string]attr.Value{}),
867+
ReadResponseCodes: types.ListValueMust(
868+
types.StringType,
869+
[]attr.Value{
870+
types.StringValue("200"),
871+
},
872+
),
873+
ResponseCodes: types.ListValueMust(types.StringType, []attr.Value{}),
874+
IgnoreResponseFields: types.ListValueMust(types.StringType, []attr.Value{}),
875+
RequestParameters: types.MapValueMust(types.StringType, map[string]attr.Value{}),
876+
DestroyHeaders: types.MapValueMust(types.StringType, map[string]attr.Value{}),
877+
DestroyRequestParameters: types.MapValueMust(types.StringType, map[string]attr.Value{}),
878+
DestroyResponseCodes: types.ListValueMust(types.StringType, []attr.Value{}),
879+
}
880+
881+
state := tfsdk.State{
882+
Schema: schemaVar,
883+
}
884+
diags := state.Set(ctx, oldState)
885+
if diags.HasError() {
886+
t.Fatalf("error setting initial state: %v", diags)
887+
}
888+
889+
req := resource2.UpgradeStateRequest{
890+
State: &state,
891+
}
892+
893+
resp := &resource2.UpgradeStateResponse{
894+
State: tfsdk.State{
895+
Schema: schemaVar,
896+
},
897+
}
898+
899+
upgrader.StateUpgrader(ctx, req, resp)
900+
if resp.Diagnostics.HasError() {
901+
t.Fatalf("upgrade failed: %v", resp.Diagnostics)
902+
}
903+
904+
var upgradedState CurlResourceModel
905+
diags = resp.State.Get(ctx, &upgradedState)
906+
if diags.HasError() {
907+
t.Fatalf("error getting upgraded state: %v", diags)
908+
}
909+
910+
// Verify the results
911+
if !upgradedState.ReadUrl.IsNull() {
912+
t.Error("ReadUrl should be null after upgrade")
913+
}
914+
915+
if !upgradedState.ReadResponseCodes.IsNull() {
916+
t.Error("ReadResponseCodes should be null after upgrade")
917+
}
918+
919+
if upgradedState.Id.ValueString() != "test-resource" {
920+
t.Error("Id was not preserved")
921+
}
922+
923+
if upgradedState.Url.ValueString() != "https://api.example.com" {
924+
t.Error("Url was not preserved")
925+
}
926+
}

0 commit comments

Comments
 (0)