From e6ee4372304eafa57c4496278d30a56133b0870d Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Tue, 6 Oct 2026 04:28:11 +0000 Subject: [PATCH] feat!: remove redundant id from coderd_agents_default_model and coderd_ai_model_price --- AGENTS.md | 2 +- docs/resources/agents_default_model.md | 4 - docs/resources/ai_model_price.md | 4 - .../provider/agents_default_model_resource.go | 19 +--- .../agents_default_model_resource_test.go | 104 +----------------- internal/provider/ai_model_price_resource.go | 14 --- .../provider/ai_model_price_resource_test.go | 19 ++-- 7 files changed, 17 insertions(+), 149 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0ba54d4d..ad2ff6e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,7 +50,7 @@ make testacc # TF_ACC=1 go test ./... -timeout 120m **Naming.** Terraform resource types are prefixed with the Coder feature area they belong to so related resources cluster together in configs and docs. Prefer an existing prefix over inventing a new one: -- `coderd_agents_*` — Coder Agents feature area (`coderd_agents_mcp_server`, `coderd_agents_model`, `coderd_agents_system_prompt`, `coderd_default_agents_model`). +- `coderd_agents_*` — Coder Agents feature area (`coderd_agents_default_model`, `coderd_agents_mcp_server`, `coderd_agents_model`, `coderd_agents_system_prompt`). - `coderd_organization_*` — organization-scoped settings (`coderd_organization_group_sync`, `coderd_organization_sync_settings`). - `coderd_oauth2_*`, `coderd_workspace_*`, etc. follow the same rule. diff --git a/docs/resources/agents_default_model.md b/docs/resources/agents_default_model.md index b29a505b..f8ede846 100644 --- a/docs/resources/agents_default_model.md +++ b/docs/resources/agents_default_model.md @@ -65,10 +65,6 @@ resource "coderd_agents_default_model" "default" { - `model_id` (String) ID of the `coderd_agents_model` to mark as the organization's default. Usually this is `coderd_agents_model..id`. - `organization_id` (String) Organization ID whose default Agents model is managed. -### Read-Only - -- `id` (String) Organization ID that identifies this organization's default Agents model selection. - ## Import Import is supported using the following syntax: diff --git a/docs/resources/ai_model_price.md b/docs/resources/ai_model_price.md index 4a6eabf6..3789afe4 100644 --- a/docs/resources/ai_model_price.md +++ b/docs/resources/ai_model_price.md @@ -69,10 +69,6 @@ resource "coderd_ai_model_price" "sonnet" { - `input_price` (Number) Price per 1M input tokens, in integer micro-units: `1000000` is $1.00 per 1M tokens. Omit it if the price is not known, which AI Gateway counts as zero cost. Set `0` to declare the tokens free of charge. - `output_price` (Number) Price per 1M output tokens, in integer micro-units: `1000000` is $1.00 per 1M tokens. Omit it if the price is not known, which AI Gateway counts as zero cost. Set `0` to declare the tokens free of charge. -### Read-Only - -- `id` (String) Model price ID, in the form `/`. - ## Import Import is supported using the following syntax: diff --git a/internal/provider/agents_default_model_resource.go b/internal/provider/agents_default_model_resource.go index c34e79ab..a71d9c0f 100644 --- a/internal/provider/agents_default_model_resource.go +++ b/internal/provider/agents_default_model_resource.go @@ -44,7 +44,6 @@ func (r *AgentsDefaultModelResource) experimentalClient() *codersdk.Experimental } type AgentsDefaultModelResourceModel struct { - ID UUID `tfsdk:"id"` OrganizationID UUID `tfsdk:"organization_id"` ModelID UUID `tfsdk:"model_id"` } @@ -52,7 +51,6 @@ type AgentsDefaultModelResourceModel struct { // legacyDefaultAgentsModelResourceModel is the v0 state shape published by // coderd_default_agents_model in provider v0.0.23. type legacyDefaultAgentsModelResourceModel struct { - ID types.String `tfsdk:"id"` ModelID types.String `tfsdk:"model_id"` } @@ -86,14 +84,6 @@ func (r *AgentsDefaultModelResource) Schema(ctx context.Context, req resource.Sc "and promotes a replacement when the current default is removed, so deleting this resource only stops " + "Terraform from managing which model is default.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - MarkdownDescription: "Organization ID that identifies this organization's default Agents model selection.", - CustomType: UUIDType, - Computed: true, - PlanModifiers: []planmodifier.String{ - useStateForUnknownUnlessChanged("organization_id"), - }, - }, "organization_id": schema.StringAttribute{ MarkdownDescription: "Organization ID whose default Agents model is managed.", CustomType: UUIDType, @@ -120,9 +110,6 @@ func (r *AgentsDefaultModelResource) MoveState(ctx context.Context) []resource.S { SourceSchema: &schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - }, "model_id": schema.StringAttribute{ Required: true, }, @@ -161,7 +148,6 @@ func (r *AgentsDefaultModelResource) MoveState(ctx context.Context) []resource.S // default organization cannot be resolved here. Leave it null and // let the subsequent apply adopt the configured organization_id. resp.Diagnostics.Append(resp.TargetState.Set(ctx, AgentsDefaultModelResourceModel{ - ID: NewUUIDNull(), OrganizationID: NewUUIDNull(), ModelID: UUIDValue(modelID), })...) @@ -284,7 +270,6 @@ func (r *AgentsDefaultModelResource) ImportState(ctx context.Context, req resour resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to get organization %q: %s", req.ID, err)) return } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), org.ID.String())...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("organization_id"), org.ID.String())...) } @@ -303,11 +288,9 @@ func (r *AgentsDefaultModelResource) setDefault(ctx context.Context, organizatio } // stateFromAgentsDefaultModelConfig maps the model config that Coder reports as the -// default into resource state. The organization UUID is the natural identity -// because each organization has at most one default model. +// default into resource state. func stateFromAgentsDefaultModelConfig(config codersdk.ChatModel) AgentsDefaultModelResourceModel { return AgentsDefaultModelResourceModel{ - ID: UUIDValue(config.OrganizationID), OrganizationID: UUIDValue(config.OrganizationID), ModelID: UUIDValue(config.ID), } diff --git a/internal/provider/agents_default_model_resource_test.go b/internal/provider/agents_default_model_resource_test.go index 1316e6d1..7dffed0b 100644 --- a/internal/provider/agents_default_model_resource_test.go +++ b/internal/provider/agents_default_model_resource_test.go @@ -18,8 +18,6 @@ import ( "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/providerserver" fwresource "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -35,24 +33,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestAgentsDefaultModelStateFromModelConfig(t *testing.T) { - t.Parallel() - - organizationID := uuid.New() - modelID := uuid.New() - state := stateFromAgentsDefaultModelConfig(codersdk.ChatModel{ - ID: modelID, - OrganizationID: organizationID, - IsDefault: true, - }) - require.Equal(t, organizationID, state.ID.ValueUUID()) - require.Equal(t, organizationID.String(), state.ID.ValueString()) - require.Equal(t, organizationID, state.OrganizationID.ValueUUID()) - require.Equal(t, organizationID.String(), state.OrganizationID.ValueString()) - require.Equal(t, modelID, state.ModelID.ValueUUID()) - require.Equal(t, modelID.String(), state.ModelID.ValueString()) -} - func TestAgentsDefaultModelMoveState(t *testing.T) { t.Parallel() @@ -69,7 +49,6 @@ func TestAgentsDefaultModelMoveState(t *testing.T) { Raw: tftypes.NewValue(sourceSchema.Type().TerraformType(ctx), nil), } require.False(t, sourceState.Set(ctx, legacyDefaultAgentsModelResourceModel{ - ID: types.StringValue("default"), ModelID: types.StringValue(modelID.String()), }).HasError()) @@ -93,7 +72,6 @@ func TestAgentsDefaultModelMoveState(t *testing.T) { var got AgentsDefaultModelResourceModel require.False(t, resp.TargetState.Get(ctx, &got).HasError()) - require.True(t, got.ID.IsNull()) require.True(t, got.OrganizationID.IsNull()) require.Equal(t, modelID, got.ModelID.ValueUUID()) @@ -145,74 +123,6 @@ func TestAgentsDefaultModelMoveState(t *testing.T) { } } -func TestAgentsDefaultModelIDPlanModifier(t *testing.T) { - t.Parallel() - - ctx := t.Context() - oldOrganizationID := uuid.New() - newOrganizationID := uuid.New() - modelID := uuid.New() - - r := &AgentsDefaultModelResource{} - var schemaResp fwresource.SchemaResponse - r.Schema(ctx, fwresource.SchemaRequest{}, &schemaResp) - require.False(t, schemaResp.Diagnostics.HasError(), schemaResp.Diagnostics) - - idAttribute, ok := schemaResp.Schema.Attributes["id"].(schema.StringAttribute) - require.True(t, ok) - require.Len(t, idAttribute.PlanModifiers, 1) - modifier := idAttribute.PlanModifiers[0] - - raw := func(id tftypes.Value, organizationID uuid.UUID) tftypes.Value { - return tftypes.NewValue(schemaResp.Schema.Type().TerraformType(ctx), map[string]tftypes.Value{ - "id": id, - "organization_id": tftypes.NewValue(tftypes.String, organizationID.String()), - "model_id": tftypes.NewValue(tftypes.String, modelID.String()), - }) - } - state := tfsdk.State{ - Schema: schemaResp.Schema, - Raw: raw(tftypes.NewValue(tftypes.String, oldOrganizationID.String()), oldOrganizationID), - } - - for _, tc := range []struct { - name string - plannedOrganization uuid.UUID - want types.String - }{ - { - name: "retains id when organization is unchanged", - plannedOrganization: oldOrganizationID, - want: types.StringValue(oldOrganizationID.String()), - }, - { - name: "leaves id unknown when organization changes", - plannedOrganization: newOrganizationID, - want: types.StringUnknown(), - }, - } { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - plan := tfsdk.Plan{ - Schema: schemaResp.Schema, - Raw: raw(tftypes.NewValue(tftypes.String, tftypes.UnknownValue), tc.plannedOrganization), - } - resp := &planmodifier.StringResponse{PlanValue: types.StringUnknown()} - modifier.PlanModifyString(ctx, planmodifier.StringRequest{ - ConfigValue: types.StringNull(), - PlanValue: types.StringUnknown(), - StateValue: types.StringValue(oldOrganizationID.String()), - Plan: plan, - State: state, - }, resp) - - require.False(t, resp.Diagnostics.HasError(), resp.Diagnostics) - require.Equal(t, tc.want, resp.PlanValue) - }) - } -} - func TestAgentsDefaultModelPatch404Diagnostics(t *testing.T) { t.Parallel() @@ -351,7 +261,6 @@ func TestAgentsDefaultModelReadCollection404(t *testing.T) { r := newAgentsDefaultModelTestResource(t, srv.URL, defaultOrganizationID) state := agentsDefaultModelTestState(t, r, AgentsDefaultModelResourceModel{ - ID: UUIDValue(targetOrganizationID), OrganizationID: UUIDValue(targetOrganizationID), ModelID: UUIDValue(modelID), }) @@ -586,7 +495,6 @@ resource "coderd_agents_default_model" "this" { }, }, ConfigStateChecks: []statecheck.StateCheck{ - statecheck.ExpectKnownValue("coderd_agents_default_model.this", tfjsonpath.New("id"), knownvalue.StringExact(orgID.String())), statecheck.ExpectKnownValue("coderd_agents_default_model.this", tfjsonpath.New("organization_id"), knownvalue.StringExact(orgID.String())), statecheck.ExpectKnownValue("coderd_agents_default_model.this", tfjsonpath.New("model_id"), knownvalue.StringExact(model.ID.String())), statecheck.ExpectKnownValue("coderd_agents_model.test", tfjsonpath.New("organization_id"), knownvalue.StringExact(orgID.String())), @@ -699,7 +607,6 @@ resource "coderd_agents_default_model" "default" { { Config: cfg("sonnet"), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("coderd_agents_default_model.default", "id", organizationID.String()), resource.TestCheckResourceAttr("coderd_agents_default_model.default", "organization_id", organizationID.String()), resource.TestCheckResourceAttrPair("coderd_agents_default_model.default", "model_id", "coderd_agents_model.sonnet", "id"), checkServerDefaultMatchesResource(ctx, t, client, organizationID, "coderd_agents_default_model.default"), @@ -721,10 +628,11 @@ resource "coderd_agents_default_model" "default" { }, { // Import by organization name; Read resolves its current default. - ResourceName: "coderd_agents_default_model.default", - ImportState: true, - ImportStateVerify: true, - ImportStateId: organization.Name, + ResourceName: "coderd_agents_default_model.default", + ImportState: true, + ImportStateVerify: true, + ImportStateId: organization.Name, + ImportStateVerifyIdentifierAttribute: "organization_id", }, }, }) @@ -864,10 +772,8 @@ resource "coderd_agents_default_model" "other_org" { { Config: cfg, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("coderd_agents_default_model.default_org", "id", defaultOrganizationID.String()), resource.TestCheckResourceAttr("coderd_agents_default_model.default_org", "organization_id", defaultOrganizationID.String()), resource.TestCheckResourceAttr("coderd_agents_default_model.default_org", "model_id", defaultOrgSecond.ID.String()), - resource.TestCheckResourceAttr("coderd_agents_default_model.other_org", "id", otherOrganization.ID.String()), resource.TestCheckResourceAttr("coderd_agents_default_model.other_org", "organization_id", otherOrganization.ID.String()), resource.TestCheckResourceAttr("coderd_agents_default_model.other_org", "model_id", otherOrgFirst.ID.String()), checkServerDefaultMatchesResource(ctx, t, client, defaultOrganizationID, "coderd_agents_default_model.default_org"), diff --git a/internal/provider/ai_model_price_resource.go b/internal/provider/ai_model_price_resource.go index 396bf735..d120a1a9 100644 --- a/internal/provider/ai_model_price_resource.go +++ b/internal/provider/ai_model_price_resource.go @@ -41,7 +41,6 @@ type AIModelPriceResource struct { } type AIModelPriceResourceModel struct { - ID types.String `tfsdk:"id"` ProviderType types.String `tfsdk:"provider_type"` Model types.String `tfsdk:"model"` InputPrice types.Int64 `tfsdk:"input_price"` @@ -188,13 +187,6 @@ func (r *AIModelPriceResource) Schema(ctx context.Context, req resource.SchemaRe "-> If Coder already has a different custom price for the same model, the plan fails. Import that price to manage it with Terraform.\n\n" + "~> **Warning**\nDestroying this resource only removes it from the Terraform state. The price stays in effect, because Coder has no API to delete a custom price.", Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - MarkdownDescription: "Model price ID, in the form `/`.", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, "provider_type": schema.StringAttribute{ MarkdownDescription: "AI provider type the price applies to. Valid values are `openai`, `anthropic`, `azure`, `bedrock`, `google`, `openrouter`, `vercel`, and `copilot`. " + "`openai-compat` is not currently supported. " + @@ -314,8 +306,6 @@ func (r *AIModelPriceResource) Create(ctx context.Context, req resource.CreateRe resp.Diagnostics.Append(aiModelPriceDiag("set", id, err)...) return } - - plan.ID = types.StringValue(id) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -340,7 +330,6 @@ func (r *AIModelPriceResource) Read(ctx context.Context, req resource.ReadReques return } - state.ID = types.StringValue(id) state.InputPrice = types.Int64PointerValue(price.InputPrice) state.OutputPrice = types.Int64PointerValue(price.OutputPrice) state.CacheReadPrice = types.Int64PointerValue(price.CacheReadPrice) @@ -364,8 +353,6 @@ func (r *AIModelPriceResource) Update(ctx context.Context, req resource.UpdateRe resp.Diagnostics.Append(aiModelPriceDiag("update", id, err)...) return } - - plan.ID = types.StringValue(id) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -394,7 +381,6 @@ func (r *AIModelPriceResource) ImportState(ctx context.Context, req resource.Imp return } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("provider_type"), providerType)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("model"), model)...) } diff --git a/internal/provider/ai_model_price_resource_test.go b/internal/provider/ai_model_price_resource_test.go index ac36c6ee..af555a9f 100644 --- a/internal/provider/ai_model_price_resource_test.go +++ b/internal/provider/ai_model_price_resource_test.go @@ -247,7 +247,6 @@ resource "coderd_ai_model_price" "conflict" { Steps: []resource.TestStep{ { Config: cfg(100, 200, "null"), - Check: resource.TestCheckResourceAttr("coderd_ai_model_price.slash", "id", "openrouter/"+slashModel), }, { Config: cfg(150, 250, "0"), @@ -262,16 +261,18 @@ resource "coderd_ai_model_price" "conflict" { }, }, { - ResourceName: "coderd_ai_model_price.test", - ImportState: true, - ImportStateId: "anthropic/" + model, - ImportStateVerify: true, + ResourceName: "coderd_ai_model_price.test", + ImportState: true, + ImportStateId: "anthropic/" + model, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "model", }, { - ResourceName: "coderd_ai_model_price.slash", - ImportState: true, - ImportStateId: "openrouter/" + slashModel, - ImportStateVerify: true, + ResourceName: "coderd_ai_model_price.slash", + ImportState: true, + ImportStateId: "openrouter/" + slashModel, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "model", }, { PreConfig: func() { upsert("openai", conflictModel, 1000000) },