fix(terraform): read team per-model rpm/tpm limits from metadata and clear them on removal (#40439)

* fix(terraform): read team per-model rpm/tpm limits from metadata and clear them on removal

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(terraform): drop explanatory comments from team per-model limit helper and tests

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-09-09 19:44:50 -07:00 committed by GitHub
parent b64197c289
commit 4ddc5e2c29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 90 additions and 6 deletions

View file

@ -263,11 +263,11 @@ func resourceLiteLLMTeamRead(d *schema.ResourceData, m interface{}) error {
d.Set("team_member_tpm_limit", *teamResp.TeamMemberTPMLimit)
}
d.Set("team_member_key_duration", GetStringValue(teamResp.TeamMemberKeyDuration, d.Get("team_member_key_duration").(string)))
if teamResp.ModelRPMLimit != nil {
d.Set("model_rpm_limit", teamResp.ModelRPMLimit)
if v := teamModelLimit(teamResp.ModelRPMLimit, teamResp.Metadata, "model_rpm_limit"); v != nil {
d.Set("model_rpm_limit", v)
}
if teamResp.ModelTPMLimit != nil {
d.Set("model_tpm_limit", teamResp.ModelTPMLimit)
if v := teamModelLimit(teamResp.ModelTPMLimit, teamResp.Metadata, "model_tpm_limit"); v != nil {
d.Set("model_tpm_limit", v)
}
if teamResp.AllowedPassthroughRoutes != nil {
d.Set("allowed_passthrough_routes", teamResp.AllowedPassthroughRoutes)
@ -364,14 +364,19 @@ func buildTeamData(d *schema.ResourceData, teamID string) map[string]interface{}
"organization_id", "tpm_limit", "rpm_limit", "max_budget", "budget_duration", "models",
"blocked", "team_member_permissions", "model_aliases", "guardrails", "prompts",
"team_member_budget", "team_member_budget_duration", "team_member_rpm_limit",
"team_member_tpm_limit", "team_member_key_duration", "model_rpm_limit",
"model_tpm_limit", "allowed_passthrough_routes",
"team_member_tpm_limit", "team_member_key_duration", "allowed_passthrough_routes",
} {
if v, ok := d.GetOk(key); ok {
teamData[key] = v
}
}
for _, key := range []string{"model_rpm_limit", "model_tpm_limit"} {
if v, ok := d.GetOk(key); ok || d.HasChange(key) {
teamData[key] = v
}
}
if v, ok := d.GetOk("soft_budget"); ok {
teamData["soft_budget"] = v
} else if d.HasChange("soft_budget") {
@ -404,6 +409,14 @@ func buildTeamMetadata(d *schema.ResourceData) map[string]interface{} {
return metadata
}
func teamModelLimit(topLevel, metadata map[string]interface{}, key string) map[string]interface{} {
if topLevel != nil {
return topLevel
}
nested, _ := metadata[key].(map[string]interface{})
return nested
}
func splitTeamMetadata(raw map[string]interface{}) (map[string]string, []string, []string) {
metadata := map[string]string{}
var tags, alertEmails []string

View file

@ -250,6 +250,77 @@ func TestTeamReadMapsNewFields(t *testing.T) {
}
}
func TestTeamReadMapsPerModelLimitsFromMetadata(t *testing.T) {
var captured map[string]interface{}
srv := newTeamTestServer(t, &captured, `{
"team_id": "team-1",
"team_info": {
"team_id": "team-1",
"team_alias": "eng",
"model_rpm_limit": null,
"model_tpm_limit": null,
"metadata": {
"department": "eng",
"model_rpm_limit": {"gpt-4o-mini": 250},
"model_tpm_limit": {"gpt-4o-mini": 5000}
}
}
}`)
defer srv.Close()
d := newTeamResourceData(t, map[string]interface{}{
"team_alias": "eng",
"model_rpm_limit": map[string]interface{}{"gpt-4o-mini": 100},
})
d.SetId("team-1")
if err := resourceLiteLLMTeamRead(d, NewClient(srv.URL, "test-key", true)); err != nil {
t.Fatalf("read returned error: %v", err)
}
if got := d.Get("model_rpm_limit"); !reflect.DeepEqual(got, map[string]interface{}{"gpt-4o-mini": 250}) {
t.Errorf("model_rpm_limit = %v, want server value 250", got)
}
if got := d.Get("model_tpm_limit"); !reflect.DeepEqual(got, map[string]interface{}{"gpt-4o-mini": 5000}) {
t.Errorf("model_tpm_limit = %v, want server value 5000", got)
}
if got := d.Get("metadata"); !reflect.DeepEqual(got, map[string]interface{}{"department": "eng"}) {
t.Errorf("metadata = %v, want per-model limits kept out of the string map", got)
}
}
func TestTeamUpdateClearsRemovedPerModelLimits(t *testing.T) {
var captured map[string]interface{}
srv := newTeamTestServer(t, &captured, `{"team_id":"team-1","team_info":{"team_id":"team-1","team_alias":"eng"}}`)
defer srv.Close()
res := ResourceLiteLLMTeam()
priorData := schema.TestResourceDataRaw(t, res.Schema, map[string]interface{}{
"team_alias": "eng",
"model_rpm_limit": map[string]interface{}{"gpt-4o-mini": 100},
"model_tpm_limit": map[string]interface{}{"gpt-4o-mini": 5000},
})
priorData.SetId("team-1")
prior := priorData.State()
config := terraform.NewResourceConfigRaw(map[string]interface{}{"team_alias": "eng"})
diff, err := res.Diff(context.Background(), prior, config, nil)
if err != nil {
t.Fatalf("diff failed: %v", err)
}
d, err := schema.InternalMap(res.Schema).Data(prior, diff)
if err != nil {
t.Fatalf("data failed: %v", err)
}
if err := resourceLiteLLMTeamUpdate(d, NewClient(srv.URL, "test-key", true)); err != nil {
t.Fatalf("update failed: %v", err)
}
for _, k := range []string{"model_rpm_limit", "model_tpm_limit"} {
if got, ok := captured[k]; !ok || !reflect.DeepEqual(got, map[string]interface{}{}) {
t.Errorf("payload %s = %v (present=%v), want explicit empty map", k, got, ok)
}
}
}
// rpm_limit_type / tpm_limit_type are accepted by /team/new but not
// /team/update, so create must send them and update must not.
func TestTeamLimitTypesSentOnCreateOnly(t *testing.T) {