Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions stackit/internal/services/opensearch/credential/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}

Expand Down
18 changes: 13 additions & 5 deletions stackit/internal/services/opensearch/instance/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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")
}

Expand Down
150 changes: 150 additions & 0 deletions stackit/internal/services/opensearch/opensearch_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Comment thread
rubenhoenle marked this conversation as resolved.
Outdated
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
Expand Down
91 changes: 26 additions & 65 deletions stackit/internal/services/rabbitmq/rabbitmq_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,7 @@ func TestAccRabbitMQResource(t *testing.T) {
}

// Run apply for an instance and produce an error in the waiter. By erroring out state checks are not run in this step.
// The second step imports the resource and runs a state check to verify that the first step wrote the IDs, despite the error.
// When importing we append "-import" to the credential ID to verify that the import isn't overwriting the instance
// ID from the first step
// The second step refreshes the resource and verifies that the IDs are passed to the read function.
func TestRabbitMQInstanceSavesIDsOnError(t *testing.T) {
projectId := uuid.NewString()
instanceId := uuid.NewString()
Expand Down Expand Up @@ -294,16 +292,15 @@ resource "stackit_rabbitmq_instance" "instance" {
{
PreConfig: func() {
s.Reset(
// respond to listing offerings
offerings,
// initial post response
testutil.MockResponse{
Description: "create",
ToJsonBody: rabbitmq.CreateInstanceResponse{
InstanceId: utils.Ptr(instanceId),
},
},
// failing waiter
testutil.MockResponse{
Description: "create waiter",
ToJsonBody: rabbitmq.Instance{
Status: utils.Ptr(rabbitmq.INSTANCESTATUS_FAILED),
},
Expand All @@ -316,49 +313,29 @@ resource "stackit_rabbitmq_instance" "instance" {
{
PreConfig: func() {
s.Reset(
// read from import
testutil.MockResponse{
ToJsonBody: rabbitmq.Instance{
Status: utils.Ptr(rabbitmq.INSTANCESTATUS_ACTIVE),
InstanceId: utils.Ptr(instanceId + "-import"),
PlanId: utils.Ptr(planId),
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("expected request to %s, got %s", expected, req.URL.Path)
}
w.WriteHeader(http.StatusInternalServerError)
},
},
// list offerings in import
offerings,
// delete
testutil.MockResponse{StatusCode: http.StatusAccepted},
// delete waiter
testutil.MockResponse{
StatusCode: http.StatusGone,
},
testutil.MockResponse{Description: "delete", StatusCode: http.StatusAccepted},
testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusGone},
)
},
ImportStateCheck: func(states []*terraform.InstanceState) error {
if len(states) != 1 {
return fmt.Errorf("expected exactly one state to be imported, got %d", len(states))
}
state := states[0]
if state.Attributes["instance_id"] != instanceId {
return fmt.Errorf("expected instance_id to be %s, got %s", instanceId, state.Attributes["instance_id"])
}
if state.Attributes["project_id"] != projectId {
return fmt.Errorf("expected project_id to be %s, got %s", projectId, state.Attributes["project_id"])
}
return nil
},
ImportState: true,
ImportStateId: fmt.Sprintf("%s,%s", projectId, instanceId),
ResourceName: "stackit_rabbitmq_instance.instance",
RefreshState: true,
ExpectError: regexp.MustCompile("Error reading instance.*"),
},
},
})
}

// Run apply for credentials and produce an error in the waiter. By erroring out state checks are not run in this step.
// The second step imports the resource and runs a state check to verify that the first step wrote the IDs, despite the error.
// When importing we append "-import" to the credential ID to verify that the import isn't overwriting the credential
// ID from the first step
// The second step refreshes the resource and verifies that the IDs are passed to the read function.
func TestRabbitMQCredentialsSavesIDsOnError(t *testing.T) {
var (
projectId = uuid.NewString()
Expand Down Expand Up @@ -400,38 +377,22 @@ resource "stackit_rabbitmq_credential" "credential" {
{
PreConfig: func() {
s.Reset(
// read from import
testutil.MockResponse{
ToJsonBody: rabbitmq.CredentialsResponse{
Id: utils.Ptr(credentialId + "-import"),
Raw: &rabbitmq.RawCredentials{},
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("expected request to %s, got %s", expected, req.URL.Path)
}
w.WriteHeader(http.StatusInternalServerError)
},
},
// delete
testutil.MockResponse{StatusCode: http.StatusAccepted},
// delete waiter
testutil.MockResponse{StatusCode: http.StatusGone},
testutil.MockResponse{Description: "delete", StatusCode: http.StatusAccepted},
testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusGone},
)
},
ImportStateCheck: func(states []*terraform.InstanceState) error {
if len(states) != 1 {
return fmt.Errorf("expected exactly one state to be imported, got %d", len(states))
}
state := states[0]
if state.Attributes["instance_id"] != instanceId {
return fmt.Errorf("expected instance_id to be %s, got %s", instanceId, state.Attributes["instance_id"])
}
if state.Attributes["project_id"] != projectId {
return fmt.Errorf("expected project_id to be %s, got %s", projectId, state.Attributes["project_id"])
}
if state.Attributes["credential_id"] != credentialId {
return fmt.Errorf("expected credential_id to be %s, got %s", credentialId, state.Attributes["credential_id"])
}
return nil
},
ImportState: true,
ImportStateId: fmt.Sprintf("%s,%s,%s", projectId, instanceId, credentialId),
ResourceName: "stackit_rabbitmq_credential.credential",
RefreshState: true,
ExpectError: regexp.MustCompile("Error reading credential.*"),
},
},
})
Expand Down
Loading
Loading