Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a7c0920

Browse files
committed
feat: Add codersdk.NullTime, change workspace build deadline
Fixes #2015
1 parent f142345 commit a7c0920

File tree

12 files changed

+248
-35
lines changed

12 files changed

+248
-35
lines changed

cli/list.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ func list() *cobra.Command {
7171
if !ptr.NilOrZero(workspace.TTLMillis) {
7272
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
7373
autostopDisplay = durationDisplay(dur)
74-
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.After(now) && status == "Running" {
75-
remaining := time.Until(workspace.LatestBuild.Deadline)
74+
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.Time.After(now) && status == "Running" {
75+
remaining := time.Until(workspace.LatestBuild.Deadline.Time)
7676
autostopDisplay = fmt.Sprintf("%s (%s)", autostopDisplay, relative(remaining))
7777
}
7878
}

cli/schedule.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ func displaySchedule(workspace codersdk.Workspace, out io.Writer) error {
280280
if workspace.LatestBuild.Transition != "start" {
281281
schedNextStop = "-"
282282
} else {
283-
schedNextStop = workspace.LatestBuild.Deadline.In(loc).Format(timeFormat + " on " + dateFormat)
284-
schedNextStop = fmt.Sprintf("%s (in %s)", schedNextStop, durationDisplay(time.Until(workspace.LatestBuild.Deadline)))
283+
schedNextStop = workspace.LatestBuild.Deadline.Time.In(loc).Format(timeFormat + " on " + dateFormat)
284+
schedNextStop = fmt.Sprintf("%s (in %s)", schedNextStop, durationDisplay(time.Until(workspace.LatestBuild.Deadline.Time)))
285285
}
286286
}
287287

cli/schedule_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ func TestScheduleOverride(t *testing.T) {
239239

240240
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
241241
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
242-
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
242+
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline.Time, time.Minute)
243243

244244
cmd, root := clitest.New(t, cmdArgs...)
245245
clitest.SetupConfig(t, client, root)
@@ -252,7 +252,7 @@ func TestScheduleOverride(t *testing.T) {
252252
// Then: the deadline of the latest build is updated assuming the units are minutes
253253
updated, err := client.Workspace(ctx, workspace.ID)
254254
require.NoError(t, err)
255-
require.WithinDuration(t, expectedDeadline, updated.LatestBuild.Deadline, time.Minute)
255+
require.WithinDuration(t, expectedDeadline, updated.LatestBuild.Deadline.Time, time.Minute)
256256
})
257257

258258
t.Run("InvalidDuration", func(t *testing.T) {
@@ -279,7 +279,7 @@ func TestScheduleOverride(t *testing.T) {
279279

280280
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
281281
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
282-
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
282+
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline.Time, time.Minute)
283283

284284
cmd, root := clitest.New(t, cmdArgs...)
285285
clitest.SetupConfig(t, client, root)

cli/ssh.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ import (
3333
"github.com/coder/coder/peer/peerwg"
3434
)
3535

36-
var workspacePollInterval = time.Minute
37-
var autostopNotifyCountdown = []time.Duration{30 * time.Minute}
36+
var (
37+
workspacePollInterval = time.Minute
38+
autostopNotifyCountdown = []time.Duration{30 * time.Minute}
39+
)
3840

3941
func ssh() *cobra.Command {
4042
var (
@@ -385,7 +387,7 @@ func notifyCondition(ctx context.Context, client *codersdk.Client, workspaceID u
385387
return time.Time{}, nil
386388
}
387389

388-
deadline = ws.LatestBuild.Deadline
390+
deadline = ws.LatestBuild.Deadline.Time
389391
callback = func() {
390392
ttl := deadline.Sub(now)
391393
var title, body string

coderd/autobuild/executor/lifecycle_executor_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func TestExecutorAutostopOK(t *testing.T) {
193193

194194
// When: the autobuild executor ticks *after* the deadline:
195195
go func() {
196-
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
196+
tickCh <- workspace.LatestBuild.Deadline.Time.Add(time.Minute)
197197
close(tickCh)
198198
}()
199199

@@ -229,15 +229,15 @@ func TestExecutorAutostopExtend(t *testing.T) {
229229
require.NotZero(t, originalDeadline)
230230

231231
// Given: we extend the workspace deadline
232-
newDeadline := originalDeadline.Add(30 * time.Minute)
232+
newDeadline := originalDeadline.Time.Add(30 * time.Minute)
233233
err := client.PutExtendWorkspace(ctx, workspace.ID, codersdk.PutExtendWorkspaceRequest{
234234
Deadline: newDeadline,
235235
})
236236
require.NoError(t, err, "extend workspace deadline")
237237

238238
// When: the autobuild executor ticks *after* the original deadline:
239239
go func() {
240-
tickCh <- originalDeadline.Add(time.Minute)
240+
tickCh <- originalDeadline.Time.Add(time.Minute)
241241
}()
242242

243243
// Then: nothing should happen and the workspace should stay running
@@ -281,7 +281,7 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) {
281281

282282
// When: the autobuild executor ticks past the TTL
283283
go func() {
284-
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
284+
tickCh <- workspace.LatestBuild.Deadline.Time.Add(time.Minute)
285285
close(tickCh)
286286
}()
287287

@@ -323,7 +323,7 @@ func TestExecutorAutostopNotEnabled(t *testing.T) {
323323

324324
// When: the autobuild executor ticks past the TTL
325325
go func() {
326-
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
326+
tickCh <- workspace.LatestBuild.Deadline.Time.Add(time.Minute)
327327
close(tickCh)
328328
}()
329329

@@ -415,7 +415,7 @@ func TestExecutorWorkspaceAutostopBeforeDeadline(t *testing.T) {
415415

416416
// When: the autobuild executor ticks before the TTL
417417
go func() {
418-
tickCh <- workspace.LatestBuild.Deadline.Add(-1 * time.Minute)
418+
tickCh <- workspace.LatestBuild.Deadline.Time.Add(-1 * time.Minute)
419419
close(tickCh)
420420
}()
421421

@@ -447,11 +447,11 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
447447

448448
// Then: the deadline should still be the original value
449449
updated := coderdtest.MustWorkspace(t, client, workspace.ID)
450-
assert.WithinDuration(t, workspace.LatestBuild.Deadline, updated.LatestBuild.Deadline, time.Minute)
450+
assert.WithinDuration(t, workspace.LatestBuild.Deadline.Time, updated.LatestBuild.Deadline.Time, time.Minute)
451451

452452
// When: the autobuild executor ticks after the original deadline
453453
go func() {
454-
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
454+
tickCh <- workspace.LatestBuild.Deadline.Time.Add(time.Minute)
455455
}()
456456

457457
// Then: the workspace should stop
@@ -478,7 +478,7 @@ func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
478478

479479
// When: the relentless onward march of time continues
480480
go func() {
481-
tickCh <- workspace.LatestBuild.Deadline.Add(newTTL + time.Minute)
481+
tickCh <- workspace.LatestBuild.Deadline.Time.Add(newTTL + time.Minute)
482482
close(tickCh)
483483
}()
484484

coderd/workspacebuilds.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,8 @@ func convertWorkspaceBuild(
638638
buildInitiator *database.User,
639639
workspace database.Workspace,
640640
workspaceBuild database.WorkspaceBuild,
641-
job database.ProvisionerJob) codersdk.WorkspaceBuild {
641+
job database.ProvisionerJob,
642+
) codersdk.WorkspaceBuild {
642643
//nolint:unconvert
643644
if workspace.ID != workspaceBuild.WorkspaceID {
644645
panic("workspace and build do not match")
@@ -671,7 +672,7 @@ func convertWorkspaceBuild(
671672
InitiatorID: workspaceBuild.InitiatorID,
672673
InitiatorUsername: initiatorName,
673674
Job: convertProvisionerJob(job),
674-
Deadline: workspaceBuild.Deadline,
675+
Deadline: codersdk.NewNullTime(workspaceBuild.Deadline, !workspaceBuild.Deadline.IsZero()),
675676
Reason: codersdk.BuildReason(workspaceBuild.Reason),
676677
}
677678
}

coderd/workspaces_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ func TestWorkspaceExtend(t *testing.T) {
11381138

11391139
workspace, err := client.Workspace(ctx, workspace.ID)
11401140
require.NoError(t, err, "fetch provisioned workspace")
1141-
oldDeadline := workspace.LatestBuild.Deadline
1141+
oldDeadline := workspace.LatestBuild.Deadline.Time
11421142

11431143
// Updating the deadline should succeed
11441144
req := codersdk.PutExtendWorkspaceRequest{
@@ -1150,7 +1150,7 @@ func TestWorkspaceExtend(t *testing.T) {
11501150
// Ensure deadline set correctly
11511151
updated, err := client.Workspace(ctx, workspace.ID)
11521152
require.NoError(t, err, "failed to fetch updated workspace")
1153-
require.WithinDuration(t, newDeadline, updated.LatestBuild.Deadline, time.Minute)
1153+
require.WithinDuration(t, newDeadline, updated.LatestBuild.Deadline.Time, time.Minute)
11541154

11551155
// Zero time should fail
11561156
err = client.PutExtendWorkspace(ctx, workspace.ID, codersdk.PutExtendWorkspaceRequest{
@@ -1189,7 +1189,7 @@ func TestWorkspaceExtend(t *testing.T) {
11891189
// Ensure deadline still set correctly
11901190
updated, err = client.Workspace(ctx, workspace.ID)
11911191
require.NoError(t, err, "failed to fetch updated workspace")
1192-
require.WithinDuration(t, oldDeadline.Add(-time.Hour), updated.LatestBuild.Deadline, time.Minute)
1192+
require.WithinDuration(t, oldDeadline.Add(-time.Hour), updated.LatestBuild.Deadline.Time, time.Minute)
11931193
}
11941194

11951195
func TestWorkspaceWatcher(t *testing.T) {

codersdk/time.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package codersdk
2+
3+
import (
4+
"bytes"
5+
"database/sql"
6+
"encoding/json"
7+
"time"
8+
9+
"golang.org/x/xerrors"
10+
)
11+
12+
var nullBytes = []byte("null")
13+
14+
// NullTime represents a nullable time.Time.
15+
// @typescript-ignore NullTime
16+
type NullTime struct {
17+
sql.NullTime
18+
}
19+
20+
// NewNullTime returns a new NullTime with the given time.Time.
21+
func NewNullTime(t time.Time, valid bool) NullTime {
22+
return NullTime{
23+
NullTime: sql.NullTime{
24+
Time: t,
25+
Valid: valid,
26+
},
27+
}
28+
}
29+
30+
// MarshalJSON implements json.Marshaler.
31+
// It will encode null if this time is null.
32+
func (t NullTime) MarshalJSON() ([]byte, error) {
33+
if !t.Valid {
34+
return []byte("null"), nil
35+
}
36+
b, err := t.Time.MarshalJSON()
37+
if err != nil {
38+
return nil, xerrors.Errorf("codersdk.NullTime: json encode failed: %w", err)
39+
}
40+
return b, nil
41+
}
42+
43+
// UnmarshalJSON implements json.Unmarshaler.
44+
// It supports string and null input.
45+
func (t *NullTime) UnmarshalJSON(data []byte) error {
46+
t.Valid = false
47+
if bytes.Equal(data, nullBytes) {
48+
return nil
49+
}
50+
err := json.Unmarshal(data, &t.Time)
51+
if err != nil {
52+
return xerrors.Errorf("codersdk.NullTime: json decode failed: %w", err)
53+
}
54+
t.Valid = true
55+
return nil
56+
}
57+
58+
// IsZero return true if the time is null or zero.
59+
func (t NullTime) IsZero() bool {
60+
return !t.Valid || t.Time.IsZero()
61+
}

0 commit comments

Comments
 (0)