From 952cf8660ad34c8347f6b95a7a2cb51930ae2101 Mon Sep 17 00:00:00 2001 From: Erik Bogado Date: Mon, 7 Sep 2026 03:33:08 -0300 Subject: [PATCH] fix(terraform): omit unset member budgets --- .../provider/litellm/resource_team_member.go | 20 +++-- .../litellm/resource_team_member_add.go | 28 ++++--- .../litellm/resource_team_member_test.go | 77 +++++++++++++++++++ 3 files changed, 103 insertions(+), 22 deletions(-) diff --git a/terraform/provider/litellm/resource_team_member.go b/terraform/provider/litellm/resource_team_member.go index 84db07239fd..3f7cb004be6 100644 --- a/terraform/provider/litellm/resource_team_member.go +++ b/terraform/provider/litellm/resource_team_member.go @@ -58,9 +58,9 @@ func resourceLiteLLMTeamMemberCreate(d *schema.ResourceData, m interface{}) erro "user_email": d.Get("user_email").(string), }, }, - "team_id": d.Get("team_id").(string), - "max_budget_in_team": d.Get("max_budget_in_team").(float64), + "team_id": d.Get("team_id").(string), } + addOptionalTeamMemberBudget(d, memberData) log.Printf("[DEBUG] Create team member request payload: %+v", memberData) @@ -94,12 +94,12 @@ func resourceLiteLLMTeamMemberUpdate(d *schema.ResourceData, m interface{}) erro client := m.(*Client) updateData := map[string]interface{}{ - "user_id": d.Get("user_id").(string), - "user_email": d.Get("user_email").(string), - "team_id": d.Get("team_id").(string), - "role": d.Get("role").(string), - "max_budget_in_team": d.Get("max_budget_in_team").(float64), + "user_id": d.Get("user_id").(string), + "user_email": d.Get("user_email").(string), + "team_id": d.Get("team_id").(string), + "role": d.Get("role").(string), } + addOptionalTeamMemberBudget(d, updateData) log.Printf("[DEBUG] Update team member request payload: %+v", updateData) @@ -118,6 +118,12 @@ func resourceLiteLLMTeamMemberUpdate(d *schema.ResourceData, m interface{}) erro return resourceLiteLLMTeamMemberRead(d, m) } +func addOptionalTeamMemberBudget(d *schema.ResourceData, payload map[string]interface{}) { + if budget, configured := d.GetOkExists("max_budget_in_team"); configured { + payload["max_budget_in_team"] = budget.(float64) + } +} + func resourceLiteLLMTeamMemberDelete(d *schema.ResourceData, m interface{}) error { client := m.(*Client) diff --git a/terraform/provider/litellm/resource_team_member_add.go b/terraform/provider/litellm/resource_team_member_add.go index da5c7a6ebd7..3db86d2fe7f 100644 --- a/terraform/provider/litellm/resource_team_member_add.go +++ b/terraform/provider/litellm/resource_team_member_add.go @@ -58,7 +58,6 @@ func resourceLiteLLMTeamMemberAddCreate(d *schema.ResourceData, m interface{}) e teamID := d.Get("team_id").(string) members := d.Get("member").(*schema.Set) - maxBudget := d.Get("max_budget_in_team").(float64) // Convert members to the expected format membersList := make([]map[string]interface{}, 0, members.Len()) @@ -77,10 +76,10 @@ func resourceLiteLLMTeamMemberAddCreate(d *schema.ResourceData, m interface{}) e } memberData := map[string]interface{}{ - "member": membersList, - "team_id": teamID, - "max_budget_in_team": maxBudget, + "member": membersList, + "team_id": teamID, } + addOptionalTeamMemberBudget(d, memberData) log.Printf("[DEBUG] Create team members request payload: %+v", memberData) @@ -109,7 +108,6 @@ func resourceLiteLLMTeamMemberAddRead(d *schema.ResourceData, m interface{}) err func resourceLiteLLMTeamMemberAddUpdate(d *schema.ResourceData, m interface{}) error { client := m.(*Client) teamID := d.Get("team_id").(string) - maxBudget := d.Get("max_budget_in_team").(float64) o, n := d.GetChange("member") oldMembers := o.(*schema.Set) @@ -142,16 +140,16 @@ func resourceLiteLLMTeamMemberAddUpdate(d *schema.ResourceData, m interface{}) e // Check if max_budget_in_team has changed if d.HasChange("max_budget_in_team") { - log.Printf("[DEBUG] max_budget_in_team changed, updating all existing members with new budget: %f", maxBudget) + log.Printf("[DEBUG] max_budget_in_team changed, updating all existing members") // Update ALL existing members with the new budget for key, newMember := range newMemberMap { if _, exists := oldMemberMap[key]; exists { updateData := map[string]interface{}{ - "team_id": teamID, - "role": newMember["role"].(string), - "max_budget_in_team": maxBudget, + "team_id": teamID, + "role": newMember["role"].(string), } + addOptionalTeamMemberBudget(d, updateData) if userID, ok := newMember["user_id"].(string); ok && userID != "" { updateData["user_id"] = userID } @@ -216,10 +214,10 @@ func resourceLiteLLMTeamMemberAddUpdate(d *schema.ResourceData, m interface{}) e // Check if member attributes have changed if memberAttributesChanged(oldMember, newMember) { updateData := map[string]interface{}{ - "team_id": teamID, - "role": newMember["role"].(string), - "max_budget_in_team": maxBudget, + "team_id": teamID, + "role": newMember["role"].(string), } + addOptionalTeamMemberBudget(d, updateData) if userID, ok := newMember["user_id"].(string); ok && userID != "" { updateData["user_id"] = userID } @@ -261,10 +259,10 @@ func resourceLiteLLMTeamMemberAddUpdate(d *schema.ResourceData, m interface{}) e if len(membersToAdd) > 0 { memberData := map[string]interface{}{ - "member": membersToAdd, - "team_id": teamID, - "max_budget_in_team": maxBudget, + "member": membersToAdd, + "team_id": teamID, } + addOptionalTeamMemberBudget(d, memberData) log.Printf("[DEBUG] Adding new team members request payload: %+v", memberData) diff --git a/terraform/provider/litellm/resource_team_member_test.go b/terraform/provider/litellm/resource_team_member_test.go index 156c4b3abfa..1cf93ed0c9a 100644 --- a/terraform/provider/litellm/resource_team_member_test.go +++ b/terraform/provider/litellm/resource_team_member_test.go @@ -10,6 +10,46 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +func TestTeamMemberCreateOmitsUnsetBudget(t *testing.T) { + var captured map[string]interface{} + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + json.Unmarshal(body, &captured) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{}`)) + })) + defer srv.Close() + + client := NewClient(srv.URL, "test-key", true) + d := schema.TestResourceDataRaw(t, resourceLiteLLMTeamMember().Schema, map[string]interface{}{ + "team_id": "team-1", + "user_id": "user-1", + "user_email": "user@example.com", + "role": "user", + }) + + if err := resourceLiteLLMTeamMemberCreate(d, client); err != nil { + t.Fatalf("create failed: %v", err) + } + if _, ok := captured["max_budget_in_team"]; ok { + t.Fatalf("create payload included unset max_budget_in_team: %v", captured) + } +} + +func TestOptionalTeamMemberBudgetPreservesExplicitZero(t *testing.T) { + d := schema.TestResourceDataRaw(t, resourceLiteLLMTeamMember().Schema, map[string]interface{}{ + "max_budget_in_team": 0.0, + }) + payload := map[string]interface{}{} + + addOptionalTeamMemberBudget(d, payload) + + if budget, ok := payload["max_budget_in_team"]; !ok || budget != 0.0 { + t.Fatalf("explicit zero budget was not preserved: %v", payload) + } +} + func TestTeamMemberUpdateSendsRole(t *testing.T) { var captured map[string]interface{} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -41,4 +81,41 @@ func TestTeamMemberUpdateSendsRole(t *testing.T) { if role != "user" { t.Fatalf("update payload sent role %v, want user", role) } + if _, ok := captured["max_budget_in_team"]; ok { + t.Fatalf("update payload included unset max_budget_in_team: %v", captured) + } +} + +func TestTeamMemberAddBudgetPresence(t *testing.T) { + for _, tc := range []struct { + name string + configured bool + budget float64 + }{ + {"omitted", false, 0}, {"zero", true, 0}, {"positive", true, 25}, + } { + t.Run(tc.name, func(t *testing.T) { + var captured map[string]interface{} + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err := json.NewDecoder(r.Body).Decode(&captured); err != nil { + t.Error(err) + } + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{}`)) + })) + defer srv.Close() + raw := map[string]interface{}{"team_id": "team-1", "member": []interface{}{map[string]interface{}{"user_id": "user-1", "role": "user"}}} + if tc.configured { + raw["max_budget_in_team"] = tc.budget + } + d := schema.TestResourceDataRaw(t, resourceLiteLLMTeamMemberAdd().Schema, raw) + if err := resourceLiteLLMTeamMemberAddCreate(d, NewClient(srv.URL, "test-key", true)); err != nil { + t.Fatal(err) + } + got, present := captured["max_budget_in_team"] + if present != tc.configured || (present && got != tc.budget) { + t.Fatalf("budget = %v, present = %t", got, present) + } + }) + } }