fix(terraform): drop litellm_key from state on 404 instead of failing the plan (#40443)

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:51 +00:00 committed by GitHub
parent ea106fd8be
commit 5264f8ed48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 1 deletions

View file

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log"
@ -19,6 +20,20 @@ type Client struct {
InsecureSkipVerify bool
}
type apiError struct {
StatusCode int
Body string
}
func (e *apiError) Error() string {
return fmt.Sprintf("API request failed with status code %d: %s", e.StatusCode, e.Body)
}
func isNotFound(err error) bool {
var apiErr *apiError
return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound
}
func NewClient(apiBase, apiKey string, insecureSkipVerify bool) *Client {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify},
@ -57,6 +72,9 @@ func (c *Client) CreateKey(key *Key) (*Key, error) {
func (c *Client) GetKey(keyID string) (*Key, error) {
resp, err := c.sendRequest("GET", fmt.Sprintf("/key/info?key=%s", keyID), nil)
if isNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}
@ -377,7 +395,7 @@ func (c *Client) sendRequest(method, path string, body interface{}) (map[string]
log.Printf("Response body: %s", c.redactSensitiveData(string(bodyBytes)))
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API request failed with status code %d: %s", resp.StatusCode, string(bodyBytes))
return nil, &apiError{StatusCode: resp.StatusCode, Body: string(bodyBytes)}
}
var result map[string]interface{}

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@ -297,6 +298,7 @@ func resourceKeyRead(ctx context.Context, d *schema.ResourceData, m interface{})
}
if key == nil {
log.Printf("[WARN] Key %s not found, removing from state", d.Id())
d.SetId("")
return nil
}

View file

@ -394,6 +394,45 @@ func TestGetKeyUnwrapsInfoEnvelope(t *testing.T) {
}
}
func TestResourceKeyReadDropsMissingKeyFromState(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"error":{"message":"Key not found in database","type":"not_found_error","param":"key","code":"404"}}`))
}))
defer srv.Close()
d := newKeyResourceData(t, map[string]interface{}{"key_alias": "stale"})
d.SetId("deleted-out-of-band")
diags := resourceKeyRead(context.Background(), d, NewClient(srv.URL, "test-key", true))
if diags.HasError() {
t.Fatalf("read of a missing key must not error, got: %v", diags)
}
if d.Id() != "" {
t.Errorf("Id = %q, want empty so Terraform plans a recreate", d.Id())
}
}
func TestResourceKeyReadStillFailsOnNon404Errors(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":{"message":"db down"}}`))
}))
defer srv.Close()
d := newKeyResourceData(t, map[string]interface{}{"key_alias": "live"})
d.SetId("still-exists")
diags := resourceKeyRead(context.Background(), d, NewClient(srv.URL, "test-key", true))
if !diags.HasError() {
t.Fatal("a 500 from /key/info must surface as an error, not be treated as a deleted key")
}
if d.Id() != "still-exists" {
t.Errorf("Id = %q, want unchanged on a transient error", d.Id())
}
}
// fakeKeyProxy serves /key/info from stored metadata and applies /key/update
// the way the proxy does: an absent "metadata" keeps the stored map, a
// present one replaces it wholesale.