mirror of https://github.com/coder/coder.git
879 lines
32 KiB
Go
879 lines
32 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/cli/clitest"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/coderd/httpapi"
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestTemplateEdit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("FirstEmptyThenModified", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// Test the cli command.
|
|
name := "new-template-name"
|
|
displayName := "New Display Name 789"
|
|
desc := "lorem ipsum dolor sit amet et cetera"
|
|
icon := "/icon/new-icon.png"
|
|
defaultTTL := 12 * time.Hour
|
|
allowUserCancelWorkspaceJobs := false
|
|
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--name", name,
|
|
"--display-name", displayName,
|
|
"--description", desc,
|
|
"--icon", icon,
|
|
"--default-ttl", defaultTTL.String(),
|
|
"--allow-user-cancel-workspace-jobs=" + strconv.FormatBool(allowUserCancelWorkspaceJobs),
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, templateAdmin, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err := inv.WithContext(ctx).Run()
|
|
|
|
require.NoError(t, err)
|
|
|
|
// Assert that the template metadata changed.
|
|
updated, err := client.Template(context.Background(), template.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, name, updated.Name)
|
|
assert.Equal(t, displayName, updated.DisplayName)
|
|
assert.Equal(t, desc, updated.Description)
|
|
assert.Equal(t, icon, updated.Icon)
|
|
assert.Equal(t, defaultTTL.Milliseconds(), updated.DefaultTTLMillis)
|
|
assert.Equal(t, allowUserCancelWorkspaceJobs, updated.AllowUserCancelWorkspaceJobs)
|
|
})
|
|
t.Run("FirstEmptyThenNotModified", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// Test the cli command.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--name", template.Name,
|
|
"--description", template.Description,
|
|
"--icon", template.Icon,
|
|
"--default-ttl", (time.Duration(template.DefaultTTLMillis) * time.Millisecond).String(),
|
|
"--activity-bump", (time.Duration(template.ActivityBumpMillis) * time.Millisecond).String(),
|
|
"--allow-user-cancel-workspace-jobs=" + strconv.FormatBool(template.AllowUserCancelWorkspaceJobs),
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, templateAdmin, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err := inv.WithContext(ctx).Run()
|
|
|
|
require.ErrorContains(t, err, "not modified")
|
|
|
|
// 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.DefaultTTLMillis, updated.DefaultTTLMillis)
|
|
assert.Equal(t, template.AllowUserCancelWorkspaceJobs, updated.AllowUserCancelWorkspaceJobs)
|
|
})
|
|
t.Run("InvalidDisplayName", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// Test the cli command.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--name", template.Name,
|
|
"--display-name", " a-b-c",
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, templateAdmin, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err := inv.WithContext(ctx).Run()
|
|
|
|
require.Error(t, err, "client call must fail")
|
|
_, isSdkError := codersdk.AsError(err)
|
|
require.True(t, isSdkError, "sdk error is expected")
|
|
|
|
// 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.DisplayName)
|
|
})
|
|
t.Run("WithPropertiesThenModified", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
|
|
initialDisplayName := "This is a template"
|
|
initialDescription := "This is description"
|
|
initialIcon := "/img/icon.png"
|
|
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.DisplayName = initialDisplayName
|
|
ctr.Description = initialDescription
|
|
ctr.Icon = initialIcon
|
|
})
|
|
|
|
// Test created template
|
|
created, err := client.Template(context.Background(), template.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, initialDisplayName, created.DisplayName)
|
|
assert.Equal(t, initialDescription, created.Description)
|
|
assert.Equal(t, initialIcon, created.Icon)
|
|
|
|
// Test the cli command.
|
|
displayName := "New Display Name 789"
|
|
description := "New Description ABC"
|
|
icon := "/icon/new-icon.png"
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--description", description,
|
|
"--display-name", displayName,
|
|
"--icon", icon,
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, templateAdmin, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err = inv.WithContext(ctx).Run()
|
|
|
|
require.NoError(t, err)
|
|
|
|
// Assert that the template metadata changed.
|
|
updated, err := client.Template(context.Background(), template.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, template.Name, updated.Name) // doesn't change
|
|
assert.Equal(t, description, updated.Description)
|
|
assert.Equal(t, displayName, updated.DisplayName)
|
|
assert.Equal(t, icon, updated.Icon)
|
|
})
|
|
t.Run("WithPropertiesThenEmptyEdit", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
|
|
initialDisplayName := "This is a template"
|
|
initialDescription := "This is description"
|
|
initialIcon := "/img/icon.png"
|
|
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.DisplayName = initialDisplayName
|
|
ctr.Description = initialDescription
|
|
ctr.Icon = initialIcon
|
|
})
|
|
|
|
// Test created template
|
|
created, err := client.Template(context.Background(), template.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, initialDisplayName, created.DisplayName)
|
|
assert.Equal(t, initialDescription, created.Description)
|
|
assert.Equal(t, initialIcon, created.Icon)
|
|
|
|
// Test the cli command.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--description", "",
|
|
"--display-name", "",
|
|
"--icon", "",
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, templateAdmin, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err = inv.WithContext(ctx).Run()
|
|
|
|
require.NoError(t, err)
|
|
|
|
// Assert that the template metadata changed.
|
|
updated, err := client.Template(context.Background(), template.ID)
|
|
require.NoError(t, err)
|
|
// Properties don't change
|
|
assert.Equal(t, template.Name, updated.Name)
|
|
// These properties are removed, as the API considers it as "delete" request
|
|
// See: https://github.com/coder/coder/issues/5066
|
|
assert.Equal(t, "", updated.Description)
|
|
assert.Equal(t, "", updated.Icon)
|
|
assert.Equal(t, "", updated.DisplayName)
|
|
})
|
|
t.Run("Autostop/startRequirement", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("BlockedAGPL", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.DefaultTTLMillis = nil
|
|
ctr.AutostopRequirement = nil
|
|
})
|
|
|
|
cases := []struct {
|
|
name string
|
|
flags []string
|
|
ok bool
|
|
}{
|
|
{
|
|
name: "Weekdays",
|
|
flags: []string{
|
|
"--autostop-requirement-weekdays", "monday",
|
|
},
|
|
},
|
|
{
|
|
name: "WeekdaysNoneAllowed",
|
|
flags: []string{
|
|
"--autostop-requirement-weekdays", "none",
|
|
},
|
|
ok: true,
|
|
},
|
|
{
|
|
name: "Weeks",
|
|
flags: []string{
|
|
"--autostop-requirement-weeks", "1",
|
|
},
|
|
},
|
|
{
|
|
name: "AutostartDays",
|
|
flags: []string{
|
|
"--autostart-requirement-weekdays", "monday",
|
|
},
|
|
},
|
|
}
|
|
|
|
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, templateAdmin, 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.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
|
|
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("BlockedNotEntitled", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.DefaultTTLMillis = nil
|
|
ctr.AutostopRequirement = 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)
|
|
tp := &http.Transport{}
|
|
defer tp.CloseIdleConnections()
|
|
rp.Transport = tp
|
|
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(templateAdmin.SessionToken())
|
|
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
|
|
|
cases := []struct {
|
|
name string
|
|
flags []string
|
|
ok bool
|
|
}{
|
|
{
|
|
name: "Weekdays",
|
|
flags: []string{
|
|
"--autostop-requirement-weekdays", "monday",
|
|
},
|
|
},
|
|
{
|
|
name: "WeekdaysNoneAllowed",
|
|
flags: []string{
|
|
"--autostop-requirement-weekdays", "none",
|
|
},
|
|
ok: true,
|
|
},
|
|
{
|
|
name: "Weeks",
|
|
flags: []string{
|
|
"--autostop-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.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
|
|
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
|
|
})
|
|
}
|
|
})
|
|
t.Run("Entitled", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.DefaultTTLMillis = nil
|
|
ctr.AutostopRequirement = 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.AutostopRequirement.DaysOfWeek, []string{"monday", "tuesday"})
|
|
assert.EqualValues(t, req.AutostopRequirement.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)
|
|
tp := &http.Transport{}
|
|
defer tp.CloseIdleConnections()
|
|
rp.Transport = tp
|
|
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(templateAdmin.SessionToken())
|
|
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
|
|
|
// Test the cli command.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--autostop-requirement-weekdays", "monday,tuesday",
|
|
"--autostop-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.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
|
|
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
|
|
})
|
|
})
|
|
|
|
t.Run("AllowUserScheduling", func(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("BlockedAGPL", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.DefaultTTLMillis = nil
|
|
ctr.AutostopRequirement = nil
|
|
ctr.FailureTTLMillis = nil
|
|
ctr.TimeTilDormantMillis = nil
|
|
})
|
|
|
|
// Test the cli command with --allow-user-autostart.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--allow-user-autostart=false",
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, templateAdmin, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err := inv.WithContext(ctx).Run()
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, "appears to be an AGPL deployment")
|
|
|
|
// Test the cli command with --allow-user-autostop.
|
|
cmdArgs = []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--allow-user-autostop=false",
|
|
}
|
|
inv, root = clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, templateAdmin, root)
|
|
|
|
ctx = testutil.Context(t, testutil.WaitLong)
|
|
err = inv.WithContext(ctx).Run()
|
|
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.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
|
|
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
|
|
assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
|
|
assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
|
|
assert.Equal(t, template.TimeTilDormantMillis, updated.TimeTilDormantMillis)
|
|
})
|
|
|
|
t.Run("BlockedNotEntitled", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// 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)
|
|
tp := &http.Transport{}
|
|
defer tp.CloseIdleConnections()
|
|
rp.Transport = tp
|
|
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(templateAdmin.SessionToken())
|
|
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
|
|
|
// Test the cli command with --allow-user-autostart.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--allow-user-autostart=false",
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, proxyClient, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err = inv.WithContext(ctx).Run()
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, "license is not entitled")
|
|
|
|
// Test the cli command with --allow-user-autostop.
|
|
cmdArgs = []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--allow-user-autostop=false",
|
|
}
|
|
inv, root = clitest.New(t, cmdArgs...)
|
|
clitest.SetupConfig(t, proxyClient, root)
|
|
|
|
ctx = testutil.Context(t, testutil.WaitLong)
|
|
err = inv.WithContext(ctx).Run()
|
|
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.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
|
|
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
|
|
assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
|
|
assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
|
|
assert.Equal(t, template.TimeTilDormantMillis, updated.TimeTilDormantMillis)
|
|
})
|
|
t.Run("Entitled", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
|
|
|
// 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.False(t, req.AllowUserAutostart)
|
|
assert.False(t, req.AllowUserAutostop)
|
|
|
|
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)
|
|
tp := &http.Transport{}
|
|
defer tp.CloseIdleConnections()
|
|
rp.Transport = tp
|
|
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(templateAdmin.SessionToken())
|
|
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
|
|
|
// Test the cli command.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--allow-user-autostart=false",
|
|
"--allow-user-autostop=false",
|
|
}
|
|
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.AutostopRequirement.DaysOfWeek, updated.AutostopRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AutostopRequirement.Weeks, updated.AutostopRequirement.Weeks)
|
|
assert.Equal(t, template.AutostartRequirement.DaysOfWeek, updated.AutostartRequirement.DaysOfWeek)
|
|
assert.Equal(t, template.AllowUserAutostart, updated.AllowUserAutostart)
|
|
assert.Equal(t, template.AllowUserAutostop, updated.AllowUserAutostop)
|
|
assert.Equal(t, template.FailureTTLMillis, updated.FailureTTLMillis)
|
|
assert.Equal(t, template.TimeTilDormantMillis, updated.TimeTilDormantMillis)
|
|
})
|
|
})
|
|
|
|
t.Run("RequireActiveVersion", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {})
|
|
|
|
// Test the cli command with --allow-user-autostart.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--require-active-version",
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
//nolint
|
|
clitest.SetupConfig(t, client, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err := inv.WithContext(ctx).Run()
|
|
require.Error(t, err)
|
|
require.ErrorContains(t, err, "appears to be an AGPL deployment")
|
|
})
|
|
t.Run("DefaultValues", func(t *testing.T) {
|
|
t.Parallel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
owner := coderdtest.CreateFirstUser(t, client)
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
ctr.Name = "random"
|
|
ctr.Icon = "/icon/foobar.png"
|
|
ctr.DisplayName = "Foobar"
|
|
ctr.Description = "Some description"
|
|
})
|
|
|
|
// We need to change some field to get a db write.
|
|
cmdArgs := []string{
|
|
"templates",
|
|
"edit",
|
|
template.Name,
|
|
"--name", "something-new",
|
|
}
|
|
inv, root := clitest.New(t, cmdArgs...)
|
|
//nolint
|
|
clitest.SetupConfig(t, client, root)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
err := inv.WithContext(ctx).Run()
|
|
require.NoError(t, err)
|
|
|
|
updated, err := client.Template(context.Background(), template.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "something-new", updated.Name)
|
|
assert.Equal(t, template.Icon, updated.Icon)
|
|
assert.Equal(t, template.DisplayName, updated.DisplayName)
|
|
assert.Equal(t, template.Description, updated.Description)
|
|
assert.Equal(t, template.DeprecationMessage, updated.DeprecationMessage)
|
|
})
|
|
}
|