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

Skip to content

Commit 78a2494

Browse files
mafredrijsjoeio
andauthored
feat: Add codersdk.NullTime, change workspace build deadline (#3552)
Fixes #2015 Co-authored-by: Joe Previte <[email protected]>
1 parent a21a6d2 commit 78a2494

18 files changed

+271
-63
lines changed

cli/list.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ func workspaceListRowFromWorkspace(now time.Time, usersByID map[uuid.UUID]coders
3838
if !ptr.NilOrZero(workspace.TTLMillis) {
3939
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
4040
autostopDisplay = durationDisplay(dur)
41-
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.After(now) && status == "Running" {
42-
remaining := time.Until(workspace.LatestBuild.Deadline)
41+
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.Time.After(now) && status == "Running" {
42+
remaining := time.Until(workspace.LatestBuild.Deadline.Time)
4343
autostopDisplay = fmt.Sprintf("%s (%s)", autostopDisplay, relative(remaining))
4444
}
4545
}

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
@@ -1169,7 +1169,7 @@ func TestWorkspaceExtend(t *testing.T) {
11691169

11701170
workspace, err := client.Workspace(ctx, workspace.ID)
11711171
require.NoError(t, err, "fetch provisioned workspace")
1172-
oldDeadline := workspace.LatestBuild.Deadline
1172+
oldDeadline := workspace.LatestBuild.Deadline.Time
11731173

11741174
// Updating the deadline should succeed
11751175
req := codersdk.PutExtendWorkspaceRequest{
@@ -1181,7 +1181,7 @@ func TestWorkspaceExtend(t *testing.T) {
11811181
// Ensure deadline set correctly
11821182
updated, err := client.Workspace(ctx, workspace.ID)
11831183
require.NoError(t, err, "failed to fetch updated workspace")
1184-
require.WithinDuration(t, newDeadline, updated.LatestBuild.Deadline, time.Minute)
1184+
require.WithinDuration(t, newDeadline, updated.LatestBuild.Deadline.Time, time.Minute)
11851185

11861186
// Zero time should fail
11871187
err = client.PutExtendWorkspace(ctx, workspace.ID, codersdk.PutExtendWorkspaceRequest{
@@ -1220,7 +1220,7 @@ func TestWorkspaceExtend(t *testing.T) {
12201220
// Ensure deadline still set correctly
12211221
updated, err = client.Workspace(ctx, workspace.ID)
12221222
require.NoError(t, err, "failed to fetch updated workspace")
1223-
require.WithinDuration(t, oldDeadline.Add(-time.Hour), updated.LatestBuild.Deadline, time.Minute)
1223+
require.WithinDuration(t, oldDeadline.Add(-time.Hour), updated.LatestBuild.Deadline.Time, time.Minute)
12241224
}
12251225

12261226
func TestWorkspaceWatcher(t *testing.T) {

codersdk/time.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
func (t NullTime) MarshalJSON() ([]byte, error) {
32+
if !t.Valid {
33+
return []byte("null"), nil
34+
}
35+
b, err := t.Time.MarshalJSON()
36+
if err != nil {
37+
return nil, xerrors.Errorf("codersdk.NullTime: json encode failed: %w", err)
38+
}
39+
return b, nil
40+
}
41+
42+
// UnmarshalJSON implements json.Unmarshaler.
43+
func (t *NullTime) UnmarshalJSON(data []byte) error {
44+
t.Valid = false
45+
if bytes.Equal(data, nullBytes) {
46+
return nil
47+
}
48+
err := json.Unmarshal(data, &t.Time)
49+
if err != nil {
50+
return xerrors.Errorf("codersdk.NullTime: json decode failed: %w", err)
51+
}
52+
t.Valid = true
53+
return nil
54+
}
55+
56+
// IsZero return true if the time is null or zero.
57+
func (t NullTime) IsZero() bool {
58+
return !t.Valid || t.Time.IsZero()
59+
}

0 commit comments

Comments
 (0)