mirror of https://github.com/coder/coder.git
chore: refactor time.Duration -> int64 milliseconds for FE consumption (#1944)
* Changes all public-facing codersdk types to use a plain int64 (milliseconds) instead of time.Duration. * Makes autostart_schedule a *string as it may not be present. * Adds a utils/ptr package with some useful methods.
This commit is contained in:
parent
51c420c90a
commit
dcf03d8ba3
|
@ -51,15 +51,15 @@ func autostartShow() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
if workspace.AutostartSchedule == "" {
|
||||
if workspace.AutostartSchedule == nil || *workspace.AutostartSchedule == "" {
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "not enabled\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
validSchedule, err := schedule.Weekly(workspace.AutostartSchedule)
|
||||
validSchedule, err := schedule.Weekly(*workspace.AutostartSchedule)
|
||||
if err != nil {
|
||||
// This should never happen.
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "invalid autostart schedule %q for workspace %s: %s\n", workspace.AutostartSchedule, workspace.Name, err.Error())
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "invalid autostart schedule %q for workspace %s: %s\n", *workspace.AutostartSchedule, workspace.Name, err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ func autostartEnable() *cobra.Command {
|
|||
}
|
||||
|
||||
err = client.UpdateWorkspaceAutostart(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: validSchedule.String(),
|
||||
Schedule: &spec,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -153,7 +153,7 @@ func autostartDisable() *cobra.Command {
|
|||
}
|
||||
|
||||
err = client.UpdateWorkspaceAutostart(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: "",
|
||||
Schedule: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
|
@ -34,7 +35,7 @@ func TestAutostart(t *testing.T) {
|
|||
)
|
||||
|
||||
err := client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched,
|
||||
Schedule: ptr.Ref(sched),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -76,7 +77,7 @@ func TestAutostart(t *testing.T) {
|
|||
// Ensure autostart schedule updated
|
||||
updated, err := client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
require.Equal(t, sched, updated.AutostartSchedule, "expected autostart schedule to be set")
|
||||
require.Equal(t, sched, *updated.AutostartSchedule, "expected autostart schedule to be set")
|
||||
|
||||
// Disable schedule
|
||||
cmd, root = clitest.New(t, "autostart", "disable", workspace.Name)
|
||||
|
@ -90,7 +91,7 @@ func TestAutostart(t *testing.T) {
|
|||
// Ensure autostart schedule updated
|
||||
updated, err = client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
require.Empty(t, updated.AutostartSchedule, "expected autostart schedule to not be set")
|
||||
require.Nil(t, updated.AutostartSchedule, "expected autostart schedule to not be set")
|
||||
})
|
||||
|
||||
t.Run("Enable_NotFound", func(t *testing.T) {
|
||||
|
@ -155,6 +156,6 @@ func TestAutostart(t *testing.T) {
|
|||
// Ensure nothing happened
|
||||
updated, err := client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
require.Equal(t, expectedSchedule, updated.AutostartSchedule, "expected default autostart schedule")
|
||||
require.Equal(t, expectedSchedule, *updated.AutostartSchedule, "expected default autostart schedule")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -40,8 +40,8 @@ func TestBump(t *testing.T) {
|
|||
expectedDeadline := workspace.LatestBuild.Deadline.Add(90 * time.Minute)
|
||||
|
||||
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
|
||||
require.WithinDuration(t, workspace.LatestBuild.Deadline, time.Now().Add(*workspace.TTL), time.Minute)
|
||||
require.NoError(t, err)
|
||||
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
|
||||
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
|
||||
|
||||
cmd, root := clitest.New(t, cmdArgs...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
@ -81,8 +81,8 @@ func TestBump(t *testing.T) {
|
|||
expectedDeadline := workspace.LatestBuild.Deadline.Add(30 * time.Minute)
|
||||
|
||||
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
|
||||
require.WithinDuration(t, workspace.LatestBuild.Deadline, time.Now().Add(*workspace.TTL), time.Minute)
|
||||
require.NoError(t, err)
|
||||
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
|
||||
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
|
||||
|
||||
cmd, root := clitest.New(t, cmdArgs...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
@ -121,8 +121,8 @@ func TestBump(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
|
||||
require.WithinDuration(t, workspace.LatestBuild.Deadline, time.Now().Add(*workspace.TTL), time.Minute)
|
||||
require.NoError(t, err)
|
||||
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
|
||||
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
|
||||
|
||||
cmd, root := clitest.New(t, cmdArgs...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
@ -147,7 +147,7 @@ func TestBump(t *testing.T) {
|
|||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.TTL = nil
|
||||
cwr.TTLMillis = nil
|
||||
})
|
||||
cmdArgs = []string{"bump", workspace.Name}
|
||||
stdoutBuf = &bytes.Buffer{}
|
||||
|
@ -199,8 +199,8 @@ func TestBump(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
|
||||
require.WithinDuration(t, workspace.LatestBuild.Deadline, time.Now().Add(*workspace.TTL), time.Minute)
|
||||
require.NoError(t, err)
|
||||
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
|
||||
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
|
||||
|
||||
cmd, root := clitest.New(t, cmdArgs...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/coder/coder/cli/cliflag"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/autobuild/schedule"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
|
@ -226,7 +227,7 @@ func create() *cobra.Command {
|
|||
TemplateID: template.ID,
|
||||
Name: workspaceName,
|
||||
AutostartSchedule: &schedSpec,
|
||||
TTL: &ttl,
|
||||
TTLMillis: ptr.Ref(ttl.Milliseconds()),
|
||||
ParameterValues: parameters,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
15
cli/list.go
15
cli/list.go
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/autobuild/schedule"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
|
@ -87,15 +88,16 @@ func list() *cobra.Command {
|
|||
|
||||
duration := time.Now().UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
|
||||
autostartDisplay := "-"
|
||||
if workspace.AutostartSchedule != "" {
|
||||
if sched, err := schedule.Weekly(workspace.AutostartSchedule); err == nil {
|
||||
if !ptr.NilOrEmpty(workspace.AutostartSchedule) {
|
||||
if sched, err := schedule.Weekly(*workspace.AutostartSchedule); err == nil {
|
||||
autostartDisplay = sched.Cron()
|
||||
}
|
||||
}
|
||||
|
||||
autostopDisplay := "-"
|
||||
if workspace.TTL != nil {
|
||||
autostopDisplay = durationDisplay(*workspace.TTL)
|
||||
if !ptr.NilOrZero(workspace.TTLMillis) {
|
||||
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
|
||||
autostopDisplay = durationDisplay(dur)
|
||||
if has, ext := hasExtension(workspace); has {
|
||||
autostopDisplay += fmt.Sprintf(" (+%s)", durationDisplay(ext.Round(time.Minute)))
|
||||
}
|
||||
|
@ -128,10 +130,11 @@ func hasExtension(ws codersdk.Workspace) (bool, time.Duration) {
|
|||
if ws.LatestBuild.Deadline.IsZero() {
|
||||
return false, 0
|
||||
}
|
||||
if ws.TTL == nil {
|
||||
if ws.TTLMillis == nil {
|
||||
return false, 0
|
||||
}
|
||||
delta := ws.LatestBuild.Deadline.Add(-*ws.TTL).Sub(ws.LatestBuild.CreatedAt)
|
||||
ttl := time.Duration(*ws.TTLMillis) * time.Millisecond
|
||||
delta := ws.LatestBuild.Deadline.Add(-ttl).Sub(ws.LatestBuild.CreatedAt)
|
||||
if delta < time.Minute {
|
||||
return false, 0
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/coder/coder/cli/cliflag"
|
||||
"github.com/coder/coder/cli/cliui"
|
||||
"github.com/coder/coder/coderd/autobuild/notify"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/cryptorand"
|
||||
)
|
||||
|
@ -290,7 +291,7 @@ func notifyCondition(ctx context.Context, client *codersdk.Client, workspaceID u
|
|||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
if ws.TTL == nil || *ws.TTL == 0 {
|
||||
if ptr.NilOrZero(ws.TTLMillis) {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
|
|
15
cli/ttl.go
15
cli/ttl.go
|
@ -49,12 +49,13 @@ func ttlShow() *cobra.Command {
|
|||
return xerrors.Errorf("get workspace: %w", err)
|
||||
}
|
||||
|
||||
if workspace.TTL == nil {
|
||||
if workspace.TTLMillis == nil || *workspace.TTLMillis == 0 {
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "not set\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", workspace.TTL)
|
||||
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
|
||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", dur)
|
||||
|
||||
return nil
|
||||
},
|
||||
|
@ -96,10 +97,10 @@ func ttlset() *cobra.Command {
|
|||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "warning: ttl rounded down to %s\n", truncated)
|
||||
}
|
||||
|
||||
err = client.UpdateWorkspaceTTL(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: &truncated,
|
||||
})
|
||||
if err != nil {
|
||||
millis := truncated.Milliseconds()
|
||||
if err = client.UpdateWorkspaceTTL(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTLMillis: &millis,
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("update workspace ttl: %w", err)
|
||||
}
|
||||
|
||||
|
@ -130,7 +131,7 @@ func ttlunset() *cobra.Command {
|
|||
}
|
||||
|
||||
err = client.UpdateWorkspaceTTL(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: nil,
|
||||
TTLMillis: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update workspace ttl: %w", err)
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/coder/coder/cli/clitest"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
|
@ -34,7 +35,7 @@ func TestTTL(t *testing.T) {
|
|||
)
|
||||
|
||||
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: &ttl,
|
||||
TTLMillis: ptr.Ref(ttl.Milliseconds()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -73,7 +74,7 @@ func TestTTL(t *testing.T) {
|
|||
// Ensure ttl updated
|
||||
updated, err := client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
require.Equal(t, ttl.Truncate(time.Minute), *updated.TTL)
|
||||
require.Equal(t, ttl.Truncate(time.Minute), time.Duration(*updated.TTLMillis)*time.Millisecond)
|
||||
require.Contains(t, stdoutBuf.String(), "warning: ttl rounded down")
|
||||
|
||||
// unset schedule
|
||||
|
@ -87,7 +88,7 @@ func TestTTL(t *testing.T) {
|
|||
// Ensure ttl updated
|
||||
updated, err = client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
require.Nil(t, updated.TTL, "expected ttl to not be set")
|
||||
require.Nil(t, updated.TTLMillis, "expected ttl to not be set")
|
||||
})
|
||||
|
||||
t.Run("ZeroInvalid", func(t *testing.T) {
|
||||
|
@ -116,7 +117,7 @@ func TestTTL(t *testing.T) {
|
|||
// Ensure ttl updated
|
||||
updated, err := client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
require.Equal(t, ttl.Truncate(time.Minute), *updated.TTL)
|
||||
require.Equal(t, ttl.Truncate(time.Minute), time.Duration(*updated.TTLMillis)*time.Millisecond)
|
||||
require.Contains(t, stdoutBuf.String(), "warning: ttl rounded down")
|
||||
|
||||
// A TTL of zero is not considered valid.
|
||||
|
@ -131,7 +132,7 @@ func TestTTL(t *testing.T) {
|
|||
// Ensure ttl remains as before
|
||||
updated, err = client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
require.Equal(t, ttl.Truncate(time.Minute), *updated.TTL)
|
||||
require.Equal(t, ttl.Truncate(time.Minute), time.Duration(*updated.TTLMillis)*time.Millisecond)
|
||||
})
|
||||
|
||||
t.Run("Set_NotFound", func(t *testing.T) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"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/google/uuid"
|
||||
|
@ -44,7 +45,7 @@ func TestExecutorAutostartOK(t *testing.T) {
|
|||
sched, err := schedule.Weekly("* * * * *")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched.String(),
|
||||
Schedule: ptr.Ref(sched.String()),
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
|
@ -95,7 +96,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
|
|||
sched, err := schedule.Weekly("* * * * *")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched.String(),
|
||||
Schedule: ptr.Ref(sched.String()),
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
|
@ -138,7 +139,7 @@ func TestExecutorAutostartAlreadyRunning(t *testing.T) {
|
|||
sched, err := schedule.Weekly("* * * * *")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched.String(),
|
||||
Schedule: ptr.Ref(sched.String()),
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
|
@ -316,12 +317,12 @@ func TestExecutorAutostopNotEnabled(t *testing.T) {
|
|||
})
|
||||
// Given: we have a user with a workspace that has no TTL set
|
||||
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.TTL = nil
|
||||
cwr.TTLMillis = nil
|
||||
})
|
||||
)
|
||||
|
||||
// Given: workspace has no TTL set
|
||||
require.Nil(t, workspace.TTL)
|
||||
require.Nil(t, workspace.TTLMillis)
|
||||
|
||||
// Given: workspace is running
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, workspace.LatestBuild.Transition)
|
||||
|
@ -359,7 +360,7 @@ func TestExecutorWorkspaceDeleted(t *testing.T) {
|
|||
sched, err := schedule.Weekly("* * * * *")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched.String(),
|
||||
Schedule: ptr.Ref(sched.String()),
|
||||
}))
|
||||
|
||||
// Given: workspace is deleted
|
||||
|
@ -402,7 +403,7 @@ func TestExecutorWorkspaceAutostartTooEarly(t *testing.T) {
|
|||
sched, err := schedule.Weekly(futureTimeCron)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched.String(),
|
||||
Schedule: ptr.Ref(sched.String()),
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
|
@ -461,7 +462,7 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
|
|||
)
|
||||
|
||||
// Given: the user changes their mind and decides their workspace should not auto-stop
|
||||
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTL: nil})
|
||||
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: nil})
|
||||
require.NoError(t, err)
|
||||
|
||||
// When: the autobuild executor ticks after the deadline
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/fullsailor/pkcs7"
|
||||
|
@ -399,8 +400,8 @@ func CreateWorkspace(t *testing.T, client *codersdk.Client, organization uuid.UU
|
|||
req := codersdk.CreateWorkspaceRequest{
|
||||
TemplateID: templateID,
|
||||
Name: randomUsername(),
|
||||
AutostartSchedule: ptr("CRON_TZ=US/Central * * * * *"),
|
||||
TTL: ptr(8 * time.Hour),
|
||||
AutostartSchedule: ptr.Ref("CRON_TZ=US/Central * * * * *"),
|
||||
TTLMillis: ptr.Ref((8 * time.Hour).Milliseconds()),
|
||||
}
|
||||
for _, mutator := range mutators {
|
||||
mutator(&req)
|
||||
|
@ -602,7 +603,3 @@ type roundTripper func(req *http.Request) (*http.Response, error)
|
|||
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return r(req)
|
||||
}
|
||||
|
||||
func ptr[T any](x T) *T {
|
||||
return &x
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// Package ptr contains some utility methods related to pointers.
|
||||
package ptr
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
type number interface {
|
||||
constraints.Integer | constraints.Float
|
||||
}
|
||||
|
||||
// Ref returns a reference to v.
|
||||
func Ref[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
// NilOrEmpty returns true if s is nil or the empty string.
|
||||
func NilOrEmpty(s *string) bool {
|
||||
return s == nil || *s == ""
|
||||
}
|
||||
|
||||
// NilOrZero returns true if v is nil or 0.
|
||||
func NilOrZero[T number](v *T) bool {
|
||||
return v == nil || *v == 0
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package ptr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
)
|
||||
|
||||
func Test_Ref_Deref(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("String", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
val := "test"
|
||||
p := ptr.Ref(val)
|
||||
assert.Equal(t, &val, p)
|
||||
})
|
||||
|
||||
t.Run("Bool", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
val := true
|
||||
p := ptr.Ref(val)
|
||||
assert.Equal(t, &val, p)
|
||||
})
|
||||
|
||||
t.Run("Int64", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
val := int64(42)
|
||||
p := ptr.Ref(val)
|
||||
assert.Equal(t, &val, p)
|
||||
})
|
||||
|
||||
t.Run("Float64", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
val := float64(3.14159)
|
||||
p := ptr.Ref(val)
|
||||
assert.Equal(t, &val, p)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_NilOrEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
nilString := (*string)(nil)
|
||||
emptyString := ""
|
||||
nonEmptyString := "hi"
|
||||
|
||||
assert.True(t, ptr.NilOrEmpty(nilString))
|
||||
assert.True(t, ptr.NilOrEmpty(&emptyString))
|
||||
assert.False(t, ptr.NilOrEmpty(&nonEmptyString))
|
||||
}
|
||||
|
||||
func Test_NilOrZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
nilInt64 := (*int64)(nil)
|
||||
nilFloat64 := (*float64)(nil)
|
||||
nilDuration := (*time.Duration)(nil)
|
||||
|
||||
zeroInt64 := int64(0)
|
||||
zeroFloat64 := float64(0.0)
|
||||
zeroDuration := time.Duration(0)
|
||||
|
||||
nonZeroInt64 := int64(1)
|
||||
nonZeroFloat64 := float64(3.14159)
|
||||
nonZeroDuration := time.Hour
|
||||
|
||||
assert.True(t, ptr.NilOrZero(nilInt64))
|
||||
assert.True(t, ptr.NilOrZero(nilFloat64))
|
||||
assert.True(t, ptr.NilOrZero(nilDuration))
|
||||
|
||||
assert.True(t, ptr.NilOrZero(&zeroInt64))
|
||||
assert.True(t, ptr.NilOrZero(&zeroFloat64))
|
||||
assert.True(t, ptr.NilOrZero(&zeroDuration))
|
||||
|
||||
assert.False(t, ptr.NilOrZero(&nonZeroInt64))
|
||||
assert.False(t, ptr.NilOrZero(&nonZeroFloat64))
|
||||
assert.False(t, ptr.NilOrZero(&nonZeroDuration))
|
||||
}
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/httpmw"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
|
@ -345,7 +346,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
|||
dbAutostartSchedule.String = *createWorkspace.AutostartSchedule
|
||||
}
|
||||
|
||||
dbTTL, err := validWorkspaceTTL(createWorkspace.TTL)
|
||||
dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: "validate workspace ttl",
|
||||
|
@ -527,20 +528,15 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var dbSched sql.NullString
|
||||
if req.Schedule != "" {
|
||||
validSched, err := schedule.Weekly(req.Schedule)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("invalid autostart schedule: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
dbSched.String = validSched.String()
|
||||
dbSched.Valid = true
|
||||
dbSched, err := validWorkspaceSchedule(req.Schedule)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("invalid autostart schedule: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := api.Database.UpdateWorkspaceAutostart(r.Context(), database.UpdateWorkspaceAutostartParams{
|
||||
err = api.Database.UpdateWorkspaceAutostart(r.Context(), database.UpdateWorkspaceAutostartParams{
|
||||
ID: workspace.ID,
|
||||
AutostartSchedule: dbSched,
|
||||
})
|
||||
|
@ -564,7 +560,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
dbTTL, err := validWorkspaceTTL(req.TTL)
|
||||
dbTTL, err := validWorkspaceTTLMillis(req.TTLMillis)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: "validate workspace ttl",
|
||||
|
@ -830,13 +826,18 @@ func convertWorkspaces(ctx context.Context, db database.Store, workspaces []data
|
|||
}
|
||||
return apiWorkspaces, nil
|
||||
}
|
||||
|
||||
func convertWorkspace(
|
||||
workspace database.Workspace,
|
||||
workspaceBuild database.WorkspaceBuild,
|
||||
job database.ProvisionerJob,
|
||||
template database.Template,
|
||||
owner database.User) codersdk.Workspace {
|
||||
var autostartSchedule *string
|
||||
if workspace.AutostartSchedule.Valid {
|
||||
autostartSchedule = &workspace.AutostartSchedule.String
|
||||
}
|
||||
|
||||
ttlMillis := convertWorkspaceTTLMillis(workspace.Ttl)
|
||||
return codersdk.Workspace{
|
||||
ID: workspace.ID,
|
||||
CreatedAt: workspace.CreatedAt,
|
||||
|
@ -848,25 +849,27 @@ func convertWorkspace(
|
|||
TemplateName: template.Name,
|
||||
Outdated: workspaceBuild.TemplateVersionID.String() != template.ActiveVersionID.String(),
|
||||
Name: workspace.Name,
|
||||
AutostartSchedule: workspace.AutostartSchedule.String,
|
||||
TTL: convertSQLNullInt64(workspace.Ttl),
|
||||
AutostartSchedule: autostartSchedule,
|
||||
TTLMillis: ttlMillis,
|
||||
}
|
||||
}
|
||||
|
||||
func convertSQLNullInt64(i sql.NullInt64) *time.Duration {
|
||||
func convertWorkspaceTTLMillis(i sql.NullInt64) *int64 {
|
||||
if !i.Valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*time.Duration)(&i.Int64)
|
||||
millis := time.Duration(i.Int64).Milliseconds()
|
||||
return &millis
|
||||
}
|
||||
|
||||
func validWorkspaceTTL(ttl *time.Duration) (sql.NullInt64, error) {
|
||||
if ttl == nil {
|
||||
func validWorkspaceTTLMillis(millis *int64) (sql.NullInt64, error) {
|
||||
if ptr.NilOrZero(millis) {
|
||||
return sql.NullInt64{}, nil
|
||||
}
|
||||
|
||||
truncated := ttl.Truncate(time.Minute)
|
||||
dur := time.Duration(*millis) * time.Millisecond
|
||||
truncated := dur.Truncate(time.Minute)
|
||||
if truncated < time.Minute {
|
||||
return sql.NullInt64{}, xerrors.New("ttl must be at least one minute")
|
||||
}
|
||||
|
@ -902,3 +905,19 @@ func validWorkspaceDeadline(old, new time.Time) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validWorkspaceSchedule(s *string) (sql.NullString, error) {
|
||||
if ptr.NilOrEmpty(s) {
|
||||
return sql.NullString{}, nil
|
||||
}
|
||||
|
||||
_, err := schedule.Weekly(*s)
|
||||
if err != nil {
|
||||
return sql.NullString{}, err
|
||||
}
|
||||
|
||||
return sql.NullString{
|
||||
Valid: true,
|
||||
String: *s,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/util/ptr"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -177,8 +178,8 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
|
|||
req := codersdk.CreateWorkspaceRequest{
|
||||
TemplateID: template.ID,
|
||||
Name: "testing",
|
||||
AutostartSchedule: ptr("CRON_TZ=US/Central * * * * *"),
|
||||
TTL: ptr(59 * time.Second),
|
||||
AutostartSchedule: ptr.Ref("CRON_TZ=US/Central * * * * *"),
|
||||
TTLMillis: ptr.Ref((59 * time.Second).Milliseconds()),
|
||||
}
|
||||
_, err := client.CreateWorkspace(context.Background(), template.OrganizationID, req)
|
||||
require.Error(t, err)
|
||||
|
@ -197,8 +198,8 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
|
|||
req := codersdk.CreateWorkspaceRequest{
|
||||
TemplateID: template.ID,
|
||||
Name: "testing",
|
||||
AutostartSchedule: ptr("CRON_TZ=US/Central * * * * *"),
|
||||
TTL: ptr(24*7*time.Hour + time.Minute),
|
||||
AutostartSchedule: ptr.Ref("CRON_TZ=US/Central * * * * *"),
|
||||
TTLMillis: ptr.Ref((24*7*time.Hour + time.Minute).Milliseconds()),
|
||||
}
|
||||
_, err := client.CreateWorkspace(context.Background(), template.OrganizationID, req)
|
||||
require.Error(t, err)
|
||||
|
@ -451,7 +452,7 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
|
||||
testCases := []struct {
|
||||
name string
|
||||
schedule string
|
||||
schedule *string
|
||||
expectedError string
|
||||
at time.Time
|
||||
expectedNext time.Time
|
||||
|
@ -459,12 +460,12 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "disable autostart",
|
||||
schedule: "",
|
||||
schedule: ptr.Ref(""),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "friday to monday",
|
||||
schedule: "CRON_TZ=Europe/Dublin 30 9 * * 1-5",
|
||||
schedule: ptr.Ref("CRON_TZ=Europe/Dublin 30 9 * * 1-5"),
|
||||
expectedError: "",
|
||||
at: time.Date(2022, 5, 6, 9, 31, 0, 0, dublinLoc),
|
||||
expectedNext: time.Date(2022, 5, 9, 9, 30, 0, 0, dublinLoc),
|
||||
|
@ -472,7 +473,7 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "monday to tuesday",
|
||||
schedule: "CRON_TZ=Europe/Dublin 30 9 * * 1-5",
|
||||
schedule: ptr.Ref("CRON_TZ=Europe/Dublin 30 9 * * 1-5"),
|
||||
expectedError: "",
|
||||
at: time.Date(2022, 5, 9, 9, 31, 0, 0, dublinLoc),
|
||||
expectedNext: time.Date(2022, 5, 10, 9, 30, 0, 0, dublinLoc),
|
||||
|
@ -481,7 +482,7 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
{
|
||||
// DST in Ireland began on Mar 27 in 2022 at 0100. Forward 1 hour.
|
||||
name: "DST start",
|
||||
schedule: "CRON_TZ=Europe/Dublin 30 9 * * *",
|
||||
schedule: ptr.Ref("CRON_TZ=Europe/Dublin 30 9 * * *"),
|
||||
expectedError: "",
|
||||
at: time.Date(2022, 3, 26, 9, 31, 0, 0, dublinLoc),
|
||||
expectedNext: time.Date(2022, 3, 27, 9, 30, 0, 0, dublinLoc),
|
||||
|
@ -490,7 +491,7 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
{
|
||||
// DST in Ireland ends on Oct 30 in 2022 at 0200. Back 1 hour.
|
||||
name: "DST end",
|
||||
schedule: "CRON_TZ=Europe/Dublin 30 9 * * *",
|
||||
schedule: ptr.Ref("CRON_TZ=Europe/Dublin 30 9 * * *"),
|
||||
expectedError: "",
|
||||
at: time.Date(2022, 10, 29, 9, 31, 0, 0, dublinLoc),
|
||||
expectedNext: time.Date(2022, 10, 30, 9, 30, 0, 0, dublinLoc),
|
||||
|
@ -498,17 +499,17 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "invalid location",
|
||||
schedule: "CRON_TZ=Imaginary/Place 30 9 * * 1-5",
|
||||
schedule: ptr.Ref("CRON_TZ=Imaginary/Place 30 9 * * 1-5"),
|
||||
expectedError: "status code 500: invalid autostart schedule: parse schedule: provided bad location Imaginary/Place: unknown time zone Imaginary/Place",
|
||||
},
|
||||
{
|
||||
name: "invalid schedule",
|
||||
schedule: "asdf asdf asdf ",
|
||||
schedule: ptr.Ref("asdf asdf asdf "),
|
||||
expectedError: `status code 500: invalid autostart schedule: validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix`,
|
||||
},
|
||||
{
|
||||
name: "only 3 values",
|
||||
schedule: "CRON_TZ=Europe/Dublin 30 9 *",
|
||||
schedule: ptr.Ref("CRON_TZ=Europe/Dublin 30 9 *"),
|
||||
expectedError: `status code 500: invalid autostart schedule: validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix`,
|
||||
},
|
||||
}
|
||||
|
@ -526,7 +527,7 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.AutostartSchedule = nil
|
||||
cwr.TTL = nil
|
||||
cwr.TTLMillis = nil
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -547,12 +548,14 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
updated, err := client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
|
||||
require.Equal(t, testCase.schedule, updated.AutostartSchedule, "expected autostart schedule to equal requested")
|
||||
|
||||
if testCase.schedule == "" {
|
||||
if testCase.schedule == nil || *testCase.schedule == "" {
|
||||
require.Nil(t, updated.AutostartSchedule)
|
||||
return
|
||||
}
|
||||
sched, err := schedule.Weekly(updated.AutostartSchedule)
|
||||
|
||||
require.EqualValues(t, *testCase.schedule, *updated.AutostartSchedule, "expected autostart schedule to equal requested")
|
||||
|
||||
sched, err := schedule.Weekly(*updated.AutostartSchedule)
|
||||
require.NoError(t, err, "parse returned schedule")
|
||||
|
||||
next := sched.Next(testCase.at)
|
||||
|
@ -569,7 +572,7 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
|
|||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
wsid = uuid.New()
|
||||
req = codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: "9 30 1-5",
|
||||
Schedule: ptr.Ref("9 30 1-5"),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -586,32 +589,32 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
|
|||
|
||||
testCases := []struct {
|
||||
name string
|
||||
ttl *time.Duration
|
||||
ttlMillis *int64
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "disable ttl",
|
||||
ttl: nil,
|
||||
ttlMillis: nil,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "below minimum ttl",
|
||||
ttl: ptr(30 * time.Second),
|
||||
ttlMillis: ptr.Ref((30 * time.Second).Milliseconds()),
|
||||
expectedError: "ttl must be at least one minute",
|
||||
},
|
||||
{
|
||||
name: "minimum ttl",
|
||||
ttl: ptr(time.Minute),
|
||||
ttlMillis: ptr.Ref(time.Minute.Milliseconds()),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "maximum ttl",
|
||||
ttl: ptr(24 * 7 * time.Hour),
|
||||
ttlMillis: ptr.Ref((24 * 7 * time.Hour).Milliseconds()),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "above maximum ttl",
|
||||
ttl: ptr(24*7*time.Hour + time.Minute),
|
||||
ttlMillis: ptr.Ref((24*7*time.Hour + time.Minute).Milliseconds()),
|
||||
expectedError: "ttl must be less than 7 days",
|
||||
},
|
||||
}
|
||||
|
@ -629,15 +632,15 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
|
|||
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.AutostartSchedule = nil
|
||||
cwr.TTL = nil
|
||||
cwr.TTLMillis = nil
|
||||
})
|
||||
)
|
||||
|
||||
// ensure test invariant: new workspaces have no autostop schedule.
|
||||
require.Nil(t, workspace.TTL, "expected newly-minted workspace to have no TTL")
|
||||
require.Nil(t, workspace.TTLMillis, "expected newly-minted workspace to have no TTL")
|
||||
|
||||
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: testCase.ttl,
|
||||
TTLMillis: testCase.ttlMillis,
|
||||
})
|
||||
|
||||
if testCase.expectedError != "" {
|
||||
|
@ -650,7 +653,7 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
|
|||
updated, err := client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch updated workspace")
|
||||
|
||||
require.Equal(t, testCase.ttl, updated.TTL, "expected autostop ttl to equal requested")
|
||||
require.Equal(t, testCase.ttlMillis, updated.TTLMillis, "expected autostop ttl to equal requested")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -661,7 +664,7 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
|
|||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
wsid = uuid.New()
|
||||
req = codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: ptr(time.Hour),
|
||||
TTLMillis: ptr.Ref(time.Hour.Milliseconds()),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -685,8 +688,8 @@ func TestWorkspaceExtend(t *testing.T) {
|
|||
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
|
||||
extend = 90 * time.Minute
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
oldDeadline = time.Now().Add(*workspace.TTL).UTC()
|
||||
newDeadline = time.Now().Add(*workspace.TTL + extend).UTC()
|
||||
oldDeadline = time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond).UTC()
|
||||
newDeadline = time.Now().Add(time.Duration(*workspace.TTLMillis)*time.Millisecond + extend).UTC()
|
||||
)
|
||||
|
||||
workspace, err := client.Workspace(ctx, workspace.ID)
|
||||
|
@ -761,7 +764,3 @@ func mustLocation(t *testing.T, location string) *time.Location {
|
|||
|
||||
return loc
|
||||
}
|
||||
|
||||
func ptr[T any](x T) *T {
|
||||
return &x
|
||||
}
|
||||
|
|
|
@ -65,10 +65,10 @@ type CreateTemplateRequest struct {
|
|||
|
||||
// CreateWorkspaceRequest provides options for creating a new workspace.
|
||||
type CreateWorkspaceRequest struct {
|
||||
TemplateID uuid.UUID `json:"template_id" validate:"required"`
|
||||
Name string `json:"name" validate:"username,required"`
|
||||
AutostartSchedule *string `json:"autostart_schedule"`
|
||||
TTL *time.Duration `json:"ttl"`
|
||||
TemplateID uuid.UUID `json:"template_id" validate:"required"`
|
||||
Name string `json:"name" validate:"username,required"`
|
||||
AutostartSchedule *string `json:"autostart_schedule"`
|
||||
TTLMillis *int64 `json:"ttl_ms,omitempty"`
|
||||
// ParameterValues allows for additional parameters to be provided
|
||||
// during the initial provision.
|
||||
ParameterValues []CreateParameterRequest `json:"parameter_values,omitempty"`
|
||||
|
|
|
@ -26,8 +26,8 @@ type Workspace struct {
|
|||
LatestBuild WorkspaceBuild `json:"latest_build"`
|
||||
Outdated bool `json:"outdated"`
|
||||
Name string `json:"name"`
|
||||
AutostartSchedule string `json:"autostart_schedule"`
|
||||
TTL *time.Duration `json:"ttl"`
|
||||
AutostartSchedule *string `json:"autostart_schedule,omitempty"`
|
||||
TTLMillis *int64 `json:"ttl_ms,omitempty"`
|
||||
}
|
||||
|
||||
// CreateWorkspaceBuildRequest provides options to update the latest workspace build.
|
||||
|
@ -139,7 +139,7 @@ func (c *Client) WatchWorkspace(ctx context.Context, id uuid.UUID) (<-chan Works
|
|||
|
||||
// UpdateWorkspaceAutostartRequest is a request to update a workspace's autostart schedule.
|
||||
type UpdateWorkspaceAutostartRequest struct {
|
||||
Schedule string `json:"schedule"`
|
||||
Schedule *string `json:"schedule"`
|
||||
}
|
||||
|
||||
// UpdateWorkspaceAutostart sets the autostart schedule for workspace by id.
|
||||
|
@ -159,7 +159,7 @@ func (c *Client) UpdateWorkspaceAutostart(ctx context.Context, id uuid.UUID, req
|
|||
|
||||
// UpdateWorkspaceTTLRequest is a request to update a workspace's TTL.
|
||||
type UpdateWorkspaceTTLRequest struct {
|
||||
TTL *time.Duration `json:"ttl"`
|
||||
TTLMillis *int64 `json:"ttl_ms"`
|
||||
}
|
||||
|
||||
// UpdateWorkspaceTTL sets the ttl for workspace by id.
|
||||
|
|
|
@ -33,7 +33,7 @@ variable "region" {
|
|||
description = "What region should your workspace live in?"
|
||||
default = "us-east-1"
|
||||
validation {
|
||||
condition = contains([
|
||||
condition = contains([
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-northeast-3",
|
||||
|
|
|
@ -30,7 +30,7 @@ variable "region" {
|
|||
description = "What region should your workspace live in?"
|
||||
default = "us-east-1"
|
||||
validation {
|
||||
condition = contains([
|
||||
condition = contains([
|
||||
"ap-northeast-1",
|
||||
"ap-northeast-2",
|
||||
"ap-northeast-3",
|
||||
|
|
|
@ -101,8 +101,7 @@ export interface CreateWorkspaceRequest {
|
|||
readonly template_id: string
|
||||
readonly name: string
|
||||
readonly autostart_schedule?: string
|
||||
// This is likely an enum in an external package ("time.Duration")
|
||||
readonly ttl?: number
|
||||
readonly ttl_ms?: number
|
||||
readonly parameter_values?: CreateParameterRequest[]
|
||||
}
|
||||
|
||||
|
@ -301,13 +300,12 @@ export interface UpdateUserProfileRequest {
|
|||
|
||||
// From codersdk/workspaces.go:141:6
|
||||
export interface UpdateWorkspaceAutostartRequest {
|
||||
readonly schedule: string
|
||||
readonly schedule?: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go:161:6
|
||||
export interface UpdateWorkspaceTTLRequest {
|
||||
// This is likely an enum in an external package ("time.Duration")
|
||||
readonly ttl?: number
|
||||
readonly ttl_ms?: number
|
||||
}
|
||||
|
||||
// From codersdk/files.go:16:6
|
||||
|
@ -372,9 +370,8 @@ export interface Workspace {
|
|||
readonly latest_build: WorkspaceBuild
|
||||
readonly outdated: boolean
|
||||
readonly name: string
|
||||
readonly autostart_schedule: string
|
||||
// This is likely an enum in an external package ("time.Duration")
|
||||
readonly ttl?: number
|
||||
readonly autostart_schedule?: string
|
||||
readonly ttl_ms?: number
|
||||
}
|
||||
|
||||
// From codersdk/workspaceresources.go:31:6
|
||||
|
|
|
@ -19,6 +19,20 @@ export default {
|
|||
|
||||
const Template: Story<WorkspaceScheduleProps> = (args) => <WorkspaceSchedule {...args} />
|
||||
|
||||
export const NoScheduleNoTTL = Template.bind({})
|
||||
NoScheduleNoTTL.args = {
|
||||
workspace: {
|
||||
...Mocks.MockWorkspace,
|
||||
|
||||
latest_build: {
|
||||
...Mocks.MockWorkspaceBuild,
|
||||
transition: "stop",
|
||||
},
|
||||
autostart_schedule: undefined,
|
||||
ttl_ms: undefined,
|
||||
},
|
||||
}
|
||||
|
||||
export const NoTTL = Template.bind({})
|
||||
NoTTL.args = {
|
||||
workspace: {
|
||||
|
@ -29,7 +43,7 @@ NoTTL.args = {
|
|||
// SEE: #1834
|
||||
deadline: "0001-01-01T00:00:00Z",
|
||||
},
|
||||
ttl: undefined,
|
||||
ttl_ms: undefined,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -42,7 +56,7 @@ ShutdownSoon.args = {
|
|||
deadline: dayjs().add(ONE, "hour").utc().format(),
|
||||
transition: "start",
|
||||
},
|
||||
ttl: 2 * 60 * 60 * 1000 * 1_000_000, // 2 hours
|
||||
ttl_ms: 2 * 60 * 60 * 1000, // 2 hours
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -56,7 +70,7 @@ ShutdownLong.args = {
|
|||
deadline: dayjs().add(SEVEN, "days").utc().format(),
|
||||
transition: "start",
|
||||
},
|
||||
ttl: 7 * 24 * 60 * 60 * 1000 * 1_000_000, // 7 days
|
||||
ttl_ms: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -69,7 +83,7 @@ WorkspaceOffShort.args = {
|
|||
...Mocks.MockWorkspaceBuild,
|
||||
transition: "stop",
|
||||
},
|
||||
ttl: 2 * 60 * 60 * 1000 * 1_000_000, // 2 hours
|
||||
ttl_ms: 2 * 60 * 60 * 1000, // 2 hours
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -82,6 +96,6 @@ WorkspaceOffLong.args = {
|
|||
...Mocks.MockWorkspaceBuild,
|
||||
transition: "stop",
|
||||
},
|
||||
ttl: 2 * 365 * 24 * 60 * 60 * 1000 * 1_000_000, // 2 years
|
||||
ttl_ms: 2 * 365 * 24 * 60 * 60 * 1000, // 2 years
|
||||
},
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ dayjs.extend(duration)
|
|||
dayjs.extend(relativeTime)
|
||||
|
||||
export const Language = {
|
||||
autoStartDisplay: (schedule: string): string => {
|
||||
autoStartDisplay: (schedule: string | undefined): string => {
|
||||
if (schedule) {
|
||||
return cronstrue.toString(stripTimezone(schedule), { throwExceptionOnParseError: false })
|
||||
}
|
||||
return "Manual"
|
||||
},
|
||||
autoStartLabel: (schedule: string): string => {
|
||||
autoStartLabel: (schedule: string | undefined): string => {
|
||||
const prefix = "Start"
|
||||
|
||||
if (schedule) {
|
||||
|
@ -40,7 +40,7 @@ export const Language = {
|
|||
// a mannual shutdown has a deadline of '"0001-01-01T00:00:00Z"'
|
||||
// SEE: #1834
|
||||
const hasDeadline = deadline.year() > 1
|
||||
const ttl = workspace.ttl
|
||||
const ttl = workspace.ttl_ms
|
||||
|
||||
if (isWorkspaceOn(workspace) && hasDeadline) {
|
||||
// Workspace is on --> derive from latest_build.deadline. Note that the
|
||||
|
@ -61,7 +61,7 @@ export const Language = {
|
|||
} else {
|
||||
// The workspace has a ttl set, but is either in an unknown state or is
|
||||
// not running. Therefore, we derive from workspace.ttl.
|
||||
const duration = dayjs.duration(ttl / 1_000_000, "milliseconds")
|
||||
const duration = dayjs.duration(ttl, "milliseconds")
|
||||
return `${duration.humanize()} after start`
|
||||
}
|
||||
},
|
||||
|
|
|
@ -123,7 +123,7 @@ describe("WorkspaceSchedulePage", () => {
|
|||
ttl: 0,
|
||||
},
|
||||
{
|
||||
ttl: undefined,
|
||||
ttl_ms: undefined,
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -133,7 +133,7 @@ describe("WorkspaceSchedulePage", () => {
|
|||
ttl: 2,
|
||||
},
|
||||
{
|
||||
ttl: 7_200_000_000_000,
|
||||
ttl_ms: 7_200_000,
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -143,7 +143,7 @@ describe("WorkspaceSchedulePage", () => {
|
|||
ttl: 8,
|
||||
},
|
||||
{
|
||||
ttl: 28_800_000_000_000,
|
||||
ttl_ms: 28_800_000,
|
||||
},
|
||||
],
|
||||
])(`formValuesToTTLRequest(%p) returns %p`, (values, request) => {
|
||||
|
@ -157,8 +157,8 @@ describe("WorkspaceSchedulePage", () => {
|
|||
[
|
||||
{
|
||||
...Mocks.MockWorkspace,
|
||||
autostart_schedule: "",
|
||||
ttl: undefined,
|
||||
autostart_schedule: undefined,
|
||||
ttl_ms: undefined,
|
||||
},
|
||||
{
|
||||
sunday: false,
|
||||
|
@ -179,7 +179,7 @@ describe("WorkspaceSchedulePage", () => {
|
|||
{
|
||||
...Mocks.MockWorkspace,
|
||||
autostart_schedule: "",
|
||||
ttl: 7_200_000_000_000,
|
||||
ttl_ms: 7_200_000,
|
||||
},
|
||||
{
|
||||
sunday: false,
|
||||
|
@ -203,7 +203,7 @@ describe("WorkspaceSchedulePage", () => {
|
|||
{
|
||||
...Mocks.MockWorkspace,
|
||||
autostart_schedule: "CRON_TZ=UTC 30 9 * * 1-5",
|
||||
ttl: 7_200_000_000_000,
|
||||
ttl_ms: 7_200_000,
|
||||
},
|
||||
{
|
||||
sunday: false,
|
||||
|
@ -224,7 +224,7 @@ describe("WorkspaceSchedulePage", () => {
|
|||
{
|
||||
...Mocks.MockWorkspace,
|
||||
autostart_schedule: "CRON_TZ=Canada/Eastern 20 16 * * 1,3-4,6",
|
||||
ttl: 28_800_000_000_000,
|
||||
ttl_ms: 28_800_000,
|
||||
},
|
||||
{
|
||||
sunday: false,
|
||||
|
|
|
@ -87,13 +87,13 @@ export const formValuesToAutoStartRequest = (
|
|||
export const formValuesToTTLRequest = (values: WorkspaceScheduleFormValues): TypesGen.UpdateWorkspaceTTLRequest => {
|
||||
return {
|
||||
// minutes to nanoseconds
|
||||
ttl: values.ttl ? values.ttl * 60 * 60 * 1000 * 1_000_000 : undefined,
|
||||
ttl_ms: values.ttl ? values.ttl * 60 * 60 * 1000 : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export const workspaceToInitialValues = (workspace: TypesGen.Workspace): WorkspaceScheduleFormValues => {
|
||||
const schedule = workspace.autostart_schedule
|
||||
const ttl = workspace.ttl ? workspace.ttl / (1_000_000 * 1000 * 60 * 60) : 0
|
||||
const ttl = workspace.ttl_ms ? workspace.ttl_ms / (1000 * 60 * 60) : 0
|
||||
|
||||
if (!schedule) {
|
||||
return {
|
||||
|
|
|
@ -164,7 +164,7 @@ export const MockWorkspace: TypesGen.Workspace = {
|
|||
owner_id: MockUser.id,
|
||||
owner_name: MockUser.username,
|
||||
autostart_schedule: MockWorkspaceAutostartEnabled.schedule,
|
||||
ttl: 2 * 60 * 60 * 1000 * 1_000_000, // 2 hours as nanoseconds
|
||||
ttl_ms: 2 * 60 * 60 * 1000, // 2 hours as milliseconds
|
||||
latest_build: MockWorkspaceBuild,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue