Files
coder/coderd/templatebuilder/errors_test.go
T
Jeremy Ruppel de31c7c18e feat: add TemplateBuilderCreateTemplate SDK types and client method (#26360)
Adds `POST /api/v2/templatebuilder/compose/template`, a synchronous
endpoint that composes a template from a base and modules, validates it
via a provisioner import job, and creates the template in a single
request.

The handler composes terraform files, bundles them as a tar, inserts the
file with hash-based dedup, creates a template version with an import
job, waits up to 2 minutes for the job to complete, classifies errors
for known failure modes (network-unreachable registry, DNS failures),
then creates the template on success. Canceled and failed jobs return
appropriate error responses.

Also adds `hclwrite.Format` to composed terraform output for canonical
HCL formatting.

Closes https://linear.app/codercom/issue/DEVEX-279

<details>
<summary>Implementation notes</summary>

- SDK types and client method in `codersdk/templatebuilder.go` with
validation tags matching the standard template creation path
(`template_display_name`, `lt=128`)
- `ClassifyProvisionerError` in `coderd/templatebuilder/errors.go`
detects DNS, connection refused, i/o timeout, and TLS handshake failures
and returns actionable messages
- `waitForProvisionerJob` polls with a ramp-up interval schedule (100ms,
200ms, 500ms, then 1s steady) and accepts an `onUpdate` callback for
future SSE streaming
- Audit logging for both template and template version creation
- TOCTOU name uniqueness: early check for fast feedback, DB unique
constraint catch for the race window (returns 409, not 500)
- Swagger annotations for all error responses (400, 404, 409, 504)

</details>

> 🤖 Generated by Coder Agents
2026-06-15 18:12:55 -04:00

73 lines
1.7 KiB
Go

package templatebuilder_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/templatebuilder"
)
func TestClassifyProvisionerError(t *testing.T) {
t.Parallel()
tests := []struct {
name string
jobError string
logs []string
contains string
exact bool
}{
{
name: "DNSFailure",
jobError: "init failed",
logs: []string{"Error: Failed to query available provider packages", "dial tcp: lookup registry.terraform.io: no such host"},
contains: "unreachable from your provisioner",
},
{
name: "ConnectionRefused",
jobError: "terraform init: connection refused",
logs: nil,
contains: "unreachable from your provisioner",
},
{
name: "IOTimeout",
jobError: "context deadline exceeded",
logs: []string{"dial tcp 1.2.3.4:443: i/o timeout"},
contains: "unreachable from your provisioner",
},
{
name: "TLSTimeout",
jobError: "init error",
logs: []string{"net/http: TLS handshake timeout"},
contains: "unreachable from your provisioner",
},
{
name: "UnknownError",
jobError: "Error: Unsupported block type",
logs: []string{"on main.tf line 5"},
contains: "Unsupported block type",
exact: true,
},
{
name: "EmptyErrorPassthrough",
jobError: "",
logs: nil,
contains: "",
exact: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
result := templatebuilder.ClassifyProvisionerError(tc.jobError, tc.logs)
if tc.exact {
require.Equal(t, tc.jobError, result)
} else {
require.Contains(t, result, tc.contains)
}
})
}
}