Merge branch 'jjs/presets-api' into jjs/presets

This commit is contained in:
Sas Swart
2025-02-12 12:51:39 +00:00
39 changed files with 514 additions and 358 deletions

View File

@ -809,7 +809,7 @@ provisioner/terraform/testdata/version:
.PHONY: provisioner/terraform/testdata/version
test:
$(GIT_FLAGS) gotestsum --format standard-quiet -- -v -short -count=1 ./...
$(GIT_FLAGS) gotestsum --format standard-quiet -- -v -short -count=1 ./... $(if $(RUN),-run $(RUN))
.PHONY: test
test-cli:

View File

@ -140,3 +140,10 @@ type Lister interface {
// This should include running and stopped containers.
List(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse, error)
}
// NoopLister is a Lister interface that never returns any containers.
type NoopLister struct{}
func (NoopLister) List(_ context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
return codersdk.WorkspaceAgentListContainersResponse{}, nil
}

View File

@ -25,6 +25,7 @@ import (
"cdr.dev/slog/sloggers/slogjson"
"cdr.dev/slog/sloggers/slogstackdriver"
"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agentexec"
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/agent/reaper"
@ -52,6 +53,7 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
blockFileTransfer bool
agentHeaderCommand string
agentHeader []string
devcontainersEnabled bool
)
cmd := &serpent.Command{
Use: "agent",
@ -314,6 +316,15 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
return xerrors.Errorf("create agent execer: %w", err)
}
var containerLister agentcontainers.Lister
if !devcontainersEnabled {
logger.Info(ctx, "agent devcontainer detection not enabled")
containerLister = &agentcontainers.NoopLister{}
} else {
logger.Info(ctx, "agent devcontainer detection enabled")
containerLister = agentcontainers.NewDocker(execer)
}
agnt := agent.New(agent.Options{
Client: client,
Logger: logger,
@ -339,6 +350,7 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
PrometheusRegistry: prometheusRegistry,
BlockFileTransfer: blockFileTransfer,
Execer: execer,
ContainerLister: containerLister,
})
promHandler := agent.PrometheusMetricsHandler(prometheusRegistry, logger)
@ -461,6 +473,13 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
Description: fmt.Sprintf("Block file transfer using known applications: %s.", strings.Join(agentssh.BlockedFileTransferCommands, ",")),
Value: serpent.BoolOf(&blockFileTransfer),
},
{
Flag: "devcontainers-enable",
Default: "false",
Env: "CODER_AGENT_DEVCONTAINERS_ENABLE",
Description: "Allow the agent to automatically detect running devcontainers.",
Value: serpent.BoolOf(&devcontainersEnabled),
},
}
return cmd

View File

@ -33,6 +33,9 @@ OPTIONS:
--debug-address string, $CODER_AGENT_DEBUG_ADDRESS (default: 127.0.0.1:2113)
The bind address to serve a debug HTTP server.
--devcontainers-enable bool, $CODER_AGENT_DEVCONTAINERS_ENABLE (default: false)
Allow the agent to automatically detect running devcontainers.
--log-dir string, $CODER_AGENT_LOG_DIR (default: /tmp)
Specify the location for the agent log files.

View File

@ -11,7 +11,7 @@ OPTIONS:
-O, --org string, $CODER_ORGANIZATION
Select which organization (uuid or name) to use.
-c, --column [id|organization id|created at|last seen at|name|version|api version|tags|key name|status|current job id|current job status|previous job id|previous job status|organization] (default: name,organization,status,key name,created at,last seen at,version,tags)
-c, --column [id|organization id|created at|last seen at|name|version|api version|tags|key name|status|current job id|current job status|current job template name|current job template icon|current job template display name|previous job id|previous job status|previous job template name|previous job template icon|previous job template display name|organization] (default: name,organization,status,key name,created at,last seen at,version,tags)
Columns to display in table output.
-o, --output table|json (default: table)

View File

@ -20,7 +20,10 @@
"current_job": null,
"previous_job": {
"id": "======[workspace build job ID]======",
"status": "succeeded"
"status": "succeeded",
"template_name": "",
"template_icon": "",
"template_display_name": ""
},
"organization_name": "Coder"
}

View File

@ -1,7 +1,7 @@
coder v0.0.0-devel
USAGE:
coder tokens remove <name>
coder tokens remove <name|id|token>
Delete a token

View File

