mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(terraform): detach keys when configured team is removed
This commit is contained in:
parent
0609e5ff64
commit
c1bc0521da
4 changed files with 70 additions and 4 deletions
|
|
@ -143,10 +143,10 @@ func (c *Client) UpdateKey(key *Key) (*Key, error) {
|
|||
updateData["model_tpm_limit"] = key.ModelTPMLimit
|
||||
}
|
||||
|
||||
// The proxy rejects an empty team_id with a 500 ("Team object not found"),
|
||||
// so only send it when set.
|
||||
if key.TeamID != "" {
|
||||
updateData["team_id"] = key.TeamID
|
||||
} else if key.clearTeamID {
|
||||
updateData["team_id"] = nil
|
||||
}
|
||||
|
||||
// The proxy rejects an empty-string budget_duration with a 400, so only
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ func resourceKeyUpdate(ctx context.Context, d *schema.ResourceData, m interface{
|
|||
|
||||
key := &Key{Key: d.Id()}
|
||||
mapResourceDataToKey(d, key)
|
||||
key.clearTeamID = d.HasChange("team_id") && key.TeamID == ""
|
||||
if !d.HasChange("duration") {
|
||||
key.Duration = ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,8 +321,6 @@ func TestUpdateKeyOmitsEmptyBudgetDuration(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// The proxy 500s on team_id: "" with "Team object not found", so a teamless key
|
||||
// must omit it from the update payload entirely.
|
||||
func TestUpdateKeyOmitsEmptyTeamID(t *testing.T) {
|
||||
var captured map[string]interface{}
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -349,6 +347,72 @@ func TestUpdateKeyOmitsEmptyTeamID(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceKeyTeamChangesConverge(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
priorTeam string
|
||||
configuredTeam interface{}
|
||||
wantTeam string
|
||||
wantPresent bool
|
||||
wantNull bool
|
||||
}{
|
||||
{name: "teamless"},
|
||||
{name: "remove", priorTeam: "team-1", wantPresent: true, wantNull: true},
|
||||
{name: "blank", priorTeam: "team-1", configuredTeam: "", wantPresent: true, wantNull: true},
|
||||
{name: "assign", configuredTeam: "team-1", wantTeam: "team-1", wantPresent: true},
|
||||
{name: "move", priorTeam: "team-1", configuredTeam: "team-2", wantTeam: "team-2", wantPresent: true},
|
||||
{name: "retain", priorTeam: "team-1", configuredTeam: "team-1", wantTeam: "team-1", wantPresent: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
storedTeam := tc.priorTeam
|
||||
updates := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if req.URL.Path == "/key/update" {
|
||||
updates++
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(req.Body).Decode(&payload); err != nil {
|
||||
t.Error(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
value, present := payload["team_id"]
|
||||
if present != tc.wantPresent || (present && (value == nil) != tc.wantNull) {
|
||||
t.Errorf("team_id = %#v, present = %v; want present = %v, null = %v", value, present, tc.wantPresent, tc.wantNull)
|
||||
}
|
||||
if present {
|
||||
storedTeam, _ = value.(string)
|
||||
}
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"key": "hash-1",
|
||||
"info": map[string]interface{}{"team_id": storedTeam, "key_alias": "test"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
res := resourceKey()
|
||||
priorData := newKeyResourceData(t, map[string]interface{}{"team_id": tc.priorTeam, "key_alias": "test", "max_budget": 10.0})
|
||||
priorData.SetId("hash-1")
|
||||
config := terraform.NewResourceConfigRaw(map[string]interface{}{"team_id": tc.configuredTeam, "key_alias": "test", "max_budget": 25.0})
|
||||
diff, err := res.Diff(context.Background(), priorData.State(), config, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
state, diags := res.Apply(context.Background(), priorData.State(), diff, NewClient(srv.URL, "test-key", true))
|
||||
if diags.HasError() {
|
||||
t.Fatalf("apply failed: %v", diags)
|
||||
}
|
||||
if updates != 1 || storedTeam != tc.wantTeam || state.Attributes["team_id"] != tc.wantTeam {
|
||||
t.Fatalf("updates = %d, stored team = %q, state team = %q; want %q", updates, storedTeam, state.Attributes["team_id"], tc.wantTeam)
|
||||
}
|
||||
nextDiff, err := res.Diff(context.Background(), state, config, nil)
|
||||
if err != nil || (nextDiff != nil && !nextDiff.Empty()) {
|
||||
t.Fatalf("subsequent plan not clean: diff = %v, error = %v", nextDiff, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceKeyUpdateFailureKeepsPriorState(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ type ModelInfo struct {
|
|||
|
||||
// Key represents a LiteLLM API key.
|
||||
type Key struct {
|
||||
clearTeamID bool
|
||||
Key string `json:"key,omitempty"`
|
||||
TokenID string `json:"token_id,omitempty"`
|
||||
Models []string `json:"models"`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue