Files
coder/cli/templatecreate_test.go
T
Nick Vigilante c6a47c4428 docs: replace references to the retired Enterprise license tier (#30853)
## Summary

Coder has only Community and Premium license tiers, but several docs
pages, CLI help strings, and SDK field comments still called features
"Enterprise" or "enterprise-only".
This PR changes them to say Premium and regenerates the reference docs
built from those strings.

Resolves
[DOCS-1298](https://linear.app/codercom/issue/DOCS-1298/remove-references-to-the-retired-enterprise-license-tier).

## Changes

- `docs/admin/users/quotas.md`: Replace "scoped to Groups in Enterprise
and organizations in Premium" with a correct sentence. Quotas are
Premium, allowances are set on groups, and each user's budget is
calculated separately in each organization (per
`GetQuotaAllowanceForUser`).
- `docs/user-guides/workspace-lifecycle.md`: Replace "Enterprise" with
Premium, and correct the quotas sentence to match the quotas page.
- `docs/ai-coder/agents/platform-controls/mcp-servers.md`: Replace
"enterprise entitlement" with Premium license.
- `cli/templatecreate.go`, `cli/templateedit.go`: Update
`--require-active-version`, `--allow-user-autostart`, and
`--allow-user-autostop` help text. Point the `--require-active-version`
help at the renamed `#template-update-policies` section, because the old
`#require-automatic-updates-enterprise` anchor is broken. Make the
errors for these flags tier-neutral.
- `enterprise/cli/features.go`: `coder features` now says "List license
features and entitlements", and its AGPL error no longer names a tier.
- `codersdk/templates.go`, `codersdk/organizations.go`: Update field
comments that feed the API reference and TypeScript types.
- `docs/reference/glossary.md`: The Premium entry now says "paid license
tier" instead of "paid enterprise tier".
- `docs/.style/style-guide/voice-and-tone.md`: The example sentence now
says "Premium licensing".
- Regenerated: CLI golden files, `docs/reference/cli/`,
`docs/manifest.json`, `docs/manifest/generated/cli.json`,
`docs/reference/api/`, `coderd/apidoc/`, and
`site/src/api/typesGenerated.ts`.

## Testing

- `go test ./cli -run 'TestCommandHelp|TestCliTemplateCreate'` and `go
test ./enterprise/cli -run
'TestEnterpriseCommandHelp|TestTemplateCreate|TestTemplateEdit|TestFeaturesList'`
pass.
- The pre-commit hook passed, including `make gen`, `fmt`, `lint/go`,
`lint/ts`, `lint/markdown`, and a slim build.

<details>
<summary>Decisions and out-of-scope items</summary>

### Decisions

- **Premium wording and legacy Enterprise licenses:** Coder still
accepts existing Enterprise licenses, and `FeatureSetEnterprise`
includes the features labeled Premium here. Docs and help text say
"Premium" because that is the tier sold today, and about 25 other docs
pages already use "Premium feature". Error strings are tier-neutral, so
they're accurate for every license holder.
- **Glossary and style guide:** The issue marked these as borderline.
Both were one-word changes, so I made them.

### Not changed

- `docs/reference/api/enterprise.md` keeps the "Enterprise" sidebar
title. It comes from the swagger tag on the `enterprise/` Go package, so
renaming it is a larger change.
- Other "Enterprise" references the review flagged as out of scope: the
`coder server --help` option group, quiet hours SDK comments, internal
`coderd` comments, and the dashboard's "Enterprise" badge on the
require-active-version option.
- No docs page says that legacy Enterprise licenses are still valid.
- Uses that don't refer to the license tier: GitHub Enterprise, Red Hat
Enterprise Linux, "enterprise environments", and `enterprise/` package
paths.

</details>

---

_This PR was generated by Coder Agents on behalf of @nickvigilante._
2026-10-15 17:46:05 +00: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 flags that require a license")
})
}