mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: mark prebuilds as such and set their preset ids (#16965)
This pull request closes https://github.com/coder/internal/issues/513
This commit is contained in:
+2
-1
@@ -67,7 +67,8 @@
|
||||
"count": 0,
|
||||
"available": 0,
|
||||
"most_recently_seen": null
|
||||
}
|
||||
},
|
||||
"template_version_preset_id": null
|
||||
},
|
||||
"latest_app_status": null,
|
||||
"outdated": false,
|
||||
|
||||
Generated
+13
@@ -11394,6 +11394,11 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"template_version_preset_id": {
|
||||
"description": "TemplateVersionPresetID is the ID of the template version preset to use for the build.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"transition": {
|
||||
"enum": [
|
||||
"start",
|
||||
@@ -11458,6 +11463,10 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"template_version_preset_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"ttl_ms": {
|
||||
"type": "integer"
|
||||
}
|
||||
@@ -17037,6 +17046,10 @@ const docTemplate = `{
|
||||
"template_version_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"template_version_preset_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"transition": {
|
||||
"enum": [
|
||||
"start",
|
||||
|
||||
Generated
+13
@@ -10160,6 +10160,11 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"template_version_preset_id": {
|
||||
"description": "TemplateVersionPresetID is the ID of the template version preset to use for the build.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"transition": {
|
||||
"enum": ["start", "stop", "delete"],
|
||||
"allOf": [
|
||||
@@ -10216,6 +10221,10 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"template_version_preset_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"ttl_ms": {
|
||||
"type": "integer"
|
||||
}
|
||||
@@ -15548,6 +15557,10 @@
|
||||
"template_version_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"template_version_preset_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"transition": {
|
||||
"enum": ["start", "stop", "delete"],
|
||||
"allOf": [
|
||||
|
||||
@@ -9788,19 +9788,20 @@ func (q *FakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.Inser
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
workspaceBuild := database.WorkspaceBuild{
|
||||
ID: arg.ID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
WorkspaceID: arg.WorkspaceID,
|
||||
TemplateVersionID: arg.TemplateVersionID,
|
||||
BuildNumber: arg.BuildNumber,
|
||||
Transition: arg.Transition,
|
||||
InitiatorID: arg.InitiatorID,
|
||||
JobID: arg.JobID,
|
||||
ProvisionerState: arg.ProvisionerState,
|
||||
Deadline: arg.Deadline,
|
||||
MaxDeadline: arg.MaxDeadline,
|
||||
Reason: arg.Reason,
|
||||
ID: arg.ID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
WorkspaceID: arg.WorkspaceID,
|
||||
TemplateVersionID: arg.TemplateVersionID,
|
||||
BuildNumber: arg.BuildNumber,
|
||||
Transition: arg.Transition,
|
||||
InitiatorID: arg.InitiatorID,
|
||||
JobID: arg.JobID,
|
||||
ProvisionerState: arg.ProvisionerState,
|
||||
Deadline: arg.Deadline,
|
||||
MaxDeadline: arg.MaxDeadline,
|
||||
Reason: arg.Reason,
|
||||
TemplateVersionPresetID: arg.TemplateVersionPresetID,
|
||||
}
|
||||
q.workspaceBuilds = append(q.workspaceBuilds, workspaceBuild)
|
||||
return nil
|
||||
|
||||
@@ -27,6 +27,8 @@ import (
|
||||
|
||||
"cdr.dev/slog"
|
||||
|
||||
"github.com/coder/quartz"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/apikey"
|
||||
"github.com/coder/coder/v2/coderd/audit"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
@@ -46,7 +48,6 @@ import (
|
||||
"github.com/coder/coder/v2/provisionerd/proto"
|
||||
"github.com/coder/coder/v2/provisionersdk"
|
||||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
"github.com/coder/quartz"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -635,6 +636,7 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
|
||||
WorkspaceBuildId: workspaceBuild.ID.String(),
|
||||
WorkspaceOwnerLoginType: string(owner.LoginType),
|
||||
WorkspaceOwnerRbacRoles: ownerRbacRoles,
|
||||
IsPrebuild: input.IsPrebuild,
|
||||
},
|
||||
LogLevel: input.LogLevel,
|
||||
},
|
||||
@@ -2460,6 +2462,7 @@ type TemplateVersionImportJob struct {
|
||||
type WorkspaceProvisionJob struct {
|
||||
WorkspaceBuildID uuid.UUID `json:"workspace_build_id"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
IsPrebuild bool `json:"is_prebuild,omitempty"`
|
||||
LogLevel string `json:"log_level,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -164,279 +164,286 @@ func TestAcquireJob(t *testing.T) {
|
||||
_, err = tc.acquire(ctx, srv)
|
||||
require.ErrorContains(t, err, "sql: no rows in result set")
|
||||
})
|
||||
t.Run(tc.name+"_WorkspaceBuildJob", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Set the max session token lifetime so we can assert we
|
||||
// create an API key with an expiration within the bounds of the
|
||||
// deployment config.
|
||||
dv := &codersdk.DeploymentValues{
|
||||
Sessions: codersdk.SessionLifetime{
|
||||
MaximumTokenDuration: serpent.Duration(time.Hour),
|
||||
},
|
||||
}
|
||||
gitAuthProvider := &sdkproto.ExternalAuthProviderResource{
|
||||
Id: "github",
|
||||
}
|
||||
|
||||
srv, db, ps, pd := setup(t, false, &overrides{
|
||||
deploymentValues: dv,
|
||||
externalAuthConfigs: []*externalauth.Config{{
|
||||
ID: gitAuthProvider.Id,
|
||||
InstrumentedOAuth2Config: &testutil.OAuth2Config{},
|
||||
}},
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
group1 := dbgen.Group(t, db, database.Group{
|
||||
Name: "group1",
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
sshKey := dbgen.GitSSHKey(t, db, database.GitSSHKey{
|
||||
UserID: user.ID,
|
||||
})
|
||||
err := db.InsertGroupMember(ctx, database.InsertGroupMemberParams{
|
||||
UserID: user.ID,
|
||||
GroupID: group1.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
link := dbgen.UserLink(t, db, database.UserLink{
|
||||
LoginType: database.LoginTypeOIDC,
|
||||
UserID: user.ID,
|
||||
OAuthExpiry: dbtime.Now().Add(time.Hour),
|
||||
OAuthAccessToken: "access-token",
|
||||
})
|
||||
dbgen.ExternalAuthLink(t, db, database.ExternalAuthLink{
|
||||
ProviderID: gitAuthProvider.Id,
|
||||
UserID: user.ID,
|
||||
})
|
||||
template := dbgen.Template(t, db, database.Template{
|
||||
Name: "template",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
file := dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
||||
versionFile := dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
TemplateID: uuid.NullUUID{
|
||||
UUID: template.ID,
|
||||
Valid: true,
|
||||
},
|
||||
JobID: uuid.New(),
|
||||
})
|
||||
externalAuthProviders, err := json.Marshal([]database.ExternalAuthProvider{{
|
||||
ID: gitAuthProvider.Id,
|
||||
Optional: gitAuthProvider.Optional,
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
err = db.UpdateTemplateVersionExternalAuthProvidersByJobID(ctx, database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams{
|
||||
JobID: version.JobID,
|
||||
ExternalAuthProviders: json.RawMessage(externalAuthProviders),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Import version job
|
||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
ID: version.JobID,
|
||||
InitiatorID: user.ID,
|
||||
FileID: versionFile.ID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
||||
TemplateVersionID: version.ID,
|
||||
UserVariableValues: []codersdk.VariableValue{
|
||||
{Name: "second", Value: "bah"},
|
||||
for _, prebuiltWorkspace := range []bool{false, true} {
|
||||
prebuiltWorkspace := prebuiltWorkspace
|
||||
t.Run(tc.name+"_WorkspaceBuildJob", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Set the max session token lifetime so we can assert we
|
||||
// create an API key with an expiration within the bounds of the
|
||||
// deployment config.
|
||||
dv := &codersdk.DeploymentValues{
|
||||
Sessions: codersdk.SessionLifetime{
|
||||
MaximumTokenDuration: serpent.Duration(time.Hour),
|
||||
},
|
||||
})),
|
||||
})
|
||||
_ = dbgen.TemplateVersionVariable(t, db, database.TemplateVersionVariable{
|
||||
TemplateVersionID: version.ID,
|
||||
Name: "first",
|
||||
Value: "first_value",
|
||||
DefaultValue: "default_value",
|
||||
Sensitive: true,
|
||||
})
|
||||
_ = dbgen.TemplateVersionVariable(t, db, database.TemplateVersionVariable{
|
||||
TemplateVersionID: version.ID,
|
||||
Name: "second",
|
||||
Value: "second_value",
|
||||
DefaultValue: "default_value",
|
||||
Required: true,
|
||||
Sensitive: false,
|
||||
})
|
||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
TemplateID: template.ID,
|
||||
OwnerID: user.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspace.ID,
|
||||
BuildNumber: 1,
|
||||
JobID: uuid.New(),
|
||||
TemplateVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
})
|
||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
||||
ID: build.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
InitiatorID: user.ID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
FileID: file.ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: build.ID,
|
||||
})),
|
||||
})
|
||||
}
|
||||
gitAuthProvider := &sdkproto.ExternalAuthProviderResource{
|
||||
Id: "github",
|
||||
}
|
||||
|
||||
startPublished := make(chan struct{})
|
||||
var closed bool
|
||||
closeStartSubscribe, err := ps.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
|
||||
wspubsub.HandleWorkspaceEvent(
|
||||
func(_ context.Context, e wspubsub.WorkspaceEvent, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if e.Kind == wspubsub.WorkspaceEventKindStateChange && e.WorkspaceID == workspace.ID {
|
||||
if !closed {
|
||||
close(startPublished)
|
||||
closed = true
|
||||
srv, db, ps, pd := setup(t, false, &overrides{
|
||||
deploymentValues: dv,
|
||||
externalAuthConfigs: []*externalauth.Config{{
|
||||
ID: gitAuthProvider.Id,
|
||||
InstrumentedOAuth2Config: &testutil.OAuth2Config{},
|
||||
}},
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
group1 := dbgen.Group(t, db, database.Group{
|
||||
Name: "group1",
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
sshKey := dbgen.GitSSHKey(t, db, database.GitSSHKey{
|
||||
UserID: user.ID,
|
||||
})
|
||||
err := db.InsertGroupMember(ctx, database.InsertGroupMemberParams{
|
||||
UserID: user.ID,
|
||||
GroupID: group1.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
link := dbgen.UserLink(t, db, database.UserLink{
|
||||
LoginType: database.LoginTypeOIDC,
|
||||
UserID: user.ID,
|
||||
OAuthExpiry: dbtime.Now().Add(time.Hour),
|
||||
OAuthAccessToken: "access-token",
|
||||
})
|
||||
dbgen.ExternalAuthLink(t, db, database.ExternalAuthLink{
|
||||
ProviderID: gitAuthProvider.Id,
|
||||
UserID: user.ID,
|
||||
})
|
||||
template := dbgen.Template(t, db, database.Template{
|
||||
Name: "template",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
file := dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
||||
versionFile := dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
TemplateID: uuid.NullUUID{
|
||||
UUID: template.ID,
|
||||
Valid: true,
|
||||
},
|
||||
JobID: uuid.New(),
|
||||
})
|
||||
externalAuthProviders, err := json.Marshal([]database.ExternalAuthProvider{{
|
||||
ID: gitAuthProvider.Id,
|
||||
Optional: gitAuthProvider.Optional,
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
err = db.UpdateTemplateVersionExternalAuthProvidersByJobID(ctx, database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams{
|
||||
JobID: version.JobID,
|
||||
ExternalAuthProviders: json.RawMessage(externalAuthProviders),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Import version job
|
||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
ID: version.JobID,
|
||||
InitiatorID: user.ID,
|
||||
FileID: versionFile.ID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
||||
TemplateVersionID: version.ID,
|
||||
UserVariableValues: []codersdk.VariableValue{
|
||||
{Name: "second", Value: "bah"},
|
||||
},
|
||||
})),
|
||||
})
|
||||
_ = dbgen.TemplateVersionVariable(t, db, database.TemplateVersionVariable{
|
||||
TemplateVersionID: version.ID,
|
||||
Name: "first",
|
||||
Value: "first_value",
|
||||
DefaultValue: "default_value",
|
||||
Sensitive: true,
|
||||
})
|
||||
_ = dbgen.TemplateVersionVariable(t, db, database.TemplateVersionVariable{
|
||||
TemplateVersionID: version.ID,
|
||||
Name: "second",
|
||||
Value: "second_value",
|
||||
DefaultValue: "default_value",
|
||||
Required: true,
|
||||
Sensitive: false,
|
||||
})
|
||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
TemplateID: template.ID,
|
||||
OwnerID: user.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspace.ID,
|
||||
BuildNumber: 1,
|
||||
JobID: uuid.New(),
|
||||
TemplateVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
})
|
||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
||||
ID: build.ID,
|
||||
OrganizationID: pd.OrganizationID,
|
||||
InitiatorID: user.ID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
FileID: file.ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: build.ID,
|
||||
IsPrebuild: prebuiltWorkspace,
|
||||
})),
|
||||
})
|
||||
|
||||
startPublished := make(chan struct{})
|
||||
var closed bool
|
||||
closeStartSubscribe, err := ps.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
|
||||
wspubsub.HandleWorkspaceEvent(
|
||||
func(_ context.Context, e wspubsub.WorkspaceEvent, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
defer closeStartSubscribe()
|
||||
if e.Kind == wspubsub.WorkspaceEventKindStateChange && e.WorkspaceID == workspace.ID {
|
||||
if !closed {
|
||||
close(startPublished)
|
||||
closed = true
|
||||
}
|
||||
}
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
defer closeStartSubscribe()
|
||||
|
||||
var job *proto.AcquiredJob
|
||||
var job *proto.AcquiredJob
|
||||
|
||||
for {
|
||||
// Grab jobs until we find the workspace build job. There is also
|
||||
// an import version job that we need to ignore.
|
||||
job, err = tc.acquire(ctx, srv)
|
||||
require.NoError(t, err)
|
||||
if _, ok := job.Type.(*proto.AcquiredJob_WorkspaceBuild_); ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
<-startPublished
|
||||
|
||||
got, err := json.Marshal(job.Type)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Validate that a session token is generated during the job.
|
||||
sessionToken := job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild.Metadata.WorkspaceOwnerSessionToken
|
||||
require.NotEmpty(t, sessionToken)
|
||||
toks := strings.Split(sessionToken, "-")
|
||||
require.Len(t, toks, 2, "invalid api key")
|
||||
key, err := db.GetAPIKeyByID(ctx, toks[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(dv.Sessions.MaximumTokenDuration.Value().Seconds()), key.LifetimeSeconds)
|
||||
require.WithinDuration(t, time.Now().Add(dv.Sessions.MaximumTokenDuration.Value()), key.ExpiresAt, time.Minute)
|
||||
|
||||
wantedMetadata := &sdkproto.Metadata{
|
||||
CoderUrl: (&url.URL{}).String(),
|
||||
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
|
||||
WorkspaceName: workspace.Name,
|
||||
WorkspaceOwner: user.Username,
|
||||
WorkspaceOwnerEmail: user.Email,
|
||||
WorkspaceOwnerName: user.Name,
|
||||
WorkspaceOwnerOidcAccessToken: link.OAuthAccessToken,
|
||||
WorkspaceOwnerGroups: []string{group1.Name},
|
||||
WorkspaceId: workspace.ID.String(),
|
||||
WorkspaceOwnerId: user.ID.String(),
|
||||
TemplateId: template.ID.String(),
|
||||
TemplateName: template.Name,
|
||||
TemplateVersion: version.Name,
|
||||
WorkspaceOwnerSessionToken: sessionToken,
|
||||
WorkspaceOwnerSshPublicKey: sshKey.PublicKey,
|
||||
WorkspaceOwnerSshPrivateKey: sshKey.PrivateKey,
|
||||
WorkspaceBuildId: build.ID.String(),
|
||||
WorkspaceOwnerLoginType: string(user.LoginType),
|
||||
WorkspaceOwnerRbacRoles: []*sdkproto.Role{{Name: "member", OrgId: pd.OrganizationID.String()}},
|
||||
}
|
||||
if prebuiltWorkspace {
|
||||
wantedMetadata.IsPrebuild = true
|
||||
}
|
||||
want, err := json.Marshal(&proto.AcquiredJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
|
||||
WorkspaceBuildId: build.ID.String(),
|
||||
WorkspaceName: workspace.Name,
|
||||
VariableValues: []*sdkproto.VariableValue{
|
||||
{
|
||||
Name: "first",
|
||||
Value: "first_value",
|
||||
Sensitive: true,
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
Value: "second_value",
|
||||
},
|
||||
},
|
||||
ExternalAuthProviders: []*sdkproto.ExternalAuthProvider{{
|
||||
Id: gitAuthProvider.Id,
|
||||
AccessToken: "access_token",
|
||||
}},
|
||||
Metadata: wantedMetadata,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.JSONEq(t, string(want), string(got))
|
||||
|
||||
// Assert that we delete the session token whenever
|
||||
// a stop is issued.
|
||||
stopbuild := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspace.ID,
|
||||
BuildNumber: 2,
|
||||
JobID: uuid.New(),
|
||||
TemplateVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionStop,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
})
|
||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
||||
ID: stopbuild.ID,
|
||||
InitiatorID: user.ID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
FileID: file.ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: stopbuild.ID,
|
||||
})),
|
||||
})
|
||||
|
||||
stopPublished := make(chan struct{})
|
||||
closeStopSubscribe, err := ps.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
|
||||
wspubsub.HandleWorkspaceEvent(
|
||||
func(_ context.Context, e wspubsub.WorkspaceEvent, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if e.Kind == wspubsub.WorkspaceEventKindStateChange && e.WorkspaceID == workspace.ID {
|
||||
close(stopPublished)
|
||||
}
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
defer closeStopSubscribe()
|
||||
|
||||
for {
|
||||
// Grab jobs until we find the workspace build job. There is also
|
||||
// an import version job that we need to ignore.
|
||||
job, err = tc.acquire(ctx, srv)
|
||||
require.NoError(t, err)
|
||||
if _, ok := job.Type.(*proto.AcquiredJob_WorkspaceBuild_); ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
_, ok := job.Type.(*proto.AcquiredJob_WorkspaceBuild_)
|
||||
require.True(t, ok, "acquired job not a workspace build?")
|
||||
|
||||
<-startPublished
|
||||
<-stopPublished
|
||||
|
||||
got, err := json.Marshal(job.Type)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Validate that a session token is generated during the job.
|
||||
sessionToken := job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild.Metadata.WorkspaceOwnerSessionToken
|
||||
require.NotEmpty(t, sessionToken)
|
||||
toks := strings.Split(sessionToken, "-")
|
||||
require.Len(t, toks, 2, "invalid api key")
|
||||
key, err := db.GetAPIKeyByID(ctx, toks[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(dv.Sessions.MaximumTokenDuration.Value().Seconds()), key.LifetimeSeconds)
|
||||
require.WithinDuration(t, time.Now().Add(dv.Sessions.MaximumTokenDuration.Value()), key.ExpiresAt, time.Minute)
|
||||
|
||||
want, err := json.Marshal(&proto.AcquiredJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
|
||||
WorkspaceBuildId: build.ID.String(),
|
||||
WorkspaceName: workspace.Name,
|
||||
VariableValues: []*sdkproto.VariableValue{
|
||||
{
|
||||
Name: "first",
|
||||
Value: "first_value",
|
||||
Sensitive: true,
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
Value: "second_value",
|
||||
},
|
||||
},
|
||||
ExternalAuthProviders: []*sdkproto.ExternalAuthProvider{{
|
||||
Id: gitAuthProvider.Id,
|
||||
AccessToken: "access_token",
|
||||
}},
|
||||
Metadata: &sdkproto.Metadata{
|
||||
CoderUrl: (&url.URL{}).String(),
|
||||
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
|
||||
WorkspaceName: workspace.Name,
|
||||
WorkspaceOwner: user.Username,
|
||||
WorkspaceOwnerEmail: user.Email,
|
||||
WorkspaceOwnerName: user.Name,
|
||||
WorkspaceOwnerOidcAccessToken: link.OAuthAccessToken,
|
||||
WorkspaceOwnerGroups: []string{group1.Name},
|
||||
WorkspaceId: workspace.ID.String(),
|
||||
WorkspaceOwnerId: user.ID.String(),
|
||||
TemplateId: template.ID.String(),
|
||||
TemplateName: template.Name,
|
||||
TemplateVersion: version.Name,
|
||||
WorkspaceOwnerSessionToken: sessionToken,
|
||||
WorkspaceOwnerSshPublicKey: sshKey.PublicKey,
|
||||
WorkspaceOwnerSshPrivateKey: sshKey.PrivateKey,
|
||||
WorkspaceBuildId: build.ID.String(),
|
||||
WorkspaceOwnerLoginType: string(user.LoginType),
|
||||
WorkspaceOwnerRbacRoles: []*sdkproto.Role{{Name: "member", OrgId: pd.OrganizationID.String()}},
|
||||
},
|
||||
},
|
||||
// Validate that a session token is deleted during a stop job.
|
||||
sessionToken = job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild.Metadata.WorkspaceOwnerSessionToken
|
||||
require.Empty(t, sessionToken)
|
||||
_, err = db.GetAPIKeyByID(ctx, key.ID)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.JSONEq(t, string(want), string(got))
|
||||
|
||||
// Assert that we delete the session token whenever
|
||||
// a stop is issued.
|
||||
stopbuild := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: workspace.ID,
|
||||
BuildNumber: 2,
|
||||
JobID: uuid.New(),
|
||||
TemplateVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionStop,
|
||||
Reason: database.BuildReasonInitiator,
|
||||
})
|
||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
||||
ID: stopbuild.ID,
|
||||
InitiatorID: user.ID,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
||||
FileID: file.ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: stopbuild.ID,
|
||||
})),
|
||||
})
|
||||
|
||||
stopPublished := make(chan struct{})
|
||||
closeStopSubscribe, err := ps.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
|
||||
wspubsub.HandleWorkspaceEvent(
|
||||
func(_ context.Context, e wspubsub.WorkspaceEvent, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if e.Kind == wspubsub.WorkspaceEventKindStateChange && e.WorkspaceID == workspace.ID {
|
||||
close(stopPublished)
|
||||
}
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
defer closeStopSubscribe()
|
||||
|
||||
// Grab jobs until we find the workspace build job. There is also
|
||||
// an import version job that we need to ignore.
|
||||
job, err = tc.acquire(ctx, srv)
|
||||
require.NoError(t, err)
|
||||
_, ok := job.Type.(*proto.AcquiredJob_WorkspaceBuild_)
|
||||
require.True(t, ok, "acquired job not a workspace build?")
|
||||
|
||||
<-stopPublished
|
||||
|
||||
// Validate that a session token is deleted during a stop job.
|
||||
sessionToken = job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild.Metadata.WorkspaceOwnerSessionToken
|
||||
require.Empty(t, sessionToken)
|
||||
_, err = db.GetAPIKeyByID(ctx, key.ID)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
})
|
||||
|
||||
}
|
||||
t.Run(tc.name+"_TemplateVersionDryRun", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, db, ps, _ := setup(t, false, nil)
|
||||
|
||||
@@ -337,7 +337,8 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
Initiator(apiKey.UserID).
|
||||
RichParameterValues(createBuild.RichParameterValues).
|
||||
LogLevel(string(createBuild.LogLevel)).
|
||||
DeploymentValues(api.Options.DeploymentValues)
|
||||
DeploymentValues(api.Options.DeploymentValues).
|
||||
TemplateVersionPresetID(createBuild.TemplateVersionPresetID)
|
||||
|
||||
var (
|
||||
previousWorkspaceBuild database.WorkspaceBuild
|
||||
@@ -1065,6 +1066,11 @@ func (api *API) convertWorkspaceBuild(
|
||||
return apiResources[i].Name < apiResources[j].Name
|
||||
})
|
||||
|
||||
var presetID *uuid.UUID
|
||||
if build.TemplateVersionPresetID.Valid {
|
||||
presetID = &build.TemplateVersionPresetID.UUID
|
||||
}
|
||||
|
||||
apiJob := convertProvisionerJob(job)
|
||||
transition := codersdk.WorkspaceTransition(build.Transition)
|
||||
return codersdk.WorkspaceBuild{
|
||||
@@ -1090,6 +1096,7 @@ func (api *API) convertWorkspaceBuild(
|
||||
Status: codersdk.ConvertWorkspaceStatus(apiJob.Status, transition),
|
||||
DailyCost: build.DailyCost,
|
||||
MatchedProvisioners: &matchedProvisioners,
|
||||
TemplateVersionPresetID: presetID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1307,6 +1307,50 @@ func TestPostWorkspaceBuild(t *testing.T) {
|
||||
require.Equal(t, wantState, gotState)
|
||||
})
|
||||
|
||||
t.Run("SetsPresetID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
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.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Presets: []*proto.Preset{{
|
||||
Name: "test",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
require.Nil(t, workspace.LatestBuild.TemplateVersionPresetID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
presets, err := client.TemplateVersionPresets(ctx, version.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(presets))
|
||||
require.Equal(t, "test", presets[0].Name)
|
||||
|
||||
build, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: version.ID,
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
TemplateVersionPresetID: presets[0].ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, build.TemplateVersionPresetID)
|
||||
|
||||
workspace, err = client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, build.TemplateVersionPresetID, workspace.LatestBuild.TemplateVersionPresetID)
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
|
||||
@@ -671,7 +671,8 @@ func createWorkspace(
|
||||
Reason(database.BuildReasonInitiator).
|
||||
Initiator(initiatorID).
|
||||
ActiveVersion().
|
||||
RichParameterValues(req.RichParameterValues)
|
||||
RichParameterValues(req.RichParameterValues).
|
||||
TemplateVersionPresetID(req.TemplateVersionPresetID)
|
||||
if req.TemplateVersionID != uuid.Nil {
|
||||
builder = builder.VersionID(req.TemplateVersionID)
|
||||
}
|
||||
|
||||
@@ -423,6 +423,51 @@ func TestWorkspace(t *testing.T) {
|
||||
require.ErrorAs(t, err, &apiError)
|
||||
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("TemplateVersionPreset", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
authz := coderdtest.AssertRBAC(t, api, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Presets: []*proto.Preset{{
|
||||
Name: "test",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
presets, err := client.TemplateVersionPresets(ctx, version.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(presets))
|
||||
require.Equal(t, "test", presets[0].Name)
|
||||
|
||||
workspace := coderdtest.CreateWorkspace(t, client, template.ID, func(request *codersdk.CreateWorkspaceRequest) {
|
||||
request.TemplateVersionPresetID = presets[0].ID
|
||||
})
|
||||
|
||||
authz.Reset() // Reset all previous checks done in setup.
|
||||
ws, err := client.Workspace(ctx, workspace.ID)
|
||||
authz.AssertChecked(t, policy.ActionRead, ws)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, user.UserID, ws.LatestBuild.InitiatorID)
|
||||
require.Equal(t, codersdk.BuildReasonInitiator, ws.LatestBuild.Reason)
|
||||
require.Equal(t, presets[0].ID, *ws.LatestBuild.TemplateVersionPresetID)
|
||||
|
||||
org, err := client.Organization(ctx, ws.OrganizationID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ws.OrganizationName, org.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResolveAutostart(t *testing.T) {
|
||||
|
||||
@@ -51,9 +51,10 @@ type Builder struct {
|
||||
logLevel string
|
||||
deploymentValues *codersdk.DeploymentValues
|
||||
|
||||
richParameterValues []codersdk.WorkspaceBuildParameter
|
||||
initiator uuid.UUID
|
||||
reason database.BuildReason
|
||||
richParameterValues []codersdk.WorkspaceBuildParameter
|
||||
initiator uuid.UUID
|
||||
reason database.BuildReason
|
||||
templateVersionPresetID uuid.UUID
|
||||
|
||||
// used during build, makes function arguments less verbose
|
||||
ctx context.Context
|
||||
@@ -73,6 +74,8 @@ type Builder struct {
|
||||
parameterNames *[]string
|
||||
parameterValues *[]string
|
||||
|
||||
prebuild bool
|
||||
|
||||
verifyNoLegacyParametersOnce bool
|
||||
}
|
||||
|
||||
@@ -168,6 +171,12 @@ func (b Builder) RichParameterValues(p []codersdk.WorkspaceBuildParameter) Build
|
||||
return b
|
||||
}
|
||||
|
||||
func (b Builder) MarkPrebuild() Builder {
|
||||
// nolint: revive
|
||||
b.prebuild = true
|
||||
return b
|
||||
}
|
||||
|
||||
// SetLastWorkspaceBuildInTx prepopulates the Builder's cache with the last workspace build. This allows us
|
||||
// to avoid a repeated database query when the Builder's caller also needs the workspace build, e.g. auto-start &
|
||||
// auto-stop.
|
||||
@@ -192,6 +201,12 @@ func (b Builder) SetLastWorkspaceBuildJobInTx(job *database.ProvisionerJob) Buil
|
||||
return b
|
||||
}
|
||||
|
||||
func (b Builder) TemplateVersionPresetID(id uuid.UUID) Builder {
|
||||
// nolint: revive
|
||||
b.templateVersionPresetID = id
|
||||
return b
|
||||
}
|
||||
|
||||
type BuildError struct {
|
||||
// Status is a suitable HTTP status code
|
||||
Status int
|
||||
@@ -295,6 +310,7 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
|
||||
input, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: workspaceBuildID,
|
||||
LogLevel: b.logLevel,
|
||||
IsPrebuild: b.prebuild,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, nil, BuildError{
|
||||
@@ -363,20 +379,23 @@ func (b *Builder) buildTx(authFunc func(action policy.Action, object rbac.Object
|
||||
var workspaceBuild database.WorkspaceBuild
|
||||
err = b.store.InTx(func(store database.Store) error {
|
||||
err = store.InsertWorkspaceBuild(b.ctx, database.InsertWorkspaceBuildParams{
|
||||
ID: workspaceBuildID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
WorkspaceID: b.workspace.ID,
|
||||
TemplateVersionID: templateVersionID,
|
||||
BuildNumber: buildNum,
|
||||
ProvisionerState: state,
|
||||
InitiatorID: b.initiator,
|
||||
Transition: b.trans,
|
||||
JobID: provisionerJob.ID,
|
||||
Reason: b.reason,
|
||||
Deadline: time.Time{}, // set by provisioner upon completion
|
||||
MaxDeadline: time.Time{}, // set by provisioner upon completion
|
||||
TemplateVersionPresetID: uuid.NullUUID{}, // TODO (sasswart): add this in from the caller
|
||||
ID: workspaceBuildID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
WorkspaceID: b.workspace.ID,
|
||||
TemplateVersionID: templateVersionID,
|
||||
BuildNumber: buildNum,
|
||||
ProvisionerState: state,
|
||||
InitiatorID: b.initiator,
|
||||
Transition: b.trans,
|
||||
JobID: provisionerJob.ID,
|
||||
Reason: b.reason,
|
||||
Deadline: time.Time{}, // set by provisioner upon completion
|
||||
MaxDeadline: time.Time{}, // set by provisioner upon completion
|
||||
TemplateVersionPresetID: uuid.NullUUID{
|
||||
UUID: b.templateVersionPresetID,
|
||||
Valid: b.templateVersionPresetID != uuid.Nil,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
code := http.StatusInternalServerError
|
||||
|
||||
@@ -41,6 +41,7 @@ var (
|
||||
lastBuildID = uuid.MustParse("12341234-0000-0000-000b-000000000000")
|
||||
lastBuildJobID = uuid.MustParse("12341234-0000-0000-000c-000000000000")
|
||||
otherUserID = uuid.MustParse("12341234-0000-0000-000d-000000000000")
|
||||
presetID = uuid.MustParse("12341234-0000-0000-000e-000000000000")
|
||||
)
|
||||
|
||||
func TestBuilder_NoOptions(t *testing.T) {
|
||||
@@ -773,6 +774,67 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceBuildWithPreset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req := require.New(t)
|
||||
asrt := assert.New(t)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var buildID uuid.UUID
|
||||
|
||||
mDB := expectDB(t,
|
||||
// Inputs
|
||||
withTemplate,
|
||||
withActiveVersion(nil),
|
||||
withLastBuildNotFound,
|
||||
withTemplateVersionVariables(activeVersionID, nil),
|
||||
withParameterSchemas(activeJobID, nil),
|
||||
withWorkspaceTags(activeVersionID, nil),
|
||||
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
|
||||
|
||||
// Outputs
|
||||
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
|
||||
asrt.Equal(userID, job.InitiatorID)
|
||||
asrt.Equal(activeFileID, job.FileID)
|
||||
input := provisionerdserver.WorkspaceProvisionJob{}
|
||||
err := json.Unmarshal(job.Input, &input)
|
||||
req.NoError(err)
|
||||
// store build ID for later
|
||||
buildID = input.WorkspaceBuildID
|
||||
}),
|
||||
|
||||
withInTx,
|
||||
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
|
||||
asrt.Equal(activeVersionID, bld.TemplateVersionID)
|
||||
asrt.Equal(workspaceID, bld.WorkspaceID)
|
||||
asrt.Equal(int32(1), bld.BuildNumber)
|
||||
asrt.Equal(userID, bld.InitiatorID)
|
||||
asrt.Equal(database.WorkspaceTransitionStart, bld.Transition)
|
||||
asrt.Equal(database.BuildReasonInitiator, bld.Reason)
|
||||
asrt.Equal(buildID, bld.ID)
|
||||
asrt.True(bld.TemplateVersionPresetID.Valid)
|
||||
asrt.Equal(presetID, bld.TemplateVersionPresetID.UUID)
|
||||
}),
|
||||
withBuild,
|
||||
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
|
||||
asrt.Equal(buildID, params.WorkspaceBuildID)
|
||||
asrt.Empty(params.Name)
|
||||
asrt.Empty(params.Value)
|
||||
}),
|
||||
)
|
||||
|
||||
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
|
||||
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
|
||||
ActiveVersion().
|
||||
TemplateVersionPresetID(presetID)
|
||||
// nolint: dogsled
|
||||
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
type txExpect func(mTx *dbmock.MockStore)
|
||||
|
||||
func expectDB(t *testing.T, opts ...txExpect) *dbmock.MockStore {
|
||||
|
||||
@@ -217,8 +217,9 @@ type CreateWorkspaceRequest struct {
|
||||
TTLMillis *int64 `json:"ttl_ms,omitempty"`
|
||||
// RichParameterValues allows for additional parameters to be provided
|
||||
// during the initial provision.
|
||||
RichParameterValues []WorkspaceBuildParameter `json:"rich_parameter_values,omitempty"`
|
||||
AutomaticUpdates AutomaticUpdates `json:"automatic_updates,omitempty"`
|
||||
RichParameterValues []WorkspaceBuildParameter `json:"rich_parameter_values,omitempty"`
|
||||
AutomaticUpdates AutomaticUpdates `json:"automatic_updates,omitempty"`
|
||||
TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"`
|
||||
}
|
||||
|
||||
func (c *Client) OrganizationByName(ctx context.Context, name string) (Organization, error) {
|
||||
|
||||
@@ -73,6 +73,7 @@ type WorkspaceBuild struct {
|
||||
Status WorkspaceStatus `json:"status" enums:"pending,starting,running,stopping,stopped,failed,canceling,canceled,deleting,deleted"`
|
||||
DailyCost int32 `json:"daily_cost"`
|
||||
MatchedProvisioners *MatchedProvisioners `json:"matched_provisioners,omitempty"`
|
||||
TemplateVersionPresetID *uuid.UUID `json:"template_version_preset_id" format:"uuid"`
|
||||
}
|
||||
|
||||
// WorkspaceResource describes resources used to create a workspace, for instance:
|
||||
|
||||
@@ -107,6 +107,8 @@ type CreateWorkspaceBuildRequest struct {
|
||||
|
||||
// Log level changes the default logging verbosity of a provider ("info" if empty).
|
||||
LogLevel ProvisionerLogLevel `json:"log_level,omitempty" validate:"omitempty,oneof=debug"`
|
||||
// TemplateVersionPresetID is the ID of the template version preset to use for the build.
|
||||
TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"`
|
||||
}
|
||||
|
||||
type WorkspaceOptions struct {
|
||||
|
||||
Generated
+7
@@ -212,6 +212,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -440,6 +441,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -1138,6 +1140,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -1439,6 +1442,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -1605,6 +1609,7 @@ Status Code **200**
|
||||
| `» status` | [codersdk.WorkspaceStatus](schemas.md#codersdkworkspacestatus) | false | | |
|
||||
| `» template_version_id` | string(uuid) | false | | |
|
||||
| `» template_version_name` | string | false | | |
|
||||
| `» template_version_preset_id` | string(uuid) | false | | |
|
||||
| `» transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
|
||||
| `» updated_at` | string(date-time) | false | | |
|
||||
| `» workspace_id` | string(uuid) | false | | |
|
||||
@@ -1707,6 +1712,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
|
||||
0
|
||||
],
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start"
|
||||
}
|
||||
```
|
||||
@@ -1909,6 +1915,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
|
||||
Generated
+26
-18
@@ -1411,21 +1411,23 @@ None
|
||||
0
|
||||
],
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------------------|-------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `dry_run` | boolean | false | | |
|
||||
| `log_level` | [codersdk.ProvisionerLogLevel](#codersdkprovisionerloglevel) | false | | Log level changes the default logging verbosity of a provider ("info" if empty). |
|
||||
| `orphan` | boolean | false | | Orphan may be set for the Destroy transition. |
|
||||
| `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values are optional. It will write params to the 'workspace' scope. This will overwrite any existing parameters with the same name. This will not delete old params not included in this list. |
|
||||
| `state` | array of integer | false | | |
|
||||
| `template_version_id` | string | false | | |
|
||||
| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | true | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|------------------------------|-------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `dry_run` | boolean | false | | |
|
||||
| `log_level` | [codersdk.ProvisionerLogLevel](#codersdkprovisionerloglevel) | false | | Log level changes the default logging verbosity of a provider ("info" if empty). |
|
||||
| `orphan` | boolean | false | | Orphan may be set for the Destroy transition. |
|
||||
| `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values are optional. It will write params to the 'workspace' scope. This will overwrite any existing parameters with the same name. This will not delete old params not included in this list. |
|
||||
| `state` | array of integer | false | | |
|
||||
| `template_version_id` | string | false | | |
|
||||
| `template_version_preset_id` | string | false | | Template version preset ID is the ID of the template version preset to use for the build. |
|
||||
| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | true | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
@@ -1469,6 +1471,7 @@ None
|
||||
],
|
||||
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"ttl_ms": 0
|
||||
}
|
||||
```
|
||||
@@ -1477,15 +1480,16 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------------------|-------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------|
|
||||
| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | |
|
||||
| `autostart_schedule` | string | false | | |
|
||||
| `name` | string | true | | |
|
||||
| `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values allows for additional parameters to be provided during the initial provision. |
|
||||
| `template_id` | string | false | | Template ID specifies which template should be used for creating the workspace. |
|
||||
| `template_version_id` | string | false | | Template version ID can be used to specify a specific version of a template for creating the workspace. |
|
||||
| `ttl_ms` | integer | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|------------------------------|-------------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------|
|
||||
| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | |
|
||||
| `autostart_schedule` | string | false | | |
|
||||
| `name` | string | true | | |
|
||||
| `rich_parameter_values` | array of [codersdk.WorkspaceBuildParameter](#codersdkworkspacebuildparameter) | false | | Rich parameter values allows for additional parameters to be provided during the initial provision. |
|
||||
| `template_id` | string | false | | Template ID specifies which template should be used for creating the workspace. |
|
||||
| `template_version_id` | string | false | | Template version ID can be used to specify a specific version of a template for creating the workspace. |
|
||||
| `template_version_preset_id` | string | false | | |
|
||||
| `ttl_ms` | integer | false | | |
|
||||
|
||||
## codersdk.CryptoKey
|
||||
|
||||
@@ -7761,6 +7765,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -8712,6 +8717,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -8741,6 +8747,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | |
|
||||
| `template_version_id` | string | false | | |
|
||||
| `template_version_name` | string | false | | |
|
||||
| `template_version_preset_id` | string | false | | |
|
||||
| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
| `workspace_id` | string | false | | |
|
||||
@@ -9408,6 +9415,7 @@ If the schedule is empty, the user will be updated to use the default schedule.|
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
|
||||
Generated
+8
@@ -34,6 +34,7 @@ of the template will be used.
|
||||
],
|
||||
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"ttl_ms": 0
|
||||
}
|
||||
```
|
||||
@@ -265,6 +266,7 @@ of the template will be used.
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -541,6 +543,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -611,6 +614,7 @@ of the template will be used.
|
||||
],
|
||||
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"ttl_ms": 0
|
||||
}
|
||||
```
|
||||
@@ -841,6 +845,7 @@ of the template will be used.
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -1103,6 +1108,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -1380,6 +1386,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
@@ -1772,6 +1779,7 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \
|
||||
"status": "pending",
|
||||
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||
"template_version_name": "string",
|
||||
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||
"transition": "start",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||
|
||||
@@ -270,6 +270,10 @@ func provisionEnv(
|
||||
"CODER_WORKSPACE_TEMPLATE_VERSION="+metadata.GetTemplateVersion(),
|
||||
"CODER_WORKSPACE_BUILD_ID="+metadata.GetWorkspaceBuildId(),
|
||||
)
|
||||
if metadata.GetIsPrebuild() {
|
||||
env = append(env, provider.IsPrebuildEnvironmentVariable()+"=true")
|
||||
}
|
||||
|
||||
for key, value := range provisionersdk.AgentScriptEnv() {
|
||||
env = append(env, key+"="+value)
|
||||
}
|
||||
|
||||
@@ -798,6 +798,44 @@ func TestProvision(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "is-prebuild",
|
||||
Files: map[string]string{
|
||||
"main.tf": `terraform {
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = "2.3.0-pre2"
|
||||
}
|
||||
}
|
||||
}
|
||||
data "coder_workspace" "me" {}
|
||||
resource "null_resource" "example" {}
|
||||
resource "coder_metadata" "example" {
|
||||
resource_id = null_resource.example.id
|
||||
item {
|
||||
key = "is_prebuild"
|
||||
value = data.coder_workspace.me.is_prebuild
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
Request: &proto.PlanRequest{
|
||||
Metadata: &proto.Metadata{
|
||||
IsPrebuild: true,
|
||||
},
|
||||
},
|
||||
Response: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{{
|
||||
Name: "example",
|
||||
Type: "null_resource",
|
||||
Metadata: []*proto.Resource_Metadata{{
|
||||
Key: "is_prebuild",
|
||||
Value: "true",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
@@ -367,6 +367,7 @@ func (p *Server) acquireAndRunOne(client proto.DRPCProvisionerDaemonClient) {
|
||||
slog.F("workspace_build_id", build.WorkspaceBuildId),
|
||||
slog.F("workspace_id", build.Metadata.WorkspaceId),
|
||||
slog.F("workspace_name", build.WorkspaceName),
|
||||
slog.F("is_prebuild", build.Metadata.IsPrebuild),
|
||||
)
|
||||
|
||||
span.SetAttributes(
|
||||
@@ -376,6 +377,7 @@ func (p *Server) acquireAndRunOne(client proto.DRPCProvisionerDaemonClient) {
|
||||
attribute.String("workspace_owner_id", build.Metadata.WorkspaceOwnerId),
|
||||
attribute.String("workspace_owner", build.Metadata.WorkspaceOwner),
|
||||
attribute.String("workspace_transition", build.Metadata.WorkspaceTransition.String()),
|
||||
attribute.Bool("is_prebuild", build.Metadata.IsPrebuild),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Generated
+3
@@ -447,6 +447,7 @@ export interface CreateWorkspaceBuildRequest {
|
||||
readonly orphan?: boolean;
|
||||
readonly rich_parameter_values?: readonly WorkspaceBuildParameter[];
|
||||
readonly log_level?: ProvisionerLogLevel;
|
||||
readonly template_version_preset_id?: string;
|
||||
}
|
||||
|
||||
// From codersdk/workspaceproxy.go
|
||||
@@ -465,6 +466,7 @@ export interface CreateWorkspaceRequest {
|
||||
readonly ttl_ms?: number;
|
||||
readonly rich_parameter_values?: readonly WorkspaceBuildParameter[];
|
||||
readonly automatic_updates?: AutomaticUpdates;
|
||||
readonly template_version_preset_id?: string;
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
@@ -3482,6 +3484,7 @@ export interface WorkspaceBuild {
|
||||
readonly status: WorkspaceStatus;
|
||||
readonly daily_cost: number;
|
||||
readonly matched_provisioners?: MatchedProvisioners;
|
||||
readonly template_version_preset_id: string | null;
|
||||
}
|
||||
|
||||
// From codersdk/workspacebuilds.go
|
||||
|
||||
@@ -369,6 +369,10 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
return;
|
||||
}
|
||||
setSelectedPresetIndex(index);
|
||||
form.setFieldValue(
|
||||
"template_version_preset_id",
|
||||
option?.value,
|
||||
);
|
||||
}}
|
||||
placeholder="Select a preset"
|
||||
selectedOption={presetOptions[selectedPresetIndex]}
|
||||
|
||||
@@ -1266,6 +1266,7 @@ export const MockWorkspaceBuild: TypesGen.WorkspaceBuild = {
|
||||
count: 1,
|
||||
available: 1,
|
||||
},
|
||||
template_version_preset_id: null,
|
||||
};
|
||||
|
||||
export const MockWorkspaceBuildAutostart: TypesGen.WorkspaceBuild = {
|
||||
@@ -1289,6 +1290,7 @@ export const MockWorkspaceBuildAutostart: TypesGen.WorkspaceBuild = {
|
||||
resources: [MockWorkspaceResource],
|
||||
status: "running",
|
||||
daily_cost: 20,
|
||||
template_version_preset_id: null,
|
||||
};
|
||||
|
||||
export const MockWorkspaceBuildAutostop: TypesGen.WorkspaceBuild = {
|
||||
@@ -1312,6 +1314,7 @@ export const MockWorkspaceBuildAutostop: TypesGen.WorkspaceBuild = {
|
||||
resources: [MockWorkspaceResource],
|
||||
status: "running",
|
||||
daily_cost: 20,
|
||||
template_version_preset_id: null,
|
||||
};
|
||||
|
||||
export const MockFailedWorkspaceBuild = (
|
||||
@@ -1337,6 +1340,7 @@ export const MockFailedWorkspaceBuild = (
|
||||
resources: [],
|
||||
status: "failed",
|
||||
daily_cost: 20,
|
||||
template_version_preset_id: null,
|
||||
});
|
||||
|
||||
export const MockWorkspaceBuildStop: TypesGen.WorkspaceBuild = {
|
||||
|
||||
Reference in New Issue
Block a user