Skip to content

Commit 2d33a62

Browse files
author
Nicholas Cecere
committed
fix(provider): resolve unknown values after apply for all resources
Fix 'Provider produced inconsistent result after apply' and 'unknown values after apply' errors across all 19 resources. Fixes #53. Code fixes: - Unwrap nested API responses (key/info, team/team_info, etc.) - Change IsNull() else-clause zeroing to IsUnknown() checks - Only set API-injected defaults when user originally configured them - Remove server-side runtime fields from resource schemas - Add UseStateForUnknown plan modifiers for stable computed fields - Fix organization update to use PATCH instead of POST - Fix guardrail litellm_params to only store user-configured keys - Fix search_tool to parse search_tool_info JSON before sending - Expand mcp_server auth_type validator to include all API values Documentation: - Rewrite all 18 resource docs to match actual Go schemas - Rewrite all 21 data source docs to match actual Go schemas - Fix examples with outdated attribute names and values - Remove YAML frontmatter, standardize code block language to hcl Testing: - All 12 unit tests pass - All 19 resources verified via live integration testing
1 parent a892628 commit 2d33a62

File tree

132 files changed

+3187
-2495
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+3187
-2495
lines changed

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.4] - 2026-02-11
9+
10+
### Fixed
11+
- **All resources**: Resolved "Provider produced inconsistent result after apply" and "unknown values after apply" errors caused by three systemic bug patterns across the provider ([#53](https://github.com/ncecere/terraform-provider-litellm/issues/53)):
12+
13+
**Pattern 1 — API response nesting:** Read functions accessed fields from the top-level response, but the LiteLLM API nests data under wrapper keys (e.g., `/key/info` returns `{"info": {...}}`, `/vector_store/info` returns `{"vector_store": {...}}`). Added unwrapping logic to all affected resources and datasources.
14+
- `litellm_key`, `litellm_team`, `litellm_organization` (resources and datasources)
15+
- `litellm_vector_store`
16+
17+
**Pattern 2 — Else-clause zeroing (`!IsNull()``IsUnknown()`):** When the API didn't echo back a field, `else if !data.X.IsNull()` clauses zeroed out user-configured values to empty lists/maps, contradicting the planned value. Changed all such clauses to `else if data.X.IsUnknown()` so concrete config values are preserved.
18+
- `litellm_organization`: `models`, `tags`, `metadata`, `model_rpm_limit`, `model_tpm_limit`
19+
- `litellm_mcp_server`: `mcp_access_groups`, `args`, `env`, `credentials`, `allowed_tools`, `extra_headers`, `static_headers`, `tool_name_to_cost_per_query`
20+
- `litellm_vector_store`: `vector_store_metadata`, `litellm_params`
21+
- `litellm_key`: `model_rpm_limit`, `model_tpm_limit`
22+
- `litellm_team`: `model_aliases`, `model_rpm_limit`, `model_tpm_limit`
23+
- `litellm_model`: `access_groups`
24+
25+
**Pattern 3 — API-injected defaults appearing in state:** The API returns default values for fields the user never configured (e.g., `budget_id`, `alias`, `allow_all_keys`, `mcp_info`, server-injected metadata keys). These caused "was null, but now has value" errors. Fixed by only setting these fields in state when the user originally configured them.
26+
- `litellm_key`: `budget_id`, `metadata` (filters server-injected `tpm_limit_type`/`rpm_limit_type`)
27+
- `litellm_team`: `metadata` (same filtering)
28+
- `litellm_organization`: `budget_id`
29+
- `litellm_mcp_server`: `alias`, `description`, `command`, `allow_all_keys`, `authorization_url`, `token_url`, `registration_url`, `mcp_info` block
30+
- `litellm_guardrail`: `default_on`, `litellm_params`
31+
- `litellm_vector_store`: `litellm_params` (filters server-injected keys)
32+
33+
- **`litellm_key`**: Fixed scalar `Optional+Computed` fields (`max_budget`, `tpm_limit`, `rpm_limit`, `max_parallel_requests`, `soft_budget`, `blocked`) remaining Unknown after apply when API returned null. Added explicit Unknown-to-Null resolution.
34+
- **`litellm_organization`**: Fixed `blocked` remaining Unknown after apply.
35+
- **`litellm_vector_store`**: Fixed create failing with "`where.vector_store_id`: A value is required" by generating a UUID client-side. Fixed create failing with `'litellm_params'` error by always sending `litellm_params` (even if empty) as the API requires it.
36+
- **`litellm_search_tool`**: Fixed create/update requests not wrapped in `{"search_tool": {...}}` as the API requires. Fixed response parsing to unwrap nested `"search_tool"` key.
37+
- **`litellm_tag`**: Fixed read function to handle changed API response format (`/tag/info` returns `{"tag-name": {...}}` map instead of array).
38+
- **`litellm_key_block`**, **`litellm_team_block`**: Added `UseStateForUnknown` plan modifiers for immutable computed attributes (`created_at`, `created_by`, `key`, `blocked`).
39+
40+
### Removed
41+
- **All resources**: Removed server-side runtime metrics from resource schemas (`spend`, `updated_at`, `status`, `budget_reset_at`, `models_updated`) that change outside Terraform and cause perpetual drift. These remain available in datasources.
42+
43+
### Added
44+
- Regression tests for key and team readback behavior with nested API responses.
45+
- Internal testing infrastructure (`internal_testing/`) with Docker Compose stack (LiteLLM proxy + Postgres 16) and Terraform test files for all 19 resources and 27 datasources.
46+
847
## [1.0.3] - 2026-02-09
948

1049
### Fixed

docs/data-sources/access_group.md

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,27 @@ Retrieves information about a specific LiteLLM access group.
44

55
## Example Usage
66

7-
### Minimal Example
8-
97
```hcl
108
data "litellm_access_group" "existing" {
11-
access_group_id = "premium-models"
12-
}
13-
```
14-
15-
### Full Example
16-
17-
```hcl
18-
data "litellm_access_group" "enterprise" {
19-
access_group_id = "enterprise-tier"
9+
access_group = "premium-models"
2010
}
2111
22-
output "access_group_info" {
23-
value = {
24-
name = data.litellm_access_group.enterprise.access_group_name
25-
members = data.litellm_access_group.enterprise.members
26-
}
12+
output "models_in_group" {
13+
value = data.litellm_access_group.existing.model_names
2714
}
2815
2916
# Create team with same model access
30-
resource "litellm_team" "enterprise_team" {
31-
team_alias = "enterprise-users"
32-
models = data.litellm_access_group.enterprise.members
17+
resource "litellm_team" "premium_team" {
18+
team_alias = "premium-users"
19+
models = data.litellm_access_group.existing.model_names
3320
}
3421
```
3522

3623
## Argument Reference
3724

38-
The following arguments are supported:
39-
40-
* `access_group_id` - (Required) The unique identifier of the access group to retrieve.
25+
* `access_group` - (Required) The name of the access group to look up.
4126

4227
## Attribute Reference
4328

44-
The following attributes are exported:
45-
4629
* `id` - The unique identifier of the access group.
47-
* `access_group_id` - The access group ID.
48-
* `access_group_name` - The human-readable name.
49-
* `description` - Description of the access group.
50-
* `members` - List of model names included in this access group.
51-
* `metadata` - JSON string containing additional metadata.
52-
* `created_at` - Creation timestamp.
53-
* `updated_at` - Last update timestamp.
30+
* `model_names` - List of model names included in this access group.

docs/data-sources/access_groups.md

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ Retrieves a list of all LiteLLM access groups.
44

55
## Example Usage
66

7-
### Minimal Example
8-
9-
```hcl
10-
data "litellm_access_groups" "all" {}
11-
```
12-
13-
### Full Example
14-
157
```hcl
168
data "litellm_access_groups" "all" {}
179
@@ -20,20 +12,16 @@ output "access_group_count" {
2012
}
2113
2214
output "access_group_names" {
23-
value = [for g in data.litellm_access_groups.all.access_groups : g.access_group_name]
15+
value = [for g in data.litellm_access_groups.all.access_groups : g.access_group]
2416
}
2517
26-
# Find groups with GPT-4 access
18+
# Find groups containing a specific model
2719
locals {
2820
gpt4_groups = [
2921
for g in data.litellm_access_groups.all.access_groups : g
30-
if contains(g.members, "gpt-4")
22+
if contains(g.model_names, "gpt-4-proxy")
3123
]
3224
}
33-
34-
output "groups_with_gpt4" {
35-
value = [for g in local.gpt4_groups : g.access_group_name]
36-
}
3725
```
3826

3927
## Argument Reference
@@ -42,13 +30,7 @@ This data source has no required arguments.
4230

4331
## Attribute Reference
4432

45-
The following attributes are exported:
46-
4733
* `id` - Placeholder identifier.
4834
* `access_groups` - List of access group objects, each containing:
49-
* `access_group_id` - The unique identifier.
50-
* `access_group_name` - The human-readable name.
51-
* `description` - Description.
52-
* `members` - List of model names.
53-
* `created_at` - Creation timestamp.
54-
* `updated_at` - Last update timestamp.
35+
* `access_group` - The access group name.
36+
* `model_names` - List of model names in this access group.

docs/data-sources/budget.md

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,40 @@ Retrieves information about a specific LiteLLM budget configuration.
44

55
## Example Usage
66

7-
### Minimal Example
8-
97
```hcl
108
data "litellm_budget" "existing" {
119
budget_id = "monthly-budget"
1210
}
13-
```
14-
15-
### Full Example
16-
17-
```hcl
18-
data "litellm_budget" "production" {
19-
budget_id = "production-budget"
20-
}
2111
2212
output "budget_info" {
2313
value = {
24-
max_budget = data.litellm_budget.production.max_budget
25-
soft_budget = data.litellm_budget.production.soft_budget
26-
budget_duration = data.litellm_budget.production.budget_duration
14+
max_budget = data.litellm_budget.existing.max_budget
15+
soft_budget = data.litellm_budget.existing.soft_budget
16+
budget_duration = data.litellm_budget.existing.budget_duration
2717
}
2818
}
2919
3020
# Use budget configuration for a new team
3121
resource "litellm_team" "new_team" {
3222
team_alias = "new-department"
33-
max_budget = data.litellm_budget.production.max_budget * 0.5
34-
budget_duration = data.litellm_budget.production.budget_duration
23+
max_budget = data.litellm_budget.existing.max_budget * 0.5
24+
budget_duration = data.litellm_budget.existing.budget_duration
3525
}
3626
```
3727

3828
## Argument Reference
3929

40-
The following arguments are supported:
41-
4230
* `budget_id` - (Required) The unique identifier of the budget to retrieve.
4331

4432
## Attribute Reference
4533

46-
The following attributes are exported:
47-
4834
* `id` - The unique identifier of the budget.
4935
* `budget_id` - The budget ID.
5036
* `max_budget` - Maximum budget amount.
5137
* `soft_budget` - Soft budget limit for alerts.
52-
* `budget_duration` - Budget reset duration (daily, weekly, monthly).
38+
* `budget_duration` - Budget reset duration (e.g., "daily", "weekly", "monthly").
5339
* `tpm_limit` - Tokens per minute limit.
5440
* `rpm_limit` - Requests per minute limit.
5541
* `max_parallel_requests` - Maximum parallel requests allowed.
5642
* `model_max_budget` - JSON string of per-model budget limits.
57-
* `created_at` - Creation timestamp.
58-
* `updated_at` - Last update timestamp.
43+
* `budget_reset_at` - Datetime when the budget is next reset.

docs/data-sources/budgets.md

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ Retrieves a list of all LiteLLM budget configurations.
44

55
## Example Usage
66

7-
### Minimal Example
8-
9-
```hcl
10-
data "litellm_budgets" "all" {}
11-
```
12-
13-
### Full Example
14-
157
```hcl
168
data "litellm_budgets" "all" {}
179
@@ -30,15 +22,6 @@ locals {
3022
output "high_value_budget_ids" {
3123
value = [for b in local.high_value_budgets : b.budget_id]
3224
}
33-
34-
# Calculate total budget allocation
35-
locals {
36-
total_budget = sum([for b in data.litellm_budgets.all.budgets : b.max_budget])
37-
}
38-
39-
output "total_allocated_budget" {
40-
value = local.total_budget
41-
}
4225
```
4326

4427
## Argument Reference
@@ -47,13 +30,13 @@ This data source has no required arguments.
4730

4831
## Attribute Reference
4932

50-
The following attributes are exported:
51-
5233
* `id` - Placeholder identifier.
5334
* `budgets` - List of budget objects, each containing:
5435
* `budget_id` - The unique identifier.
5536
* `max_budget` - Maximum budget amount.
5637
* `soft_budget` - Soft budget limit.
38+
* `max_parallel_requests` - Maximum parallel requests allowed.
39+
* `tpm_limit` - Tokens per minute limit.
40+
* `rpm_limit` - Requests per minute limit.
5741
* `budget_duration` - Budget reset duration.
58-
* `created_at` - Creation timestamp.
59-
* `updated_at` - Last update timestamp.
42+
* `model_max_budget` - JSON string of per-model budget limits.

docs/data-sources/credential.md

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
---
2-
# generated by https://github.com/hashicorp/terraform-plugin-docs
3-
page_title: "litellm_credential Data Source - terraform-provider-litellm"
4-
subcategory: ""
5-
description: |-
6-
Retrieves information about an existing LiteLLM credential.
7-
---
8-
91
# litellm_credential (Data Source)
102

113
Retrieves information about an existing LiteLLM credential. This data source allows you to reference credentials that were created outside of Terraform or in other Terraform configurations.
124

135
## Example Usage
146

15-
```terraform
7+
```hcl
168
# Retrieve an existing credential by name
179
data "litellm_credential" "existing_openai" {
1810
credential_name = "openai-production-key"
@@ -35,7 +27,7 @@ resource "litellm_model" "gpt4_with_existing_cred" {
3527

3628
## Example Usage with Model ID
3729

38-
```terraform
30+
```hcl
3931
# Retrieve a credential associated with a specific model
4032
data "litellm_credential" "model_specific_cred" {
4133
credential_name = "claude-api-key"
@@ -54,7 +46,7 @@ resource "litellm_vector_store" "knowledge_base" {
5446

5547
## Example Usage for Cross-Reference
5648

57-
```terraform
49+
```hcl
5850
# Get credential info to use in other resources
5951
data "litellm_credential" "shared_cred" {
6052
credential_name = "shared-api-key"
@@ -100,7 +92,7 @@ For security reasons, the `credential_values` (sensitive data like API keys) are
10092
### 1. Cross-Stack References
10193
Use data sources to reference credentials created in other Terraform configurations or stacks:
10294

103-
```terraform
95+
```hcl
10496
data "litellm_credential" "shared_openai" {
10597
credential_name = "openai-shared-key"
10698
}
@@ -119,7 +111,7 @@ resource "litellm_model" "gpt4" {
119111
### 2. Conditional Logic
120112
Use credential information for conditional resource creation:
121113

122-
```terraform
114+
```hcl
123115
data "litellm_credential" "optional_cred" {
124116
credential_name = var.credential_name
125117
}
@@ -136,7 +128,7 @@ resource "litellm_vector_store" "conditional_store" {
136128
### 3. Validation and Verification
137129
Verify that required credentials exist before creating dependent resources:
138130

139-
```terraform
131+
```hcl
140132
data "litellm_credential" "required_cred" {
141133
credential_name = "production-api-key"
142134
}
@@ -151,3 +143,4 @@ resource "litellm_model" "production_model" {
151143
credential_name = data.litellm_credential.required_cred.credential_name
152144
}
153145
}
146+
```

docs/data-sources/guardrail.md

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,34 @@ Retrieves information about a specific LiteLLM guardrail configuration.
44

55
## Example Usage
66

7-
### Minimal Example
8-
97
```hcl
108
data "litellm_guardrail" "existing" {
11-
guardrail_name = "content-safety"
12-
}
13-
```
14-
15-
### Full Example
16-
17-
```hcl
18-
data "litellm_guardrail" "safety_filter" {
19-
guardrail_name = "content-safety-filter"
9+
guardrail_id = "my-guardrail-id"
2010
}
2111
2212
output "guardrail_info" {
2313
value = {
24-
name = data.litellm_guardrail.safety_filter.guardrail_name
25-
config = data.litellm_guardrail.safety_filter.litellm_params
14+
name = data.litellm_guardrail.existing.guardrail_name
15+
guardrail = data.litellm_guardrail.existing.guardrail
16+
mode = data.litellm_guardrail.existing.mode
17+
default_on = data.litellm_guardrail.existing.default_on
2618
}
2719
}
2820
```
2921

3022
## Argument Reference
3123

32-
The following arguments are supported:
33-
34-
* `guardrail_name` - (Required) The name of the guardrail to retrieve.
24+
* `guardrail_id` - (Required) The unique identifier of the guardrail to retrieve.
3525

3626
## Attribute Reference
3727

38-
The following attributes are exported:
39-
4028
* `id` - The unique identifier of the guardrail.
4129
* `guardrail_id` - The guardrail ID.
42-
* `guardrail_name` - The guardrail name.
43-
* `litellm_params` - JSON string containing guardrail configuration.
44-
* `description` - Description of the guardrail.
45-
* `metadata` - JSON string containing additional metadata.
30+
* `guardrail_name` - Human-readable name for the guardrail.
31+
* `guardrail` - The guardrail integration type (e.g., "aporia", "bedrock", "lakera").
32+
* `mode` - When to apply the guardrail (e.g., "pre_call", "post_call", "during_call", or a JSON array of modes).
33+
* `default_on` - Whether the guardrail is enabled by default for all requests.
34+
* `litellm_params` - JSON string containing additional provider-specific parameters.
35+
* `guardrail_info` - JSON string containing additional guardrail metadata.
4636
* `created_at` - Creation timestamp.
4737
* `updated_at` - Last update timestamp.

0 commit comments

Comments
 (0)