Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: enforce template-level constraints for TTL and autostart#2018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
20 commits
Select commitHold shift + click to select a range
1713431
RED: add failing unit tests
johnstcnJun 2, 2022
f11f942
RED: add migrations and make gen
johnstcnJun 2, 2022
c5c9a7a
ORANGE: fix audit/diff tests
johnstcnJun 2, 2022
74f4dee
fixup! RED: add migrations and make gen
johnstcnJun 2, 2022
7da27a0
fixup! RED: add migrations and make gen
johnstcnJun 2, 2022
5ad048b
feat: schedule: add schedule.Min function to determine minimum interval
johnstcnJun 2, 2022
65b980e
ORANGE: coderd: fix a whole bunch of tests
johnstcnJun 2, 2022
aa298a6
RED: fast-fail some slow-failing tests
johnstcnJun 2, 2022
a5ec9bf
GREEN: fix even more tests
johnstcnJun 3, 2022
a6255d6
GREEN: fix final unit test
johnstcnJun 3, 2022
245795e
cli: templatecreate: add CLI flags --max-ttl --min-autostart-interval
johnstcnJun 3, 2022
709db5b
fixup! cli: templatecreate: add CLI flags --max-ttl --min-autostart-i…
johnstcnJun 3, 2022
c4a0927
make gen
johnstcnJun 3, 2022
244e479
update test entities
johnstcnJun 3, 2022
60ef479
Merge remote-tracking branch 'origin/main' into 1433-template-constra…
johnstcnJun 7, 2022
c9aab8b
fixup! Merge remote-tracking branch 'origin/main' into 1433-template-…
johnstcnJun 7, 2022
f997af2
coderd: default newly-minted workspaces to template max_ttl
johnstcnJun 7, 2022
d7f697b
address PR comment
johnstcnJun 7, 2022
ebe4c15
fix broken unit test
johnstcnJun 7, 2022
08ef2eb
fix some more tests
johnstcnJun 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletionscli/autostart_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"testing"
"time"

"github.com/stretchr/testify/require"

Expand DownExpand Up@@ -158,4 +159,26 @@ func TestAutostart(t *testing.T) {
require.NoError(t, err, "fetch updated workspace")
require.Equal(t, expectedSchedule, *updated.AutostartSchedule, "expected default autostart schedule")
})

t.Run("BelowTemplateConstraint", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Question(gut-check): Should there be a similar case forAboveTemplateConstraint ?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't see any reason to enforce an upper bound on the interval between successive autostarts.

t.Parallel()

var (
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds())
})
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
cmdArgs = []string{"autostart", "enable", workspace.Name, "--minute", "*", "--hour", "*"}
)

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

err := cmd.Execute()
require.ErrorContains(t, err, "schedule: Minimum autostart interval 1m0s below template minimum 1h0m0s")
})
}
12 changes: 12 additions & 0 deletionscli/bump_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import (

"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
)

Expand DownExpand Up@@ -152,12 +153,23 @@ func TestBump(t *testing.T) {
cmdArgs = []string{"bump", workspace.Name}
stdoutBuf = &bytes.Buffer{}
)
// Unset the workspace TTL
err = client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: nil})
require.NoError(t, err)
workspace, err = client.Workspace(ctx, workspace.ID)
require.NoError(t, err)
require.Nil(t, workspace.TTLMillis)

// Given: we wait for the workspace to build
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
workspace, err = client.Workspace(ctx, workspace.ID)
require.NoError(t, err)

// TODO(cian): need to stop and start the workspace as we do not update the deadline yet
// see: https://github.com/coder/coder/issues/1783
coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStop, database.WorkspaceTransitionStart)

// Assert test invariant: workspace has no TTL set
require.Zero(t, workspace.LatestBuild.Deadline)
require.NoError(t, err)
Expand Down
55 changes: 39 additions & 16 deletionscli/create.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,20 +61,6 @@ func create() *cobra.Command {
}
}

tz, err := time.LoadLocation(tzName)
if err != nil {
return xerrors.Errorf("Invalid workspace autostart timezone: %w", err)
}
schedSpec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", tz.String(), autostartMinute, autostartHour, autostartDow)
_, err = schedule.Weekly(schedSpec)
if err != nil {
return xerrors.Errorf("invalid workspace autostart schedule: %w", err)
}

if ttl == 0 {
return xerrors.Errorf("TTL must be at least 1 minute")
}

_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName)
if err == nil {
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
Expand DownExpand Up@@ -129,6 +115,23 @@ func create() *cobra.Command {
}
}

schedSpec, err := validSchedule(
autostartMinute,
autostartHour,
autostartDow,
tzName,
time.Duration(template.MinAutostartIntervalMillis)*time.Millisecond,
)
if err != nil {
return xerrors.Errorf("Invalid autostart schedule: %w", err)
}
if ttl < time.Minute {
return xerrors.Errorf("TTL must be at least 1 minute")
}
if ttlMax := time.Duration(template.MaxTTLMillis) * time.Millisecond; ttl > ttlMax {
return xerrors.Errorf("TTL must be below template maximum %s", ttlMax)
}

