Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/data-sources/task.md
Original file line number Diff line number Diff line change
@@ -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 generated by tfplugindocs -->
## 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.
14 changes: 14 additions & 0 deletions examples/data-sources/coder_task/data-source.tf
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 6 additions & 0 deletions integration/coder-ai-task/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}

Expand Down
4 changes: 3 additions & 1 deletion integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
40 changes: 40 additions & 0 deletions provider/ai_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we set optional true, is that possible for computed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should according to the documentation for Computed:

If Required and Optional are both false, the attribute will be considered "read only" for the practitioner, with only the provider able to set its value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah TIL, I had assumed computed already meant read-only.

},
"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.",
},
},
}
}
81 changes: 81 additions & 0 deletions provider/ai_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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`),
}},
})
})
}
2 changes: 1 addition & 1 deletion provider/helpers/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
3 changes: 2 additions & 1 deletion provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
8 changes: 4 additions & 4 deletions provider/script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +134 to +137
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by go fmt

warningContains string
}{
{
Expand Down