mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TemplateBuilderVariableType enumerates the variable types
|
||||
@@ -124,3 +126,37 @@ func (c *Client) TemplateBuilderCompose(ctx context.Context, req TemplateBuilder
|
||||
}
|
||||
return io.ReadAll(res.Body)
|
||||
}
|
||||
|
||||
// TemplateBuilderCreateTemplateRequest is the request body for
|
||||
// POST /api/v2/templatebuilder/compose/template.
|
||||
type TemplateBuilderCreateTemplateRequest struct {
|
||||
BaseTemplateID string `json:"base_template_id"`
|
||||
Modules []TemplateBuilderComposeModule `json:"modules"`
|
||||
OrganizationID uuid.UUID `json:"organization_id" format:"uuid" validate:"required"`
|
||||
Name string `json:"name" validate:"required,template_name"`
|
||||
DisplayName string `json:"display_name,omitempty" validate:"template_display_name"`
|
||||
Description string `json:"description,omitempty" validate:"lt=128"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
ProvisionerTags map[string]string `json:"provisioner_tags,omitempty"`
|
||||
}
|
||||
|
||||
// TemplateBuilderCreateTemplateResponse is the response body for
|
||||
// POST /api/v2/templatebuilder/compose/template.
|
||||
type TemplateBuilderCreateTemplateResponse struct {
|
||||
Template Template `json:"template"`
|
||||
}
|
||||
|
||||
// TemplateBuilderCreateTemplate composes a template from a base and modules,
|
||||
// validates it via a provisioner import job, and creates the template.
|
||||
func (c *Client) TemplateBuilderCreateTemplate(ctx context.Context, req TemplateBuilderCreateTemplateRequest) (TemplateBuilderCreateTemplateResponse, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost, "/api/v2/templatebuilder/compose/template", req)
|
||||
if err != nil {
|
||||
return TemplateBuilderCreateTemplateResponse{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return TemplateBuilderCreateTemplateResponse{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp TemplateBuilderCreateTemplateResponse
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user