Skip to content

Commit 5fe8d85

Browse files
authored
feat: add coder_task data source (#460)
Adds a `coder_task` data source to support use cases that would otherwise cause a cycle error.
1 parent e0b1ec1 commit 5fe8d85

File tree

10 files changed

+195
-7
lines changed

10 files changed

+195
-7
lines changed

docs/data-sources/task.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "coder_task Data Source - terraform-provider-coder"
4+
subcategory: ""
5+
description: |-
6+
Use this data source to read information about Coder Tasks.
7+
---
8+
9+
# coder_task (Data Source)
10+
11+
Use this data source to read information about Coder Tasks.
12+
13+
## Example Usage
14+
15+
```terraform
16+
provider "coder" {}
17+
18+
data "coder_workspace" "me" {}
19+
data "coder_task" "me" {}
20+
21+
resource "coder_ai_task" "task" {
22+
count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0
23+
app_id = module.example-agent.task_app_id
24+
}
25+
26+
module "example-agent" {
27+
count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0
28+
prompt = data.coder_ai_task.me.prompt
29+
}
30+
```
31+
32+
<!-- schema generated by tfplugindocs -->
33+
## Schema
34+
35+
### Read-Only
36+
37+
- `enabled` (Boolean) True when executing in a Coder Task context, false when in a Coder Workspace context.
38+
39+
-> The `enabled` field is only populated in Coder v2.28 and later.
40+
- `id` (String) The UUID of the task, if executing in a Coder Task context. Empty in a Coder Workspace context.
41+
- `prompt` (String) The prompt text provided to the task by Coder, if executing in a Coder Task context. Empty in a Coder Workspace context.
42+
43+
-> The `prompt` field is only populated in Coder v2.28 and later.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
provider "coder" {}
2+
3+
data "coder_workspace" "me" {}
4+
data "coder_task" "me" {}
5+
6+
resource "coder_ai_task" "task" {
7+
count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0
8+
app_id = module.example-agent.task_app_id
9+
}
10+
11+
module "example-agent" {
12+
count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0
13+
prompt = data.coder_ai_task.me.prompt
14+
}

integration/coder-ai-task/main.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ data "coder_parameter" "ai_prompt" {
3232
mutable = true
3333
}
3434

35+
data "coder_task" "me" {}
36+
3537
resource "coder_ai_task" "task" {
3638
sidebar_app {
3739
id = coder_app.ai_interface.id
@@ -46,6 +48,10 @@ locals {
4648
"ai_task.prompt" = coder_ai_task.task.prompt
4749
"ai_task.enabled" = tostring(coder_ai_task.task.enabled)
4850
"app.id" = coder_app.ai_interface.id
51+
52+
"task.id" = data.coder_task.me.id
53+
"task.prompt" = data.coder_task.me.prompt
54+
"task.enabled" = tostring(data.coder_task.me.enabled)
4955
}
5056
}
5157

integration/integration_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,12 @@ func TestIntegration(t *testing.T) {
220220
"ai_task.app_id": `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`,
221221
"ai_task.enabled": "false",
222222
"app.id": `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`,
223+
"task.id": `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`,
224+
"task.prompt": "",
225+
"task.enabled": "false",
223226
},
224227
},
225228
} {
226-
tt := tt
227229
t.Run(tt.name, func(t *testing.T) {
228230
t.Parallel()
229231
if coderVersion != "latest" && semver.Compare(coderVersion, tt.minVersion) < 0 {

provider/ai_task.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,43 @@ func aiTaskResource() *schema.Resource {
115115
},
116116
}
117117
}
118+
119+
func taskDatasource() *schema.Resource {
120+
return &schema.Resource{
121+
Description: "Use this data source to read information about Coder Tasks.",
122+
ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics {
123+
diags := diag.Diagnostics{}
124+
125+
idStr := os.Getenv("CODER_TASK_ID")
126+
if idStr == "" || idStr == uuid.Nil.String() {
127+
rd.SetId(uuid.NewString())
128+
_ = rd.Set("enabled", false)
129+
} else if _, err := uuid.Parse(idStr); err == nil {
130+
rd.SetId(idStr)
131+
_ = rd.Set("enabled", true)
132+
} else { // invalid UUID
133+
diags = append(diags, errorAsDiagnostics(err)...)
134+
}
135+
136+
_ = rd.Set("prompt", os.Getenv("CODER_TASK_PROMPT"))
137+
return diags
138+
},
139+
Schema: map[string]*schema.Schema{
140+
"id": {
141+
Type: schema.TypeString,
142+
Computed: true,
143+
Description: "The UUID of the task, if executing in a Coder Task context. Empty in a Coder Workspace context.",
144+
},
145+
"prompt": {
146+
Type: schema.TypeString,
147+
Computed: true,
148+
Description: "The prompt text provided to the task by Coder, if executing in a Coder Task context. Empty in a Coder Workspace context.\n\n -> The `prompt` field is only populated in Coder v2.28 and later.",
149+
},
150+
"enabled": {
151+
Type: schema.TypeBool,
152+
Computed: true,
153+
Description: "True when executing in a Coder Task context, false when in a Coder Workspace context.\n\n -> The `enabled` field is only populated in Coder v2.28 and later.",
154+
},
155+
},
156+
}
157+
}

provider/ai_task_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"regexp"
55
"testing"
66

7+
"github.com/google/uuid"
78
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
89
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
910
"github.com/stretchr/testify/require"
@@ -214,3 +215,83 @@ func TestAITask(t *testing.T) {
214215
})
215216
})
216217
}
218+
219+
func TestTaskDatasource(t *testing.T) {
220+
t.Run("Exists", func(t *testing.T) {
221+
t.Setenv("CODER_TASK_ID", "7d8d4c2e-fb57-44f9-a183-22509819c2e7")
222+
t.Setenv("CODER_TASK_PROMPT", "some task prompt")
223+
resource.Test(t, resource.TestCase{
224+
ProviderFactories: coderFactory(),
225+
IsUnitTest: true,
226+
Steps: []resource.TestStep{{
227+
Config: `
228+
provider "coder" {}
229+
data "coder_task" "me" {}
230+
`,
231+
Check: func(s *terraform.State) error {
232+
require.Len(t, s.Modules, 1)
233+
require.Len(t, s.Modules[0].Resources, 1)
234+
resource := s.Modules[0].Resources["data.coder_task.me"]
235+
require.NotNil(t, resource)
236+
237+
taskID := resource.Primary.Attributes["id"]
238+
require.Equal(t, "7d8d4c2e-fb57-44f9-a183-22509819c2e7", taskID)
239+
240+
taskPromptValue := resource.Primary.Attributes["prompt"]
241+
require.Equal(t, "some task prompt", taskPromptValue)
242+
243+
enabledValue := resource.Primary.Attributes["enabled"]
244+
require.Equal(t, "true", enabledValue)
245+
return nil
246+
},
247+
}},
248+
})
249+
})
250+
251+
t.Run("NotExists", func(t *testing.T) {
252+
resource.Test(t, resource.TestCase{
253+
ProviderFactories: coderFactory(),
254+
IsUnitTest: true,
255+
Steps: []resource.TestStep{{
256+
Config: `
257+
provider "coder" {}
258+
data "coder_task" "me" {}
259+
`,
260+
Check: func(s *terraform.State) error {
261+
require.Len(t, s.Modules, 1)
262+
require.Len(t, s.Modules[0].Resources, 1)
263+
resource := s.Modules[0].Resources["data.coder_task.me"]
264+
require.NotNil(t, resource)
265+
266+
taskID := resource.Primary.Attributes["id"]
267+
require.NotEmpty(t, taskID)
268+
require.NotEqual(t, uuid.Nil.String(), taskID)
269+
_, err := uuid.Parse(taskID)
270+
require.NoError(t, err)
271+
272+
taskPromptValue := resource.Primary.Attributes["prompt"]
273+
require.Empty(t, taskPromptValue)
274+
275+
enabledValue := resource.Primary.Attributes["enabled"]
276+
require.Equal(t, "false", enabledValue)
277+
return nil
278+
},
279+
}},
280+
})
281+
})
282+
283+
t.Run("InvalidTaskID", func(t *testing.T) {
284+
t.Setenv("CODER_TASK_ID", "not a valid UUID")
285+
resource.Test(t, resource.TestCase{
286+
ProviderFactories: coderFactory(),
287+
IsUnitTest: true,
288+
Steps: []resource.TestStep{{
289+
Config: `
290+
provider "coder" {}
291+
data "coder_task" "me" {}
292+
`,
293+
ExpectError: regexp.MustCompile(`invalid UUID`),
294+
}},
295+
})
296+
})
297+
}