Comment on lines +118 to +134
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Review: We're duplicating some validations here because I want the errors to show upbefore the whole workspace plan happens.

greyscaled reacted with thumbs up emoji
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Interesting to do it in the cli. Can we somehow put the validation on a datastructure and reuse it for the backend + frontend? Like reuse the same code?

Not a huge deal.

johnstcn reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It gets validated on the BE as well but if we don't at least validate in thecreate command the user ends up seeing the workspace dry run, confirming the create, andthen getting the validation error, which is annoying.

And yes, this is definitely a good candidate to be DRYed up a bit.

templateVersion, err := client.TemplateVersion(cmd.Context(), template.ActiveVersionID)
if err != nil {
return err
Expand DownExpand Up@@ -226,7 +229,7 @@ func create() *cobra.Command {
workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.CreateWorkspaceRequest{
TemplateID: template.ID,
Name: workspaceName,
AutostartSchedule:&schedSpec,
AutostartSchedule: schedSpec,
TTLMillis: ptr.Ref(ttl.Milliseconds()),
ParameterValues: parameters,
})
Expand DownExpand Up@@ -262,7 +265,27 @@ func create() *cobra.Command {
cliflag.StringVarP(cmd.Flags(), &autostartMinute, "autostart-minute", "", "CODER_WORKSPACE_AUTOSTART_MINUTE", "0", "Specify the minute(s) at which the workspace should autostart (e.g. 0).")
cliflag.StringVarP(cmd.Flags(), &autostartHour, "autostart-hour", "", "CODER_WORKSPACE_AUTOSTART_HOUR", "9", "Specify the hour(s) at which the workspace should autostart (e.g. 9).")
cliflag.StringVarP(cmd.Flags(), &autostartDow, "autostart-day-of-week", "", "CODER_WORKSPACE_AUTOSTART_DOW", "MON-FRI", "Specify the days(s) on which the workspace should autostart (e.g. MON,TUE,WED,THU,FRI)")
cliflag.StringVarP(cmd.Flags(), &tzName, "tz", "", "TZ", "", "Specify your timezone location for workspace autostart (e.g. US/Central).")
cliflag.StringVarP(cmd.Flags(), &tzName, "tz", "", "TZ", "UTC", "Specify your timezone location for workspace autostart (e.g. US/Central).")
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Review: defaulting TZ to UTC here explicitly. Will hopefully have some fancier auto-detection in a separate issue.

greyscaled reacted with thumbs up emoji
cliflag.DurationVarP(cmd.Flags(), &ttl, "ttl", "", "CODER_WORKSPACE_TTL", 8*time.Hour, "Specify a time-to-live (TTL) for the workspace (e.g. 8h).")
return cmd
}

func validSchedule(minute, hour, dow, tzName string, min time.Duration) (*string, error) {
_, err := time.LoadLocation(tzName)
if err != nil {
return nil, xerrors.Errorf("Invalid workspace autostart timezone: %w", err)
}

schedSpec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", tzName, minute, hour, dow)

sched, err := schedule.Weekly(schedSpec)
if err != nil {
return nil, err
}

if schedMin := sched.Min(); schedMin < min {
return nil, xerrors.Errorf("minimum autostart interval %s is above template constraint %s", schedMin, min)
}

return &schedSpec, nil
}
72 changes: 58 additions & 14 deletionscli/create_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ import (
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
Expand DownExpand Up@@ -62,6 +63,57 @@ func TestCreate(t *testing.T) {
<-doneChan
})

t.Run("AboveTemplateMaxTTL", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.MaxTTLMillis = ptr.Ref((12 * time.Hour).Milliseconds())
})
args := []string{
"create",
"my-workspace",
"--template", template.Name,
"--ttl", "12h1m",
"-y", // don't bother with waiting
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Review: I'm slightly side-stepping here because I don't want to deal with confirms

greyscaled reacted with thumbs up emoji
}
cmd, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
err := cmd.Execute()
assert.ErrorContains(t, err, "TTL must be below template maximum 12h0m0s")
})

t.Run("BelowTemplateMinAutostartInterval", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.MinAutostartIntervalMillis = ptr.Ref(time.Hour.Milliseconds())
})
args := []string{
"create",
"my-workspace",
"--template", template.Name,
"--autostart-minute", "*", // Every minute
"--autostart-hour", "*", // Every hour
"-y", // don't bother with waiting
}
cmd, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
err := cmd.Execute()
assert.ErrorContains(t, err, "minimum autostart interval 1m0s is above template constraint 1h0m0s")
})

