mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: import value from legacy variable to build parameter (#6556)
This commit is contained in:
Generated
+3
@@ -8021,6 +8021,9 @@ const docTemplate = `{
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"legacy_variable_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"mutable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
Generated
+3
@@ -7215,6 +7215,9 @@
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"legacy_variable_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"mutable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -2826,6 +2826,7 @@ func (q *fakeQuerier) InsertTemplateVersionParameter(_ context.Context, arg data
|
||||
ValidationMax: arg.ValidationMax,
|
||||
ValidationMonotonic: arg.ValidationMonotonic,
|
||||
Required: arg.Required,
|
||||
LegacyVariableName: arg.LegacyVariableName,
|
||||
}
|
||||
q.templateVersionParameters = append(q.templateVersionParameters, param)
|
||||
return param, nil
|
||||
|
||||
Generated
+3
@@ -353,6 +353,7 @@ CREATE TABLE template_version_parameters (
|
||||
validation_error text DEFAULT ''::text NOT NULL,
|
||||
validation_monotonic text DEFAULT ''::text NOT NULL,
|
||||
required boolean DEFAULT true NOT NULL,
|
||||
legacy_variable_name text DEFAULT ''::text NOT NULL,
|
||||
CONSTRAINT validation_monotonic_order CHECK ((validation_monotonic = ANY (ARRAY['increasing'::text, 'decreasing'::text, ''::text])))
|
||||
);
|
||||
|
||||
@@ -382,6 +383,8 @@ COMMENT ON COLUMN template_version_parameters.validation_monotonic IS 'Validatio
|
||||
|
||||
COMMENT ON COLUMN template_version_parameters.required IS 'Is parameter required?';
|
||||
|
||||
COMMENT ON COLUMN template_version_parameters.legacy_variable_name IS 'Name of the legacy variable for migration purposes';
|
||||
|
||||
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 legacy_variable_name;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE template_version_parameters ADD COLUMN legacy_variable_name text NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN template_version_parameters.legacy_variable_name IS 'Name of the legacy variable for migration purposes';
|
||||
@@ -1470,6 +1470,8 @@ type TemplateVersionParameter struct {
|
||||
ValidationMonotonic string `db:"validation_monotonic" json:"validation_monotonic"`
|
||||
// Is parameter required?
|
||||
Required bool `db:"required" json:"required"`
|
||||
// Name of the legacy variable for migration purposes
|
||||
LegacyVariableName string `db:"legacy_variable_name" json:"legacy_variable_name"`
|
||||
}
|
||||
|
||||
type TemplateVersionVariable struct {
|
||||
|
||||
@@ -3610,7 +3610,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 FROM template_version_parameters WHERE template_version_id = $1
|
||||
SELECT template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, legacy_variable_name FROM template_version_parameters WHERE template_version_id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateVersionID uuid.UUID) ([]TemplateVersionParameter, error) {
|
||||
@@ -3637,6 +3637,7 @@ func (q *sqlQuerier) GetTemplateVersionParameters(ctx context.Context, templateV
|
||||
&i.ValidationError,
|
||||
&i.ValidationMonotonic,
|
||||
&i.Required,
|
||||
&i.LegacyVariableName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3667,7 +3668,8 @@ INSERT INTO
|
||||
validation_max,
|
||||
validation_error,
|
||||
validation_monotonic,
|
||||
required
|
||||
required,
|
||||
legacy_variable_name
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@@ -3684,8 +3686,9 @@ VALUES
|
||||
$11,
|
||||
$12,
|
||||
$13,
|
||||
$14
|
||||
) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required
|
||||
$14,
|
||||
$15
|
||||
) RETURNING template_version_id, name, description, type, mutable, default_value, icon, options, validation_regex, validation_min, validation_max, validation_error, validation_monotonic, required, legacy_variable_name
|
||||
`
|
||||
|
||||
type InsertTemplateVersionParameterParams struct {
|
||||
@@ -3703,6 +3706,7 @@ type InsertTemplateVersionParameterParams struct {
|
||||
ValidationError string `db:"validation_error" json:"validation_error"`
|
||||
ValidationMonotonic string `db:"validation_monotonic" json:"validation_monotonic"`
|
||||
Required bool `db:"required" json:"required"`
|
||||
LegacyVariableName string `db:"legacy_variable_name" json:"legacy_variable_name"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg InsertTemplateVersionParameterParams) (TemplateVersionParameter, error) {
|
||||
@@ -3721,6 +3725,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins
|
||||
arg.ValidationError,
|
||||
arg.ValidationMonotonic,
|
||||
arg.Required,
|
||||
arg.LegacyVariableName,
|
||||
)
|
||||
var i TemplateVersionParameter
|
||||
err := row.Scan(
|
||||
@@ -3738,6 +3743,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins
|
||||
&i.ValidationError,
|
||||
&i.ValidationMonotonic,
|
||||
&i.Required,
|
||||
&i.LegacyVariableName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ INSERT INTO
|
||||
validation_max,
|
||||
validation_error,
|
||||
validation_monotonic,
|
||||
required
|
||||
required,
|
||||
legacy_variable_name
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@@ -31,7 +32,8 @@ VALUES
|
||||
$11,
|
||||
$12,
|
||||
$13,
|
||||
$14
|
||||
$14,
|
||||
$15
|
||||
) RETURNING *;
|
||||
|
||||
-- name: GetTemplateVersionParameters :many
|
||||
|
||||
@@ -835,6 +835,7 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
|
||||
ValidationMax: richParameter.ValidationMax,
|
||||
ValidationMonotonic: richParameter.ValidationMonotonic,
|
||||
Required: richParameter.Required,
|
||||
LegacyVariableName: richParameter.LegacyVariableName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("insert parameter: %w", err)
|
||||
|
||||
@@ -1622,6 +1622,7 @@ func convertTemplateVersionParameter(param database.TemplateVersionParameter) (c
|
||||
ValidationError: param.ValidationError,
|
||||
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
|
||||
Required: param.Required,
|
||||
LegacyVariableName: param.LegacyVariableName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
+33
-20
@@ -496,6 +496,39 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
apiLastBuildParameters := convertWorkspaceBuildParameters(lastBuildParameters)
|
||||
|
||||
legacyParameters, err := api.Database.ParameterValues(ctx, database.ParameterValuesParams{
|
||||
Scopes: []database.ParameterScope{database.ParameterScopeWorkspace},
|
||||
ScopeIds: []uuid.UUID{workspace.ID},
|
||||
})
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Error fetching previous legacy parameters.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Rich parameters migration: include legacy variables to the last build parameters
|
||||
for _, templateVersionParameter := range templateVersionParameters {
|
||||
// Check if parameter is defined in previous build
|
||||
if _, found := findWorkspaceBuildParameter(apiLastBuildParameters, templateVersionParameter.Name); found {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if legacy variable is defined
|
||||
for _, legacyParameter := range legacyParameters {
|
||||
if legacyParameter.Name != templateVersionParameter.LegacyVariableName {
|
||||
continue
|
||||
}
|
||||
|
||||
apiLastBuildParameters = append(apiLastBuildParameters, codersdk.WorkspaceBuildParameter{
|
||||
Name: templateVersionParameter.Name,
|
||||
Value: legacyParameter.SourceValue,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = codersdk.ValidateWorkspaceBuildParameters(templateVersionParameters, createBuild.RichParameterValues, apiLastBuildParameters)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
@@ -525,26 +558,6 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
legacyParameters, err := api.Database.ParameterValues(ctx, database.ParameterValuesParams{
|
||||
Scopes: []database.ParameterScope{database.ParameterScopeWorkspace},
|
||||
ScopeIds: []uuid.UUID{workspace.ID},
|
||||
})
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Error fetching previous legacy parameters.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if createBuild.Transition == codersdk.WorkspaceTransitionStart &&
|
||||
len(legacyParameters) > 0 && len(parameters) > 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Rich parameters can't be used together with legacy parameters.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var workspaceBuild database.WorkspaceBuild
|
||||
var provisionerJob database.ProvisionerJob
|
||||
// This must happen in a transaction to ensure history can be inserted, and
|
||||
|
||||
@@ -971,3 +971,170 @@ func TestWorkspaceBuildValidateRichParameters(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMigrateLegacyToRichParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
// 1. Prepare a template with legacy parameters.
|
||||
templateVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: []*proto.Parse_Response{{
|
||||
Type: &proto.Parse_Response_Complete{
|
||||
Complete: &proto.Parse_Complete{
|
||||
ParameterSchemas: []*proto.ParameterSchema{
|
||||
{
|
||||
AllowOverrideSource: true,
|
||||
Name: "example",
|
||||
Description: "description 1",
|
||||
DefaultSource: &proto.ParameterSource{
|
||||
Scheme: proto.ParameterSource_DATA,
|
||||
Value: "tomato",
|
||||
},
|
||||
DefaultDestination: &proto.ParameterDestination{
|
||||
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ProvisionComplete,
|
||||
ProvisionPlan: echo.ProvisionComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, templateVersion.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, templateVersion.ID)
|
||||
|
||||
// Create a workspace
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.ParameterValues = []codersdk.CreateParameterRequest{
|
||||
{
|
||||
Name: "example",
|
||||
SourceValue: "carrot",
|
||||
SourceScheme: codersdk.ParameterSourceSchemeData,
|
||||
DestinationScheme: codersdk.ParameterDestinationSchemeEnvironmentVariable,
|
||||
},
|
||||
}
|
||||
})
|
||||
workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
require.Equal(t, codersdk.WorkspaceStatusRunning, workspaceBuild.Status)
|
||||
|
||||
// 2. Upload the template with legacy and rich parameters.
|
||||
templateWithParameters := &echo.Responses{
|
||||
Parse: []*proto.Parse_Response{{
|
||||
Type: &proto.Parse_Response_Complete{
|
||||
Complete: &proto.Parse_Complete{
|
||||
ParameterSchemas: []*proto.ParameterSchema{
|
||||
{
|
||||
AllowOverrideSource: true,
|
||||
Name: "example",
|
||||
Description: "description 1",
|
||||
DefaultSource: &proto.ParameterSource{
|
||||
Scheme: proto.ParameterSource_DATA,
|
||||
Value: "tomato",
|
||||
},
|
||||
DefaultDestination: &proto.ParameterDestination{
|
||||
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionPlan: []*proto.Provision_Response{
|
||||
{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "new_example",
|
||||
Type: "string",
|
||||
Mutable: true,
|
||||
Required: true,
|
||||
LegacyVariableName: "example",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: echo.ProvisionComplete,
|
||||
}
|
||||
templateVersion = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, templateWithParameters, template.ID)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, templateVersion.ID)
|
||||
|
||||
// Check if rich parameters are expected
|
||||
richParameters, err := client.TemplateVersionRichParameters(ctx, templateVersion.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, richParameters, 1)
|
||||
require.Equal(t, "new_example", richParameters[0].Name)
|
||||
|
||||
// Update workspace to use rich parameters and template variables
|
||||
workspaceBuild, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: templateVersion.ID,
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Eventually(t, func() bool {
|
||||
workspaceBuild = coderdtest.AwaitWorkspaceBuildJob(t, client, workspaceBuild.ID)
|
||||
return codersdk.WorkspaceStatusRunning == workspaceBuild.Status
|
||||
}, testutil.WaitLong, testutil.IntervalFast)
|
||||
|
||||
// Check if variable value has been imported
|
||||
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, buildParameters, 1)
|
||||
require.Equal(t, "carrot", buildParameters[0].Value)
|
||||
|
||||
// 3. Upload the template with rich parameters only
|
||||
templateWithParameters = &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Provision_Response{
|
||||
{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "new_example",
|
||||
Type: "string",
|
||||
Mutable: true,
|
||||
Required: true,
|
||||
LegacyVariableName: "example",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: echo.ProvisionComplete,
|
||||
}
|
||||
templateVersion = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, templateWithParameters, template.ID)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, templateVersion.ID)
|
||||
|
||||
// Check if rich parameters are expected
|
||||
richParameters, err = client.TemplateVersionRichParameters(ctx, templateVersion.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, richParameters, 1)
|
||||
require.Equal(t, "new_example", richParameters[0].Name)
|
||||
|
||||
// Update workspace to use rich parameters and template variables
|
||||
workspaceBuild, err = client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: templateVersion.ID,
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Eventually(t, func() bool {
|
||||
workspaceBuild = coderdtest.AwaitWorkspaceBuildJob(t, client, workspaceBuild.ID)
|
||||
return codersdk.WorkspaceStatusRunning == workspaceBuild.Status
|
||||
}, testutil.WaitLong, testutil.IntervalFast)
|
||||
|
||||
// Check if build parameters have been pulled from last build
|
||||
buildParameters, err = client.WorkspaceBuildParameters(ctx, workspaceBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, buildParameters, 1)
|
||||
require.Equal(t, "carrot", buildParameters[0].Value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user