diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d52ff5cd..8481aa8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Check Go Versions run: ./scripts/check_go_version.sh @@ -28,7 +28,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.24.2" + go-version: "1.24.6" id: go - name: Get dependencies @@ -75,7 +75,7 @@ jobs: needs: test steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Check Go Versions run: ./scripts/check_go_version.sh @@ -86,7 +86,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.24.2" + go-version: "1.24.6" - name: Import GPG key id: import_gpg @@ -97,7 +97,7 @@ jobs: passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6.3.0 + uses: goreleaser/goreleaser-action@v6.4.0 with: version: '~> v2' args: release --clean diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d93f59f7..0141f75e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: timeout-minutes: 5 steps: - name: Check out code into the Go module directory - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Check Go Versions run: ./scripts/check_go_version.sh @@ -28,7 +28,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.24.2" + go-version: "1.24.6" id: go - name: Get dependencies @@ -96,7 +96,7 @@ jobs: - "1.11.*" steps: - name: Check out code into the Go module directory - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Check Go Versions run: ./scripts/check_go_version.sh @@ -104,7 +104,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.24.2" + go-version: "1.24.6" id: go - uses: hashicorp/setup-terraform@v3 @@ -129,7 +129,7 @@ jobs: timeout-minutes: 5 steps: - name: Check out code into the Go module directory - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Check Go Versions run: ./scripts/check_go_version.sh @@ -137,7 +137,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.24.2" + go-version: "1.24.6" id: go - uses: hashicorp/setup-terraform@v3 diff --git a/README.md b/README.md index 4ae9be15..cff57558 100644 --- a/README.md +++ b/README.md @@ -87,15 +87,21 @@ To run these integration tests locally: > For example, `CODER_IMAGE=example.com/repo/coder CODER_VERSION=foobar make test-integration`. ### How to create a new release + > [!Warning] -> Before creating a new release, make sure you have pulled the latest commit from the main branch i.e. `git pull origin main` +> +> - Before creating a new release, make sure you have pulled the latest commit from the main branch i.e. `git pull origin main` +> - If you have already published a previous tag, make sure you do not tag the same commit with the new tag. +> See: [goreleaser-action/512](https://github.com/goreleaser/goreleaser-action/issues/512) 1. Create a new tag with a version number (following semantic versioning): + ```console git tag -a v2.1.2 -m "v2.1.2" ``` 2. Push the tag to the remote repository: + ```console git push origin tag v2.1.2 ``` diff --git a/docs/data-sources/external_auth.md b/docs/data-sources/external_auth.md index d1e6d649..4337301b 100644 --- a/docs/data-sources/external_auth.md +++ b/docs/data-sources/external_auth.md @@ -21,7 +21,7 @@ data "coder_external_auth" "github" { } data "coder_external_auth" "azure-identity" { - id = "azure-identiy" + id = "azure-identity" optional = true } ``` 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/docs/resources/ai_task.md b/docs/resources/ai_task.md index bd5cfcdd..3619c680 100644 --- a/docs/resources/ai_task.md +++ b/docs/resources/ai_task.md @@ -22,9 +22,14 @@ Use this resource to define Coder tasks. ### 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) A unique identifier for this resource. - `prompt` (String) The prompt text provided to the task by Coder. + -> The `prompt` field is only populated in Coder v2.28 and later. + ### Nested Schema for `sidebar_app` diff --git a/docs/resources/app.md b/docs/resources/app.md index 35a9951b..67efb5fc 100644 --- a/docs/resources/app.md +++ b/docs/resources/app.md @@ -61,7 +61,7 @@ resource "coder_app" "vim" { ### Optional -- `command` (String) A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either `command` or `url` may be specified, but not both. +- `command` (String) A command to run in a terminal opening this app. In the web, this will open in a new tab. In the CLI, this will SSH and execute the command. Either `command` or `url` may be specified, but not both. Conflicts with `subdomain`. - `display_name` (String) A display name to identify the app. Defaults to the slug. - `external` (Boolean) Specifies whether `url` is opened on the client machine instead of proxied through the workspace. - `group` (String) The name of a group that this app belongs to. diff --git a/examples/data-sources/coder_external_auth/data-source.tf b/examples/data-sources/coder_external_auth/data-source.tf index 330ff216..06436086 100644 --- a/examples/data-sources/coder_external_auth/data-source.tf +++ b/examples/data-sources/coder_external_auth/data-source.tf @@ -6,6 +6,6 @@ data "coder_external_auth" "github" { } data "coder_external_auth" "azure-identity" { - id = "azure-identiy" + id = "azure-identity" optional = true } 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/go.mod b/go.mod index bee6e849..30280981 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,19 @@ module github.com/coder/terraform-provider-coder/v2 -go 1.24.2 +go 1.24.6 require ( github.com/docker/docker v26.1.5+incompatible github.com/google/uuid v1.6.0 github.com/hashicorp/go-cty v1.5.0 - github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 github.com/masterminds/semver v1.5.0 github.com/mitchellh/mapstructure v1.5.0 github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.11.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 - golang.org/x/mod v0.29.0 + golang.org/x/mod v0.30.0 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 ) @@ -78,13 +78,13 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/tools v0.38.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/grpc v1.75.1 // indirect diff --git a/go.sum b/go.sum index c4d9af40..5bd42620 100644 --- a/go.sum +++ b/go.sum @@ -107,8 +107,8 @@ github.com/hashicorp/terraform-json v0.27.1 h1:zWhEracxJW6lcjt/JvximOYyc12pS/gaK github.com/hashicorp/terraform-json v0.27.1/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU= github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM= -github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= -github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= +github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 h1:mlAq/OrMlg04IuJT7NpefI1wwtdpWudnEmjuQs04t/4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1/go.mod h1:GQhpKVvvuwzD79e8/NZ+xzj+ZpWovdPAe8nfV/skwNU= github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= @@ -226,30 +226,30 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -264,16 +264,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -281,8 +281,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/integration/coder-ai-task/main.tf b/integration/coder-ai-task/main.tf index 263d7298..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 @@ -41,10 +43,15 @@ resource "coder_ai_task" "task" { locals { # NOTE: these must all be strings in the output output = { - "ai_task.id" = coder_ai_task.task.id - "ai_task.app_id" = coder_ai_task.task.app_id - "ai_task.prompt" = coder_ai_task.task.prompt - "app.id" = coder_app.ai_interface.id + "ai_task.id" = coder_ai_task.task.id + "ai_task.app_id" = coder_ai_task.task.app_id + "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 4ff35e06..bc6fac96 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -43,7 +43,7 @@ func TestIntegration(t *testing.T) { coderImg := os.Getenv("CODER_IMAGE") if coderImg == "" { - coderImg = "ghcr.io/coder/coder" + coderImg = "ghcr.io/coder/coder-preview" } coderVersion := os.Getenv("CODER_VERSION") @@ -215,14 +215,17 @@ func TestIntegration(t *testing.T) { name: "coder-ai-task", minVersion: "v2.26.0", expectedOutput: map[string]string{ - "ai_task.id": `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`, - "ai_task.prompt": "default", - "ai_task.app_id": `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`, - "app.id": `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`, + "ai_task.id": `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, + "ai_task.prompt": "", + "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 6ceda464..1bb6cf65 100644 --- a/provider/ai_task.go +++ b/provider/ai_task.go @@ -32,21 +32,16 @@ func aiTaskResource() *schema.Resource { CreateContext: func(c context.Context, resourceData *schema.ResourceData, i any) diag.Diagnostics { var diags diag.Diagnostics - if idStr := os.Getenv("CODER_TASK_ID"); idStr != "" { - resourceData.SetId(idStr) + if id, err := uuid.Parse(os.Getenv("CODER_TASK_ID")); err == nil && id != uuid.Nil { + resourceData.SetId(id.String()) + resourceData.Set("enabled", true) } else { resourceData.SetId(uuid.NewString()) - - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "`CODER_TASK_ID` should be set. If you are seeing this message, the version of the Coder Terraform provider you are using is likely too new for your current Coder version.", - }) + resourceData.Set("enabled", false) } if prompt := os.Getenv("CODER_TASK_PROMPT"); prompt != "" { resourceData.Set("prompt", prompt) - } else { - resourceData.Set("prompt", "default") } var ( @@ -100,7 +95,7 @@ func aiTaskResource() *schema.Resource { }, "prompt": { Type: schema.TypeString, - Description: "The prompt text provided to the task by Coder.", + Description: "The prompt text provided to the task by Coder.\n\n -> The `prompt` field is only populated in Coder v2.28 and later.", Computed: true, }, "app_id": { @@ -112,6 +107,51 @@ func aiTaskResource() *schema.Resource { ValidateFunc: validation.IsUUID, ConflictsWith: []string{"sidebar_app"}, }, + "enabled": { + Type: schema.TypeBool, + 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.", + Computed: true, + }, + }, + } +} + +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 d7357646..a67ba060 100644 --- a/provider/ai_task_test.go +++ b/provider/ai_task_test.go @@ -4,11 +4,68 @@ 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" ) +func TestAITask_Enabled(t *testing.T) { + t.Run("EnabledWhenTask", func(t *testing.T) { + t.Setenv("CODER_TASK_ID", "7d8d4c2e-fb57-44f9-a183-22509819c2e7") + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + resource "coder_ai_task" "test" { + app_id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc" + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + resource := state.Modules[0].Resources["coder_ai_task.test"] + require.NotNil(t, resource) + + require.Equal(t, "true", resource.Primary.Attributes["enabled"]) + + return nil + }, + }}, + }) + }) + + t.Run("DisabledWhenWorkspace", func(t *testing.T) { + t.Setenv("CODER_TASK_ID", "") + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" { + } + resource "coder_ai_task" "test" { + app_id = "9a3ff7b4-4b3f-48c6-8d3a-a8118ac921fc" + } + `, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + resource := state.Modules[0].Resources["coder_ai_task.test"] + require.NotNil(t, resource) + + require.Equal(t, "false", resource.Primary.Attributes["enabled"]) + + return nil + }, + }}, + }) + }) +} + func TestAITask(t *testing.T) { t.Setenv("CODER_TASK_ID", "7d8d4c2e-fb57-44f9-a183-22509819c2e7") t.Setenv("CODER_TASK_PROMPT", "some task prompt") @@ -35,6 +92,7 @@ func TestAITask(t *testing.T) { "id", "prompt", "app_id", + "enabled", } { value := resource.Primary.Attributes[key] require.NotNil(t, value) @@ -97,6 +155,7 @@ func TestAITask(t *testing.T) { "id", "prompt", "app_id", + "enabled", } { value := resource.Primary.Attributes[key] require.NotNil(t, value) @@ -156,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/app.go b/provider/app.go index dd0428a1..fb567573 100644 --- a/provider/app.go +++ b/provider/app.go @@ -85,8 +85,9 @@ func appResource() *schema.Resource { Type: schema.TypeString, Description: "A command to run in a terminal opening this app. In the web, " + "this will open in a new tab. In the CLI, this will SSH and execute the command. " + - "Either `command` or `url` may be specified, but not both.", - ConflictsWith: []string{"url"}, + "Either `command` or `url` may be specified, but not both. " + + "Conflicts with `subdomain`.", + ConflictsWith: []string{"url", "subdomain"}, Optional: true, ForceNew: true, }, diff --git a/provider/app_test.go b/provider/app_test.go index b8d4c8e7..17b3dce4 100644 --- a/provider/app_test.go +++ b/provider/app_test.go @@ -109,25 +109,6 @@ func TestApp(t *testing.T) { } `, external: true, - }, { - name: "ConflictsWithSubdomain", - config: ` - provider "coder" {} - resource "coder_agent" "dev" { - os = "linux" - arch = "amd64" - } - resource "coder_app" "test" { - agent_id = coder_agent.dev.id - slug = "test" - display_name = "Testing" - url = "https://google.com" - external = true - subdomain = true - open_in = "slim-window" - } - `, - expectError: regexp.MustCompile("conflicts with subdomain"), }} for _, tc := range cases { tc := tc @@ -564,8 +545,6 @@ func TestApp(t *testing.T) { } for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { t.Parallel() @@ -596,4 +575,133 @@ func TestApp(t *testing.T) { }) } }) + + t.Run("ConflictsWith", func(t *testing.T) { + t.Parallel() + + type healthcheck struct { + url string + interval int + threshold int + } + + dummyURL := "https://google.com" + dummyCommand := "read -p \\\"Workspace spawned. Press enter to continue...\\\"" + dummyExternal := true + dummySubdomain := true + dummyHealthcheck := healthcheck{ + url: "https://google.com", + interval: 5, + threshold: 6, + } + dummyShare := "owner" + + cases := []struct { + name string + url string + command string + subdomain bool + healthcheck healthcheck + external bool + share string + expectError *regexp.Regexp + }{ + { + name: "CommandAndSubdomain", + command: dummyCommand, + subdomain: dummySubdomain, + expectError: regexp.MustCompile("conflicts with subdomain"), + }, + { + name: "URLAndCommand", + url: dummyURL, + command: dummyCommand, + expectError: regexp.MustCompile("conflicts with command"), + }, + { + name: "HealthcheckAndCommand", + healthcheck: dummyHealthcheck, + command: dummyCommand, + expectError: regexp.MustCompile("conflicts with command"), + }, + { + name: "ExternalAndHealthcheck", + external: dummyExternal, + healthcheck: dummyHealthcheck, + expectError: regexp.MustCompile("conflicts with healthcheck"), + }, + { + name: "ExternalAndCommand", + external: dummyExternal, + command: dummyCommand, + expectError: regexp.MustCompile("conflicts with command"), + }, + { + name: "ExternalAndSubdomain", + external: dummyExternal, + subdomain: dummySubdomain, + expectError: regexp.MustCompile("conflicts with subdomain"), + }, + { + name: "ExternalAndShare", + external: dummyExternal, + share: dummyShare, + expectError: regexp.MustCompile("conflicts with share"), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + extraLines := []string{} + if c.command != "" { + extraLines = append(extraLines, fmt.Sprintf("command = %q", c.command)) + } + if c.subdomain { + extraLines = append(extraLines, "subdomain = true") + } + if c.external { + extraLines = append(extraLines, "external = true") + } + if c.url != "" { + extraLines = append(extraLines, fmt.Sprintf("url = %q", c.url)) + } + if c.healthcheck != (healthcheck{}) { + extraLines = append(extraLines, fmt.Sprintf(`healthcheck { + url = %q + interval = %d + threshold = %d + }`, c.healthcheck.url, c.healthcheck.interval, c.healthcheck.threshold)) + } + if c.share != "" { + extraLines = append(extraLines, fmt.Sprintf("share = %q", c.share)) + } + + config := fmt.Sprintf(` + provider "coder" {} + resource "coder_agent" "dev" { + os = "linux" + arch = "amd64" + } + resource "coder_app" "code-server" { + agent_id = coder_agent.dev.id + slug = "code-server" + display_name = "Testing" + open_in = "slim-window" + %s + } + `, strings.Join(extraLines, "\n ")) + + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: config, + ExpectError: c.expectError, + }}, + }) + }) + } + }) } 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 }{ {