feat: add dynamic parameters websocket endpoint (#17165)

This commit is contained in:
ケイラ
2025-04-10 13:08:50 -07:00
committed by GitHub
parent c9682cb6cf
commit 859dd2fc3f
19 changed files with 2291 additions and 347 deletions
+2 -3
View File
@@ -9,9 +9,8 @@ import (
"github.com/spf13/afero/tarfs" "github.com/spf13/afero/tarfs"
) )
// FromTarReader creates a read-only in-memory FS
func FromTarReader(r io.Reader) fs.FS { func FromTarReader(r io.Reader) fs.FS {
tr := tar.NewReader(r) tr := tar.NewReader(r)
tfs := tarfs.New(tr) return afero.NewIOFS(tarfs.New(tr))
rofs := afero.NewReadOnlyFs(tfs)
return afero.NewIOFS(rofs)
} }
+30 -67
View File
@@ -5764,6 +5764,35 @@ const docTemplate = `{
} }
} }
}, },
"/templateversions/{templateversion}/dynamic-parameters": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Templates"
],
"summary": "Open dynamic parameters WebSocket by template version",
"operationId": "open-dynamic-parameters-websocket-by-template-version",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Template version ID",
"name": "templateversion",
"in": "path",
"required": true
}
],
"responses": {
"101": {
"description": "Switching Protocols"
}
}
}
},
"/templateversions/{templateversion}/external-auth": { "/templateversions/{templateversion}/external-auth": {
"get": { "get": {
"security": [ "security": [
@@ -11332,73 +11361,7 @@ const docTemplate = `{
} }
}, },
"codersdk.CreateTestAuditLogRequest": { "codersdk.CreateTestAuditLogRequest": {
"type": "object", "type": "object"
"properties": {
"action": {
"enum": [
"create",
"write",
"delete",
"start",
"stop"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.AuditAction"
}
]
},
"additional_fields": {
"type": "array",
"items": {
"type": "integer"
}
},
"build_reason": {
"enum": [
"autostart",
"autostop",
"initiator"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.BuildReason"
}
]
},
"organization_id": {
"type": "string",
"format": "uuid"
},
"request_id": {
"type": "string",
"format": "uuid"
},
"resource_id": {
"type": "string",
"format": "uuid"
},
"resource_type": {
"enum": [
"template",
"template_version",
"user",
"workspace",
"workspace_build",
"git_ssh_key",
"auditable_group"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.ResourceType"
}
]
},
"time": {
"type": "string",
"format": "date-time"
}
}
}, },
"codersdk.CreateTokenRequest": { "codersdk.CreateTokenRequest": {
"type": "object", "type": "object",
+28 -57
View File
@@ -5097,6 +5097,33 @@
} }
} }
}, },
"/templateversions/{templateversion}/dynamic-parameters": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": ["Templates"],
"summary": "Open dynamic parameters WebSocket by template version",
"operationId": "open-dynamic-parameters-websocket-by-template-version",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Template version ID",
"name": "templateversion",
"in": "path",
"required": true
}
],
"responses": {
"101": {
"description": "Switching Protocols"
}
}
}
},
"/templateversions/{templateversion}/external-auth": { "/templateversions/{templateversion}/external-auth": {
"get": { "get": {
"security": [ "security": [
@@ -10100,63 +10127,7 @@
} }
}, },
"codersdk.CreateTestAuditLogRequest": { "codersdk.CreateTestAuditLogRequest": {
"type": "object", "type": "object"
"properties": {
"action": {
"enum": ["create", "write", "delete", "start", "stop"],
"allOf": [
{
"$ref": "#/definitions/codersdk.AuditAction"
}
]
},
"additional_fields": {
"type": "array",
"items": {
"type": "integer"
}
},
"build_reason": {
"enum": ["autostart", "autostop", "initiator"],
"allOf": [
{
"$ref": "#/definitions/codersdk.BuildReason"
}
]
},
"organization_id": {
"type": "string",
"format": "uuid"
},
"request_id": {
"type": "string",
"format": "uuid"
},
"resource_id": {
"type": "string",
"format": "uuid"
},
"resource_type": {
"enum": [
"template",
"template_version",
"user",
"workspace",
"workspace_build",
"git_ssh_key",
"auditable_group"
],
"allOf": [
{
"$ref": "#/definitions/codersdk.ResourceType"
}
]
},
"time": {
"type": "string",
"format": "date-time"
}
}
}, },
"codersdk.CreateTokenRequest": { "codersdk.CreateTokenRequest": {
"type": "object", "type": "object",
+7
View File
@@ -43,6 +43,7 @@ import (
"github.com/coder/coder/v2/coderd/cryptokeys" "github.com/coder/coder/v2/coderd/cryptokeys"
"github.com/coder/coder/v2/coderd/entitlements" "github.com/coder/coder/v2/coderd/entitlements"
"github.com/coder/coder/v2/coderd/files"
"github.com/coder/coder/v2/coderd/idpsync" "github.com/coder/coder/v2/coderd/idpsync"
"github.com/coder/coder/v2/coderd/runtimeconfig" "github.com/coder/coder/v2/coderd/runtimeconfig"
"github.com/coder/coder/v2/coderd/webpush" "github.com/coder/coder/v2/coderd/webpush"
@@ -557,6 +558,7 @@ func New(options *Options) *API {
TemplateScheduleStore: options.TemplateScheduleStore, TemplateScheduleStore: options.TemplateScheduleStore,
UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore, UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore,
AccessControlStore: options.AccessControlStore, AccessControlStore: options.AccessControlStore,
FileCache: files.NewFromStore(options.Database),
Experiments: experiments, Experiments: experiments,
WebpushDispatcher: options.WebPushDispatcher, WebpushDispatcher: options.WebPushDispatcher,
healthCheckGroup: &singleflight.Group[string, *healthsdk.HealthcheckReport]{}, healthCheckGroup: &singleflight.Group[string, *healthsdk.HealthcheckReport]{},
@@ -1096,6 +1098,10 @@ func New(options *Options) *API {
// The idea is to return an empty [], so that the coder CLI won't get blocked accidentally. // The idea is to return an empty [], so that the coder CLI won't get blocked accidentally.
r.Get("/schema", templateVersionSchemaDeprecated) r.Get("/schema", templateVersionSchemaDeprecated)
r.Get("/parameters", templateVersionParametersDeprecated) r.Get("/parameters", templateVersionParametersDeprecated)
r.Group(func(r chi.Router) {
r.Use(httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentDynamicParameters))
r.Get("/dynamic-parameters", api.templateVersionDynamicParameters)
})
r.Get("/rich-parameters", api.templateVersionRichParameters) r.Get("/rich-parameters", api.templateVersionRichParameters)
r.Get("/external-auth", api.templateVersionExternalAuth) r.Get("/external-auth", api.templateVersionExternalAuth)
r.Get("/variables", api.templateVersionVariables) r.Get("/variables", api.templateVersionVariables)
@@ -1545,6 +1551,7 @@ type API struct {
// passed to dbauthz. // passed to dbauthz.
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore] AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
PortSharer atomic.Pointer[portsharing.PortSharer] PortSharer atomic.Pointer[portsharing.PortSharer]
FileCache files.Cache
UpdatesProvider tailnet.WorkspaceUpdatesProvider UpdatesProvider tailnet.WorkspaceUpdatesProvider
+137 -4
View File
@@ -35,10 +35,14 @@ import (
"github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/wsjson"
"github.com/coder/coder/v2/examples" "github.com/coder/coder/v2/examples"
"github.com/coder/coder/v2/provisioner/terraform/tfparse" "github.com/coder/coder/v2/provisioner/terraform/tfparse"
"github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk"
sdkproto "github.com/coder/coder/v2/provisionersdk/proto" sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/preview"
previewtypes "github.com/coder/preview/types"
"github.com/coder/websocket"
) )
// @Summary Get template version by ID // @Summary Get template version by ID
@@ -266,6 +270,135 @@ func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Reque
}) })
} }
// @Summary Open dynamic parameters WebSocket by template version
// @ID open-dynamic-parameters-websocket-by-template-version
// @Security CoderSessionToken
// @Tags Templates
// @Param templateversion path string true "Template version ID" format(uuid)
// @Success 101
// @Router /templateversions/{templateversion}/dynamic-parameters [get]
func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
templateVersion := httpmw.TemplateVersionParam(r)
// Check that the job has completed successfully
job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID)
if httpapi.Is404Error(err) {
httpapi.ResourceNotFound(rw)
return
}
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching provisioner job.",
Detail: err.Error(),
})
return
}
if !job.CompletedAt.Valid {
httpapi.Write(ctx, rw, http.StatusTooEarly, codersdk.Response{
Message: "Template version job has not finished",
})
return
}
// Having the Terraform plan available for the evaluation engine is helpful
// for populating values from data blocks, but isn't strictly required. If
// we don't have a cached plan available, we just use an empty one instead.
plan := json.RawMessage("{}")
tf, err := api.Database.GetTemplateVersionTerraformValues(ctx, templateVersion.ID)
if err == nil {
plan = tf.CachedPlan
}
input := preview.Input{
PlanJSON: plan,
ParameterValues: map[string]string{},
// TODO: write a db query that fetches all of the data needed to fill out
// this owner value
Owner: previewtypes.WorkspaceOwner{
Groups: []string{"Everyone"},
},
}
// nolint:gocritic // We need to fetch the templates files for the Terraform
// evaluator, and the user likely does not have permission.
fileCtx := dbauthz.AsProvisionerd(ctx)
fileID, err := api.Database.GetFileIDByTemplateVersionID(fileCtx, templateVersion.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error finding template version Terraform.",
Detail: err.Error(),
})
return
}
fs, err := api.FileCache.Acquire(fileCtx, fileID)
defer api.FileCache.Release(fileID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: "Internal error fetching template version Terraform.",
Detail: err.Error(),
})
return
}
conn, err := websocket.Accept(rw, r, nil)
if err != nil {
httpapi.Write(ctx, rw, http.StatusUpgradeRequired, codersdk.Response{
Message: "Failed to accept WebSocket.",
Detail: err.Error(),
})
return
}
stream := wsjson.NewStream[codersdk.DynamicParametersRequest, codersdk.DynamicParametersResponse](conn, websocket.MessageText, websocket.MessageText, api.Logger)
// Send an initial form state, computed without any user input.
result, diagnostics := preview.Preview(ctx, input, fs)
response := codersdk.DynamicParametersResponse{
ID: -1,
Diagnostics: previewtypes.Diagnostics(diagnostics),
}
if result != nil {
response.Parameters = result.Parameters
}
err = stream.Send(response)
if err != nil {
stream.Drop()
return
}
// As the user types into the form, reprocess the state using their input,
// and respond with updates.
updates := stream.Chan()
for {
select {
case <-ctx.Done():
stream.Close(websocket.StatusGoingAway)
return
case update, ok := <-updates:
if !ok {
// The connection has been closed, so there is no one to write to
return
}
input.ParameterValues = update.Inputs
result, diagnostics := preview.Preview(ctx, input, fs)
response := codersdk.DynamicParametersResponse{
ID: update.ID,
Diagnostics: previewtypes.Diagnostics(diagnostics),
}
if result != nil {
response.Parameters = result.Parameters
}
err = stream.Send(response)
if err != nil {
stream.Drop()
return
}
}
}
}
// @Summary Get rich parameters by template version // @Summary Get rich parameters by template version
// @ID get-rich-parameters-by-template-version // @ID get-rich-parameters-by-template-version
// @Security CoderSessionToken // @Security CoderSessionToken
@@ -287,8 +420,8 @@ func (api *API) templateVersionRichParameters(rw http.ResponseWriter, r *http.Re
return return
} }
if !job.CompletedAt.Valid { if !job.CompletedAt.Valid {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ httpapi.Write(ctx, rw, http.StatusTooEarly, codersdk.Response{
Message: "Job hasn't completed!", Message: "Template version job has not finished",
}) })
return return
} }
@@ -428,7 +561,7 @@ func (api *API) templateVersionVariables(rw http.ResponseWriter, r *http.Request
} }
if !job.CompletedAt.Valid { if !job.CompletedAt.Valid {
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
Message: "Job hasn't completed!", Message: "Template version job has not finished",
}) })
return return
} }
@@ -483,7 +616,7 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques
return return
} }
if !job.CompletedAt.Valid { if !job.CompletedAt.Valid {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ httpapi.Write(ctx, rw, http.StatusTooEarly, codersdk.Response{
Message: "Template version import job hasn't completed!", Message: "Template version import job hasn't completed!",
}) })
return return
+75 -11
View File
@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"net/http" "net/http"
"os"
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
@@ -27,6 +28,7 @@ import (
"github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil" "github.com/coder/coder/v2/testutil"
"github.com/coder/websocket"
) )
func TestTemplateVersion(t *testing.T) { func TestTemplateVersion(t *testing.T) {
@@ -1207,7 +1209,7 @@ func TestTemplateVersionDryRun(t *testing.T) {
_, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{}) _, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{})
var apiErr *codersdk.Error var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr) require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) require.Equal(t, http.StatusTooEarly, apiErr.StatusCode())
}) })
t.Run("Cancel", func(t *testing.T) { t.Run("Cancel", func(t *testing.T) {
@@ -2056,11 +2058,7 @@ func TestTemplateArchiveVersions(t *testing.T) {
// Create some unused versions // Create some unused versions
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
unused := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{ unused := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil, func(req *codersdk.CreateTemplateVersionRequest) {
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
}, func(req *codersdk.CreateTemplateVersionRequest) {
req.TemplateID = template.ID req.TemplateID = template.ID
}) })
expArchived = append(expArchived, unused.ID) expArchived = append(expArchived, unused.ID)
@@ -2069,11 +2067,7 @@ func TestTemplateArchiveVersions(t *testing.T) {
// Create some used template versions // Create some used template versions
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
used := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{ used := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil, func(req *codersdk.CreateTemplateVersionRequest) {
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
}, func(req *codersdk.CreateTemplateVersionRequest) {
req.TemplateID = template.ID req.TemplateID = template.ID
}) })
coderdtest.AwaitTemplateVersionJobCompleted(t, client, used.ID) coderdtest.AwaitTemplateVersionJobCompleted(t, client, used.ID)
@@ -2140,3 +2134,73 @@ func TestTemplateArchiveVersions(t *testing.T) {
require.NoError(t, err, "fetch all versions") require.NoError(t, err, "fetch all versions")
require.Len(t, remaining, totalVersions-len(expArchived)-len(allFailed)+1, "remaining versions") require.Len(t, remaining, totalVersions-len(expArchived)-len(allFailed)+1, "remaining versions")
} }
func TestTemplateVersionDynamicParameters(t *testing.T) {
t.Parallel()
cfg := coderdtest.DeploymentValues(t)
cfg.Experiments = []string{string(codersdk.ExperimentDynamicParameters)}
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, DeploymentValues: cfg})
owner := coderdtest.CreateFirstUser(t, ownerClient)
templateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
dynamicParametersTerraformSource, err := os.ReadFile("testdata/dynamicparameters/groups/main.tf")
require.NoError(t, err)
dynamicParametersTerraformPlan, err := os.ReadFile("testdata/dynamicparameters/groups/plan.json")
require.NoError(t, err)
files := echo.WithExtraFiles(map[string][]byte{
"main.tf": dynamicParametersTerraformSource,
})
files.ProvisionPlan = []*proto.Response{{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Plan: dynamicParametersTerraformPlan,
},
},
}}
version := coderdtest.CreateTemplateVersion(t, templateAdmin, owner.OrganizationID, files)
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, version.ID)
_ = coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID)
ctx := testutil.Context(t, testutil.WaitShort)
stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, version.ID)
require.NoError(t, err)
defer stream.Close(websocket.StatusGoingAway)
previews := stream.Chan()
// Should automatically send a form state with all defaulted/empty values
preview := testutil.RequireRecvCtx(ctx, t, previews)
require.Empty(t, preview.Diagnostics)
require.Equal(t, "group", preview.Parameters[0].Name)
require.True(t, preview.Parameters[0].Value.Valid())
require.Equal(t, "Everyone", preview.Parameters[0].Value.Value.AsString())
// Send a new value, and see it reflected
err = stream.Send(codersdk.DynamicParametersRequest{
ID: 1,
Inputs: map[string]string{"group": "Bloob"},
})
require.NoError(t, err)
preview = testutil.RequireRecvCtx(ctx, t, previews)
require.Equal(t, 1, preview.ID)
require.Empty(t, preview.Diagnostics)
require.Equal(t, "group", preview.Parameters[0].Name)
require.True(t, preview.Parameters[0].Value.Valid())
require.Equal(t, "Bloob", preview.Parameters[0].Value.Value.AsString())
// Back to default
err = stream.Send(codersdk.DynamicParametersRequest{
ID: 3,
Inputs: map[string]string{},
})
require.NoError(t, err)
preview = testutil.RequireRecvCtx(ctx, t, previews)
require.Equal(t, 3, preview.ID)
require.Empty(t, preview.Diagnostics)
require.Equal(t, "group", preview.Parameters[0].Name)
require.True(t, preview.Parameters[0].Value.Valid())
require.Equal(t, "Everyone", preview.Parameters[0].Value.Value.AsString())
}
+25
View File
@@ -0,0 +1,25 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
}
}
data "coder_workspace_owner" "me" {}
output "groups" {
value = data.coder_workspace_owner.me.groups
}
data "coder_parameter" "group" {
name = "group"
default = try(data.coder_workspace_owner.me.groups[0], "")
dynamic "option" {
for_each = data.coder_workspace_owner.me.groups
content {
name = option.value
value = option.value
}
}
}
+92
View File
@@ -0,0 +1,92 @@
{
"terraform_version": "1.11.2",
"format_version": "1.2",
"checks": [],
"complete": true,
"timestamp": "2025-04-02T01:29:59Z",
"variables": {},
"prior_state": {
"values": {
"root_module": {
"resources": [
{
"mode": "data",
"name": "me",
"type": "coder_workspace_owner",
"address": "data.coder_workspace_owner.me",
"provider_name": "registry.terraform.io/coder/coder",
"schema_version": 0,
"values": {
"id": "25e81ec3-0eb9-4ee3-8b6d-738b8552f7a9",
"name": "default",
"email": "default@example.com",
"groups": [],
"full_name": "default",
"login_type": null,
"rbac_roles": [],
"session_token": "",
"ssh_public_key": "",
"ssh_private_key": "",
"oidc_access_token": ""
},
"sensitive_values": {
"groups": [],
"rbac_roles": [],
"ssh_private_key": true
}
}
],
"child_modules": []
}
},
"format_version": "1.0",
"terraform_version": "1.11.2"
},
"configuration": {
"root_module": {
"resources": [
{
"mode": "data",
"name": "me",
"type": "coder_workspace_owner",
"address": "data.coder_workspace_owner.me",
"schema_version": 0,
"provider_config_key": "coder"
}
],
"variables": {},
"module_calls": {}
},
"provider_config": {
"coder": {
"name": "coder",
"full_name": "registry.terraform.io/coder/coder"
}
}
},
"planned_values": {
"root_module": {
"resources": [],
"child_modules": []
}
},
"resource_changes": [],
"relevant_attributes": [
{
"resource": "data.coder_workspace_owner.me",
"attribute": ["full_name"]
},
{
"resource": "data.coder_workspace_owner.me",
"attribute": ["email"]
},
{
"resource": "data.coder_workspace_owner.me",
"attribute": ["id"]
},
{
"resource": "data.coder_workspace_owner.me",
"attribute": ["name"]
}
]
}
+33
View File
@@ -21,6 +21,7 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/websocket"
"cdr.dev/slog" "cdr.dev/slog"
) )
@@ -336,6 +337,38 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
return resp, err return resp, err
} }
func (c *Client) Dial(ctx context.Context, path string, opts *websocket.DialOptions) (*websocket.Conn, error) {
u, err := c.URL.Parse(path)
if err != nil {
return nil, err
}
tokenHeader := c.SessionTokenHeader
if tokenHeader == "" {
tokenHeader = SessionTokenHeader
}
if opts == nil {
opts = &websocket.DialOptions{}
}
if opts.HTTPHeader == nil {
opts.HTTPHeader = http.Header{}
}
if opts.HTTPHeader.Get("tokenHeader") == "" {
opts.HTTPHeader.Set(tokenHeader, c.SessionToken())
}
conn, resp, err := websocket.Dial(ctx, u.String(), opts)
if resp.Body != nil {
resp.Body.Close()
}
if err != nil {
return nil, err
}
return conn, nil
}
// ExpectJSONMime is a helper function that will assert the content type // ExpectJSONMime is a helper function that will assert the content type
// of the response is application/json. // of the response is application/json.
func ExpectJSONMime(res *http.Response) error { func ExpectJSONMime(res *http.Response) error {
+26
View File
@@ -9,6 +9,10 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/coder/coder/v2/codersdk/wsjson"
previewtypes "github.com/coder/preview/types"
"github.com/coder/websocket"
) )
type TemplateVersionWarning string type TemplateVersionWarning string
@@ -123,6 +127,28 @@ func (c *Client) CancelTemplateVersion(ctx context.Context, version uuid.UUID) e
return nil return nil
} }
type DynamicParametersRequest struct {
// ID identifies the request. The response contains the same
// ID so that the client can match it to the request.
ID int `json:"id"`
Inputs map[string]string `json:"inputs"`
}
type DynamicParametersResponse struct {
ID int `json:"id"`
Diagnostics previewtypes.Diagnostics `json:"diagnostics"`
Parameters []previewtypes.Parameter `json:"parameters"`
// TODO: Workspace tags
}
func (c *Client) TemplateVersionDynamicParameters(ctx context.Context, version uuid.UUID) (*wsjson.Stream[DynamicParametersResponse, DynamicParametersRequest], error) {
conn, err := c.Dial(ctx, fmt.Sprintf("/api/v2/templateversions/%s/dynamic-parameters", version), nil)
if err != nil {
return nil, err
}
return wsjson.NewStream[DynamicParametersResponse, DynamicParametersRequest](conn, websocket.MessageText, websocket.MessageText, c.Logger()), nil
}
// TemplateVersionParameters returns parameters a template version exposes. // TemplateVersionParameters returns parameters a template version exposes.
func (c *Client) TemplateVersionRichParameters(ctx context.Context, version uuid.UUID) ([]TemplateVersionParameter, error) { func (c *Client) TemplateVersionRichParameters(ctx context.Context, version uuid.UUID) ([]TemplateVersionParameter, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/rich-parameters", version), nil) res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/rich-parameters", version), nil)
+6 -3
View File
@@ -18,9 +18,12 @@ type Decoder[T any] struct {
logger slog.Logger logger slog.Logger
} }
// Chan starts the decoder reading from the websocket and returns a channel for reading the // Chan returns a `chan` that you can read incoming messages from. The returned
// resulting values. The chan T is closed if the underlying websocket is closed, or we encounter an // `chan` will be closed when the WebSocket connection is closed. If there is an
// error. We also close the underlying websocket if we encounter an error reading or decoding. // error reading from the WebSocket or decoding a value the WebSocket will be
// closed.
//
// Safety: Chan must only be called once. Successive calls will panic.
func (d *Decoder[T]) Chan() <-chan T { func (d *Decoder[T]) Chan() <-chan T {
if !d.chanCalled.CompareAndSwap(false, true) { if !d.chanCalled.CompareAndSwap(false, true) {
panic("chan called more than once") panic("chan called more than once")
+44
View File
@@ -0,0 +1,44 @@
package wsjson
import (
"cdr.dev/slog"
"github.com/coder/websocket"
)
// Stream is a two-way messaging interface over a WebSocket connection.
type Stream[R any, W any] struct {
conn *websocket.Conn
r *Decoder[R]
w *Encoder[W]
}
func NewStream[R any, W any](conn *websocket.Conn, readType, writeType websocket.MessageType, logger slog.Logger) *Stream[R, W] {
return &Stream[R, W]{
conn: conn,
r: NewDecoder[R](conn, readType, logger),
// We intentionally don't call `NewEncoder` because it calls `CloseRead`.
w: &Encoder[W]{conn: conn, typ: writeType},
}
}
// Chan returns a `chan` that you can read incoming messages from. The returned
// `chan` will be closed when the WebSocket connection is closed. If there is an
// error reading from the WebSocket or decoding a value the WebSocket will be
// closed.
//
// Safety: Chan must only be called once. Successive calls will panic.
func (s *Stream[R, W]) Chan() <-chan R {
return s.r.Chan()
}
func (s *Stream[R, W]) Send(v W) error {
return s.w.Encode(v)
}
func (s *Stream[R, W]) Close(c websocket.StatusCode) error {
return s.conn.Close(c, "")
}
func (s *Stream[R, W]) Drop() {
_ = s.conn.Close(websocket.StatusInternalError, "dropping connection")
}
+2 -42
View File
@@ -1334,52 +1334,12 @@ This is required on creation to enable a user-flow of validating a template work
## codersdk.CreateTestAuditLogRequest ## codersdk.CreateTestAuditLogRequest
```json ```json
{ {}
"action": "create",
"additional_fields": [
0
],
"build_reason": "autostart",
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"resource_type": "template",
"time": "2019-08-24T14:15:22Z"
}
``` ```
### Properties ### Properties
| Name | Type | Required | Restrictions | Description | None
|---------------------|------------------------------------------------|----------|--------------|-------------|
| `action` | [codersdk.AuditAction](#codersdkauditaction) | false | | |
| `additional_fields` | array of integer | false | | |
| `build_reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
| `organization_id` | string | false | | |
| `request_id` | string | false | | |
| `resource_id` | string | false | | |
| `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | |
| `time` | string | false | | |
#### Enumerated Values
| Property | Value |
|-----------------|--------------------|
| `action` | `create` |
| `action` | `write` |
| `action` | `delete` |
| `action` | `start` |
| `action` | `stop` |
| `build_reason` | `autostart` |
| `build_reason` | `autostop` |
| `build_reason` | `initiator` |
| `resource_type` | `template` |
| `resource_type` | `template_version` |
| `resource_type` | `user` |
| `resource_type` | `workspace` |
| `resource_type` | `workspace_build` |
| `resource_type` | `git_ssh_key` |
| `resource_type` | `auditable_group` |
## codersdk.CreateTokenRequest ## codersdk.CreateTokenRequest
+26
View File
@@ -2541,6 +2541,32 @@ Status Code **200**
To perform this operation, you must be authenticated. [Learn more](authentication.md). To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Open dynamic parameters WebSocket by template version
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/dynamic-parameters \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templateversions/{templateversion}/dynamic-parameters`
### Parameters
| Name | In | Type | Required | Description |
|-------------------|------|--------------|----------|---------------------|
| `templateversion` | path | string(uuid) | true | Template version ID |
### Responses
| Status | Meaning | Description | Schema |
|--------|--------------------------------------------------------------------------|---------------------|--------|
| 101 | [Switching Protocols](https://tools.ietf.org/html/rfc7231#section-6.2.2) | Switching Protocols | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get external auth by template version ## Get external auth by template version
### Code samples ### Code samples
+79 -42
View File
@@ -64,6 +64,14 @@ replace github.com/lib/pq => github.com/coder/pq v1.10.5-0.20240813183442-0c420c
// used in conjunction with agent-exec. See https://github.com/coder/coder/pull/15817 // used in conjunction with agent-exec. See https://github.com/coder/coder/pull/15817
replace github.com/charmbracelet/bubbletea => github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41 replace github.com/charmbracelet/bubbletea => github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41
// Trivy has some issues that we're floating patches for, and will hopefully
// be upstreamed eventually.
replace github.com/aquasecurity/trivy => github.com/emyrk/trivy v0.0.0-20250320190949-47caa1ac2d53
// afero/tarfs has a bug that breaks our usage. A PR has been submitted upstream.
// https://github.com/spf13/afero/pull/487
replace github.com/spf13/afero => github.com/aslilac/afero v0.0.0-20250403163713-f06e86036696
require ( require (
cdr.dev/slog v1.6.2-0.20241112041820-0ec81e6e67bb cdr.dev/slog v1.6.2-0.20241112041820-0ec81e6e67bb
cloud.google.com/go/compute/metadata v0.6.0 cloud.google.com/go/compute/metadata v0.6.0
@@ -74,10 +82,10 @@ require (
github.com/aquasecurity/trivy-iac v0.8.0 github.com/aquasecurity/trivy-iac v0.8.0
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
github.com/awalterschulze/gographviz v2.0.3+incompatible github.com/awalterschulze/gographviz v2.0.3+incompatible
github.com/aws/smithy-go v1.22.2 github.com/aws/smithy-go v1.22.3
github.com/bgentry/speakeasy v0.2.0 github.com/bgentry/speakeasy v0.2.0
github.com/bramvdbogaerde/go-scp v1.5.0 github.com/bramvdbogaerde/go-scp v1.5.0
github.com/briandowns/spinner v1.18.1 github.com/briandowns/spinner v1.23.0
github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5
github.com/cenkalti/backoff/v4 v4.3.0 github.com/cenkalti/backoff/v4 v4.3.0
github.com/cespare/xxhash/v2 v2.3.0 github.com/cespare/xxhash/v2 v2.3.0
@@ -94,8 +102,8 @@ require (
github.com/coder/quartz v0.1.2 github.com/coder/quartz v0.1.2
github.com/coder/retry v1.5.1 github.com/coder/retry v1.5.1
github.com/coder/serpent v0.10.0 github.com/coder/serpent v0.10.0
github.com/coder/terraform-provider-coder/v2 v2.3.1-0.20250407075538-3a2c18dab13e github.com/coder/terraform-provider-coder/v2 v2.4.0-pre0
github.com/coder/websocket v1.8.12 github.com/coder/websocket v1.8.13
github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0 github.com/coder/wgtunnel v0.1.13-0.20240522110300-ade90dfb2da0
github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-oidc/v3 v3.14.1
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
@@ -137,7 +145,7 @@ require (
github.com/hashicorp/yamux v0.1.2 github.com/hashicorp/yamux v0.1.2
github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02
github.com/imulab/go-scim/pkg/v2 v2.2.0 github.com/imulab/go-scim/pkg/v2 v2.2.0
github.com/jedib0t/go-pretty/v6 v6.6.0 github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/jmoiron/sqlx v1.4.0 github.com/jmoiron/sqlx v1.4.0
github.com/justinas/nosurf v1.1.1 github.com/justinas/nosurf v1.1.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
@@ -161,7 +169,7 @@ require (
github.com/prometheus/client_golang v1.21.1 github.com/prometheus/client_golang v1.21.1
github.com/prometheus/client_model v0.6.1 github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.63.0 github.com/prometheus/common v0.63.0
github.com/quasilyte/go-ruleguard/dsl v0.3.21 github.com/quasilyte/go-ruleguard/dsl v0.3.22
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.25.2 github.com/shirou/gopsutil/v4 v4.25.2
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
@@ -189,7 +197,7 @@ require (
go.uber.org/mock v0.5.0 go.uber.org/mock v0.5.0
go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516
golang.org/x/crypto v0.37.0 golang.org/x/crypto v0.37.0
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
golang.org/x/mod v0.24.0 golang.org/x/mod v0.24.0
golang.org/x/net v0.38.0 golang.org/x/net v0.38.0
golang.org/x/oauth2 v0.29.0 golang.org/x/oauth2 v0.29.0
@@ -216,7 +224,7 @@ require (
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/logging v1.12.0 // indirect cloud.google.com/go/logging v1.12.0 // indirect
cloud.google.com/go/longrunning v0.6.2 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/DataDog/appsec-internal-go v1.9.0 // indirect github.com/DataDog/appsec-internal-go v1.9.0 // indirect
@@ -237,7 +245,7 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/akutz/memconn v0.1.0 // indirect github.com/akutz/memconn v0.1.0 // indirect
@@ -248,37 +256,37 @@ require (
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c // indirect github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.0 github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.1 github.com/aws/aws-sdk-go-v2/config v1.29.9
github.com/aws/aws-sdk-go-v2/credentials v1.17.54 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.62 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.24 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.5.1 github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.5.1
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 // indirect github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.11 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bep/godartsass/v2 v2.3.2 // indirect github.com/bep/godartsass/v2 v2.3.2 // indirect
github.com/bep/golibsass v1.2.0 // indirect github.com/bep/golibsass v1.2.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudflare/circl v1.6.0 // indirect
github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/continuity v0.4.5 // indirect
github.com/coreos/go-iptables v0.6.0 // indirect github.com/coreos/go-iptables v0.6.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/docker/cli v27.4.1+incompatible // indirect github.com/docker/cli v27.5.0+incompatible // indirect
github.com/docker/docker v27.2.0+incompatible // indirect github.com/docker/docker v27.5.0+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd // indirect github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd // indirect
@@ -288,16 +296,16 @@ require (
github.com/elastic/go-windows v1.0.0 // indirect github.com/elastic/go-windows v1.0.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-chi/hostrouter v0.2.0 // indirect github.com/go-chi/hostrouter v0.2.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.22.8 // indirect github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
@@ -311,12 +319,12 @@ require (
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/gohugoio/hashstructure v0.3.0 // indirect github.com/gohugoio/hashstructure v0.3.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/nftables v0.2.0 // indirect github.com/google/nftables v0.2.0 // indirect
github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
github.com/google/s2a-go v0.1.9 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
@@ -334,7 +342,7 @@ require (
github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-terraform-address v0.0.0-20240523040243-ccea9d309e0c github.com/hashicorp/go-terraform-address v0.0.0-20240523040243-ccea9d309e0c
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect github.com/hashicorp/terraform-plugin-go v0.26.0 // indirect
@@ -343,7 +351,7 @@ require (
github.com/hdevalence/ed25519consensus v0.1.0 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/illarion/gonotify v1.0.1 // indirect github.com/illarion/gonotify v1.0.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.3.5 // indirect github.com/jsimonetti/rtnetlink v1.3.5 // indirect
@@ -353,9 +361,9 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
@@ -391,7 +399,7 @@ require (
github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/riandyrn/otelchi v0.5.1 // indirect github.com/riandyrn/otelchi v0.5.1 // indirect
@@ -399,7 +407,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
@@ -420,8 +428,8 @@ require (
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tinylib/msgp v1.2.1 // indirect github.com/tinylib/msgp v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.7.0 // indirect
github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect github.com/u-root/uio v0.0.0-20240209044354-b3d14b93376a // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
@@ -479,11 +487,40 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
) )
require github.com/mark3labs/mcp-go v0.17.0 require (
github.com/coder/preview v0.0.0-20250409162646-62939c63c71a
github.com/mark3labs/mcp-go v0.17.0
)
require ( require (
cel.dev/expr v0.19.1 // indirect
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/iam v1.2.2 // indirect
cloud.google.com/go/monitoring v1.21.2 // indirect
cloud.google.com/go/storage v1.49.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
github.com/aquasecurity/go-version v0.0.1 // indirect
github.com/aquasecurity/trivy v0.58.2 // indirect
github.com/aws/aws-sdk-go v1.55.6 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/hashicorp/go-getter v1.7.8 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/liamg/memoryfs v1.6.0 // indirect
github.com/moby/sys/user v0.3.0 // indirect github.com/moby/sys/user v0.3.0 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
) )
+1584 -113
View File
File diff suppressed because it is too large Load Diff
+36 -2
View File
@@ -211,6 +211,8 @@ type Responses struct {
// transition responses. They are prioritized over the generic responses. // transition responses. They are prioritized over the generic responses.
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response
ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Response ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Response
ExtraFiles map[string][]byte
} }
// Tar returns a tar archive of responses to provisioner operations. // Tar returns a tar archive of responses to provisioner operations.
@@ -226,8 +228,12 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
if responses == nil { if responses == nil {
responses = &Responses{ responses = &Responses{
ParseComplete, ApplyComplete, PlanComplete, Parse: ParseComplete,
nil, nil, ProvisionApply: ApplyComplete,
ProvisionPlan: PlanComplete,
ProvisionApplyMap: nil,
ProvisionPlanMap: nil,
ExtraFiles: nil,
} }
} }
if responses.ProvisionPlan == nil { if responses.ProvisionPlan == nil {
@@ -327,6 +333,25 @@ func TarWithOptions(ctx context.Context, logger slog.Logger, responses *Response
} }
} }
} }
for name, content := range responses.ExtraFiles {
logger.Debug(ctx, "extra file", slog.F("name", name))
err := writer.WriteHeader(&tar.Header{
Name: name,
Size: int64(len(content)),
Mode: 0o644,
})
if err != nil {
return nil, err
}
n, err := writer.Write(content)
if err != nil {
return nil, err
}
logger.Debug(context.Background(), "extra file written", slog.F("name", name), slog.F("bytes_written", n))
}
// `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball. // `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball.
err := writer.Close() err := writer.Close()
if err != nil { if err != nil {
@@ -347,3 +372,12 @@ func WithResources(resources []*proto.Resource) *Responses {
}}}}, }}}},
} }
} }
func WithExtraFiles(extraFiles map[string][]byte) *Responses {
return &Responses{
Parse: ParseComplete,
ProvisionApply: ApplyComplete,
ProvisionPlan: PlanComplete,
ExtraFiles: extraFiles,
}
}
+6 -3
View File
@@ -32,8 +32,10 @@ func main() {
// Serpent has some types referenced in the codersdk. // Serpent has some types referenced in the codersdk.
// We want the referenced types generated. // We want the referenced types generated.
referencePackages := map[string]string{ referencePackages := map[string]string{
"github.com/coder/serpent": "Serpent", "github.com/coder/preview": "",
"tailscale.com/derp": "", "github.com/coder/serpent": "Serpent",
"github.com/hashicorp/hcl/v2": "Hcl",
"tailscale.com/derp": "",
// Conflicting name "DERPRegion" // Conflicting name "DERPRegion"
"tailscale.com/tailcfg": "Tail", "tailscale.com/tailcfg": "Tail",
"tailscale.com/net/netcheck": "Netcheck", "tailscale.com/net/netcheck": "Netcheck",
@@ -88,7 +90,8 @@ func TypeMappings(gen *guts.GoParser) error {
gen.IncludeCustomDeclaration(map[string]guts.TypeOverride{ gen.IncludeCustomDeclaration(map[string]guts.TypeOverride{
"github.com/coder/coder/v2/codersdk.NullTime": config.OverrideNullable(config.OverrideLiteral(bindings.KeywordString)), "github.com/coder/coder/v2/codersdk.NullTime": config.OverrideNullable(config.OverrideLiteral(bindings.KeywordString)),
// opt.Bool can return 'null' if unset // opt.Bool can return 'null' if unset
"tailscale.com/types/opt.Bool": config.OverrideNullable(config.OverrideLiteral(bindings.KeywordBoolean)), "tailscale.com/types/opt.Bool": config.OverrideNullable(config.OverrideLiteral(bindings.KeywordBoolean)),
"github.com/hashicorp/hcl/v2.Expression": config.OverrideLiteral(bindings.KeywordUnknown),
}) })
err := gen.IncludeCustom(map[string]string{ err := gen.IncludeCustom(map[string]string{
+53
View File
@@ -706,6 +706,21 @@ export const DisplayApps: DisplayApp[] = [
"web_terminal", "web_terminal",
]; ];
// From codersdk/templateversions.go
export interface DynamicParametersRequest {
readonly id: number;
readonly inputs: Record<string, string>;
}
// From codersdk/templateversions.go
export interface DynamicParametersResponse {
readonly id: number;
// this is likely an enum in an external package "github.com/coder/preview/types.Diagnostics"
readonly diagnostics: readonly (HclDiagnostic | null)[];
// external type "github.com/coder/preview/types.Parameter", to include this type the package must be explicitly included in the parsing
readonly parameters: readonly unknown[];
}
// From codersdk/externalauth.go // From codersdk/externalauth.go
export type EnhancedExternalAuthProvider = export type EnhancedExternalAuthProvider =
| "azure-devops" | "azure-devops"
@@ -982,6 +997,44 @@ export interface HTTPCookieConfig {
readonly same_site?: string; readonly same_site?: string;
} }
// From hcl/diagnostic.go
export interface HclDiagnostic {
readonly Severity: HclDiagnosticSeverity;
readonly Summary: string;
readonly Detail: string;
readonly Subject: HclRange | null;
readonly Context: HclRange | null;
readonly Expression: unknown;
readonly EvalContext: HclEvalContext | null;
// empty interface{} type, falling back to unknown
readonly Extra: unknown;
}
// From hcl/diagnostic.go
export type HclDiagnosticSeverity = number;
// From hcl/eval_context.go
export interface HclEvalContext {
// external type "github.com/zclconf/go-cty/cty.Value", to include this type the package must be explicitly included in the parsing
readonly Variables: Record<string, unknown>;
// external type "github.com/zclconf/go-cty/cty/function.Function", to include this type the package must be explicitly included in the parsing
readonly Functions: Record<string, unknown>;
}
// From hcl/pos.go
export interface HclPos {
readonly Line: number;
readonly Column: number;
readonly Byte: number;
}
// From hcl/pos.go
export interface HclRange {
readonly Filename: string;
readonly Start: HclPos;
readonly End: HclPos;
}
// From health/model.go // From health/model.go
export type HealthCode = export type HealthCode =
| "EACS03" | "EACS03"