diff --git a/stackit/internal/services/opensearch/credential/resource.go b/stackit/internal/services/opensearch/credential/resource.go index 041718dd4..1510cd89a 100644 --- a/stackit/internal/services/opensearch/credential/resource.go +++ b/stackit/internal/services/opensearch/credential/resource.go @@ -15,7 +15,6 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "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/planmodifier" @@ -190,7 +189,11 @@ func (r *credentialResource) Create(ctx context.Context, req resource.CreateRequ return } credentialId := *credentialsResp.Id - ctx = tflog.SetField(ctx, "credential_id", credentialId) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "instance_id": instanceId, + "credential_id": credentialId, + }) waitResp, err := wait.CreateCredentialsWaitHandler(ctx, r.client, projectId, instanceId, credentialId).WaitWithContext(ctx) if err != nil { @@ -311,9 +314,11 @@ func (r *credentialResource) ImportState(ctx context.Context, req resource.Impor return } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[1])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("credential_id"), idParts[2])...) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "instance_id": idParts[1], + "credential_id": idParts[2], + }) tflog.Info(ctx, "OpenSearch credential state imported") } diff --git a/stackit/internal/services/opensearch/instance/resource.go b/stackit/internal/services/opensearch/instance/resource.go index e23554952..844216c79 100644 --- a/stackit/internal/services/opensearch/instance/resource.go +++ b/stackit/internal/services/opensearch/instance/resource.go @@ -20,7 +20,6 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" - "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/planmodifier" @@ -366,9 +365,16 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques } ctx = core.LogResponse(ctx) - + if createResp.InstanceId == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "API response did not include instance ID") + } instanceId := *createResp.InstanceId - ctx = tflog.SetField(ctx, "instance_id", instanceId) + + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "instance_id": instanceId, + }) + waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, instanceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err)) @@ -557,8 +563,10 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS return } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[1])...) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "instance_id": idParts[1], + }) tflog.Info(ctx, "OpenSearch instance state imported") } diff --git a/stackit/internal/services/opensearch/opensearch_acc_test.go b/stackit/internal/services/opensearch/opensearch_acc_test.go index 223b12cee..1c722ec90 100644 --- a/stackit/internal/services/opensearch/opensearch_acc_test.go +++ b/stackit/internal/services/opensearch/opensearch_acc_test.go @@ -3,9 +3,12 @@ package opensearch_test import ( "context" "fmt" + "net/http" + "regexp" "strings" "testing" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stackitcloud/stackit-sdk-go/core/config" @@ -235,6 +238,153 @@ func TestAccOpenSearchResource(t *testing.T) { }) } +func TestOpensearchInstanceSavesIDsOnError(t *testing.T) { + var ( + projectId = uuid.NewString() + instanceId = uuid.NewString() + ) + const ( + name = "opensearch-instance-test" + version = "version" + planName = "plan-name" + ) + s := testutil.NewMockServer(t) + defer s.Server.Close() + tfConfig := fmt.Sprintf(` +provider "stackit" { + opensearch_custom_endpoint = "%s" + service_account_token = "mock-server-needs-no-auth" +} + +resource "stackit_opensearch_instance" "instance" { + project_id = "%s" + name = "%s" + version = "%s" + plan_name = "%s" +} +`, s.Server.URL, projectId, name, version, planName) + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "offerings", + ToJsonBody: &opensearch.ListOfferingsResponse{ + Offerings: &[]opensearch.Offering{ + { + Name: utils.Ptr("offering-name"), + Version: utils.Ptr(version), + Plans: &[]opensearch.Plan{ + { + Id: utils.Ptr("plan-id"), + Name: utils.Ptr(planName), + }, + }, + }, + }, + }, + }, + testutil.MockResponse{ + Description: "create instance", + ToJsonBody: &opensearch.CreateInstanceResponse{ + InstanceId: utils.Ptr(instanceId), + }, + }, + testutil.MockResponse{Description: "failing waiter", StatusCode: http.StatusInternalServerError}, + ) + }, + Config: tfConfig, + ExpectError: regexp.MustCompile("Error creating instance.*"), + }, + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "refresh", + Handler: func(w http.ResponseWriter, req *http.Request) { + expected := fmt.Sprintf("/v1/projects/%s/instances/%s", projectId, instanceId) + if req.URL.Path != expected { + t.Errorf(fmt.Sprintf("unexpected URL path: got %s, want %s", req.URL.Path, expected), http.StatusBadRequest) + } + w.WriteHeader(http.StatusInternalServerError) + }, + }, + testutil.MockResponse{Description: "delete"}, + testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusGone}, + ) + }, + RefreshState: true, + ExpectError: regexp.MustCompile("Error reading instance.*"), + }, + }, + }) +} + +func TestOpensearchCredentialSavesIDsOnError(t *testing.T) { + var ( + projectId = uuid.NewString() + instanceId = uuid.NewString() + credentialId = uuid.NewString() + ) + s := testutil.NewMockServer(t) + defer s.Server.Close() + tfConfig := fmt.Sprintf(` +provider "stackit" { + opensearch_custom_endpoint = "%s" + service_account_token = "mock-server-needs-no-auth" +} + +resource "stackit_opensearch_credential" "credential" { + project_id = "%s" + instance_id = "%s" +} +`, s.Server.URL, projectId, instanceId) + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "create credential", + ToJsonBody: &opensearch.CredentialsResponse{ + Id: utils.Ptr(credentialId), + }, + }, + testutil.MockResponse{Description: "create waiter", StatusCode: http.StatusInternalServerError}, + ) + }, + Config: tfConfig, + ExpectError: regexp.MustCompile("Error creating credential.*"), + }, + { + PreConfig: func() { + s.Reset( + testutil.MockResponse{ + Description: "refresh", + Handler: func(w http.ResponseWriter, req *http.Request) { + expected := fmt.Sprintf("/v1/projects/%s/instances/%s/credentials/%s", projectId, instanceId, credentialId) + if req.URL.Path != expected { + t.Errorf(fmt.Sprintf("unexpected URL path: got %s, want %s", req.URL.Path, expected), http.StatusBadRequest) + } + w.WriteHeader(http.StatusInternalServerError) + }, + }, + testutil.MockResponse{Description: "delete"}, + testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusGone}, + ) + }, + RefreshState: true, + ExpectError: regexp.MustCompile("Error reading credential.*"), + }, + }, + }) +} + func testAccCheckOpenSearchDestroy(s *terraform.State) error { ctx := context.Background() var client *opensearch.APIClient