Skip to content

Commit 4a410c6

Browse files
feat: add task_id, prompt and app_id fields to coder_ai_task
Closes coder/internal#977 Adds one required field `app_id`, two read-only computed fields `task_id` and `prompt`, as well as deprecates the `sidebar_app` field.
1 parent 71c6d7d commit 4a410c6

File tree

4 files changed

+132
-23
lines changed

4 files changed

+132
-23
lines changed

docs/resources/ai_task.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ Use this resource to define Coder tasks.
1717

1818
### Required
1919

20-
- `sidebar_app` (Block Set, Min: 1, Max: 1) The coder_app to display in the sidebar. Usually a chat interface with the AI agent running in the workspace, like https://github.com/coder/agentapi. (see [below for nested schema](#nestedblock--sidebar_app))
20+
- `app_id` (String) The ID of the coder_app resource that provides the AI interface for this task.
21+
- `sidebar_app` (Block Set, Min: 1, Max: 1, Deprecated) The coder_app to display in the sidebar. Usually a chat interface with the AI agent running in the workspace, like https://github.com/coder/agentapi. (see [below for nested schema](#nestedblock--sidebar_app))
2122

2223
### Read-Only
2324

2425
- `id` (String) A unique identifier for this resource.
26+
- `prompt` (String) The prompt text provided to the task by Coder.
27+
- `task_id` (String) A unique identifier for the Coder Task resource.
2528

2629
<a id="nestedblock--sidebar_app"></a>
2730
### Nested Schema for `sidebar_app`

provider/ai_task.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,33 @@ type AITaskSidebarApp struct {
2121
// TaskPromptParameterName is the name of the parameter which is *required* to be defined when a coder_ai_task is used.
2222
const TaskPromptParameterName = "AI Prompt"
2323

24-
func aiTask() *schema.Resource {
24+
func aiTaskResource() *schema.Resource {
2525
return &schema.Resource{
2626
SchemaVersion: 1,
2727

2828
Description: "Use this resource to define Coder tasks.",
2929
CreateContext: func(c context.Context, resourceData *schema.ResourceData, i any) diag.Diagnostics {
3030
resourceData.SetId(uuid.NewString())
31+
32+
var (
33+
appID = resourceData.Get("app_id").(string)
34+
sidebarAppSet = resourceData.Get("sidebar_app").(*schema.Set)
35+
)
36+
37+
if appID == "" && sidebarAppSet.Len() > 0 {
38+
sidebarApps := sidebarAppSet.List()
39+
sidebarApp := sidebarApps[0].(map[string]any)
40+
41+
if id, ok := sidebarApp["id"].(string); ok && id != "" {
42+
appID = id
43+
resourceData.Set("app_id", id)
44+
}
45+
}
46+
47+
if appID == "" {
48+
return diag.Errorf("'app_id' must be set")
49+
}
50+
3151
return nil
3252
},
3353
ReadContext: schema.NoopContext,
@@ -39,11 +59,13 @@ func aiTask() *schema.Resource {
3959
Computed: true,
4060
},
4161
"sidebar_app": {
42-
Type: schema.TypeSet,
43-
Description: "The coder_app to display in the sidebar. Usually a chat interface with the AI agent running in the workspace, like https://github.com/coder/agentapi.",
44-
ForceNew: true,
45-
Required: true,
46-
MaxItems: 1,
62+
Type: schema.TypeSet,
63+
Description: "The coder_app to display in the sidebar. Usually a chat interface with the AI agent running in the workspace, like https://github.com/coder/agentapi.",
64+
Deprecated: "This field has been deprecated in favor of the `app_id` field.",
65+
ForceNew: true,
66+
Optional: true,
67+
MaxItems: 1,
68+
ConflictsWith: []string{"app_id"},
4769
Elem: &schema.Resource{
4870
Schema: map[string]*schema.Schema{
4971
"id": {
@@ -56,6 +78,25 @@ func aiTask() *schema.Resource {
5678
},
5779
},
5880
},
81+
"task_id": {
82+
Type: schema.TypeString,
83+
Description: "A unique identifier for the Coder Task resource.",
84+
Computed: true,
85+
},
86+
"prompt": {
87+
Type: schema.TypeString,
88+
Description: "The prompt text provided to the task by Coder.",
89+
Computed: true,
90+
},
91+
"app_id": {
92+
Type: schema.TypeString,
93+
Description: "The ID of the coder_app resource that provides the AI interface for this task.",
94+
ForceNew: true,
95+
Optional: true,
96+
Computed: true,
97+
ValidateFunc: validation.IsUUID,
98+
ConflictsWith: []string{"sidebar_app"},
99+
},
59100
},
60101
}
61102
}

provider/ai_task_test.go

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,44 +22,90 @@ func TestAITask(t *testing.T) {
2222
Config: `
2323
provider "coder" {
2424
}
25-
resource "coder_agent" "dev" {
26-
os = "linux"
27-
arch = "amd64"
25+
resource "coder_ai_task" "test" {
26+
app_id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
2827
}
29-
resource "coder_app" "code-server" {
30-
agent_id = coder_agent.dev.id
31-
slug = "code-server"
32-
display_name = "code-server"
33-
icon = "builtin:vim"
34-
url = "http://localhost:13337"
35-
open_in = "slim-window"
28+
`,
29+
Check: func(state *terraform.State) error {
30+
require.Len(t, state.Modules, 1)
31+
resource := state.Modules[0].Resources["coder_ai_task.test"]
32+
require.NotNil(t, resource)
33+
for _, key := range []string{
34+
"id",
35+
"app_id",
36+
} {
37+
value := resource.Primary.Attributes[key]
38+
require.NotNil(t, value)
39+
require.Greater(t, len(value), 0)
40+
}
41+
return nil
42+
},
43+
}},
44+
})
45+
})
46+
47+
t.Run("InvalidAppID", func(t *testing.T) {
48+
t.Parallel()
49+
50+
resource.Test(t, resource.TestCase{
51+
ProviderFactories: coderFactory(),
52+
IsUnitTest: true,
53+
Steps: []resource.TestStep{{
54+
Config: `
55+
provider "coder" {
56+
}
57+
resource "coder_ai_task" "test" {
58+
app_id = "not-a-uuid"
59+
}
60+
`,
61+
ExpectError: regexp.MustCompile(`expected "app_id" to be a valid UUID`),
62+
}},
63+
})
64+
})
65+
66+
t.Run("DeprecatedSidebarApp", func(t *testing.T) {
67+
t.Parallel()
68+
69+
resource.Test(t, resource.TestCase{
70+
ProviderFactories: coderFactory(),
71+
IsUnitTest: true,
72+
Steps: []resource.TestStep{{
73+
Config: `
74+
provider "coder" {
3675
}
3776
resource "coder_ai_task" "test" {
3877
sidebar_app {
39-
id = coder_app.code-server.id
78+
id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
4079
}
4180
}
4281
`,
4382
Check: func(state *terraform.State) error {
4483
require.Len(t, state.Modules, 1)
4584
resource := state.Modules[0].Resources["coder_ai_task.test"]
4685
require.NotNil(t, resource)
86+
4787
for _, key := range []string{
4888
"id",
49-
"sidebar_app.#",
5089
} {
5190
value := resource.Primary.Attributes[key]
5291
require.NotNil(t, value)
5392
require.Greater(t, len(value), 0)
5493
}
94+
5595
require.Equal(t, "1", resource.Primary.Attributes["sidebar_app.#"])
96+
sidebarAppID := resource.Primary.Attributes["sidebar_app.0.id"]
97+
require.Equal(t, "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc", sidebarAppID)
98+
99+
actualAppID := resource.Primary.Attributes["app_id"]
100+
require.Equal(t, "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc", actualAppID)
101+
56102
return nil
57103
},
58104
}},
59105
})
60106
})
61107