@ -3,6 +3,7 @@ package cli
import (
"fmt"
"os"
"strings"
"time"
"golang.org/x/exp/slices"
@ -223,7 +224,7 @@ func (r *RootCmd) listTokens() *serpent.Command {
func (r *RootCmd) removeToken() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "remove <name>",
Use: "remove <name|id|token>",
Aliases: []string{"delete"},
Short: "Delete a token",
Middleware: serpent.Chain(
@ -233,7 +234,12 @@ func (r *RootCmd) removeToken() *serpent.Command {
Handler: func(inv *serpent.Invocation) error {
token, err := client.APIKeyByName(inv.Context(), codersdk.Me, inv.Args[0])
if err != nil {
return xerrors.Errorf("fetch api key by name %s: %w", inv.Args[0], err)
// If it's a token, we need to extract the ID
maybeID := strings.Split(inv.Args[0], "-")[0]
token, err = client.APIKeyByID(inv.Context(), codersdk.Me, maybeID)
if err != nil {
return xerrors.Errorf("fetch api key by name or id: %w", err)
}
}
err = client.DeleteAPIKey(inv.Context(), codersdk.Me, token.ID)

View File

@ -93,7 +93,7 @@ func TestTokens(t *testing.T) {
require.Contains(t, res, secondTokenID)
// Test creating a token for third user from second user's (non-admin) session
inv, root = clitest.New(t, "tokens", "create", "--name", "token-two", "--user", thirdUser.ID.String())
inv, root = clitest.New(t, "tokens", "create", "--name", "failed-token", "--user", thirdUser.ID.String())
clitest.SetupConfig(t, secondUserClient, root)
buf = new(bytes.Buffer)
inv.Stdout = buf
@ -113,6 +113,7 @@ func TestTokens(t *testing.T) {
require.Len(t, tokens, 1)
require.Equal(t, id, tokens[0].ID)
// Delete by name
inv, root = clitest.New(t, "tokens", "rm", "token-one")
clitest.SetupConfig(t, client, root)
buf = new(bytes.Buffer)
@ -122,4 +123,37 @@ func TestTokens(t *testing.T) {
res = buf.String()
require.NotEmpty(t, res)
require.Contains(t, res, "deleted")
// Delete by ID
inv, root = clitest.New(t, "tokens", "rm", secondTokenID)
clitest.SetupConfig(t, client, root)
buf = new(bytes.Buffer)
inv.Stdout = buf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
res = buf.String()
require.NotEmpty(t, res)
require.Contains(t, res, "deleted")
// Create third token
inv, root = clitest.New(t, "tokens", "create", "--name", "token-three")
clitest.SetupConfig(t, client, root)
buf = new(bytes.Buffer)
inv.Stdout = buf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
res = buf.String()
require.NotEmpty(t, res)
fourthToken := res
// Delete by token
inv, root = clitest.New(t, "tokens", "rm", fourthToken)
clitest.SetupConfig(t, client, root)
buf = new(bytes.Buffer)
inv.Stdout = buf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
res = buf.String()
require.NotEmpty(t, res)
require.Contains(t, res, "deleted")
}

56
coderd/apidoc/docs.go generated
View File

@ -5643,44 +5643,6 @@ const docTemplate = `{
}
}
},
"/templateversions/{templateversion}/presets/parameters": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Templates"
],
"summary": "Get template version preset parameters",
"operationId": "get-template-version-preset-parameters",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Template version ID",
"name": "templateversion",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.PresetParameter"
}
}
}
}
}
},
"/templateversions/{templateversion}/resources": {
"get": {
"security": [
@ -13051,6 +13013,12 @@ const docTemplate = `{
},
"name": {
"type": "string"
},
"parameters": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.PresetParameter"
}
}
}
},
@ -13060,9 +13028,6 @@ const docTemplate = `{
"name": {
"type": "string"
},
"presetID": {
"type": "string"
},
"value": {
"type": "string"
}
@ -13207,6 +13172,15 @@ const docTemplate = `{
"$ref": "#/definitions/codersdk.ProvisionerJobStatus"
}
]
},
"template_display_name": {
"type": "string"
},
"template_icon": {
"type": "string"
},
"template_name": {
"type": "string"
}
}
},

View File

@ -4985,40 +4985,6 @@
}
}
},
"/templateversions/{templateversion}/presets/parameters": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Templates"],
"summary": "Get template version preset parameters",
"operationId": "get-template-version-preset-parameters",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Template version ID",
"name": "templateversion",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.PresetParameter"
}
}
}
}
}
},
"/templateversions/{templateversion}/resources": {
"get": {
"security": [
@ -11776,6 +11742,12 @@
},
"name": {
"type": "string"
},
"parameters": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.PresetParameter"
}
}
}
},
@ -11785,9 +11757,6 @@
"name": {
"type": "string"
},
"presetID": {
"type": "string"
},
"value": {
"type": "string"
}
@ -11928,6 +11897,15 @@
"$ref": "#/definitions/codersdk.ProvisionerJobStatus"
}
]
},
"template_display_name": {
"type": "string"
},
"template_icon": {
"type": "string"
},
"template_name": {
"type": "string"
}
}
},

View File

