Skip to content

Commit f31e6e0

Browse files
chore(provisioner): support updated coder_ai_task resource (#20160)
Closes coder/internal#978 - Introduce `CODER_TASK_ID` and `CODER_TASK_PROMPT` to the provisioner environment - Make use of new `app_id` field in provider, with a fallback to `sidebar_app.id` for backwards compatibility **For now** I've left the `taskPrompt` and `taskID` as a TODO as we do not yet create these values.
1 parent 8942b50 commit f31e6e0

22 files changed

Lines changed: 482 additions & 1055 deletions

‎cli/testdata/coder_provisioner_list_--output_json.golden‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"last_seen_at": "====[timestamp]=====",
88
"name": "test-daemon",
99
"version": "v0.0.0-devel",
10-
"api_version": "1.10",
10+
"api_version": "1.11",
1111
"provisioners": [
1212
"echo"
1313
],

‎coderd/provisionerdserver/provisionerdserver.go‎

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,14 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
597597
return nil, failJob(fmt.Sprintf("get workspace build parameters: %s", err))
598598
}
599599

600+
// TODO(DanielleMaywood):
601+
// Plumb a task prompt into this when we have the new data-model ready
602+
var taskPrompt string
603+
604+
// TODO(DanielleMaywood):
605+
// Plumb a task ID into this when we have the new data-model ready
606+
var taskID string
607+
600608
dbExternalAuthProviders := []database.ExternalAuthProvider{}
601609
err = json.Unmarshal(templateVersion.ExternalAuthProviders, &dbExternalAuthProviders)
602610
if err != nil {
@@ -721,6 +729,8 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
721729
WorkspaceOwnerRbacRoles: ownerRbacRoles,
722730
RunningAgentAuthTokens: runningAgentAuthTokens,
723731
PrebuiltWorkspaceBuildStage: input.PrebuiltWorkspaceBuildStage,
732+
TaskId: taskID,
733+
TaskPrompt: taskPrompt,
724734
},
725735
LogLevel: input.LogLevel,
726736
},
@@ -1976,27 +1986,34 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
19761986
}
19771987
}
19781988

