From 78ca41bafac4ee3abb2cc4c63479284496fd20ce Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Mon, 1 Jun 2026 08:32:01 +0000 Subject: [PATCH] feat(enterprise/cli): rename boundary command to agent-firewall Rename the `coder boundary` CLI subcommand to `coder agent-firewall` as part of the Boundaries to Agent Firewall rebrand (AIGOV-236). `coder boundary` is retained as a hidden, deprecated alias that prints a deprecation notice to stderr before running. Both commands use separate builder functions that share the same boundary base command and license verification logic. Updates error messages, golden files, CLI reference docs, and prose docs to use the new naming. --- cli/root.go | 9 ++-- docs/ai-coder/agent-firewall/version.md | 6 +-- docs/manifest.json | 4 +- .../cli/{boundary.md => agent-firewall.md} | 4 +- docs/reference/cli/index.md | 2 +- enterprise/cli/boundary.go | 51 +++++++++++++++--- enterprise/cli/boundary_test.go | 52 ++++++++++++------- enterprise/cli/root.go | 3 +- enterprise/cli/testdata/coder_--help.golden | 4 +- ...den => coder_agent-firewall_--help.golden} | 2 +- 10 files changed, 96 insertions(+), 41 deletions(-) rename docs/reference/cli/{boundary.md => agent-firewall.md} (98%) rename enterprise/cli/testdata/{coder_boundary_--help.golden => coder_agent-firewall_--help.golden} (98%) diff --git a/cli/root.go b/cli/root.go index a40ac7c3c2..ed89a00ddc 100644 --- a/cli/root.go +++ b/cli/root.go @@ -343,10 +343,11 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err // support links. return } - if cmd.Name() == "boundary" { - // The boundary command is integrated from the boundary package - // and has YAML-only options (e.g., allowlist from config file) - // that don't have flags or env vars. + if cmd.Name() == "agent-firewall" || cmd.Name() == "boundary" { + // The agent-firewall command (and its "boundary" alias) is + // integrated from the boundary package and has YAML-only + // options (e.g., allowlist from config file) that don't + // have flags or env vars. return } merr = errors.Join( diff --git a/docs/ai-coder/agent-firewall/version.md b/docs/ai-coder/agent-firewall/version.md index e8bdef5556..28de4d238c 100644 --- a/docs/ai-coder/agent-firewall/version.md +++ b/docs/ai-coder/agent-firewall/version.md @@ -13,12 +13,12 @@ v4.7.0 or newer**. ### Coder v2.30.0+ Since Coder v2.30.0, Agent Firewall is embedded inside the Coder binary, and -you don't need to install it separately. The `coder boundary` subcommand is +you don't need to install it separately. The `coder agent-firewall` subcommand is available directly from the Coder CLI. ### Claude Code Module v4.7.0+ -Since Claude Code module v4.7.0, the embedded `coder boundary` subcommand is +Since Claude Code module v4.7.0, the embedded `coder agent-firewall` subcommand is used by default. This means you don't need to set `boundary_version`; the boundary version is tied to your Coder version. @@ -27,7 +27,7 @@ boundary version is tied to your Coder version. ### Using Coder Before v2.30.0 with Claude Code Module v4.7.0+ If you're using Coder before v2.30.0 with Claude Code module v4.7.0 or newer, -the `coder boundary` subcommand isn't available in your Coder installation. In +the `coder agent-firewall` subcommand isn't available in your Coder installation. In this case, you need to: 1. Set `use_boundary_directly = true` in your Terraform module configuration diff --git a/docs/manifest.json b/docs/manifest.json index cbbeceefbe..a71fd205da 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1626,9 +1626,9 @@ "path": "reference/cli/autoupdate.md" }, { - "title": "boundary", + "title": "agent-firewall", "description": "Network isolation tool for monitoring and restricting HTTP/HTTPS requests", - "path": "reference/cli/boundary.md" + "path": "reference/cli/agent-firewall.md" }, { "title": "coder", diff --git a/docs/reference/cli/boundary.md b/docs/reference/cli/agent-firewall.md similarity index 98% rename from docs/reference/cli/boundary.md rename to docs/reference/cli/agent-firewall.md index 79af765679..add4098c6b 100644 --- a/docs/reference/cli/boundary.md +++ b/docs/reference/cli/agent-firewall.md @@ -1,12 +1,12 @@ -# boundary +# agent-firewall Network isolation tool for monitoring and restricting HTTP/HTTPS requests ## Usage ```console -coder boundary [flags] [args...] +coder agent-firewall [flags] [args...] ``` ## Description diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md index 211cba86c8..bbb7e85a31 100644 --- a/docs/reference/cli/index.md +++ b/docs/reference/cli/index.md @@ -66,7 +66,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr | [support](./support.md) | Commands for troubleshooting issues with a Coder deployment. | | [server](./server.md) | Start a Coder server | | [provisioner](./provisioner.md) | View and manage provisioner daemons and jobs | -| [boundary](./boundary.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests | +| [agent-firewall](./agent-firewall.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests | | [features](./features.md) | List Enterprise features | | [licenses](./licenses.md) | Add, delete, and list licenses | | [groups](./groups.md) | Manage groups | diff --git a/enterprise/cli/boundary.go b/enterprise/cli/boundary.go index 104b2c6de2..212fe264ba 100644 --- a/enterprise/cli/boundary.go +++ b/enterprise/cli/boundary.go @@ -1,6 +1,7 @@ package cli import ( + "fmt" "net/http" "os" "runtime/debug" @@ -41,28 +42,31 @@ func (r *RootCmd) verifyLicense(inv *serpent.Invocation) error { entitlements, err := client.Entitlements(inv.Context()) if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusNotFound { - return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot use the boundary command") + return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot use the agent-firewall command") } else if err != nil { return xerrors.Errorf("failed to get entitlements: %w", err) } feature := entitlements.Features[codersdk.FeatureBoundary] if feature.Entitlement == codersdk.EntitlementNotEntitled { - return xerrors.Errorf("your license is not entitled to use the boundary feature") + return xerrors.Errorf("your license is not entitled to use the agent-firewall feature") } if !feature.Enabled { // Feature is entitled but disabled (shouldn't happen for FeatureBoundary // since it's in AlwaysEnable(), but handle it gracefully). - return xerrors.Errorf("the boundary feature is disabled in your deployment configuration") + return xerrors.Errorf("the agent-firewall feature is disabled in your deployment configuration") } return nil } -func (r *RootCmd) boundary() *serpent.Command { +// buildAgentFirewallCmd builds the agent-firewall command. The returned command +// uses the boundary base command from the external boundary package, wrapped +// with license verification. +func (r *RootCmd) buildAgentFirewallCmd() *serpent.Command { version := getBoundaryVersion() - cmd := boundarycli.BaseCommand(version) // Package coder/boundary/cli exports a "base command" designed to be integrated as a subcommand. - cmd.Use += " [args...]" // The base command looks like `boundary -- command`. Serpent adds the flags piece, but we need to add the args. + cmd := boundarycli.BaseCommand(version) + cmd.Use = "agent-firewall [args...]" // Wrap the handler to check for FeatureBoundary entitlement. originalHandler := cmd.Handler @@ -78,9 +82,42 @@ func (r *RootCmd) boundary() *serpent.Command { return err } - // Call the original handler if entitlement check passes. return originalHandler(inv) } return cmd } + +// buildBoundaryAliasCmd builds a hidden, deprecated "boundary" command that +// prints a deprecation notice and then runs the same logic as agent-firewall. +func (r *RootCmd) buildBoundaryAliasCmd() *serpent.Command { + version := getBoundaryVersion() + cmd := boundarycli.BaseCommand(version) + cmd.Use = "boundary [args...]" + cmd.Hidden = true + + originalHandler := cmd.Handler + cmd.Handler = func(inv *serpent.Invocation) error { + _, _ = fmt.Fprintln(inv.Stderr, "DEPRECATED: 'coder boundary' is deprecated, use 'coder agent-firewall' instead.") + + if isChild() { + return originalHandler(inv) + } + + if err := r.verifyLicense(inv); err != nil { + return err + } + + return originalHandler(inv) + } + + return cmd +} + +func (r *RootCmd) agentFirewall() *serpent.Command { + return r.buildAgentFirewallCmd() +} + +func (r *RootCmd) boundaryAlias() *serpent.Command { + return r.buildBoundaryAliasCmd() +} diff --git a/enterprise/cli/boundary_test.go b/enterprise/cli/boundary_test.go index 2457f4ca63..0c8f4c7bc3 100644 --- a/enterprise/cli/boundary_test.go +++ b/enterprise/cli/boundary_test.go @@ -24,7 +24,25 @@ import ( // Actually testing the functionality of coder/boundary takes place in the // coder/boundary repo, since it's a dependency of coder. // Here we want to test basically that integrating it as a subcommand doesn't break anything. -func TestBoundarySubcommand(t *testing.T) { +func TestAgentFirewallSubcommand(t *testing.T) { + t.Parallel() + + inv, _ := newCLI(t, "agent-firewall", "--help") + var buf bytes.Buffer + inv.Stdout = &buf + inv.Stderr = &buf + + err := inv.Run() + require.NoError(t, err) + + // Verify help output contains expected information. + // We're simply confirming that `coder agent-firewall --help` ran without a runtime error as + // a good chunk of serpent's self validation logic happens at runtime. + output := buf.String() + assert.Contains(t, output, boundarycli.BaseCommand("dev").Short) +} + +func TestBoundaryAlias(t *testing.T) { t.Parallel() inv, _ := newCLI(t, "boundary", "--help") @@ -35,14 +53,12 @@ func TestBoundarySubcommand(t *testing.T) { err := inv.Run() require.NoError(t, err) - // Verify help output contains expected information. - // We're simply confirming that `coder boundary --help` ran without a runtime error as - // a good chunk of serpents self validation logic happens at runtime. + // The alias should dispatch to the same command and display help. output := buf.String() assert.Contains(t, output, boundarycli.BaseCommand("dev").Short) } -func TestBoundaryLicenseVerification(t *testing.T) { +func TestAgentFirewallLicenseVerification(t *testing.T) { t.Parallel() t.Run("EntitledAndEnabled", func(t *testing.T) { @@ -56,13 +72,13 @@ func TestBoundaryLicenseVerification(t *testing.T) { }, }) - inv, conf := newCLI(t, "boundary", "--version") + inv, conf := newCLI(t, "agent-firewall", "--version") //nolint:gocritic // requires owner clitest.SetupConfig(t, client, conf) ctx := testutil.Context(t, testutil.WaitShort) err := inv.WithContext(ctx).Run() - // Should succeed - boundary --version should work with valid license. + // Should succeed - agent-firewall --version should work with valid license. require.NoError(t, err) }) @@ -122,13 +138,13 @@ func TestBoundaryLicenseVerification(t *testing.T) { proxyClient.SetSessionToken(client.SessionToken()) t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections) - inv, conf := newCLI(t, "boundary", "--version") + inv, conf := newCLI(t, "agent-firewall", "--version") clitest.SetupConfig(t, proxyClient, conf) ctx := testutil.Context(t, testutil.WaitShort) err = inv.WithContext(ctx).Run() require.Error(t, err) - require.ErrorContains(t, err, "your license is not entitled to use the boundary feature") + require.ErrorContains(t, err, "your license is not entitled to use the agent-firewall feature") }) t.Run("FeatureDisabled", func(t *testing.T) { @@ -186,13 +202,13 @@ func TestBoundaryLicenseVerification(t *testing.T) { proxyClient.SetSessionToken(client.SessionToken()) t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections) - inv, conf := newCLI(t, "boundary", "--version") + inv, conf := newCLI(t, "agent-firewall", "--version") clitest.SetupConfig(t, proxyClient, conf) ctx := testutil.Context(t, testutil.WaitShort) err = inv.WithContext(ctx).Run() require.Error(t, err) - require.ErrorContains(t, err, "the boundary feature is disabled in your deployment configuration") + require.ErrorContains(t, err, "the agent-firewall feature is disabled in your deployment configuration") }) t.Run("AGPLDeployment", func(t *testing.T) { @@ -223,7 +239,7 @@ func TestBoundaryLicenseVerification(t *testing.T) { proxyClient.SetSessionToken(client.SessionToken()) t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections) - inv, conf := newCLI(t, "boundary", "--version") + inv, conf := newCLI(t, "agent-firewall", "--version") clitest.SetupConfig(t, proxyClient, conf) ctx := testutil.Context(t, testutil.WaitShort) @@ -233,11 +249,11 @@ func TestBoundaryLicenseVerification(t *testing.T) { }) } -// TestBoundaryChildProcessSkipsCheck verifies that when CHILD=true, the license -// check is skipped. This simulates boundary re-executing itself to run the -// target process. We use a proxy that would fail the license check to verify -// it's skipped. -func TestBoundaryChildProcessSkipsCheck(t *testing.T) { +// TestAgentFirewallChildProcessSkipsCheck verifies that when CHILD=true, the +// license check is skipped. This simulates boundary re-executing itself to run +// the target process. We use a proxy that would fail the license check to +// verify it's skipped. +func TestAgentFirewallChildProcessSkipsCheck(t *testing.T) { // Cannot use t.Parallel() with t.Setenv(). client, _ := coderdenttest.New(t, &coderdenttest.Options{ LicenseOptions: &coderdenttest.LicenseOptions{ @@ -290,7 +306,7 @@ func TestBoundaryChildProcessSkipsCheck(t *testing.T) { proxyClient.SetSessionToken(client.SessionToken()) t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections) - inv, conf := newCLI(t, "boundary", "--version") + inv, conf := newCLI(t, "agent-firewall", "--version") clitest.SetupConfig(t, proxyClient, conf) // Set CHILD=true to simulate boundary re-execution. This should skip the diff --git a/enterprise/cli/root.go b/enterprise/cli/root.go index baba6830e6..b211c0d598 100644 --- a/enterprise/cli/root.go +++ b/enterprise/cli/root.go @@ -18,7 +18,8 @@ func (r *RootCmd) enterpriseOnly() []*serpent.Command { agplcli.ExperimentalCommand(append(r.AGPLExperimental(), r.enterpriseExperimental()...)), // New commands that don't exist in AGPL: - r.boundary(), + r.agentFirewall(), + r.boundaryAlias(), r.workspaceProxy(), r.features(), r.licenses(), diff --git a/enterprise/cli/testdata/coder_--help.golden b/enterprise/cli/testdata/coder_--help.golden index 1db07b1801..373a3609e4 100644 --- a/enterprise/cli/testdata/coder_--help.golden +++ b/enterprise/cli/testdata/coder_--help.golden @@ -14,9 +14,9 @@ USAGE: $ coder templates init SUBCOMMANDS: - aibridge Manage AI Bridge. - boundary Network isolation tool for monitoring and restricting + agent-firewall Network isolation tool for monitoring and restricting HTTP/HTTPS requests + aibridge Manage AI Bridge. external-workspaces Create or manage external workspaces features List Enterprise features groups Manage groups diff --git a/enterprise/cli/testdata/coder_boundary_--help.golden b/enterprise/cli/testdata/coder_agent-firewall_--help.golden similarity index 98% rename from enterprise/cli/testdata/coder_boundary_--help.golden rename to enterprise/cli/testdata/coder_agent-firewall_--help.golden index 74f46947c1..5c6dcf7adb 100644 --- a/enterprise/cli/testdata/coder_boundary_--help.golden +++ b/enterprise/cli/testdata/coder_agent-firewall_--help.golden @@ -1,7 +1,7 @@ coder v0.0.0-devel USAGE: - coder boundary [flags] [args...] + coder agent-firewall [flags] [args...] Network isolation tool for monitoring and restricting HTTP/HTTPS requests