62-
t.Run("InvalidSidebarAppID", func(t *testing.T) {
108+
t.Run("ConflictingFields", func(t *testing.T) {
63109
t.Parallel()
64110

65111
resource.Test(t, resource.TestCase{
@@ -70,12 +116,31 @@ func TestAITask(t *testing.T) {
70116
provider "coder" {
71117
}
72118
resource "coder_ai_task" "test" {
119+
app_id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
73120
sidebar_app {
74-
id = "not-a-uuid"
121+
id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
75122
}
76123
}
77124
`,
78-
ExpectError: regexp.MustCompile(`expected "sidebar_app.0.id" to be a valid UUID`),
125+
ExpectError: regexp.MustCompile(`"app_id": conflicts with sidebar_app`),
126+
}},
127+
})
128+
})
129+
130+
t.Run("NoAppID", func(t *testing.T) {
131+
t.Parallel()
132+
133+
resource.Test(t, resource.TestCase{
134+
ProviderFactories: coderFactory(),
135+
IsUnitTest: true,
136+
Steps: []resource.TestStep{{
137+
Config: `
138+
provider "coder" {
139+
}
140+
resource "coder_ai_task" "test" {
141+
}
142+
`,
143+
ExpectError: regexp.MustCompile(`'app_id' must be set`),
79144
}},
80145
})
81146
})

provider/provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func New() *schema.Provider {
6868
ResourcesMap: map[string]*schema.Resource{
6969
"coder_agent": agentResource(),
7070
"coder_agent_instance": agentInstanceResource(),
71-
"coder_ai_task": aiTask(),
71+
"coder_ai_task": aiTaskResource(),
7272
"coder_app": appResource(),
7373
"coder_metadata": metadataResource(),
7474
"coder_script": scriptResource(),

0 commit comments

Comments
 (0)