fix(terraform): send changed litellm_key duration on /key/update so expires is recomputed (#40511)

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-10 02:57:07 +00:00 committed by GitHub
parent fb603fdb6b
commit ea106fd8be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 45 additions and 3 deletions

View file

@ -76,7 +76,7 @@ The following arguments are supported:
* `key_alias` - (Optional) Alias for this key. This provides a human-readable identifier for the key.
* `duration` - (Optional) Duration for which this key is valid. This sets an expiration time for the key.
* `duration` - (Optional) How long the key stays valid, e.g. "30d" or "12h". The proxy stores this as an absolute `expires` timestamp. Changing the value resets the expiry to the time of the update plus the new duration; removing it from the configuration leaves the current expiry in place.
* `aliases` - (Optional) Map of model aliases. This allows you to create custom names for models when using this key.

View file

@ -103,6 +103,9 @@ func (c *Client) UpdateKey(key *Key) (*Key, error) {
if key.BudgetDuration != "" {
updateData["budget_duration"] = key.BudgetDuration
}
if key.Duration != "" {
updateData["duration"] = key.Duration
}
// Only add pointer fields if they are explicitly set
if key.MaxBudget != nil {

View file

@ -88,8 +88,9 @@ func resourceKey() *schema.Resource {
Optional: true,
},
"duration": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
Description: "How long the key stays valid, e.g. \"30d\" or \"12h\". Changing it resets the expiry to the time of the update plus the new duration; removing it leaves the current expiry in place",
},
"aliases": {
Type: schema.TypeMap,
@ -310,6 +311,9 @@ func resourceKeyUpdate(ctx context.Context, d *schema.ResourceData, m interface{
key := &Key{Key: d.Id()}
mapResourceDataToKey(d, key)
if !d.HasChange("duration") {
key.Duration = ""
}
key.ModelRPMLimit = changedMap(d, "model_rpm_limit")
key.ModelTPMLimit = changedMap(d, "model_tpm_limit")

View file

@ -524,3 +524,38 @@ func TestKeyReadKeepsOnlyDeclaredMetadata(t *testing.T) {
t.Errorf("metadata in state = %v, want %v", got, want)
}
}
func TestKeyUpdateSendsChangedDuration(t *testing.T) {
proxy := &fakeKeyProxy{metadata: map[string]interface{}{}}
srv := httptest.NewServer(proxy.handler())
defer srv.Close()
client := NewClient(srv.URL, "test-key", true)
applyKeyUpdate(t, client,
map[string]string{"key_alias": "alias-1", "duration": "30d"},
map[string]interface{}{"key_alias": "alias-1", "duration": "90d"},
)
if got := proxy.updates[0]["duration"]; got != "90d" {
t.Errorf("update payload duration = %v, want 90d", got)
}
}
func TestKeyUpdateOmitsUnchangedDuration(t *testing.T) {
proxy := &fakeKeyProxy{metadata: map[string]interface{}{}}
srv := httptest.NewServer(proxy.handler())
defer srv.Close()
client := NewClient(srv.URL, "test-key", true)
applyKeyUpdate(t, client,
map[string]string{"key_alias": "alias-1", "duration": "30d"},
map[string]interface{}{"key_alias": "alias-2", "duration": "30d"},
)
if got := proxy.updates[0]["key_alias"]; got != "alias-2" {
t.Fatalf("update payload key_alias = %v, want alias-2", got)
}
if v, present := proxy.updates[0]["duration"]; present {
t.Errorf("update payload unexpectedly contains duration = %v", v)
}
}