provider/helpers/validation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ func ValidateURL(value any, label string) ([]string, []error) {
1717
if _, err := url.Parse(val); err != nil {
1818
return nil, []error{err}
1919
}
20-
20+
2121
return nil, nil
2222
}

provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func New() *schema.Provider {
6464
"coder_external_auth": externalAuthDataSource(),
6565
"coder_workspace_owner": workspaceOwnerDataSource(),
6666
"coder_workspace_preset": workspacePresetDataSource(),
67+
"coder_task": taskDatasource(),
6768
},
6869
ResourcesMap: map[string]*schema.Resource{
6970
"coder_agent": agentResource(),

provider/provider_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ func TestProviderEmpty(t *testing.T) {
3737
}
3838
data "coder_parameter" "param" {
3939
name = "hey"
40-
}`,
40+
}
41+
data "coder_task" "me" {}`,
4142
Check: func(state *terraform.State) error {
4243
return nil
4344
},

provider/script_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,10 @@ func TestValidateCronExpression(t *testing.T) {
131131
t.Parallel()
132132

133133
tests := []struct {
134-
name string
135-
cronExpr string
136-
expectWarnings bool
137-
expectErrors bool
134+
name string
135+
cronExpr string
136+
expectWarnings bool
137+
expectErrors bool
138138
warningContains string
139139
}{
140140
{

0 commit comments

Comments
 (0)