Files
coder/enterprise/cli/boundary.go
T
Sas Swart 52722b800b chore: rename boundary command to agent-firewall (#25889)
Renames the `coder boundary` CLI subcommand to `coder agent-firewall` as
part of the Boundaries → Agent Firewall rebrand.

`coder boundary` is retained as a hidden, deprecated alias that prints a
deprecation notice to stderr before running. Both commands use separate
builder functions backed by the same boundary base command and license
verification logic.

Closes https://linear.app/codercom/issue/AIGOV-236

<details><summary>Implementation notes</summary>

**Approach:** Two separate `*serpent.Command` objects (not `Aliases`) so
the deprecated `boundary` path can print a stderr warning while
`agent-firewall` stays clean.

**Changes:**
- `enterprise/cli/boundary.go`: Split old `boundary()` into
`buildAgentFirewallCmd()` and `buildBoundaryAliasCmd()`. Error messages
in `verifyLicense` now reference "agent-firewall".
- `enterprise/cli/root.go`: Register both commands.
- `cli/root.go`: Update YAML-only option validation bypass for the new
command name.
- Tests: Rename to `TestAgentFirewallSubcommand`, add
`TestBoundaryAlias`, update license verification tests to use
`agent-firewall`.
- Golden files and CLI reference docs regenerated.
- `docs/ai-coder/agent-firewall/version.md` and `docs/manifest.json`
updated.

</details>

> Generated with [Coder Agents](https://coder.com/agents) by @SasSwart
2026-06-04 11:14:36 +02:00

114 lines
3.0 KiB
Go

package cli
import (
"net/http"
"os"
"runtime/debug"
"golang.org/x/xerrors"
boundarycli "github.com/coder/boundary/cli"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
func isChild() bool {
return os.Getenv("CHILD") == "true"
}
func getBoundaryVersion() string {
const boundaryModulePath = "github.com/coder/boundary"
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
return "unknown"
}
for _, module := range buildInfo.Deps {
if module.Path == boundaryModulePath {
return module.Version
}
}
return "unknown"
}
func (r *RootCmd) verifyLicense(inv *serpent.Invocation) error {
client, err := r.InitClient(inv)
if err != nil {
return err
}
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 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 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 agent-firewall feature is disabled in your deployment configuration")
}
return nil
}
// agentFirewall 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) agentFirewall() *serpent.Command {
version := getBoundaryVersion()
cmd := boundarycli.BaseCommand(version)
cmd.Use = "agent-firewall [args...]"
// Wrap the handler to check for FeatureBoundary entitlement.
originalHandler := cmd.Handler
cmd.Handler = func(inv *serpent.Invocation) error {
// Boundary re-executes itself with CHILD=true to run the target process
// inside a jailed network namespace. Skip the license check for child
// processes since the parent already verified entitlement.
if isChild() {
return originalHandler(inv)
}
if err := r.verifyLicense(inv); err != nil {
return err
}
return originalHandler(inv)
}
return cmd
}
// boundaryAlias builds a hidden, deprecated "boundary" command that
// prints a deprecation notice and then runs the same logic as agent-firewall.
func (r *RootCmd) boundaryAlias() *serpent.Command {
version := getBoundaryVersion()
cmd := boundarycli.BaseCommand(version)
cmd.Use = "boundary [args...]"
cmd.Hidden = true
cmd.Deprecated = "use 'coder agent-firewall' instead"
originalHandler := cmd.Handler
cmd.Handler = func(inv *serpent.Invocation) error {
if isChild() {
return originalHandler(inv)
}
if err := r.verifyLicense(inv); err != nil {
return err
}
return originalHandler(inv)
}
return cmd
}