1979-
var sidebarAppID uuid.NullUUID
1989+
var taskAppID uuid.NullUUID
19801990
var hasAITask bool
1981-
var warnUnknownSidebarAppID bool
1991+
var warnUnknownTaskAppID bool
19821992
if tasks := jobType.WorkspaceBuild.GetAiTasks(); len(tasks) > 0 {
19831993
hasAITask = true
19841994
task := tasks[0]
1985-
if task == nil || task.GetSidebarApp() == nil || len(task.GetSidebarApp().GetId()) == 0 {
1986-
return xerrors.Errorf("update ai task: sidebar app is nil or empty")
1995+
if task == nil {
1996+
return xerrors.Errorf("update ai task: task is nil")
1997+
}
1998+
1999+
appID := task.GetAppId()
2000+
if appID == "" && task.GetSidebarApp() != nil {
2001+
appID = task.GetSidebarApp().GetId()
2002+
}
2003+
if appID == "" {
2004+
return xerrors.Errorf("update ai task: app id is empty")
19872005
}
19882006

1989-
sidebarTaskID := task.GetSidebarApp().GetId()
1990-
if !slices.Contains(appIDs, sidebarTaskID) {
1991-
warnUnknownSidebarAppID = true
2007+
if !slices.Contains(appIDs, appID) {
2008+
warnUnknownTaskAppID = true
19922009
}
19932010

1994-
id, err := uuid.Parse(task.GetSidebarApp().GetId())
2011+
id, err := uuid.Parse(appID)
19952012
if err != nil {
1996-
return xerrors.Errorf("parse sidebar app id: %w", err)
2013+
return xerrors.Errorf("parse app id: %w", err)
19972014
}
19982015

1999-
sidebarAppID = uuid.NullUUID{UUID: id, Valid: true}
2016+
taskAppID = uuid.NullUUID{UUID: id, Valid: true}
20002017
}
20012018

20022019
// This is a hacky workaround for the issue with tasks 'disappearing' on stop:
@@ -2008,19 +2025,19 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
20082025
BuildNumber: workspaceBuild.BuildNumber - 1,
20092026
}); err == nil {
20102027
hasAITask = prevBuild.HasAITask.Bool
2011-
sidebarAppID = prevBuild.AITaskSidebarAppID
2012-
warnUnknownSidebarAppID = false
2013-
s.Logger.Debug(ctx, "task workaround: reused has_ai_task and sidebar_app_id from previous build to keep track of task",
2028+
taskAppID = prevBuild.AITaskSidebarAppID
2029+
warnUnknownTaskAppID = false
2030+
s.Logger.Debug(ctx, "task workaround: reused has_ai_task and app_id from previous build to keep track of task",
20142031
slog.F("job_id", job.ID.String()),
20152032
slog.F("build_number", prevBuild.BuildNumber),
20162033
slog.F("workspace_id", workspace.ID),
20172034
slog.F("workspace_build_id", workspaceBuild.ID),
20182035
slog.F("transition", string(workspaceBuild.Transition)),
2019-
slog.F("sidebar_app_id", sidebarAppID.UUID),
2036+
slog.F("sidebar_app_id", taskAppID.UUID),
20202037
slog.F("has_ai_task", hasAITask),
20212038
)
20222039
} else {
2023-
s.Logger.Error(ctx, "task workaround: tracking via has_ai_task and sidebar_app from previous build failed",
2040+
s.Logger.Error(ctx, "task workaround: tracking via has_ai_task and app_id from previous build failed",
20242041
slog.Error(err),
20252042
slog.F("job_id", job.ID.String()),
20262043
slog.F("workspace_id", workspace.ID),
@@ -2030,14 +2047,14 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
20302047
}
20312048
}
20322049

2033-
if warnUnknownSidebarAppID {
2050+
if warnUnknownTaskAppID {
20342051
// Ref: https://github.com/coder/coder/issues/18776
20352052
// This can happen for a number of reasons:
20362053
// 1. Misconfigured template
20372054
// 2. Count=0 on the agent due to stop transition, meaning the associated coder_app was not inserted.
20382055
// Failing the build at this point is not ideal, so log a warning instead.
2039-
s.Logger.Warn(ctx, "unknown ai_task_sidebar_app_id",
2040-
slog.F("ai_task_sidebar_app_id", sidebarAppID.UUID.String()),
2056+
s.Logger.Warn(ctx, "unknown ai_task_app_id",
2057+
slog.F("ai_task_app_id", taskAppID.UUID.String()),
20412058
slog.F("job_id", job.ID.String()),
20422059
slog.F("workspace_id", workspace.ID),
20432060
slog.F("workspace_build_id", workspaceBuild.ID),
@@ -2051,13 +2068,13 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
20512068
Level: []database.LogLevel{database.LogLevelWarn, database.LogLevelWarn, database.LogLevelWarn, database.LogLevelWarn},
20522069
Stage: []string{"Cleaning Up", "Cleaning Up", "Cleaning Up", "Cleaning Up"},
20532070
Output: []string{
2054-
fmt.Sprintf("Unknown ai_task_sidebar_app_id %q. This workspace will be unable to run AI tasks. This may be due to a template configuration issue, please check with the template author.", sidebarAppID.UUID.String()),
2071+
fmt.Sprintf("Unknown ai_task_app_id %q. This workspace will be unable to run AI tasks. This may be due to a template configuration issue, please check with the template author.", taskAppID.UUID.String()),
20552072
"Template author: double-check the following:",
20562073
" - You have associated the coder_ai_task with a valid coder_app in your template (ref: https://registry.terraform.io/providers/coder/coder/latest/docs/resources/ai_task).",
20572074
" - You have associated the coder_agent with at least one other compute resource. Agents with no other associated resources are not inserted into the database.",
20582075
},
20592076
}); err != nil {
2060-
s.Logger.Error(ctx, "insert provisioner job log for ai task sidebar app id warning",
2077+
s.Logger.Error(ctx, "insert provisioner job log for ai task app id warning",
20612078
slog.F("job_id", jobID),
20622079
slog.F("workspace_id", workspace.ID),
20632080
slog.F("workspace_build_id", workspaceBuild.ID),
@@ -2066,7 +2083,7 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
20662083
}
20672084
// Important: reset hasAITask and sidebarAppID so that we don't run into a fk constraint violation.
20682085
hasAITask = false
2069-
sidebarAppID = uuid.NullUUID{}
2086+
taskAppID = uuid.NullUUID{}
20702087
}
20712088

20722089
if hasAITask && workspaceBuild.Transition == database.WorkspaceTransitionStart {
@@ -2103,7 +2120,7 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
21032120
Bool: hasExternalAgent,
21042121
Valid: true,
21052122
},
2106-
SidebarAppID: sidebarAppID,
2123+
SidebarAppID: taskAppID,
21072124
UpdatedAt: now,
21082125
}); err != nil {
21092126
return xerrors.Errorf("update workspace build ai tasks and external agent flag: %w", err)

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ require (
101101
github.com/coder/quartz v0.2.1
102102
github.com/coder/retry v1.5.1
103103
github.com/coder/serpent v0.10.0
104-
github.com/coder/terraform-provider-coder/v2 v2.11.0
104+
github.com/coder/terraform-provider-coder/v2 v2.12.0-pre0
105105
github.com/coder/websocket v1.8.13
106106
github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0
107107
github.com/coreos/go-oidc/v3 v3.16.0

‎go.sum‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -948,8 +948,8 @@ github.com/coder/tailscale v1.1.1-0.20250829055706-6eafe0f9199e h1:9RKGKzGLHtTvV
948948
github.com/coder/tailscale v1.1.1-0.20250829055706-6eafe0f9199e/go.mod h1:jU9T1vEs+DOs8NtGp1F2PT0/TOGVwtg/JCCKYRgvMOs=
949949
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0=
950950
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
951-
github.com/coder/terraform-provider-coder/v2 v2.11.0 h1:E9mjwCDHoKTp9agCmNQ5viky4dKrt0Gx0vDxsLEPRf0=
952-
github.com/coder/terraform-provider-coder/v2 v2.11.0/go.mod h1:q0cAdlM1cafFGQ8Vug5kwU+34S+wtJsqylxTjVXX+rw=
951+
github.com/coder/terraform-provider-coder/v2 v2.12.0-pre0 h1:Jcp/NCDfrYtsVlXOr4pEZ9EBtTbKTA/HeToIOI0nx4k=
952+
github.com/coder/terraform-provider-coder/v2 v2.12.0-pre0/go.mod h1:q0cAdlM1cafFGQ8Vug5kwU+34S+wtJsqylxTjVXX+rw=
953953
github.com/coder/trivy v0.0.0-20250807211036-0bb0acd620a8 h1:VYB/6cIIKsVkwXOAWbqpj4Ux+WwF/XTnRyvHcwfHZ7A=
954954
github.com/coder/trivy v0.0.0-20250807211036-0bb0acd620a8/go.mod h1:O73tP+UvJlI2GQZD060Jt0sf+6alKcGAgORh6sgB0+M=
955955
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=

‎provisioner/terraform/provision.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ func provisionEnv(
266266
"CODER_WORKSPACE_TEMPLATE_NAME="+metadata.GetTemplateName(),
267267
"CODER_WORKSPACE_TEMPLATE_VERSION="+metadata.GetTemplateVersion(),
268268
"CODER_WORKSPACE_BUILD_ID="+metadata.GetWorkspaceBuildId(),
269+
"CODER_TASK_ID="+metadata.GetTaskId(),
270+
"CODER_TASK_PROMPT="+metadata.GetTaskPrompt(),
269271
)
270272
if metadata.GetPrebuiltWorkspaceBuildStage().IsPrebuild() {
271273
env = append(env, provider.IsPrebuildEnvironmentVariable()+"=true")

‎provisioner/terraform/provision_test.go‎

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,29 +1048,6 @@ func TestProvision(t *testing.T) {
10481048
}},
10491049
},
10501050
},
1051-
{
1052-
Name: "ai-task-required-prompt-param",
1053-
Files: map[string]string{
1054-
"main.tf": `terraform {
1055-
required_providers {
1056-
coder = {
1057-
source = "coder/coder"
1058-
version = ">= 2.7.0"
1059-
}
1060-
}
1061-
}
1062-
resource "coder_ai_task" "a" {
1063-
sidebar_app {
1064-
id = "7128be08-8722-44cb-bbe1-b5a391c4d94b" # fake ID, irrelevant here anyway but needed for validation
1065-
}
1066-
}
1067-
`,
1068-
},
1069-
Request: &proto.PlanRequest{},
1070-
Response: &proto.PlanComplete{
1071-
Error: fmt.Sprintf("plan resources: coder_parameter named '%s' is required when 'coder_ai_task' resource is defined", provider.TaskPromptParameterName),
1072-
},
1073-
},
10741051
{
10751052
Name: "ai-task-multiple-allowed-in-plan",
10761053
Files: map[string]string{
@@ -1160,6 +1137,39 @@ func TestProvision(t *testing.T) {
11601137
},
11611138
SkipCacheProviders: true,
11621139
},
1140+
{
1141+
Name: "ai-task-app-id",
1142+
Files: map[string]string{
1143+
"main.tf": `terraform {
1144+
required_providers {
1145+
coder = {
1146+
source = "coder/coder"
1147+
version = "= 2.12.0-pre0"
1148+
}
1149+
}
1150+
}
1151+
resource "coder_ai_task" "my-task" {
1152+
app_id = "7128be08-8722-44cb-bbe1-b5a391c4d94b" # fake ID, irrelevant here anyway but needed for validation
1153+
}
1154+
`,
1155+
},
1156+
Response: &proto.PlanComplete{
1157+
Resources: []*proto.Resource{
1158+
{
1159+
Name: "my-task",
1160+
Type: "coder_ai_task",
1161+
},
1162+
},
1163+
AiTasks: []*proto.AITask{
1164+
{
1165+
Id: "my-task",
1166+
AppId: "7128be08-8722-44cb-bbe1-b5a391c4d94b",
1167+
},
1168+
},
1169+
HasAiTasks: true,
1170+
},
1171+
SkipCacheProviders: true,
1172+
},
11631173
}
11641174

11651175
// Remove unused cache dirs before running tests.

‎provisioner/terraform/resources.go‎

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"math"
7-
"slices"
87
"strings"
98

109
"github.com/awalterschulze/gographviz"
@@ -1023,14 +1022,11 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
10231022
return nil, xerrors.Errorf("decode coder_ai_task attributes: %w", err)
10241023
}
10251024

1026-
if len(task.SidebarApp) < 1 {
1027-
return nil, xerrors.Errorf("coder_ai_task has no sidebar_app defined")
1028-
}
1029-
10301025
aiTasks = append(aiTasks, &proto.AITask{
1031-
Id: task.ID,
1026+
Id: task.ID,
1027+
AppId: task.AppID,
10321028
SidebarApp: &proto.AITaskSidebarApp{
1033-
Id: task.SidebarApp[0].ID,
1029+
Id: task.AppID,
10341030
},
10351031
})
10361032
}
@@ -1066,14 +1062,6 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
10661062
}
10671063