t.Run("CreateErrInvalidTz", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
Expand All@@ -74,19 +126,15 @@ func TestCreate(t *testing.T) {
"my-workspace",
"--template", template.Name,
"--tz", "invalid",
"-y",
}
cmd, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.EqualError(t, err, "Invalid workspace autostart timezone: unknown time zone invalid")
}()
<-doneChan
err := cmd.Execute()
assert.ErrorContains(t, err, "Invalid autostart schedule: Invalid workspace autostart timezone: unknown time zone invalid")
})

t.Run("CreateErrInvalidTTL", func(t *testing.T) {
Expand All@@ -101,19 +149,15 @@ func TestCreate(t *testing.T) {
"my-workspace",
"--template", template.Name,
"--ttl", "0s",
"-y",
}
cmd, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
assert.EqualError(t, err, "TTL must be at least 1 minute")
}()
<-doneChan
err := cmd.Execute()
assert.EqualError(t, err, "TTL must be at least 1 minute")
Comment on lines -111 to +160
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Review: as above, removing parallelism from this test as it's not strictly necessary.

})

t.Run("CreateFromListWithSkip", func(t *testing.T) {
Expand Down
25 changes: 17 additions & 8 deletionscli/templatecreate.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,16 +14,19 @@ import (

"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisionerd"
"github.com/coder/coder/provisionersdk"
)

func templateCreate() *cobra.Command {
var (
directory string
provisioner string
parameterFile string
directory string
provisioner string
parameterFile string
maxTTL time.Duration
minAutostartInterval time.Duration
)
cmd := &cobra.Command{
Use: "create [name]",
Expand DownExpand Up@@ -92,11 +95,15 @@ func templateCreate() *cobra.Command {
return err
}

_, err = client.CreateTemplate(cmd.Context(), organization.ID, codersdk.CreateTemplateRequest{
Name: templateName,
VersionID: job.ID,
ParameterValues: parameters,
})
createReq := codersdk.CreateTemplateRequest{
Name: templateName,
VersionID: job.ID,
ParameterValues: parameters,
MaxTTLMillis: ptr.Ref(maxTTL.Milliseconds()),
MinAutostartIntervalMillis: ptr.Ref(minAutostartInterval.Milliseconds()),
}

_, err = client.CreateTemplate(cmd.Context(), organization.ID, createReq)
if err != nil {
return err
}
Expand All@@ -115,6 +122,8 @@ func templateCreate() *cobra.Command {
cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from")
cmd.Flags().StringVarP(&provisioner, "test.provisioner", "", "terraform", "Customize the provisioner backend")
cmd.Flags().StringVarP(&parameterFile, "parameter-file", "", "", "Specify a file path with parameter values.")
cmd.Flags().DurationVarP(&maxTTL, "max-ttl", "", 168*time.Hour, "Specify a maximum TTL for worksapces created from this template.")
cmd.Flags().DurationVarP(&minAutostartInterval, "min-autostart-interval", "", time.Hour, "Specify a minimum autostart interval for workspaces created from this template.")
Copy link
Contributor

@greyscaledgreyscaledJun 6, 2022
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍🏻

// This is for testing!
err := cmd.Flags().MarkHidden("test.provisioner")
if err != nil {
Expand Down
11 changes: 10 additions & 1 deletioncli/templatecreate_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,16 @@ func TestTemplateCreate(t *testing.T) {
Parse: echo.ParseComplete,
Provision: echo.ProvisionComplete,
})
cmd, root := clitest.New(t, "templates", "create", "my-template", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho))
args := []string{
"templates",
"create",
"my-template",
"--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho),
"--max-ttl", "24h",
"--min-autostart-interval", "2h",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍🏻

}
cmd, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
Expand Down
33 changes: 33 additions & 0 deletionscli/ttl_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,4 +168,37 @@ func TestTTL(t *testing.T) {
err := cmd.Execute()
require.ErrorContains(t, err, "status code 403: Forbidden", "unexpected error")
})

t.Run("TemplateMaxTTL", func(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.MaxTTLMillis = ptr.Ref((8 * time.Hour).Milliseconds())
})
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.TTLMillis = ptr.Ref((8 * time.Hour).Milliseconds())
})
cmdArgs = []string{"ttl", "set", workspace.Name, "24h"}
stdoutBuf = &bytes.Buffer{}
)

cmd, root := clitest.New(t, cmdArgs...)
clitest.SetupConfig(t, client, root)
cmd.SetOut(stdoutBuf)

err := cmd.Execute()
require.ErrorContains(t, err, "ttl_ms: ttl must be below template maximum 8h0m0s")

// Ensure ttl not updated
updated, err := client.Workspace(ctx, workspace.ID)
require.NoError(t, err, "fetch updated workspace")
require.NotNil(t, updated.TTLMillis)
require.Equal(t, (8 * time.Hour).Milliseconds(), *updated.TTLMillis)
})
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp