diff --git a/docs/data-sources/task.md b/docs/data-sources/task.md new file mode 100644 index 00000000..43396eae --- /dev/null +++ b/docs/data-sources/task.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coder_task Data Source - terraform-provider-coder" +subcategory: "" +description: |- + Use this data source to read information about Coder Tasks. +--- + +# coder_task (Data Source) + +Use this data source to read information about Coder Tasks. + +## Example Usage + +```terraform +provider "coder" {} + +data "coder_workspace" "me" {} +data "coder_task" "me" {} + +resource "coder_ai_task" "task" { + count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0 + app_id = module.example-agent.task_app_id +} + +module "example-agent" { + count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0 + prompt = data.coder_ai_task.me.prompt +} +``` + + +## Schema + +### Read-Only + +- `enabled` (Boolean) True when executing in a Coder Task context, false when in a Coder Workspace context. + + -> The `enabled` field is only populated in Coder v2.28 and later. +- `id` (String) The UUID of the task, if executing in a Coder Task context. Empty in a Coder Workspace context. +- `prompt` (String) The prompt text provided to the task by Coder, if executing in a Coder Task context. Empty in a Coder Workspace context. + + -> The `prompt` field is only populated in Coder v2.28 and later. diff --git a/examples/data-sources/coder_task/data-source.tf b/examples/data-sources/coder_task/data-source.tf new file mode 100644 index 00000000..af2098e1 --- /dev/null +++ b/examples/data-sources/coder_task/data-source.tf @@ -0,0 +1,14 @@ +provider "coder" {} + +data "coder_workspace" "me" {} +data "coder_task" "me" {} + +resource "coder_ai_task" "task" { + count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0 + app_id = module.example-agent.task_app_id +} + +module "example-agent" { + count = data.coder_task.me.enabled ? data.coder_workspace.me.start_count : 0 + prompt = data.coder_ai_task.me.prompt +} diff --git a/integration/coder-ai-task/main.tf b/integration/coder-ai-task/main.tf index aed9a796..50e5289d 100644 --- a/integration/coder-ai-task/main.tf +++ b/integration/coder-ai-task/main.tf @@ -32,6 +32,8 @@ data "coder_parameter" "ai_prompt" { mutable = true } +data "coder_task" "me" {} + resource "coder_ai_task" "task" { sidebar_app { id = coder_app.ai_interface.id @@ -46,6 +48,10 @@ locals { "ai_task.prompt" = coder_ai_task.task.prompt "ai_task.enabled" = tostring(coder_ai_task.task.enabled) "app.id" = coder_app.ai_interface.id + + "task.id" = data.coder_task.me.id + "task.prompt" = data.coder_task.me.prompt + "task.enabled" = tostring(data.coder_task.me.enabled) } } diff --git a/integration/integration_test.go b/integration/integration_test.go index c86397da..bc6fac96 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -220,10 +220,12 @@ func TestIntegration(t *testing.T) { "ai_task.app_id": `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, "ai_task.enabled": "false", "app.id": `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, + "task.id": `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, + "task.prompt": "", + "task.enabled": "false", }, }, } { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if coderVersion != "latest" && semver.Compare(coderVersion, tt.minVersion) < 0 { diff --git a/provider/ai_task.go b/provider/ai_task.go index 01baf9ea..281299b3 100644 --- a/provider/ai_task.go +++ b/provider/ai_task.go @@ -115,3 +115,43 @@ func aiTaskResource() *schema.Resource { }, } } + +func taskDatasource() *schema.Resource { + return &schema.Resource{ + Description: "Use this data source to read information about Coder Tasks.", + ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { + diags := diag.Diagnostics{} + + idStr := os.Getenv("CODER_TASK_ID") + if idStr == "" || idStr == uuid.Nil.String() { + rd.SetId(uuid.NewString()) + _ = rd.Set("enabled", false) + } else if _, err := uuid.Parse(idStr); err == nil { + rd.SetId(idStr) + _ = rd.Set("enabled", true) + } else { // invalid UUID + diags = append(diags, errorAsDiagnostics(err)...) + } + + _ = rd.Set("prompt", os.Getenv("CODER_TASK_PROMPT")) + return diags + }, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The UUID of the task, if executing in a Coder Task context. Empty in a Coder Workspace context.", + }, + "prompt": { + Type: schema.TypeString, + Computed: true, + 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.", + }, + "enabled": { + Type: schema.TypeBool, + Computed: true, + 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.", + }, + }, + } +} diff --git a/provider/ai_task_test.go b/provider/ai_task_test.go index 3108fb9d..a67ba060 100644 --- a/provider/ai_task_test.go +++ b/provider/ai_task_test.go @@ -4,6 +4,7 @@ import ( "regexp" "testing" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" @@ -214,3 +215,83 @@ func TestAITask(t *testing.T) { }) }) } + +func TestTaskDatasource(t *testing.T) { + t.Run("Exists", func(t *testing.T) { + t.Setenv("CODER_TASK_ID", "7d8d4c2e-fb57-44f9-a183-22509819c2e7") + t.Setenv("CODER_TASK_PROMPT", "some task prompt") + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_task" "me" {} + `, + Check: func(s *terraform.State) error { + require.Len(t, s.Modules, 1) + require.Len(t, s.Modules[0].Resources, 1) + resource := s.Modules[0].Resources["data.coder_task.me"] + require.NotNil(t, resource) + + taskID := resource.Primary.Attributes["id"] + require.Equal(t, "7d8d4c2e-fb57-44f9-a183-22509819c2e7", taskID) + + taskPromptValue := resource.Primary.Attributes["prompt"] + require.Equal(t, "some task prompt", taskPromptValue) + + enabledValue := resource.Primary.Attributes["enabled"] + require.Equal(t, "true", enabledValue) + return nil + }, + }}, + }) + }) + + t.Run("NotExists", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_task" "me" {} + `, + Check: func(s *terraform.State) error { + require.Len(t, s.Modules, 1) + require.Len(t, s.Modules[0].Resources, 1) + resource := s.Modules[0].Resources["data.coder_task.me"] + require.NotNil(t, resource) + + taskID := resource.Primary.Attributes["id"] + require.NotEmpty(t, taskID) + require.NotEqual(t, uuid.Nil.String(), taskID) + _, err := uuid.Parse(taskID) + require.NoError(t, err) + + taskPromptValue := resource.Primary.Attributes["prompt"] + require.Empty(t, taskPromptValue) + + enabledValue := resource.Primary.Attributes["enabled"] + require.Equal(t, "false", enabledValue) + return nil + }, + }}, + }) + }) + + t.Run("InvalidTaskID", func(t *testing.T) { + t.Setenv("CODER_TASK_ID", "not a valid UUID") + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + data "coder_task" "me" {} + `, + ExpectError: regexp.MustCompile(`invalid UUID`), + }}, + }) + }) +} diff --git a/provider/helpers/validation.go b/provider/helpers/validation.go index 9cc21b89..e58a3b03 100644 --- a/provider/helpers/validation.go +++ b/provider/helpers/validation.go @@ -17,6 +17,6 @@ func ValidateURL(value any, label string) ([]string, []error) { if _, err := url.Parse(val); err != nil { return nil, []error{err} } - + return nil, nil } diff --git a/provider/provider.go b/provider/provider.go index 2b6409ba..7e4451b8 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -64,6 +64,7 @@ func New() *schema.Provider { "coder_external_auth": externalAuthDataSource(), "coder_workspace_owner": workspaceOwnerDataSource(), "coder_workspace_preset": workspacePresetDataSource(), + "coder_task": taskDatasource(), }, ResourcesMap: map[string]*schema.Resource{ "coder_agent": agentResource(), diff --git a/provider/provider_test.go b/provider/provider_test.go index 4bf98b32..606ae72b 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -37,7 +37,8 @@ func TestProviderEmpty(t *testing.T) { } data "coder_parameter" "param" { name = "hey" - }`, + } + data "coder_task" "me" {}`, Check: func(state *terraform.State) error { return nil }, diff --git a/provider/script_test.go b/provider/script_test.go index 64808372..8e6221f1 100644 --- a/provider/script_test.go +++ b/provider/script_test.go @@ -131,10 +131,10 @@ func TestValidateCronExpression(t *testing.T) { t.Parallel() tests := []struct { - name string - cronExpr string - expectWarnings bool - expectErrors bool + name string + cronExpr string + expectWarnings bool + expectErrors bool warningContains string }{ {