Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
23 changes: 23 additions & 0 deletions internal/providers/pluginfw/products/app/resource_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ func (a *resourceApp) Create(ctx context.Context, req resource.CreateRequest, re
return
}

if err := waitForAppDeleted(ctx, w, appGoSdk.Name); err != nil {
resp.Diagnostics.AddError("failed to wait for app deletion", err.Error())
return
}

// Create the app
var forceSendFields []string
if !app.NoCompute.IsNull() {
Expand Down Expand Up @@ -211,6 +216,24 @@ func (a *resourceApp) waitForApp(ctx context.Context, w *databricks.WorkspaceCli
})
}

func waitForAppDeleted(ctx context.Context, w *databricks.WorkspaceClient, name string) error {
retrier := retries.New[struct{}](retries.WithTimeout(-1), retries.WithRetryFunc(shouldRetry))
_, err := retrier.Run(ctx, func(ctx context.Context) (*struct{}, error) {
app, err := w.Apps.GetByName(ctx, name)
if apierr.IsMissing(err) {
return &struct{}{}, nil
}
if err != nil {
return nil, retries.Halt(err)
}
if app.ComputeStatus.State == apps.ComputeStateDeleting {
return nil, retries.Continues(fmt.Sprintf("app %s is still deleting", name))
}
return &struct{}{}, nil
})
return err
}

func (a *resourceApp) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName)

Expand Down
83 changes: 83 additions & 0 deletions internal/providers/pluginfw/products/app/resource_app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package app

import (
"context"
"testing"

"github.com/databricks/databricks-sdk-go/apierr"
"github.com/databricks/databricks-sdk-go/experimental/mocks"
"github.com/databricks/databricks-sdk-go/service/apps"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestWaitForAppDeleted_AppDoesNotExist(t *testing.T) {
ctx := context.Background()
mockClient := mocks.NewMockWorkspaceClient(t)
mockAppsAPI := mockClient.GetMockAppsAPI()

mockAppsAPI.EXPECT().GetByName(mock.Anything, "test-app").Return(nil, &apierr.APIError{
StatusCode: 404,
Message: "App not found",
})

err := waitForAppDeleted(ctx, mockClient.WorkspaceClient, "test-app")
assert.NoError(t, err)
}

func TestWaitForAppDeleted_AppInDeletingState(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
mockClient := mocks.NewMockWorkspaceClient(t)
mockAppsAPI := mockClient.GetMockAppsAPI()

mockAppsAPI.EXPECT().GetByName(mock.Anything, "test-app").Return(&apps.App{
Name: "test-app",
ComputeStatus: &apps.ComputeStatus{
State: apps.ComputeStateDeleting,
Message: "App is being deleted",
},
}, nil).Once()

mockAppsAPI.EXPECT().GetByName(mock.Anything, "test-app").Return(nil, &apierr.APIError{
StatusCode: 404,
Message: "App not found",
}).Run(func(_ context.Context, _ string) {
cancel()
}).Once()

err := waitForAppDeleted(ctx, mockClient.WorkspaceClient, "test-app")
assert.NoError(t, err)
}

func TestWaitForAppDeleted_AppNotInDeletingState_ReturnsImmediately(t *testing.T) {
ctx := context.Background()
mockClient := mocks.NewMockWorkspaceClient(t)
mockAppsAPI := mockClient.GetMockAppsAPI()

// If app exists but is not in DELETING state, return immediately.
// The subsequent Create() call will fail with a proper API error.
mockAppsAPI.EXPECT().GetByName(mock.Anything, "test-app").Return(&apps.App{
Name: "test-app",
ComputeStatus: &apps.ComputeStatus{
State: apps.ComputeStateActive,
Message: "App is active",
},
}, nil).Once()

err := waitForAppDeleted(ctx, mockClient.WorkspaceClient, "test-app")
assert.NoError(t, err)
}

func TestWaitForAppDeleted_APIError(t *testing.T) {
ctx := context.Background()
mockClient := mocks.NewMockWorkspaceClient(t)
mockAppsAPI := mockClient.GetMockAppsAPI()
mockAppsAPI.EXPECT().GetByName(mock.Anything, "test-app").Return(nil, &apierr.APIError{
StatusCode: 500,
Message: "Internal server error",
})

err := waitForAppDeleted(ctx, mockClient.WorkspaceClient, "test-app")
assert.Error(t, err)
assert.Contains(t, err.Error(), "Internal server error")
}