Skip to content

Commit adc0503

Browse files
committed
fix: handle user deletion config drift
1 parent d085864 commit adc0503

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-1
lines changed

internal/provider/user_resource.go

+18
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp
248248

249249
client := r.data.Client
250250

251+
// Lookup by ID to handle imports
251252
user, err := client.User(ctx, data.ID.ValueString())
252253
if err != nil {
253254
if isNotFound(err) {
@@ -274,6 +275,23 @@ func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp
274275
data.LoginType = types.StringValue(string(user.LoginType))
275276
data.Suspended = types.BoolValue(user.Status == codersdk.UserStatusSuspended)
276277

278+
// Also query by username to check for deletion or username reassignment
279+
userByName, err := client.User(ctx, data.Username.ValueString())
280+
if err != nil {
281+
if isNotFound(err) {
282+
resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("User with ID %q not found. Marking as deleted.", data.ID.ValueString()))
283+
resp.State.RemoveResource(ctx)
284+
return
285+
}
286+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get current user, got error: %s", err))
287+
return
288+
}
289+
if userByName.ID != data.ID.ValueUUID() {
290+
resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("The username %q has been reassigned to a new user. Marking as deleted.", user.Username))
291+
resp.State.RemoveResource(ctx)
292+
return
293+
}
294+
277295
// Save updated data into Terraform state
278296
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
279297
}

internal/provider/user_resource_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/coder/coder/v2/coderd/util/ptr"
1111
"github.com/coder/terraform-provider-coderd/integration"
1212
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
13+
"github.com/hashicorp/terraform-plugin-testing/terraform"
1314
"github.com/stretchr/testify/require"
1415
)
1516

@@ -101,6 +102,19 @@ func TestAccUserResource(t *testing.T) {
101102
resource.TestCheckResourceAttr("coderd_user.test", "login_type", "github"),
102103
),
103104
},
105+
// Verify config drift via deletion is handled
106+
{
107+
Config: cfg4.String(t),
108+
Check: func(*terraform.State) error {
109+
user, err := client.User(ctx, "exampleNew")
110+
if err != nil {
111+
return err
112+
}
113+
return client.DeleteUser(ctx, user.ID)
114+
},
115+
// The Plan should be to create the entire resource
116+
ExpectNonEmptyPlan: true,
117+
},
104118
},
105119
})
106120
}

internal/provider/util.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"os"
1010
"path/filepath"
11+
"strings"
1112

1213
"github.com/coder/coder/v2/codersdk"
1314
"github.com/google/uuid"
@@ -110,5 +111,15 @@ func memberDiff(currentMembers []uuid.UUID, plannedMembers []UUID) (add, remove
110111

111112
func isNotFound(err error) bool {
112113
var sdkErr *codersdk.Error
113-
return errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound
114+
if !errors.As(err, &sdkErr) {
115+
return false
116+
}
117+
if sdkErr.StatusCode() == http.StatusNotFound {
118+
return true
119+
}
120+
// `httpmw/ExtractUserContext` returns a 400 w/ this message if the user is not found
121+
if sdkErr.StatusCode() == http.StatusBadRequest && strings.Contains(sdkErr.Message, "must be an existing uuid or username") {
122+
return true
123+
}
124+
return false
114125
}

0 commit comments

Comments
 (0)