10681064
hasAITasks := hasAITaskResources(graph)
1069-
if hasAITasks {
1070-
hasPromptParam := slices.ContainsFunc(parameters, func(param *proto.RichParameter) bool {
1071-
return param.Name == provider.TaskPromptParameterName
1072-
})
1073-
if !hasPromptParam {
1074-
return nil, xerrors.Errorf("coder_parameter named '%s' is required when 'coder_ai_task' resource is defined", provider.TaskPromptParameterName)
1075-
}
1076-
}
10771065

10781066
return &State{
10791067
Resources: resources,

‎provisioner/terraform/resources_test.go‎

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import (
1616
"github.com/stretchr/testify/require"
1717
protobuf "google.golang.org/protobuf/proto"
1818

19-
"github.com/coder/terraform-provider-coder/v2/provider"
20-
2119
"cdr.dev/slog"
2220
"cdr.dev/slog/sloggers/slogtest"
2321

@@ -1528,26 +1526,6 @@ func TestAITasks(t *testing.T) {
15281526
t.Parallel()
15291527
ctx, logger := ctxAndLogger(t)
15301528

1531-
t.Run("Prompt parameter is required", func(t *testing.T) {
1532-
t.Parallel()
1533-
1534-
// nolint:dogsled
1535-
_, filename, _, _ := runtime.Caller(0)
1536-
1537-
dir := filepath.Join(filepath.Dir(filename), "testdata", "resources", "ai-tasks-missing-prompt")
1538-
tfPlanRaw, err := os.ReadFile(filepath.Join(dir, "ai-tasks-missing-prompt.tfplan.json"))
1539-
require.NoError(t, err)
1540-
var tfPlan tfjson.Plan
1541-
err = json.Unmarshal(tfPlanRaw, &tfPlan)
1542-
require.NoError(t, err)
1543-
tfPlanGraph, err := os.ReadFile(filepath.Join(dir, "ai-tasks-missing-prompt.tfplan.dot"))
1544-
require.NoError(t, err)
1545-
1546-
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule, tfPlan.PriorState.Values.RootModule}, string(tfPlanGraph), logger)
1547-
require.Nil(t, state)
1548-
require.ErrorContains(t, err, fmt.Sprintf("coder_parameter named '%s' is required when 'coder_ai_task' resource is defined", provider.TaskPromptParameterName))
1549-
})
1550-
15511529
t.Run("Multiple tasks can be defined", func(t *testing.T) {
15521530
t.Parallel()
15531531

‎provisioner/terraform/testdata/resources/ai-tasks-missing-prompt/ai-tasks-missing-prompt.tfplan.dot‎

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)