mirror of https://github.com/coder/coder.git
feat: Support rich parameters in autobuilds (#5916)
This commit is contained in:
parent
cf8d4029fb
commit
23176bf036
|
@ -268,37 +268,59 @@ func build(ctx context.Context, store database.Store, workspace database.Workspa
|
|||
return xerrors.Errorf("Unsupported transition: %q", trans)
|
||||
}
|
||||
|
||||
newProvisionerJob, err := store.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
||||
ID: provisionerJobID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
InitiatorID: workspace.OwnerID,
|
||||
OrganizationID: template.OrganizationID,
|
||||
Provisioner: template.Provisioner,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
StorageMethod: priorJob.StorageMethod,
|
||||
FileID: priorJob.FileID,
|
||||
Tags: priorJob.Tags,
|
||||
Input: input,
|
||||
})
|
||||
lastBuildParameters, err := store.GetWorkspaceBuildParameters(ctx, priorHistory.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert provisioner job: %w", err)
|
||||
return xerrors.Errorf("fetch prior workspace build parameters: %w", err)
|
||||
}
|
||||
_, err = store.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
|
||||
ID: workspaceBuildID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
WorkspaceID: workspace.ID,
|
||||
TemplateVersionID: priorHistory.TemplateVersionID,
|
||||
BuildNumber: priorBuildNumber + 1,
|
||||
ProvisionerState: priorHistory.ProvisionerState,
|
||||
InitiatorID: workspace.OwnerID,
|
||||
Transition: trans,
|
||||
JobID: newProvisionerJob.ID,
|
||||
Reason: buildReason,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace build: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
return store.InTx(func(db database.Store) error {
|
||||
newProvisionerJob, err := store.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
||||
ID: provisionerJobID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
InitiatorID: workspace.OwnerID,
|
||||
OrganizationID: template.OrganizationID,
|
||||
Provisioner: template.Provisioner,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
StorageMethod: priorJob.StorageMethod,
|
||||
FileID: priorJob.FileID,
|
||||
Tags: priorJob.Tags,
|
||||
Input: input,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert provisioner job: %w", err)
|
||||
}
|
||||
workspaceBuild, err := store.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
|
||||
ID: workspaceBuildID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
WorkspaceID: workspace.ID,
|
||||
TemplateVersionID: priorHistory.TemplateVersionID,
|
||||
BuildNumber: priorBuildNumber + 1,
|
||||
ProvisionerState: priorHistory.ProvisionerState,
|
||||
InitiatorID: workspace.OwnerID,
|
||||
Transition: trans,
|
||||
JobID: newProvisionerJob.ID,
|
||||
Reason: buildReason,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace build: %w", err)
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(lastBuildParameters))
|
||||
values := make([]string, 0, len(lastBuildParameters))
|
||||
for _, param := range lastBuildParameters {
|
||||
names = append(names, param.Name)
|
||||
values = append(values, param.Value)
|
||||
}
|
||||
err = db.InsertWorkspaceBuildParameters(ctx, database.InsertWorkspaceBuildParametersParams{
|
||||
WorkspaceBuildID: workspaceBuild.ID,
|
||||
Name: names,
|
||||
Value: values,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert workspace build parameters: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
}
|
||||
|
|
|
@ -8,12 +8,16 @@ import (
|
|||
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/coder/coder/coderd/autobuild/executor"
|
||||
"github.com/coder/coder/coderd/autobuild/schedule"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -540,6 +544,67 @@ func TestExecutorAutostartMultipleOK(t *testing.T) {
|
|||
assert.Len(t, stats2.Transitions, 0)
|
||||
}
|
||||
|
||||
func TestExecutorAutostartWithParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
stringParameterName = "string_parameter"
|
||||
stringParameterValue = "abc"
|
||||
|
||||
numberParameterName = "number_parameter"
|
||||
numberParameterValue = "7"
|
||||
)
|
||||
|
||||
var (
|
||||
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
|
||||
tickCh = make(chan time.Time)
|
||||
statsCh = make(chan executor.Stats)
|
||||
client = coderdtest.New(t, &coderdtest.Options{
|
||||
AutobuildTicker: tickCh,
|
||||
IncludeProvisionerDaemon: true,
|
||||
AutobuildStats: statsCh,
|
||||
})
|
||||
|
||||
richParameters = []*proto.RichParameter{
|
||||
{Name: stringParameterName, Type: "string", Mutable: true},
|
||||
{Name: numberParameterName, Type: "number", Mutable: true},
|
||||
}
|
||||
|
||||
// Given: we have a user with a workspace that has autostart enabled
|
||||
workspace = mustProvisionWorkspaceWithParameters(t, client, richParameters, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.AutostartSchedule = ptr.Ref(sched.String())
|
||||
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
|
||||
{
|
||||
Name: stringParameterName,
|
||||
Value: stringParameterValue,
|
||||
},
|
||||
{
|
||||
Name: numberParameterName,
|
||||
Value: numberParameterValue,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
// Given: workspace is stopped
|
||||
workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
|
||||
// When: the autobuild executor ticks after the scheduled time
|
||||
go func() {
|
||||
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
// Then: the workspace with parameters should eventually be started
|
||||
stats := <-statsCh
|
||||
assert.NoError(t, stats.Error)
|
||||
assert.Len(t, stats.Transitions, 1)
|
||||
assert.Contains(t, stats.Transitions, workspace.ID)
|
||||
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID])
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
mustWorkspaceParameters(t, client, workspace.LatestBuild.ID)
|
||||
}
|
||||
|
||||
func mustProvisionWorkspace(t *testing.T, client *codersdk.Client, mut ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace {
|
||||
t.Helper()
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
@ -551,6 +616,34 @@ func mustProvisionWorkspace(t *testing.T, client *codersdk.Client, mut ...func(*
|
|||
return coderdtest.MustWorkspace(t, client, ws.ID)
|
||||
}
|
||||
|
||||
func mustProvisionWorkspaceWithParameters(t *testing.T, client *codersdk.Client, richParameters []*proto.RichParameter, mut ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace {
|
||||
t.Helper()
|
||||
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: richParameters,
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: []*proto.Provision_Response{
|
||||
{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, mut...)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, ws.LatestBuild.ID)
|
||||
return coderdtest.MustWorkspace(t, client, ws.ID)
|
||||
}
|
||||
|
||||
func mustSchedule(t *testing.T, s string) *schedule.Schedule {
|
||||
t.Helper()
|
||||
sched, err := schedule.Weekly(s)
|
||||
|
@ -558,6 +651,13 @@ func mustSchedule(t *testing.T, s string) *schedule.Schedule {
|
|||
return sched
|
||||
}
|
||||
|
||||
func mustWorkspaceParameters(t *testing.T, client *codersdk.Client, workspaceID uuid.UUID) {
|
||||
ctx := context.Background()
|
||||
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceID)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, buildParameters)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import (
|
|||
|
||||
func ValidateWorkspaceBuildParameters(richParameters []TemplateVersionParameter, buildParameters []WorkspaceBuildParameter) error {
|
||||
for _, buildParameter := range buildParameters {
|
||||
if buildParameter.Name == "" {
|
||||
return xerrors.Errorf(`workspace build parameter name is missing`)
|
||||
}
|
||||
richParameter, found := findTemplateVersionParameter(richParameters, buildParameter.Name)
|
||||
if !found {
|
||||
return xerrors.Errorf(`workspace build parameter is not defined in the template ("coder_parameter"): %s`, buildParameter.Name)
|
||||
|
|
Loading…
Reference in New Issue