@ -788,6 +788,7 @@ func New(options *Options) *API {
httpmw.AttachRequestID,
httpmw.ExtractRealIP(api.RealIPConfig),
httpmw.Logger(api.Logger),
singleSlashMW,
rolestore.CustomRoleMW,
prometheusMW,
// Build-Version is helpful for debugging.
@ -1057,10 +1058,7 @@ func New(options *Options) *API {
r.Get("/rich-parameters", api.templateVersionRichParameters)
r.Get("/external-auth", api.templateVersionExternalAuth)
r.Get("/variables", api.templateVersionVariables)
r.Route("/presets", func(r chi.Router) {
r.Get("/", api.templateVersionPresets)
r.Get("/parameters", api.templateVersionPresetParameters)
})
r.Get("/presets", api.templateVersionPresets)
r.Get("/resources", api.templateVersionResources)
r.Get("/logs", api.templateVersionLogs)
r.Route("/dry-run", func(r chi.Router) {
@ -1735,3 +1733,31 @@ func ReadExperiments(log slog.Logger, raw []string) codersdk.Experiments {
}
return exps
}
var multipleSlashesRe = regexp.MustCompile(`/+`)
func singleSlashMW(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
var path string
rctx := chi.RouteContext(r.Context())
if rctx != nil && rctx.RoutePath != "" {
path = rctx.RoutePath
} else {
path = r.URL.Path
}
// Normalize multiple slashes to a single slash
newPath := multipleSlashesRe.ReplaceAllString(path, "/")
// Apply the cleaned path
// The approach is consistent with: https://github.com/go-chi/chi/blob/e846b8304c769c4f1a51c9de06bebfaa4576bd88/middleware/strip.go#L24-L28
if rctx != nil {
rctx.RoutePath = newPath
} else {
r.URL.Path = newPath
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}

View File

@ -0,0 +1,69 @@
package coderd
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"github.com/stretchr/testify/assert"
)
func TestStripSlashesMW(t *testing.T) {
t.Parallel()
tests := []struct {
name string
inputPath string
wantPath string
}{
{"No changes", "/api/v1/buildinfo", "/api/v1/buildinfo"},
{"Double slashes", "/api//v2//buildinfo", "/api/v2/buildinfo"},
{"Triple slashes", "/api///v2///buildinfo", "/api/v2/buildinfo"},
{"Leading slashes", "///api/v2/buildinfo", "/api/v2/buildinfo"},
{"Root path", "/", "/"},
{"Double slashes root", "//", "/"},
{"Only slashes", "/////", "/"},
}
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
for _, tt := range tests {
tt := tt
t.Run("chi/"+tt.name, func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest("GET", tt.inputPath, nil)
rec := httptest.NewRecorder()
// given
rctx := chi.NewRouteContext()
rctx.RoutePath = tt.inputPath
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
// when
singleSlashMW(handler).ServeHTTP(rec, req)
updatedCtx := chi.RouteContext(req.Context())
// then
assert.Equal(t, tt.inputPath, req.URL.Path)
assert.Equal(t, tt.wantPath, updatedCtx.RoutePath)
})
t.Run("stdlib/"+tt.name, func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest("GET", tt.inputPath, nil)
rec := httptest.NewRecorder()
// when
singleSlashMW(handler).ServeHTTP(rec, req)
// then
assert.Equal(t, tt.wantPath, req.URL.Path)
assert.Nil(t, chi.RouteContext(req.Context()))
})
}
}

View File

@ -886,7 +886,6 @@ func (s *MethodTestSuite) TestOrganization() {
JobID: job.ID,
})
insertPresetParams := database.InsertPresetParams{
ID: uuid.New(),
TemplateVersionID: workspaceBuild.TemplateVersionID,
Name: "test",
}
@ -3817,13 +3816,11 @@ func (s *MethodTestSuite) TestSystemFunctions() {
CreatedBy: user.ID,
})
preset, err := db.InsertPreset(ctx, database.InsertPresetParams{
ID: uuid.New(),
TemplateVersionID: templateVersion.ID,
Name: "test",
})
require.NoError(s.T(), err)
_, err = db.InsertPresetParameters(ctx, database.InsertPresetParametersParams{
ID: uuid.New(),
TemplateVersionPresetID: preset.ID,
Names: []string{"test"},
Values: []string{"test"},

View File

@ -8149,6 +8149,7 @@ func (q *FakeQuerier) InsertPreset(_ context.Context, arg database.InsertPresetP
q.mutex.Lock()
defer q.mutex.Unlock()
//nolint:gosimple // arg needs to keep its type for interface reasons and that type is not appropriate for preset below.
preset := database.TemplateVersionPreset{
ID: uuid.New(),
TemplateVersionID: arg.TemplateVersionID,

View File

@ -1266,14 +1266,14 @@ COMMENT ON COLUMN template_version_parameters.display_order IS 'Specifies the or
COMMENT ON COLUMN template_version_parameters.ephemeral IS 'The value of an ephemeral parameter will not be preserved between consecutive workspace builds.';
CREATE TABLE template_version_preset_parameters (
id uuid NOT NULL,
id uuid DEFAULT gen_random_uuid() NOT NULL,
template_version_preset_id uuid NOT NULL,
name text NOT NULL,
value text NOT NULL
);
CREATE TABLE template_version_presets (
id uuid NOT NULL,
id uuid DEFAULT gen_random_uuid() NOT NULL,
template_version_id uuid NOT NULL,
name text NOT NULL,
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL

View File

@ -0,0 +1,5 @@
ALTER TABLE template_version_presets
ALTER COLUMN id DROP DEFAULT;
ALTER TABLE template_version_preset_parameters
ALTER COLUMN id DROP DEFAULT;

View File

@ -0,0 +1,5 @@
ALTER TABLE template_version_presets
ALTER COLUMN id SET DEFAULT gen_random_uuid();
ALTER TABLE template_version_preset_parameters
ALTER COLUMN id SET DEFAULT gen_random_uuid();

View File

@ -5494,25 +5494,19 @@ func (q *sqlQuerier) GetPresetsByTemplateVersionID(ctx context.Context, template
const insertPreset = `-- name: InsertPreset :one
INSERT INTO
template_version_presets (id, template_version_id, name, created_at)
template_version_presets (template_version_id, name, created_at)
VALUES
($1, $2, $3, $4) RETURNING id, template_version_id, name, created_at
($1, $2, $3) RETURNING id, template_version_id, name, created_at
`
type InsertPresetParams struct {
ID uuid.UUID `db:"id" json:"id"`
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
Name string `db:"name" json:"name"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error) {
row := q.db.QueryRowContext(ctx, insertPreset,
arg.ID,
arg.TemplateVersionID,
arg.Name,
arg.CreatedAt,
)
row := q.db.QueryRowContext(ctx, insertPreset, arg.TemplateVersionID, arg.Name, arg.CreatedAt)
var i TemplateVersionPreset
err := row.Scan(
&i.ID,
@ -5525,29 +5519,22 @@ func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (
const insertPresetParameters = `-- name: InsertPresetParameters :many
INSERT INTO
template_version_preset_parameters (id, template_version_preset_id, name, value)
template_version_preset_parameters (template_version_preset_id, name, value)
SELECT
$1,
$2,
unnest($3 :: TEXT[]),
unnest($4 :: TEXT[])
unnest($2 :: TEXT[]),
unnest($3 :: TEXT[])
RETURNING id, template_version_preset_id, name, value
`
type InsertPresetParametersParams struct {
ID uuid.UUID `db:"id" json:"id"`
TemplateVersionPresetID uuid.UUID `db:"template_version_preset_id" json:"template_version_preset_id"`
Names []string `db:"names" json:"names"`
Values []string `db:"values" json:"values"`
}
func (q *sqlQuerier) InsertPresetParameters(ctx context.Context, arg InsertPresetParametersParams) ([]TemplateVersionPresetParameter, error) {
rows, err := q.db.QueryContext(ctx, insertPresetParameters,
arg.ID,
arg.TemplateVersionPresetID,
pq.Array(arg.Names),
pq.Array(arg.Values),
)
rows, err := q.db.QueryContext(ctx, insertPresetParameters, arg.TemplateVersionPresetID, pq.Array(arg.Names), pq.Array(arg.Values))
if err != nil {
return nil, err
}
@ -5755,7 +5742,10 @@ SELECT
current_job.id AS current_job_id,
current_job.job_status AS current_job_status,
previous_job.id AS previous_job_id,
previous_job.job_status AS previous_job_status
previous_job.job_status AS previous_job_status,
COALESCE(tmpl.name, ''::text) AS current_job_template_name,
COALESCE(tmpl.display_name, ''::text) AS current_job_template_display_name,
COALESCE(tmpl.icon, ''::text) AS current_job_template_icon
FROM
provisioner_daemons pd
JOIN
@ -5780,6 +5770,10 @@ LEFT JOIN
LIMIT 1
)
)
LEFT JOIN
template_versions version ON version.id = (current_job.input->>'template_version_id')::uuid
LEFT JOIN
templates tmpl ON tmpl.id = version.template_id
WHERE
pd.organization_id = $2::uuid
AND (COALESCE(array_length($3::uuid[], 1), 0) = 0 OR pd.id = ANY($3::uuid[]))
@ -5803,6 +5797,9 @@ type GetProvisionerDaemonsWithStatusByOrganizationRow struct {
CurrentJobStatus NullProvisionerJobStatus `db:"current_job_status" json:"current_job_status"`
PreviousJobID uuid.NullUUID `db:"previous_job_id" json:"previous_job_id"`
PreviousJobStatus NullProvisionerJobStatus `db:"previous_job_status" json:"previous_job_status"`
CurrentJobTemplateName string `db:"current_job_template_name" json:"current_job_template_name"`
CurrentJobTemplateDisplayName string `db:"current_job_template_display_name" json:"current_job_template_display_name"`
CurrentJobTemplateIcon string `db:"current_job_template_icon" json:"current_job_template_icon"`
}
func (q *sqlQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.Context, arg GetProvisionerDaemonsWithStatusByOrganizationParams) ([]GetProvisionerDaemonsWithStatusByOrganizationRow, error) {
@ -5837,6 +5834,9 @@ func (q *sqlQuerier) GetProvisionerDaemonsWithStatusByOrganization(ctx context.C
&i.CurrentJobStatus,
&i.PreviousJobID,
&i.PreviousJobStatus,
&i.CurrentJobTemplateName,
&i.CurrentJobTemplateDisplayName,
&i.CurrentJobTemplateIcon,
); err != nil {
return nil, err
}

View File

@ -1,14 +1,13 @@
-- name: InsertPreset :one
INSERT INTO
template_version_presets (id, template_version_id, name, created_at)
template_version_presets (template_version_id, name, created_at)
VALUES
(@id, @template_version_id, @name, @created_at) RETURNING *;
(@template_version_id, @name, @created_at) RETURNING *;
-- name: InsertPresetParameters :many
INSERT INTO
template_version_preset_parameters (id, template_version_preset_id, name, value)
template_version_preset_parameters (template_version_preset_id, name, value)
SELECT
@id,
@template_version_preset_id,
unnest(@names :: TEXT[]),
unnest(@values :: TEXT[])

View File

@ -44,7 +44,10 @@ SELECT
current_job.id AS current_job_id,
current_job.job_status AS current_job_status,
previous_job.id AS previous_job_id,
previous_job.job_status AS previous_job_status
previous_job.job_status AS previous_job_status,
COALESCE(tmpl.name, ''::text) AS current_job_template_name,
COALESCE(tmpl.display_name, ''::text) AS current_job_template_display_name,
COALESCE(tmpl.icon, ''::text) AS current_job_template_icon
FROM
provisioner_daemons pd
JOIN
@ -69,6 +72,10 @@ LEFT JOIN
LIMIT 1
)
)
LEFT JOIN
template_versions version ON version.id = (current_job.input->>'template_version_id')::uuid
LEFT JOIN
templates tmpl ON tmpl.id = version.template_id
WHERE
pd.organization_id = @organization_id::uuid
AND (COALESCE(array_length(@ids::uuid[], 1), 0) = 0 OR pd.id = ANY(@ids::uuid[]))

View File

@ -29,32 +29,6 @@ func (api *API) templateVersionPresets(rw http.ResponseWriter, r *http.Request)
return
}
var res []codersdk.Preset
for _, preset := range presets {
res = append(res, codersdk.Preset{
ID: preset.ID,
Name: preset.Name,
})
}
httpapi.Write(ctx, rw, http.StatusOK, res)
}
// @Summary Get template version preset parameters
// @ID get-template-version-preset-parameters
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param templateversion path string true "Template version ID" format(uuid)
// @Success 200 {array} codersdk.PresetParameter
// @Router /templateversions/{templateversion}/presets/parameters [get]
func (api *API) templateVersionPresetParameters(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
templateVersion := httpmw.TemplateVersionParam(r)
// TODO (sasswart): Test case: what if a user tries to read presets or preset parameters from a different org?
// TODO (sasswart): Do a prelim auth check here.
presetParams, err := api.Database.GetPresetParametersByTemplateVersionID(ctx, templateVersion.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@ -64,14 +38,20 @@ func (api *API) templateVersionPresetParameters(rw http.ResponseWriter, r *http.
return
}
var res []codersdk.PresetParameter
var res []codersdk.Preset
for _, preset := range presets {
sdkPreset := codersdk.Preset{
ID: preset.ID,
Name: preset.Name,
}
for _, presetParam := range presetParams {
res = append(res, codersdk.PresetParameter{
PresetID: presetParam.TemplateVersionPresetID,
sdkPreset.Parameters = append(sdkPreset.Parameters, codersdk.PresetParameter{
Name: presetParam.Name,
Value: presetParam.Value,
})
}
res = append(res, sdkPreset)
}
httpapi.Write(ctx, rw, http.StatusOK, res)
}

View File

@ -1,7 +1,6 @@
package coderd_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
@ -11,12 +10,29 @@ import (
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)
func TestTemplateVersionPresets(t *testing.T) {
// TODO (sasswart): Test case: what if a user tries to read presets or preset parameters from a different org?
t.Parallel()
ctx := context.Background()
givenPreset := codersdk.Preset{
Name: "My Preset",
Parameters: []codersdk.PresetParameter{
{
Name: "preset_param1",
Value: "A1B2C3",
},
{
Name: "preset_param2",
Value: "D4E5F6",
},
},
}
ctx := testutil.Context(t, testutil.WaitShort)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
@ -25,15 +41,22 @@ func TestTemplateVersionPresets(t *testing.T) {
// nolint:gocritic // This is a test
provisionerCtx := dbauthz.AsProvisionerd(ctx)
preset, err := db.InsertPreset(provisionerCtx, database.InsertPresetParams{
Name: "My Preset",
dbPreset, err := db.InsertPreset(provisionerCtx, database.InsertPresetParams{
Name: givenPreset.Name,
TemplateVersionID: version.ID,
})
require.NoError(t, err)
var presetParameterNames []string
var presetParameterValues []string
for _, presetParameter := range givenPreset.Parameters {
presetParameterNames = append(presetParameterNames, presetParameter.Name)
presetParameterValues = append(presetParameterValues, presetParameter.Value)
}
_, err = db.InsertPresetParameters(provisionerCtx, database.InsertPresetParametersParams{
TemplateVersionPresetID: preset.ID,
Names: []string{"preset_param1", "preset_param2"},
Values: []string{"A1B2C3", "D4E5F6"},
TemplateVersionPresetID: dbPreset.ID,
Names: presetParameterNames,
Values: presetParameterValues,
})
require.NoError(t, err)
@ -41,16 +64,13 @@ func TestTemplateVersionPresets(t *testing.T) {
require.NoError(t, err)
userCtx := dbauthz.As(ctx, userSubject)
presets, err := client.TemplateVersionPresets(userCtx, version.ID)
gotPresets, err := client.TemplateVersionPresets(userCtx, version.ID)
require.NoError(t, err)
require.Equal(t, 1, len(presets))
require.Equal(t, "My Preset", presets[0].Name)
presetParams, err := client.TemplateVersionPresetParameters(userCtx, version.ID)
require.NoError(t, err)
require.Equal(t, 2, len(presetParams))
require.Equal(t, "preset_param1", presetParams[0].Name)
require.Equal(t, "A1B2C3", presetParams[0].Value)
require.Equal(t, "preset_param2", presetParams[1].Name)
require.Equal(t, "D4E5F6", presetParams[1].Value)
require.Equal(t, 1, len(gotPresets))
require.Equal(t, givenPreset.Name, gotPresets[0].Name)
for _, presetParameter := range givenPreset.Parameters {
require.Contains(t, gotPresets[0].Parameters, presetParameter)
}
}

View File

@ -61,6 +61,9 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
currentJob = &codersdk.ProvisionerDaemonJob{
ID: dbDaemon.CurrentJobID.UUID,
Status: codersdk.ProvisionerJobStatus(dbDaemon.CurrentJobStatus.ProvisionerJobStatus),
TemplateName: dbDaemon.CurrentJobTemplateName,
TemplateIcon: dbDaemon.CurrentJobTemplateIcon,
TemplateDisplayName: dbDaemon.CurrentJobTemplateDisplayName,
}
}
if dbDaemon.PreviousJobID.Valid {

View File

@ -1830,7 +1830,6 @@ func InsertWorkspacePresetsAndParameters(ctx context.Context, logger slog.Logger
func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store, templateVersionID uuid.UUID, protoPreset *sdkproto.Preset, t time.Time) error {
err := db.InTx(func(tx database.Store) error {
dbPreset, err := tx.InsertPreset(ctx, database.InsertPresetParams{
ID: uuid.New(),
TemplateVersionID: templateVersionID,
Name: protoPreset.Name,
CreatedAt: t,
@ -1846,7 +1845,6 @@ func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store,
presetParameterValues = append(presetParameterValues, parameter.Value)
}
_, err = tx.InsertPresetParameters(ctx, database.InsertPresetParametersParams{
ID: uuid.New(),
TemplateVersionPresetID: dbPreset.ID,
Names: presetParameterNames,
Values: presetParameterValues,

View File

@ -13,10 +13,10 @@ import (
type Preset struct {
ID uuid.UUID
Name string
Parameters []PresetParameter
}
type PresetParameter struct {
PresetID uuid.UUID
Name string
Value string
}
@ -34,17 +34,3 @@ func (c *Client) TemplateVersionPresets(ctx context.Context, templateVersionID u
var presets []Preset
return presets, json.NewDecoder(res.Body).Decode(&presets)
}
// TemplateVersionPresetParameters returns the parameters associated with the given presets.
func (c *Client) TemplateVersionPresetParameters(ctx context.Context, templateVersionID uuid.UUID) ([]PresetParameter, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/presets/parameters", templateVersionID), nil)
if err != nil {
return nil, xerrors.Errorf("do request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, ReadBodyAsError(res)
}
var parameters []PresetParameter
return parameters, json.NewDecoder(res.Body).Decode(&parameters)
}

View File

@ -71,6 +71,9 @@ type ProvisionerDaemon struct {
type ProvisionerDaemonJob struct {
ID uuid.UUID `json:"id" format:"uuid" table:"id"`
Status ProvisionerJobStatus `json:"status" enums:"pending,running,succeeded,canceling,canceled,failed" table:"status"`
TemplateName string `json:"template_name" table:"template name"`
TemplateIcon string `json:"template_icon" table:"template icon"`
TemplateDisplayName string `json:"template_display_name" table:"template display name"`
}
// MatchedProvisioners represents the number of provisioner daemons

View File

@ -309,7 +309,10 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -319,7 +322,10 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"

View File

@ -1629,7 +1629,10 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -1639,7 +1642,10 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"
@ -1677,7 +1683,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|----------------------|--------------------------------------------------------------------------------|----------|--------------|------------------|
|-----------------------------|--------------------------------------------------------------------------------|----------|--------------|------------------|
| `[array item]` | array | false | | |
| `» daemons` | array | false | | |
| `»» api_version` | string | false | | |
@ -1685,6 +1691,9 @@ Status Code **200**
| `»» current_job` | [codersdk.ProvisionerDaemonJob](schemas.md#codersdkprovisionerdaemonjob) | false | | |
| `»»» id` | string(uuid) | false | | |
| `»»» status` | [codersdk.ProvisionerJobStatus](schemas.md#codersdkprovisionerjobstatus) | false | | |
| `»»» template_display_name` | string | false | | |
| `»»» template_icon` | string | false | | |
| `»»» template_name` | string | false | | |
| `»» id` | string(uuid) | false | | |
| `»» key_id` | string(uuid) | false | | |
| `»» key_name` | string | false | | Optional fields. |

View File

@ -31,7 +31,10 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -41,7 +44,10 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"
@ -67,13 +73,16 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/provisi
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|---------------------|--------------------------------------------------------------------------------|----------|--------------|------------------|
|----------------------------|--------------------------------------------------------------------------------|----------|--------------|------------------|
| `[array item]` | array | false | | |
| `» api_version` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» current_job` | [codersdk.ProvisionerDaemonJob](schemas.md#codersdkprovisionerdaemonjob) | false | | |
| `»» id` | string(uuid) | false | | |
| `»» status` | [codersdk.ProvisionerJobStatus](schemas.md#codersdkprovisionerjobstatus) | false | | |
| `»» template_display_name` | string | false | | |
| `»» template_icon` | string | false | | |
| `»» template_name` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» key_id` | string(uuid) | false | | |
| `» key_name` | string | false | | Optional fields. |

View File

@ -4432,23 +4432,29 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
```json
{
"id": "string",
"name": "string"
"name": "string",
"parameters": [
{
"name": "string",
"value": "string"
}
]
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------|--------|----------|--------------|-------------|
|--------------|---------------------------------------------------------------|----------|--------------|-------------|
| `id` | string | false | | |
| `name` | string | false | | |
| `parameters` | array of [codersdk.PresetParameter](#codersdkpresetparameter) | false | | |
## codersdk.PresetParameter
```json
{
"name": "string",
"presetID": "string",
"value": "string"
}
```
@ -4456,9 +4462,8 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
### Properties
| Name | Type | Required | Restrictions | Description |
|------------|--------|----------|--------------|-------------|
|---------|--------|----------|--------------|-------------|
| `name` | string | false | | |
| `presetID` | string | false | | |
| `value` | string | false | | |
## codersdk.PrometheusConfig
@ -4522,7 +4527,10 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -4532,7 +4540,10 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"
@ -4579,16 +4590,22 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
```json
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|----------|----------------------------------------------------------------|----------|--------------|-------------|
|-------------------------|----------------------------------------------------------------|----------|--------------|-------------|
| `id` | string | false | | |
| `status` | [codersdk.ProvisionerJobStatus](#codersdkprovisionerjobstatus) | false | | |
| `template_display_name` | string | false | | |
| `template_icon` | string | false | | |
| `template_name` | string | false | | |
#### Enumerated Values
@ -4844,7 +4861,10 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -4854,7 +4874,10 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"
@ -9862,7 +9885,10 @@ Zero means unspecified. There might be a limit, but the client need not try to r
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -9872,7 +9898,10 @@ Zero means unspecified. There might be a limit, but the client need not try to r
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"
@ -9998,7 +10027,10 @@ Zero means unspecified. There might be a limit, but the client need not try to r
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -10008,7 +10040,10 @@ Zero means unspecified. There might be a limit, but the client need not try to r
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"
@ -10065,7 +10100,10 @@ Zero means unspecified. There might be a limit, but the client need not try to r
"created_at": "2019-08-24T14:15:22Z",
"current_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"key_id": "1e779c8a-6786-4c89-b7c3-a6666f5fd6b5",
@ -10075,7 +10113,10 @@ Zero means unspecified. There might be a limit, but the client need not try to r
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
"previous_job": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"status": "pending"
"status": "pending",
"template_display_name": "string",
"template_icon": "string",
"template_name": "string"
},
"provisioners": [
"string"

View File

@ -2699,7 +2699,13 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/p
[
{
"id": "string",
"name": "string"
"name": "string",
"parameters": [
{
"name": "string",
"value": "string"
}
]
}
]
```
@ -2719,58 +2725,9 @@ Status Code **200**
| `[array item]` | array | false | | |
| `» id` | string | false | | |
| `» name` | string | false | | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get template version preset parameters
### Code samples
```shell
# Example request using curl
curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/presets/parameters \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`GET /templateversions/{templateversion}/presets/parameters`
### Parameters
| Name | In | Type | Required | Description |
|-------------------|------|--------------|----------|---------------------|
| `templateversion` | path | string(uuid) | true | Template version ID |
### Example responses
> 200 Response
```json
[
{
"name": "string",
"presetID": "string",
"value": "string"
}
]
```
### Responses
| Status | Meaning | Description | Schema |
|--------|---------------------------------------------------------|-------------|-------------------------------------------------------------------------|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.PresetParameter](schemas.md#codersdkpresetparameter) |
<h3 id="get-template-version-preset-parameters-responseschema">Response Schema</h3>
Status Code **200**
| Name | Type | Required | Restrictions | Description |
|----------------|--------|----------|--------------|-------------|
| `[array item]` | array | false | | |
| `» name` | string | false | | |
| `» presetID` | string | false | | |
| `» value` | string | false | | |
| `» parameters` | array | false | | |
| `»» name` | string | false | | |
| `»» value` | string | false | | |
To perform this operation, you must be authenticated. [Learn more](authentication.md).

View File

@ -27,8 +27,8 @@ Select which organization (uuid or name) to use.
### -c, --column
| | |
|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Type | <code>[id\|organization id\|created at\|last seen at\|name\|version\|api version\|tags\|key name\|status\|current job id\|current job status\|previous job id\|previous job status\|organization]</code> |
|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Type | <code>[id\|organization id\|created at\|last seen at\|name\|version\|api version\|tags\|key name\|status\|current job id\|current job status\|current job template name\|current job template icon\|current job template display name\|previous job id\|previous job status\|previous job template name\|previous job template icon\|previous job template display name\|organization]</code> |
| Default | <code>name,organization,status,key name,created at,last seen at,version,tags</code> |
Columns to display in table output.

View File

@ -11,5 +11,5 @@ Aliases:
## Usage
```console
coder tokens remove <name>
coder tokens remove <name|id|token>
```

View File

@ -374,6 +374,7 @@ resource "docker_container" "workspace" {
"CODER_PROC_PRIO_MGMT=1",
"CODER_PROC_OOM_SCORE=10",
"CODER_PROC_NICE_SCORE=1",
"CODER_AGENT_DEVCONTAINERS_ENABLE=1",
]
host {
host = "host.docker.internal"

View File

@ -11,7 +11,7 @@ OPTIONS:
-O, --org string, $CODER_ORGANIZATION
Select which organization (uuid or name) to use.
-c, --column [id|organization id|created at|last seen at|name|version|api version|tags|key name|status|current job id|current job status|previous job id|previous job status|organization] (default: name,organization,status,key name,created at,last seen at,version,tags)
-c, --column [id|organization id|created at|last seen at|name|version|api version|tags|key name|status|current job id|current job status|current job template name|current job template icon|current job template display name|previous job id|previous job status|previous job template name|previous job template icon|previous job template display name|organization] (default: name,organization,status,key name,created at,last seen at,version,tags)
Columns to display in table output.
-o, --output table|json (default: table)

View File

@ -764,7 +764,7 @@ export const createServer = async (
async function waitForPort(
port: number,
host = "0.0.0.0",
timeout = 30000,
timeout = 60_000,
): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeout) {

View File

@ -1554,11 +1554,11 @@ export interface PprofConfig {
export interface Preset {
readonly ID: string;
readonly Name: string;
readonly Parameters: readonly PresetParameter[];
}
// From codersdk/presets.go
export interface PresetParameter {
readonly PresetID: string;
readonly Name: string;
readonly Value: string;
}
@ -1604,6 +1604,9 @@ export interface ProvisionerDaemon {
export interface ProvisionerDaemonJob {
readonly id: string;
readonly status: ProvisionerJobStatus;
readonly template_name: string;
readonly template_icon: string;
readonly template_display_name: string;
}
// From codersdk/client.go

View File

@ -52,6 +52,12 @@ export default defineConfig({
"csrf_token=JXm9hOUdZctWt0ZZGAy9xiS/gxMKYOThdxjjMnMUyn4=; Path=/; HttpOnly; SameSite=Lax",
},
proxy: {
"//": {
changeOrigin: true,
target: process.env.CODER_HOST || "http://localhost:3000",
secure: process.env.NODE_ENV === "production",
rewrite: (path) => path.replace(/\/+/g, "/"),
},
"/api": {
ws: true,
changeOrigin: true,
@ -84,6 +90,7 @@ export default defineConfig({
secure: process.env.NODE_ENV === "production",
},
},
allowedHosts: true,
},
resolve: {
alias: {