diff --git a/terraform/provider/CHANGELOG.md b/terraform/provider/CHANGELOG.md index 0e60c2a7806..89a036277bb 100644 --- a/terraform/provider/CHANGELOG.md +++ b/terraform/provider/CHANGELOG.md @@ -52,6 +52,7 @@ longer signal it. ### Fixed - **model**: `additional_drop_params` in `litellm_model.additional_litellm_params` is now sent to the proxy as the deployment's `additional_drop_params` list, the same list a `model_list` entry takes. Before, the provider never sent the list and instead deleted every key it named from `litellm_params`, so a `store` or `vertex_location` listed there was lost and requests still carried the parameters (#38349). The value must be a JSON list of parameter names such as `jsonencode(["store"])`, which `terraform plan` now checks, and `null` entries are skipped as before. When the configuration sets the list, refresh reads the stored list back from the proxy, so a list changed outside Terraform shows up as drift. On a model an older provider created, the proxy has no stored list, so the first plan after upgrading shows the list and every listed key the configuration sets as changes, since that apply writes them. A write-only secret such as `model_api_key_wo` is never shown in a plan +- **provider**: The provider now honors the standard `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables. Its HTTP client was a bare `http.Transport`, built that way to support `insecure_skip_verify`, which left out Go's `ProxyFromEnvironment`, so every request went direct even when the LiteLLM gateway is only reachable through a forward proxy (for example a Tailscale userspace-networking proxy on a CI runner) - **user**: `litellm_user` updates no longer send `blocked`, which `/user/update` writes into the user table where no such column exists, so every update failed with a 400 (`Could not find field at upsertOneLiteLLM_UserTable.create.blocked`). Create still sends `blocked`, including `false`, so a `default_internal_user_params.blocked` on the proxy cannot block the auto-created key. Changing `blocked` after creation has no effect, since the proxy has no user-level block; use `litellm_key_block` instead - **key**: `litellm_key` and `litellm_key_block` drop a key deleted outside Terraform from state again now that `/key/info` serves deleted keys with `status = "deleted"` instead of a 404, so plan recreates it, and destroying a `litellm_key` the proxy already deleted succeeds instead of failing on the 404 `No keys found` from `/key/delete` - **model**: `litellm_model` refresh now reads the `{"data": [...]}` envelope `/model/info` returns, so `model_info` fields changed outside Terraform show up as drift instead of silently keeping the previous state diff --git a/terraform/provider/litellm/client.go b/terraform/provider/litellm/client.go index ce8358670ea..e1a52dc4c6b 100644 --- a/terraform/provider/litellm/client.go +++ b/terraform/provider/litellm/client.go @@ -37,9 +37,10 @@ func isNotFound(err error) bool { } func NewClient(apiBase, apiKey string, insecureSkipVerify bool) *Client { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}, - } + // Clone the default transport so the client keeps its proxy (HTTP_PROXY, HTTPS_PROXY, NO_PROXY), + // dial and idle-connection settings; a bare http.Transport has no Proxy func and ignores them. + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: insecureSkipVerify} return &Client{ APIBase: apiBase, diff --git a/terraform/provider/litellm/client_test.go b/terraform/provider/litellm/client_test.go index 77c7bf817d8..054010af4c3 100644 --- a/terraform/provider/litellm/client_test.go +++ b/terraform/provider/litellm/client_test.go @@ -4,6 +4,7 @@ import ( "errors" "net/http" "net/http/httptest" + "reflect" "strings" "testing" ) @@ -62,6 +63,23 @@ func TestSendRequestAcceptsFullSuccessRange(t *testing.T) { } } +func TestNewClientHonorsProxyEnvironment(t *testing.T) { + for _, insecure := range []bool{false, true} { + c := NewClient("https://litellm.example.com", "sk-test", insecure) + + tr, ok := c.httpClient.Transport.(*http.Transport) + if !ok { + t.Fatalf("transport is %T, want *http.Transport", c.httpClient.Transport) + } + if tr.Proxy == nil || reflect.ValueOf(tr.Proxy).Pointer() != reflect.ValueOf(http.ProxyFromEnvironment).Pointer() { + t.Errorf("insecure=%v: transport does not use http.ProxyFromEnvironment, so HTTP_PROXY/HTTPS_PROXY are ignored", insecure) + } + if tr.TLSClientConfig == nil || tr.TLSClientConfig.InsecureSkipVerify != insecure { + t.Errorf("insecure=%v: TLSClientConfig.InsecureSkipVerify not set from the argument", insecure) + } + } +} + func TestRedactSensitiveDataNestedCredentialValues(t *testing.T) { c := NewClient("http://localhost:4000", "sk-test", false)