mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat(coderd): support ephemeral parameters (#8367)
This commit is contained in:
3
coderd/apidoc/docs.go
generated
3
coderd/apidoc/docs.go
generated
@ -9175,6 +9175,9 @@ const docTemplate = `{
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"ephemeral": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
|
3
coderd/apidoc/swagger.json
generated
3
coderd/apidoc/swagger.json
generated
@ -8284,6 +8284,9 @@
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"ephemeral": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -75,6 +75,7 @@ func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk
|
||||
ValidationError: param.ValidationError,
|
||||
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
|
||||
Required: param.Required,
|
||||
Ephemeral: param.Ephemeral,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3966,6 +3966,7 @@ func (q *fakeQuerier) InsertTemplateVersionParameter(_ context.Context, arg data
|
||||
ValidationMonotonic: arg.ValidationMonotonic,
|
||||
Required: arg.Required,
|
||||
DisplayOrder: arg.DisplayOrder,
|
||||
Ephemeral: arg.Ephemeral,
|
||||
}
|
||||
q.templateVersionParameters = append(q.templateVersionParameters, param)
|
||||
return param, nil
|
||||
|
3
coderd/database/dump.sql
generated
3
coderd/database/dump.sql
generated
@ -465,6 +465,7 @@ CREATE TABLE template_version_parameters (
|
||||
required boolean DEFAULT true NOT NULL,
|
||||
display_name text DEFAULT ''::text NOT NULL,
|
||||
display_order integer DEFAULT 0 NOT NULL,
|
||||
ephemeral boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT validation_monotonic_order CHECK ((validation_monotonic = ANY (ARRAY['increasing'::text, 'decreasing'::text, ''::text])))
|
||||
);
|
||||
|
||||
@ -498,6 +499,8 @@ COMMENT ON COLUMN template_version_parameters.display_name IS 'Display name of t
|
||||
|
||||
COMMENT ON COLUMN template_version_parameters.display_order IS 'Specifies the order in which to display parameters in user interfaces.';
|
||||
|
||||
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_variables (
|
||||
template_version_id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
|
@ -0,0 +1 @@
|
||||
ALTER TABLE template_version_parameters DROP COLUMN ephemeral;
|
@ -0,0 +1,4 @@
|
||||
ALTER TABLE template_version_parameters ADD COLUMN ephemeral boolean NOT NULL DEFAULT false;
|
||||
|
||||
COMMENT ON COLUMN template_version_parameters.ephemeral
|
||||
IS 'The value of an ephemeral parameter will not be preserved between consecutive workspace builds.';
|
@ -1643,6 +1643,8 @@ type TemplateVersionParameter struct {
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
// Specifies the order in which to display parameters in user interfaces.
|
||||
DisplayOrder int32 `db:"display_order" json:"display_order"`
|
||||
// The value of an ephemeral parameter will not be preserved between consecutive workspace builds.
|
||||
Ephemeral bool `db:"ephemeral" json:"ephemeral"`
|
||||
}
|
||||
|
||||
type TemplateVersionVariable struct {
|
||||
|
@ -4174,7 +4174,7 @@ func (q *sqlQuerier) UpdateTemplateScheduleByID(ctx context.Context, arg UpdateT
|
||||
}
|
||||
|
||||
const getTemplateVersionParameters = `-- name: GetTemplateVersionParameters :many
|
||||
SELECT template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order FROM template_version_parameters WHERE template_version_id = $1 ORDER BY display_order ASC, LOWER(name) ASC
|
||||
SELECT template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral FROM template_version_parameters WHERE template_version_id = $1 ORDER BY display_order ASC, LOWER(name) ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) {
|
||||
@ -4203,6 +4203,7 @@ func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateV
|
||||
&i.Required,
|
||||
&i.DisplayName,
|
||||
&i.DisplayOrder,
|
||||
&i.Ephemeral,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -4235,7 +4236,8 @@ INSERT INTO
|
||||
validation_monotonic,
|
||||
required,
|
||||
display_name,
|
||||
display_order
|
||||
display_order,
|
||||
ephemeral
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@ -4254,8 +4256,9 @@ VALUES
|
||||
$13,
|
||||
$14,
|
||||
$15,
|
||||
$16
|
||||
) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order
|
||||
$16,
|
||||
$17
|
||||
) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, display_name, display_order, ephemeral
|
||||
`
|
||||
|
||||
type InsertTemplateVersionParameterParams struct {
|
||||
@ -4275,6 +4278,7 @@ type InsertTemplateVersionParameterParams struct {
|
||||
Required bool `db:"required" json:"required"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
DisplayOrder int32 `db:"display_order" json:"display_order"`
|
||||
Ephemeral bool `db:"ephemeral" json:"ephemeral"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg InsertTemplateVersionParameterParams) (TemplateVersionParameter, error) {
|
||||
@ -4295,6 +4299,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins
|
||||
arg.Required,
|
||||
arg.DisplayName,
|
||||
arg.DisplayOrder,
|
||||
arg.Ephemeral,
|
||||
)
|
||||
var i TemplateVersionParameter
|
||||
err := row.Scan(
|
||||
@ -4314,6 +4319,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins
|
||||
&i.Required,
|
||||
&i.DisplayName,
|
||||
&i.DisplayOrder,
|
||||
&i.Ephemeral,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ INSERT INTO
|
||||
validation_monotonic,
|
||||
required,
|
||||
display_name,
|
||||
display_order
|
||||
display_order,
|
||||
ephemeral
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@ -35,7 +36,8 @@ VALUES
|
||||
$13,
|
||||
$14,
|
||||
$15,
|
||||
$16
|
||||
$16,
|
||||
$17
|
||||
) RETURNING *;
|
||||
|
||||
-- name: GetTemplateVersionParameters :many
|
||||
|
@ -789,6 +789,8 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
|
||||
server.Logger.Info(ctx, "inserting template import job parameter",
|
||||
slog.F("job_id", job.ID.String()),
|
||||
slog.F("parameter_name", richParameter.Name),
|
||||
slog.F("type", richParameter.Type),
|
||||
slog.F("ephemeral", richParameter.Ephemeral),
|
||||
)
|
||||
options, err := json.Marshal(richParameter.Options)
|
||||
if err != nil {
|
||||
@ -826,6 +828,7 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
|
||||
ValidationMonotonic: richParameter.ValidationMonotonic,
|
||||
Required: richParameter.Required,
|
||||
DisplayOrder: richParameter.Order,
|
||||
Ephemeral: richParameter.Ephemeral,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("insert parameter: %w", err)
|
||||
|
@ -1484,6 +1484,7 @@ func convertTemplateVersionParameter(param database.TemplateVersionParameter) (c
|
||||
ValidationError: param.ValidationError,
|
||||
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
|
||||
Required: param.Required,
|
||||
Ephemeral: param.Ephemeral,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -2518,6 +2518,131 @@ func TestWorkspaceWithOptionalRichParameters(t *testing.T) {
|
||||
require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters)
|
||||
}
|
||||
|
||||
func TestWorkspaceWithEphemeralRichParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
firstParameterName = "first_parameter"
|
||||
firstParameterType = "string"
|
||||
firstParameterDescription = "This is first parameter"
|
||||
firstParameterMutable = true
|
||||
firstParameterDefaultValue = "1"
|
||||
firstParameterValue = "i_am_first_parameter"
|
||||
|
||||
ephemeralParameterName = "second_parameter"
|
||||
ephemeralParameterType = "string"
|
||||
ephemeralParameterDescription = "This is second parameter"
|
||||
ephemeralParameterDefaultValue = ""
|
||||
ephemeralParameterMutable = true
|
||||
ephemeralParameterValue = "i_am_ephemeral"
|
||||
)
|
||||
|
||||
// Create template version with ephemeral parameter
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Provision_Response{
|
||||
{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: firstParameterName,
|
||||
Type: firstParameterType,
|
||||
Description: firstParameterDescription,
|
||||
DefaultValue: firstParameterDefaultValue,
|
||||
Mutable: firstParameterMutable,
|
||||
},
|
||||
{
|
||||
Name: ephemeralParameterName,
|
||||
Type: ephemeralParameterType,
|
||||
Description: ephemeralParameterDescription,
|
||||
DefaultValue: ephemeralParameterDefaultValue,
|
||||
Mutable: ephemeralParameterMutable,
|
||||
Ephemeral: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
|
||||
// Create workspace with default values
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||
|
||||
// Verify workspace build parameters (default values)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
workspaceBuildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedBuildParameters := []codersdk.WorkspaceBuildParameter{
|
||||
{Name: firstParameterName, Value: firstParameterDefaultValue},
|
||||
{Name: ephemeralParameterName, Value: ephemeralParameterDefaultValue},
|
||||
}
|
||||
require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters)
|
||||
|
||||
// Trigger workspace build job with ephemeral parameter
|
||||
workspaceBuild, err = client.CreateWorkspaceBuild(ctx, workspaceBuild.WorkspaceID, codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
RichParameterValues: []codersdk.WorkspaceBuildParameter{
|
||||
{
|
||||
Name: ephemeralParameterName,
|
||||
Value: ephemeralParameterValue,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspaceBuild = coderdtest.AwaitWorkspaceBuildJob(t, client, workspaceBuild.ID)
|
||||
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||
|
||||
// Verify workspace build parameters (including ephemeral)
|
||||
workspaceBuildParameters, err = client.WorkspaceBuildParameters(ctx, workspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedBuildParameters = []codersdk.WorkspaceBuildParameter{
|
||||
{Name: firstParameterName, Value: firstParameterDefaultValue},
|
||||
{Name: ephemeralParameterName, Value: ephemeralParameterValue},
|
||||
}
|
||||
require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters)
|
||||
|
||||
// Trigger workspace build one more time without the ephemeral parameter
|
||||
workspaceBuild, err = client.CreateWorkspaceBuild(ctx, workspaceBuild.WorkspaceID, codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
RichParameterValues: []codersdk.WorkspaceBuildParameter{
|
||||
{
|
||||
Name: firstParameterName,
|
||||
Value: firstParameterValue,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspaceBuild = coderdtest.AwaitWorkspaceBuildJob(t, client, workspaceBuild.ID)
|
||||
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||
|
||||
// Verify workspace build parameters (ephemeral should be back to default)
|
||||
workspaceBuildParameters, err = client.WorkspaceBuildParameters(ctx, workspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedBuildParameters = []codersdk.WorkspaceBuildParameter{
|
||||
{Name: firstParameterName, Value: firstParameterValue},
|
||||
{Name: ephemeralParameterName, Value: ephemeralParameterDefaultValue},
|
||||
}
|
||||
require.ElementsMatch(t, expectedBuildParameters, workspaceBuildParameters)
|
||||
}
|
||||
|
||||
func TestWorkspaceLock(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
Reference in New Issue
Block a user