mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add user quiet hours schedule and restart requirement feature flag (#8115)
This commit is contained in:
@@ -149,8 +149,6 @@ func (r *RootCmd) create() *clibase.Cmd {
|
||||
var ttlMillis *int64
|
||||
if stopAfter > 0 {
|
||||
ttlMillis = ptr.Ref(stopAfter.Milliseconds())
|
||||
} else if template.MaxTTLMillis > 0 {
|
||||
ttlMillis = &template.MaxTTLMillis
|
||||
}
|
||||
|
||||
workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, workspaceOwner, codersdk.CreateWorkspaceRequest{
|
||||
|
||||
@@ -316,7 +316,8 @@ func TestScheduleOverride(t *testing.T) {
|
||||
stdoutBuf = &bytes.Buffer{}
|
||||
)
|
||||
require.Zero(t, template.DefaultTTLMillis)
|
||||
require.Zero(t, template.MaxTTLMillis)
|
||||
require.Empty(t, template.RestartRequirement.DaysOfWeek)
|
||||
require.Zero(t, template.RestartRequirement.Weeks)
|
||||
|
||||
// Unset the workspace TTL
|
||||
err = client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTLMillis: nil})
|
||||
|
||||
@@ -496,6 +496,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
FilesRateLimit: filesRateLimit,
|
||||
HTTPClient: httpClient,
|
||||
TemplateScheduleStore: &atomic.Pointer[schedule.TemplateScheduleStore]{},
|
||||
UserQuietHoursScheduleStore: &atomic.Pointer[schedule.UserQuietHoursScheduleStore]{},
|
||||
SSHConfig: codersdk.SSHConfigResponse{
|
||||
HostnamePrefix: cfg.SSHConfig.DeploymentName.String(),
|
||||
SSHConfigOptions: configSSHOptions,
|
||||
|
||||
+55
-7
@@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
@@ -20,6 +21,8 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
|
||||
icon string
|
||||
defaultTTL time.Duration
|
||||
maxTTL time.Duration
|
||||
restartRequirementDaysOfWeek []string
|
||||
restartRequirementWeeks int64
|
||||
failureTTL time.Duration
|
||||
inactivityTTL time.Duration
|
||||
allowUserCancelWorkspaceJobs bool
|
||||
@@ -48,7 +51,15 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
if maxTTL != 0 || !allowUserAutostart || !allowUserAutostop || failureTTL != 0 || inactivityTTL != 0 {
|
||||
unsetRestartRequirementDaysOfWeek := len(restartRequirementDaysOfWeek) == 1 && restartRequirementDaysOfWeek[0] == "none"
|
||||
requiresEntitlement := (len(restartRequirementDaysOfWeek) > 0 && !unsetRestartRequirementDaysOfWeek) ||
|
||||
restartRequirementWeeks > 0 ||
|
||||
!allowUserAutostart ||
|
||||
!allowUserAutostop ||
|
||||
maxTTL != 0 ||
|
||||
failureTTL != 0 ||
|
||||
inactivityTTL != 0
|
||||
if requiresEntitlement {
|
||||
entitlements, err := client.Entitlements(inv.Context())
|
||||
var sdkErr *codersdk.Error
|
||||
if xerrors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound {
|
||||
@@ -71,14 +82,27 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
|
||||
return xerrors.Errorf("get workspace template: %w", err)
|
||||
}
|
||||
|
||||
// Copy the default value if the list is empty, or if the user
|
||||
// specified the "none" value clear the list.
|
||||
if len(restartRequirementDaysOfWeek) == 0 {
|
||||
restartRequirementDaysOfWeek = template.RestartRequirement.DaysOfWeek
|
||||
}
|
||||
if unsetRestartRequirementDaysOfWeek {
|
||||
restartRequirementDaysOfWeek = []string{}
|
||||
}
|
||||
|
||||
// NOTE: coderd will ignore empty fields.
|
||||
req := codersdk.UpdateTemplateMeta{
|
||||
Name: name,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
Icon: icon,
|
||||
DefaultTTLMillis: defaultTTL.Milliseconds(),
|
||||
MaxTTLMillis: maxTTL.Milliseconds(),
|
||||
Name: name,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
Icon: icon,
|
||||
DefaultTTLMillis: defaultTTL.Milliseconds(),
|
||||
MaxTTLMillis: maxTTL.Milliseconds(),
|
||||
RestartRequirement: &codersdk.TemplateRestartRequirement{
|
||||
DaysOfWeek: restartRequirementDaysOfWeek,
|
||||
Weeks: restartRequirementWeeks,
|
||||
},
|
||||
FailureTTLMillis: failureTTL.Milliseconds(),
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs,
|
||||
@@ -126,6 +150,30 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
|
||||
Description: "Edit the template maximum time before shutdown - workspaces created from this template must shutdown within the given duration after starting. This is an enterprise-only feature.",
|
||||
Value: clibase.DurationOf(&maxTTL),
|
||||
},
|
||||
{
|
||||
Flag: "restart-requirement-weekdays",
|
||||
Description: "Edit the template restart requirement weekdays - workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the restart requirement for the template), pass 'none'.",
|
||||
// TODO(@dean): unhide when we delete max_ttl
|
||||
Hidden: true,
|
||||
Value: clibase.Validate(clibase.StringArrayOf(&restartRequirementDaysOfWeek), func(value *clibase.StringArray) error {
|
||||
v := value.GetSlice()
|
||||
if len(v) == 1 && v[0] == "none" {
|
||||
return nil
|
||||
}
|
||||
_, err := codersdk.WeekdaysToBitmap(v)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid restart requirement days of week %q: %w", strings.Join(v, ","), err)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
{
|
||||
Flag: "restart-requirement-weeks",
|
||||
Description: "Edit the template restart requirement weeks - workspaces created from this template must be restarted on an n-weekly basis.",
|
||||
// TODO(@dean): unhide when we delete max_ttl
|
||||
Hidden: true,
|
||||
Value: clibase.Int64Of(&restartRequirementWeeks),
|
||||
},
|
||||
{
|
||||
Flag: "failure-ttl",
|
||||
Description: "Specify a failure TTL for workspaces created from this template. This licensed feature's default is 0h (off).",
|
||||
|
||||
+313
-8
@@ -242,6 +242,292 @@ func TestTemplateEdit(t *testing.T) {
|
||||
assert.Equal(t, "", updated.Icon)
|
||||
assert.Equal(t, "", updated.DisplayName)
|
||||
})
|
||||
t.Run("RestartRequirement", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("BlockedAGPL", 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, nil)
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.DefaultTTLMillis = nil
|
||||
ctr.RestartRequirement = nil
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
flags []string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "Weekdays",
|
||||
flags: []string{
|
||||
"--restart-requirement-weekdays", "monday",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WeekdaysNoneAllowed",
|
||||
flags: []string{
|
||||
"--restart-requirement-weekdays", "none",
|
||||
},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "Weeks",
|
||||
flags: []string{
|
||||
"--restart-requirement-weeks", "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmdArgs := []string{
|
||||
"templates",
|
||||
"edit",
|
||||
template.Name,
|
||||
}
|
||||
cmdArgs = append(cmdArgs, c.flags...)
|
||||
inv, root := clitest.New(t, cmdArgs...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
err := inv.WithContext(ctx).Run()
|
||||
if c.ok {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "appears to be an AGPL deployment")
|
||||
}
|
||||
|
||||
// Assert that the template metadata did not change.
|
||||
updated, err := client.Template(context.Background(), template.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, template.Name, updated.Name)
|
||||
assert.Equal(t, template.Description, updated.Description)
|
||||
assert.Equal(t, template.Icon, updated.Icon)
|
||||
assert.Equal(t, template.DisplayName, updated.DisplayName)
|
||||
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, template.RestartRequirement.DaysOfWeek, updated.RestartRequirement.DaysOfWeek)
|
||||
assert.Equal(t, template.RestartRequirement.Weeks, updated.RestartRequirement.Weeks)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BlockedNotEntitled", 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, nil)
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.DefaultTTLMillis = nil
|
||||
ctr.RestartRequirement = nil
|
||||
})
|
||||
|
||||
// Make a proxy server that will return a valid entitlements
|
||||
// response, but without advanced scheduling entitlement.
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v2/entitlements" {
|
||||
res := codersdk.Entitlements{
|
||||
Features: map[codersdk.FeatureName]codersdk.Feature{},
|
||||
Warnings: []string{},
|
||||
Errors: []string{},
|
||||
HasLicense: true,
|
||||
Trial: true,
|
||||
RequireTelemetry: false,
|
||||
}
|
||||
for _, feature := range codersdk.FeatureNames {
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementNotEntitled,
|
||||
Enabled: false,
|
||||
Limit: nil,
|
||||
Actual: nil,
|
||||
}
|
||||
}
|
||||
httpapi.Write(r.Context(), w, http.StatusOK, res)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
rp.Transport = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
t.Cleanup(proxy.Close)
|
||||
|
||||
// Create a new client that uses the proxy server.
|
||||
proxyURL, err := url.Parse(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
proxyClient := codersdk.New(proxyURL)
|
||||
proxyClient.SetSessionToken(client.SessionToken())
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
flags []string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "Weekdays",
|
||||
flags: []string{
|
||||
"--restart-requirement-weekdays", "monday",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "WeekdaysNoneAllowed",
|
||||
flags: []string{
|
||||
"--restart-requirement-weekdays", "none",
|
||||
},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "Weeks",
|
||||
flags: []string{
|
||||
"--restart-requirement-weeks", "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cmdArgs := []string{
|
||||
"templates",
|
||||
"edit",
|
||||
template.Name,
|
||||
}
|
||||
cmdArgs = append(cmdArgs, c.flags...)
|
||||
inv, root := clitest.New(t, cmdArgs...)
|
||||
clitest.SetupConfig(t, proxyClient, root)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
err = inv.WithContext(ctx).Run()
|
||||
if c.ok {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "license is not entitled")
|
||||
}
|
||||
|
||||
// Assert that the template metadata did not change.
|
||||
updated, err := client.Template(context.Background(), template.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, template.Name, updated.Name)
|
||||
assert.Equal(t, template.Description, updated.Description)
|
||||
assert.Equal(t, template.Icon, updated.Icon)
|
||||
assert.Equal(t, template.DisplayName, updated.DisplayName)
|
||||
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, template.RestartRequirement.DaysOfWeek, updated.RestartRequirement.DaysOfWeek)
|
||||
assert.Equal(t, template.RestartRequirement.Weeks, updated.RestartRequirement.Weeks)
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Entitled", 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, nil)
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.DefaultTTLMillis = nil
|
||||
ctr.RestartRequirement = nil
|
||||
})
|
||||
|
||||
// Make a proxy server that will return a valid entitlements
|
||||
// response, including a valid advanced scheduling entitlement.
|
||||
var updateTemplateCalled int64
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v2/entitlements" {
|
||||
res := codersdk.Entitlements{
|
||||
Features: map[codersdk.FeatureName]codersdk.Feature{},
|
||||
Warnings: []string{},
|
||||
Errors: []string{},
|
||||
HasLicense: true,
|
||||
Trial: true,
|
||||
RequireTelemetry: false,
|
||||
}
|
||||
for _, feature := range codersdk.FeatureNames {
|
||||
var one int64 = 1
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementNotEntitled,
|
||||
Enabled: true,
|
||||
Limit: &one,
|
||||
Actual: &one,
|
||||
}
|
||||
}
|
||||
httpapi.Write(r.Context(), w, http.StatusOK, res)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(r.URL.Path, "/api/v2/templates/") {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
_ = r.Body.Close()
|
||||
|
||||
var req codersdk.UpdateTemplateMeta
|
||||
err = json.Unmarshal(body, &req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, req.RestartRequirement.DaysOfWeek, []string{"monday", "tuesday"})
|
||||
assert.EqualValues(t, req.RestartRequirement.Weeks, 3)
|
||||
|
||||
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||
atomic.AddInt64(&updateTemplateCalled, 1)
|
||||
// We still want to call the real route.
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
rp.Transport = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
// Create a new client that uses the proxy server.
|
||||
proxyURL, err := url.Parse(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
proxyClient := codersdk.New(proxyURL)
|
||||
proxyClient.SetSessionToken(client.SessionToken())
|
||||
|
||||
// Test the cli command.
|
||||
cmdArgs := []string{
|
||||
"templates",
|
||||
"edit",
|
||||
template.Name,
|
||||
"--restart-requirement-weekdays", "monday,tuesday",
|
||||
"--restart-requirement-weeks", "3",
|
||||
}
|
||||
inv, root := clitest.New(t, cmdArgs...)
|
||||
clitest.SetupConfig(t, proxyClient, root)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
err = inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, 1, atomic.LoadInt64(&updateTemplateCalled))
|
||||
|
||||
// Assert that the template metadata did not change. We verify the
|
||||
// correct request gets sent to the server already.
|
||||
updated, err := client.Template(context.Background(), template.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, template.Name, updated.Name)
|
||||
assert.Equal(t, template.Description, updated.Description)
|
||||
assert.Equal(t, template.Icon, updated.Icon)
|
||||
assert.Equal(t, template.DisplayName, updated.DisplayName)
|
||||
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, template.RestartRequirement.DaysOfWeek, updated.RestartRequirement.DaysOfWeek)
|
||||
assert.Equal(t, template.RestartRequirement.Weeks, updated.RestartRequirement.Weeks)
|
||||
})
|
||||
})
|
||||
// TODO(@dean): remove this test when we remove max_ttl
|
||||
t.Run("MaxTTL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("BlockedAGPL", func(t *testing.T) {
|
||||
@@ -317,7 +603,11 @@ func TestTemplateEdit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
httputil.NewSingleHostReverseProxy(client.URL).ServeHTTP(w, r)
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
rp.Transport = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -404,7 +694,11 @@ func TestTemplateEdit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
httputil.NewSingleHostReverseProxy(client.URL).ServeHTTP(w, r)
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
rp.Transport = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -452,7 +746,7 @@ func TestTemplateEdit(t *testing.T) {
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.DefaultTTLMillis = nil
|
||||
ctr.MaxTTLMillis = nil
|
||||
ctr.RestartRequirement = nil
|
||||
ctr.FailureTTLMillis = nil
|
||||
ctr.InactivityTTLMillis = nil
|
||||
})
|
||||
@@ -495,7 +789,8 @@ func TestTemplateEdit(t *testing.T) {
|
||||
assert.Equal(t, template.Icon, updated.Icon)
|
||||
assert.Equal(t, template.DisplayName, updated.DisplayName)
|
||||
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis)
|
||||
assert.Equal(t, template.RestartRequirement.DaysOfWeek, updated.RestartRequirement.DaysOfWeek)
|
||||
assert.Equal(t, template.RestartRequirement.Weeks, updated.RestartRequirement.Weeks)
|
||||
assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
|
||||
assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
|
||||
assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
|
||||
@@ -535,7 +830,11 @@ func TestTemplateEdit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
httputil.NewSingleHostReverseProxy(client.URL).ServeHTTP(w, r)
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
rp.Transport = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -583,7 +882,8 @@ func TestTemplateEdit(t *testing.T) {
|
||||
assert.Equal(t, template.Icon, updated.Icon)
|
||||
assert.Equal(t, template.DisplayName, updated.DisplayName)
|
||||
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis)
|
||||
assert.Equal(t, template.RestartRequirement.DaysOfWeek, updated.RestartRequirement.DaysOfWeek)
|
||||
assert.Equal(t, template.RestartRequirement.Weeks, updated.RestartRequirement.Weeks)
|
||||
assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
|
||||
assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
|
||||
assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
|
||||
@@ -639,7 +939,11 @@ func TestTemplateEdit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
httputil.NewSingleHostReverseProxy(client.URL).ServeHTTP(w, r)
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
rp.Transport = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -675,7 +979,8 @@ func TestTemplateEdit(t *testing.T) {
|
||||
assert.Equal(t, template.Icon, updated.Icon)
|
||||
assert.Equal(t, template.DisplayName, updated.DisplayName)
|
||||
assert.Equal(t, template.DefaultTTLMillis, updated.DefaultTTLMillis)
|
||||
assert.Equal(t, template.MaxTTLMillis, updated.MaxTTLMillis)
|
||||
assert.Equal(t, template.RestartRequirement.DaysOfWeek, updated.RestartRequirement.DaysOfWeek)
|
||||
assert.Equal(t, template.RestartRequirement.Weeks, updated.RestartRequirement.Weeks)
|
||||
assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
|
||||
assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
|
||||
assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
|
||||
|
||||
+14
@@ -377,6 +377,20 @@ telemetrywhen required by your organization's security policy.
|
||||
anonymized application tracing to help improve our product. Disabling
|
||||
telemetry also disables this option.
|
||||
|
||||
[1mUser Quiet Hours Schedule Options[0m
|
||||
Allow users to set quiet hours schedules each day for workspaces to avoid
|
||||
workspaces stopping during the day due to template max TTL.
|
||||
|
||||
--default-quiet-hours-schedule string, $CODER_QUIET_HOURS_DEFAULT_SCHEDULE
|
||||
The default daily cron schedule applied to users that haven't set a
|
||||
custom quiet hours schedule themselves. The quiet hours schedule
|
||||
determines when workspaces will be force stopped due to the template's
|
||||
max TTL, and will round the max TTL up to be within the user's quiet
|
||||
hours window (or default). The format is the same as the standard cron
|
||||
format, but the day-of-month, month and day-of-week must be *. Only
|
||||
one hour and minute can be specified (ranges or comma separated values
|
||||
are not supported).
|
||||
|
||||
[1m⚠️ Dangerous Options[0m
|
||||
--dangerous-allow-path-app-sharing bool, $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING
|
||||
Allow workspace apps that are not served from subdomains to be shared.
|
||||
|
||||
+12
@@ -369,3 +369,15 @@ supportLinks: []
|
||||
# "tunnel.example.com".
|
||||
# (default: <unset>, type: string)
|
||||
wgtunnelHost: ""
|
||||
# Allow users to set quiet hours schedules each day for workspaces to avoid
|
||||
# workspaces stopping during the day due to template max TTL.
|
||||
userQuietHoursSchedule:
|
||||
# The default daily cron schedule applied to users that haven't set a custom quiet
|
||||
# hours schedule themselves. The quiet hours schedule determines when workspaces
|
||||
# will be force stopped due to the template's max TTL, and will round the max TTL
|
||||
# up to be within the user's quiet hours window (or default). The format is the
|
||||
# same as the standard cron format, but the day-of-month, month and day-of-week
|
||||
# must be *. Only one hour and minute can be specified (ranges or comma separated
|
||||
# values are not supported).
|
||||
# (default: <unset>, type: string)
|
||||
defaultQuietHoursSchedule: ""
|
||||
|
||||
Reference in New Issue
Block a user