Skip to content

Commit bf7817e

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 bf7817e

File tree

4 files changed

+150
-27
lines changed

4 files changed

+150
-27
lines changed

docs/resources/ai_task.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ Use this resource to define Coder tasks.
1515
<!-- schema generated by tfplugindocs -->
1616
## Schema
1717

18-
### Required
18+
### Optional
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, 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.
2527

2628
<a id="nestedblock--sidebar_app"></a>
2729
### Nested Schema for `sidebar_app`

provider/ai_task.go

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package provider
22

33
import (
44
"context"
5+
"os"
56

67
"github.com/google/uuid"
78
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -21,13 +22,43 @@ type AITaskSidebarApp struct {
2122
// TaskPromptParameterName is the name of the parameter which is *required* to be defined when a coder_ai_task is used.
2223
const TaskPromptParameterName = "AI Prompt"
2324

24-
func aiTask() *schema.Resource {
25+
func aiTaskResource() *schema.Resource {
2526
return &schema.Resource{
2627
SchemaVersion: 1,
2728

2829
Description: "Use this resource to define Coder tasks.",
2930
CreateContext: func(c context.Context, resourceData *schema.ResourceData, i any) diag.Diagnostics {
30-
resourceData.SetId(uuid.NewString())
31+
if idStr := os.Getenv("CODER_TASK_ID"); idStr != "" {
32+
resourceData.SetId(idStr)
33+
} else {
34+
resourceData.SetId(uuid.NewString())
35+
}
36+
37+
if prompt := os.Getenv("CODER_TASK_PROMPT"); prompt != "" {
38+
resourceData.Set("prompt", prompt)
39+
} else {
40+
resourceData.Set("prompt", "default")
41+
}
42+
43+
var (
44+
appID = resourceData.Get("app_id").(string)
45+
sidebarAppSet = resourceData.Get("sidebar_app").(*schema.Set)
46+
)
47+
48+
if appID == "" && sidebarAppSet.Len() > 0 {
49+
sidebarApps := sidebarAppSet.List()
50+
sidebarApp := sidebarApps[0].(map[string]any)
51+
52+
if id, ok := sidebarApp["id"].(string); ok && id != "" {
53+
appID = id
54+
resourceData.Set("app_id", id)
55+
}
56+
}
57+
58+
if appID == "" {
59+
return diag.Errorf("'app_id' must be set")
60+
}
61+
3162
return nil
3263
},
3364
ReadContext: schema.NoopContext,
@@ -39,11 +70,13 @@ func aiTask() *schema.Resource {
3970
Computed: true,
4071
},
4172
"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,
73+
Type: schema.TypeSet,
74+
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.",
75+
Deprecated: "This field has been deprecated in favor of the `app_id` field.",
76+
ForceNew: true,
77+
Optional: true,
78+
MaxItems: 1,
79+
ConflictsWith: []string{"app_id"},
4780
Elem: &schema.Resource{
4881
Schema: map[string]*schema.Schema{
4982
"id": {
@@ -56,6 +89,20 @@ func aiTask() *schema.Resource {
5689
},
5790
},
5891
},
92+
"prompt": {
93+
Type: schema.TypeString,
94+
Description: "The prompt text provided to the task by Coder.",
95+
Computed: true,
96+
},
97+
"app_id": {
98+
Type: schema.TypeString,
99+
Description: "The ID of the coder_app resource that provides the AI interface for this task.",
100+
ForceNew: true,
101+
Optional: true,
102+
Computed: true,
103+
ValidateFunc: validation.IsUUID,
104+
ConflictsWith: []string{"sidebar_app"},
105+
},
59106
},
60107
}
61108
}

provider/ai_task_test.go

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,48 @@ import (
1010
)
1111

1212
func TestAITask(t *testing.T) {
13-
t.Parallel()
14-
1513
t.Run("OK", func(t *testing.T) {
14+
t.Setenv("CODER_TASK_ID", "7d8d4c2e-fb57-44f9-a183-22509819c2e7")
15+
t.Setenv("CODER_TASK_PROMPT", "some task prompt")
16+
17+
resource.Test(t, resource.TestCase{
18+
ProviderFactories: coderFactory(),
19+
IsUnitTest: true,
20+
Steps: []resource.TestStep{{
21+
Config: `
22+
provider "coder" {
23+
}
24+
resource "coder_ai_task" "test" {
25+
app_id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
26+
}
27+
`,
28+
Check: func(state *terraform.State) error {
29+
require.Len(t, state.Modules, 1)
30+
resource := state.Modules[0].Resources["coder_ai_task.test"]
31+
require.NotNil(t, resource)
32+
for _, key := range []string{
33+
"id",
34+
"prompt",
35+
"app_id",
36+
} {
37+
value := resource.Primary.Attributes[key]
38+
require.NotNil(t, value)
39+
require.Greater(t, len(value), 0)
40+
}
41+
42+
taskID := resource.Primary.Attributes["id"]
43+
require.Equal(t, "7d8d4c2e-fb57-44f9-a183-22509819c2e7", taskID)
44+
45+
taskPrompt := resource.Primary.Attributes["prompt"]
46+
require.Equal(t, "some task prompt", taskPrompt)
47+
48+
return nil
49+
},
50+
}},
51+
})
52+
})
53+
54+
t.Run("InvalidAppID", func(t *testing.T) {
1655
t.Parallel()
1756

1857
resource.Test(t, resource.TestCase{
@@ -22,44 +61,60 @@ func TestAITask(t *testing.T) {
2261
Config: `
2362
provider "coder" {
2463
}
25-
resource "coder_agent" "dev" {
26-
os = "linux"
27-
arch = "amd64"
64+
resource "coder_ai_task" "test" {
65+
app_id = "not-a-uuid"
2866
}
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"
67+
`,
68+
ExpectError: regexp.MustCompile(`expected "app_id" to be a valid UUID`),
69+
}},
70+
})
71+
})
72+
73+
t.Run("DeprecatedSidebarApp", func(t *testing.T) {
74+
t.Parallel()
75+
76+
resource.Test(t, resource.TestCase{
77+
ProviderFactories: coderFactory(),
78+
IsUnitTest: true,
79+
Steps: []resource.TestStep{{
80+
Config: `
81+
provider "coder" {
3682
}
3783
resource "coder_ai_task" "test" {
3884
sidebar_app {
39-
id = coder_app.code-server.id
85+
id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
4086
}
4187
}
4288
`,
4389
Check: func(state *terraform.State) error {
4490
require.Len(t, state.Modules, 1)
4591
resource := state.Modules[0].Resources["coder_ai_task.test"]
4692
require.NotNil(t, resource)
93+
4794
for _, key := range []string{
4895
"id",
49-
"sidebar_app.#",
96+
"prompt",
97+
"app_id",
5098
} {
5199
value := resource.Primary.Attributes[key]
52100
require.NotNil(t, value)
53101
require.Greater(t, len(value), 0)
54102
}
103+
55104
require.Equal(t, "1", resource.Primary.Attributes["sidebar_app.#"])
105+
sidebarAppID := resource.Primary.Attributes["sidebar_app.0.id"]
106+
require.Equal(t, "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc", sidebarAppID)
107+
108+
actualAppID := resource.Primary.Attributes["app_id"]
109+
require.Equal(t, "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc", actualAppID)
110+
56111
return nil
57112
},
58113
}},
59114
})
60115
})
61116

62-
t.Run("InvalidSidebarAppID", func(t *testing.T) {
117+
t.Run("ConflictingFields", func(t *testing.T) {
63118
t.Parallel()
64119

65120
resource.Test(t, resource.TestCase{
@@ -70,12 +125,31 @@ func TestAITask(t *testing.T) {
70125
provider "coder" {
71126
}
72127
resource "coder_ai_task" "test" {
128+
app_id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
73129
sidebar_app {
74-
id = "not-a-uuid"
130+
id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc"
75131
}
76132
}
77133
`,
78-
ExpectError: regexp.MustCompile(`expected "sidebar_app.0.id" to be a valid UUID`),
134+
ExpectError: regexp.MustCompile(`"app_id": conflicts with sidebar_app`),
135+
}},
136+
})
137+
})
138+
139+
t.Run("NoAppID", func(t *testing.T) {
140+
t.Parallel()
141+
142+
resource.Test(t, resource.TestCase{
143+
ProviderFactories: coderFactory(),
144+
IsUnitTest: true,
145+
Steps: []resource.TestStep{{
146+
Config: `
147+
provider "coder" {
148+
}
149+
resource "coder_ai_task" "test" {
150+
}
151+
`,
152+
ExpectError: regexp.MustCompile(`'app_id' must be set`),
79153
}},
80154
})
81155
})

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)