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.
This commit is contained in:
Sas Swart
2026-06-01 08:32:01 +00:00
parent 76d3181aba
commit 78ca41bafa
10 changed files with 96 additions and 41 deletions
+5 -4
View File
@@ -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(
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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",
@@ -1,12 +1,12 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# 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
+1 -1
View File
@@ -66,7 +66,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr
| [<code>support</code>](./support.md) | Commands for troubleshooting issues with a Coder deployment. |
| [<code>server</code>](./server.md) | Start a Coder server |
| [<code>provisioner</code>](./provisioner.md) | View and manage provisioner daemons and jobs |
| [<code>boundary</code>](./boundary.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests |
| [<code>agent-firewall</code>](./agent-firewall.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests |
| [<code>features</code>](./features.md) | List Enterprise features |
| [<code>licenses</code>](./licenses.md) | Add, delete, and list licenses |
| [<code>groups</code>](./groups.md) | Manage groups |
+44 -7
View File
@@ -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()
}
+34 -18
View File
@@ -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
+2 -1
View File
@@ -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(),
+2 -2
View File
@@ -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
@@ -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