Files
coder/cli/templatecreate_test.go
T
Ethan 78b5a0f5a2 feat(cli): add --agents-allowed to template commands (#27517)
Relates to CODAGT-713

Depends on #27515

This adds `--agents-allowed` to `coder templates create` and `coder templates edit`. Template creation defaults the option to true, matching the per-template API and database default, while template editing only changes the value when the flag is explicitly supplied so unrelated edits preserve the existing setting.

The generated CLI help and reference documentation include the new option. #27518 updates the Coder Agents platform controls documentation to describe the completed per-template model.
2026-08-06 14:45:38 +10:00

373 lines
10 KiB
Go

package cli_test
import (
"bytes"
"context"
"os"
"path/filepath"
"testing"
"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/database"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
"github.com/coder/coder/v2/testutil/expecter"
)
func TestCliTemplateCreate(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
logger := testutil.Logger(t)
ctx := testutil.Context(t, testutil.WaitMedium)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
args := []string{
"templates",
"create",
"my-template",
"--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho),
"--default-ttl", "24h",
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
stdout := expecter.NewAttachedToInvocation(t, inv)
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
clitest.Start(t, inv)
matches := []struct {
match string
write string
}{
{match: "Upload", write: "yes"},
{match: "compute.main"},
{match: "smith (linux, i386)"},
{match: "Confirm create?", write: "yes"},
}
for _, m := range matches {
stdout.ExpectMatch(ctx, m.match)
if len(m.write) > 0 {
stdin.WriteLine(m.write)
}
}
})
t.Run("AgentsAllowed", 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())
for _, tt := range []struct {
name string
flag string
agentsAllowed bool
}{
{
name: "DefaultTrue",
agentsAllowed: true,
},
{
name: "False",
flag: "--agents-allowed=false",
agentsAllowed: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
templateName := coderdtest.RandomUsername(t)
source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
args := []string{
"templates",
"create",
templateName,
"--yes",
"--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho),
}
if tt.flag != "" {
args = append(args, tt.flag)
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, templateAdmin, root)
require.NoError(t, inv.Run())
template, err := client.TemplateByName(t.Context(), owner.OrganizationID, templateName)
require.NoError(t, err)
require.Equal(t, tt.agentsAllowed, template.AgentsAllowed)
})
}
})
t.Run("CreateNoLockfile", func(t *testing.T) {
t.Parallel()
logger := testutil.Logger(t)
ctx := testutil.Context(t, testutil.WaitMedium)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
args := []string{
"templates",
"create",
"my-template",
"--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho),
"--default-ttl", "24h",
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
stdout := expecter.NewAttachedToInvocation(t, inv)
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
execDone := make(chan error)
go func() {
execDone <- inv.Run()
}()
matches := []struct {
match string
write string
}{
{match: "No .terraform.lock.hcl file found"},
{match: "Upload", write: "no"},
}
for _, m := range matches {
stdout.ExpectMatch(ctx, m.match)
if len(m.write) > 0 {
stdin.WriteLine(m.write)
}
}
// cmd should error once we say no.
require.Error(t, <-execDone)
})
t.Run("CreateNoLockfileIgnored", func(t *testing.T) {
t.Parallel()
logger := testutil.Logger(t)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
args := []string{
"templates",
"create",
"my-template",
"--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho),
"--default-ttl", "24h",
"--ignore-lockfile",
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
stdout := expecter.NewAttachedToInvocation(t, inv)
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
execDone := make(chan error)
go func() {
execDone <- inv.Run()
}()
{
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()
stdout.ExpectNoMatchBefore(ctx, "No .terraform.lock.hcl file found", "Upload")
stdin.WriteLine("no")
}
// cmd should error once we say no.
require.Error(t, <-execDone)
})
t.Run("CreateStdin", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client)
source, err := echo.Tar(completeWithAgent())
require.NoError(t, err)
args := []string{
"templates",
"create",
"my-template",
"--directory", "-",
"--test.provisioner", string(database.ProvisionerTypeEcho),
"--default-ttl", "24h",
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
inv.Stdin = bytes.NewReader(source)
require.NoError(t, inv.Run())
})
t.Run("Recreate template with same name (create, delete, create)", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client)
create := func() error {
source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
args := []string{
"templates",
"create",
"my-template",
"--yes",
"--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho),
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
return inv.Run()
}
del := func() error {
args := []string{
"templates",
"delete",
"my-template",
"--yes",
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
return inv.Run()
}
err := create()
require.NoError(t, err, "Template must be created without error")
err = del()
require.NoError(t, err, "Template must be deleted without error")
err = create()
require.NoError(t, err, "Template must be recreated without error")
})
t.Run("WithVariablesFileWithTheRequiredValue", func(t *testing.T) {
t.Parallel()
logger := testutil.Logger(t)
ctx := testutil.Context(t, testutil.WaitMedium)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client)
templateVariables := []*proto.TemplateVariable{
{
Name: "first_variable",
Description: "This is the first variable.",
Type: "string",
Required: true,
Sensitive: true,
},
{
Name: "second_variable",
Description: "This is the second variable",
Type: "string",
DefaultValue: "abc",
Required: false,
Sensitive: true,
},
}
source := clitest.CreateTemplateVersionSource(t,
createEchoResponsesWithTemplateVariables(templateVariables))
tempDir := t.TempDir()
removeTmpDirUntilSuccessAfterTest(t, tempDir)
variablesFile, _ := os.CreateTemp(tempDir, "variables*.yaml")
_, _ = variablesFile.WriteString(`first_variable: foobar`)
inv, root := clitest.New(t, "templates", "create", "my-template", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--variables-file", variablesFile.Name())
clitest.SetupConfig(t, client, root)
stdout := expecter.NewAttachedToInvocation(t, inv)
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
clitest.Start(t, inv)
matches := []struct {
match string
write string
}{
{match: "Upload", write: "yes"},
{match: "Confirm create?", write: "yes"},
}
for _, m := range matches {
stdout.ExpectMatch(ctx, m.match)
if len(m.write) > 0 {
stdin.WriteLine(m.write)
}
}
})
t.Run("WithVariableOption", func(t *testing.T) {
t.Parallel()
logger := testutil.Logger(t)
ctx := testutil.Context(t, testutil.WaitMedium)
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client)
templateVariables := []*proto.TemplateVariable{
{
Name: "first_variable",
Description: "This is the first variable.",
Type: "string",
Required: true,
Sensitive: true,
},
}
source := clitest.CreateTemplateVersionSource(t,
createEchoResponsesWithTemplateVariables(templateVariables))
inv, root := clitest.New(t, "templates", "create", "my-template", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--variable", "first_variable=foobar")
clitest.SetupConfig(t, client, root)
stdout := expecter.NewAttachedToInvocation(t, inv)
stdin := testutil.NewWriterAttachedToInvocation(t, logger.Named("stdin"), inv)
clitest.Start(t, inv)
matches := []struct {
match string
write string
}{
{match: "Upload", write: "yes"},
{match: "Confirm create?", write: "yes"},
}
for _, m := range matches {
stdout.ExpectMatch(ctx, m.match)
if len(m.write) > 0 {
stdin.WriteLine(m.write)
}
}
})
t.Run("RequireActiveVersionInvalid", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
})
coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
args := []string{
"templates",
"create",
"my-template",
"--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho),
"--require-active-version",
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, client, root)
err := inv.Run()
require.Error(t, err)
require.Contains(t, err.Error(), "your deployment appears to be an AGPL deployment, so you cannot set enterprise-only flags")
})
}