Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e5b6b18
Enhance add-access-rule command UX with intuitive name-based flags
rkoster Apr 10, 2026
f14301a
Remove access rule names per RFC updates
rkoster Apr 15, 2026
25a55f9
Refine access-rules output to show separate host, domain, and path co…
rkoster Apr 15, 2026
6121923
Rebrand RFC terminology: access rules → route policies, selector → so…
rkoster Apr 21, 2026
1df8f98
Add name-based source flags to remove-route-policy
rkoster Jun 17, 2026
6108fec
Add CAPI version check for route policy commands
rkoster Jun 17, 2026
08f4b62
Add route policies column to cf domains output
rkoster Jun 17, 2026
fed4288
Add cf/cli to .gitignore to prevent binary commits
rkoster Jun 17, 2026
3afb795
Add unit tests for route policy commands, actor, and ccv3 client
rkoster Jun 17, 2026
cc26579
Fix: reject --source-org with --source-app when --source-space is mis…
rkoster Jun 17, 2026
504b0ea
test: add scope/enforce coverage for CreatePrivateDomain in domain_te…
rkoster Jun 17, 2026
9125dd3
refactor: consolidate Add/RemoveRoutePolicyArgs into single RoutePoli…
rkoster Jun 17, 2026
e8fdbb3
test: add dedicated tests for route_policy_source_flags and create-pr…
rkoster Jun 17, 2026
2375f4d
test: add --enforce-route-policies and --scope coverage to create-sha…
rkoster Jun 17, 2026
64d47d4
refactor: extract shared --enforce-route-policies / --scope test beha…
rkoster Jun 17, 2026
59f8468
chore: remove devbox.json and devbox.lock from tracked files
rkoster Jun 17, 2026
cd4b60f
Remove whitespace-only changes from unrelated files
rkoster Jun 17, 2026
7dfb878
fix: guard AddRoutePolicy against non-enforcing domains
rkoster Jun 22, 2026
316c9af
refactor: move GetRoutesByDomain to route.go
rkoster Jun 22, 2026
9adda5d
refactor: simplify GetRoutesByDomain — drop redundant copy loop
rkoster Jun 22, 2026
ab71adf
feat: add -n short flag for --hostname on route policy commands
rkoster Jun 22, 2026
0d8afe7
refactor: extract resolveOrgGUID/resolveSpaceGUID to eliminate duplic…
rkoster Jun 22, 2026
f73911a
refactor: convert source flags test to package v7_test with v7fakes
rkoster Jun 22, 2026
cb0db1b
perf: use ?include=source to resolve policy source names in one API call
rkoster Jun 22, 2026
1ee6df8
refactor: filter routes slice before map, pre-populate domain cache, …
rkoster Jun 22, 2026
b8a3568
feat: register PATCH /v3/route_policies/:guid in CAPI metadata client
rkoster Jun 22, 2026
c727417
feat: add RoutePolicyAmbiguityError for route-policy label disambigua…
rkoster Jun 22, 2026
557a1a8
feat: implement GetRoutePolicyLabels and UpdateRoutePolicyLabels acto…
rkoster Jun 22, 2026
fd5563e
feat: add route-policy methods to Actor/SetLabelActor interfaces, reg…
rkoster Jun 22, 2026
a1224ca
feat: add route-policy support to labels command and label updater
rkoster Jun 22, 2026
e7fee02
feat: add route-policy support to cf labels command
rkoster Jun 22, 2026
396be1e
feat: add --source flag to set-label and unset-label commands
rkoster Jun 22, 2026
49d6eed
fix: refactor testForResourceType helper to take explicit plural URI …
rkoster Jun 22, 2026
467dd5c
fix: remove orphaned comment fragment in route_policy_source_flags_te…
rkoster Jun 22, 2026
a5d7829
test: document that route-policy ResourceName is passed as-is to labe…
rkoster Jun 22, 2026
e74021d
test: document that route-policy ResourceName is passed as-is to labe…
rkoster Jun 22, 2026
6deeb07
fix: remove duplicate RunSpecs in route_policy_resource_test.go
rkoster Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ _testmain.go
# Built binaries
*.exe
/cli
/cf/cli

out/
release/*
Expand Down
13 changes: 13 additions & 0 deletions actor/actionerror/domain_not_enforcing_route_policies_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package actionerror

import "fmt"

// DomainNotEnforcingRoutePoliciesError is returned when a user attempts to
// add a route policy to a domain that does not have enforce_route_policies enabled.
type DomainNotEnforcingRoutePoliciesError struct {
Name string
}

func (e DomainNotEnforcingRoutePoliciesError) Error() string {
return fmt.Sprintf("Domain '%s' does not have route policy enforcement enabled.", e.Name)
}
17 changes: 17 additions & 0 deletions actor/actionerror/route_policy_ambiguity_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package actionerror

import "fmt"

// RoutePolicyAmbiguityError is returned when a route has multiple policies
// and no --source flag was given to disambiguate.
type RoutePolicyAmbiguityError struct {
RouteURL string
Count int
}

func (e RoutePolicyAmbiguityError) Error() string {
return fmt.Sprintf(
"Route '%s' has %d policies. Specify one with --source.",
e.RouteURL, e.Count,
)
}
11 changes: 11 additions & 0 deletions actor/actionerror/route_policy_not_found_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package actionerror

import "fmt"

type RoutePolicyNotFoundError struct {
Source string
}

func (e RoutePolicyNotFoundError) Error() string {
return fmt.Sprintf("Route policy with source '%s' not found.", e.Source)
}
3 changes: 3 additions & 0 deletions actor/v7action/cloud_controller_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type CloudControllerClient interface {
CancelDeployment(deploymentGUID string) (ccv3.Warnings, error)
ContinueDeployment(deploymentGUID string) (ccv3.Warnings, error)
CopyPackage(sourcePackageGUID string, targetAppGUID string) (resources.Package, ccv3.Warnings, error)
CreateRoutePolicy(routePolicy resources.RoutePolicy) (resources.RoutePolicy, ccv3.Warnings, error)
CreateApplication(app resources.Application) (resources.Application, ccv3.Warnings, error)
CreateApplicationDeployment(dep resources.Deployment) (string, ccv3.Warnings, error)
CreateApplicationProcessScale(appGUID string, process resources.Process) (resources.Process, ccv3.Warnings, error)
Expand All @@ -42,6 +43,7 @@ type CloudControllerClient interface {
CreateSpace(space resources.Space) (resources.Space, ccv3.Warnings, error)
CreateSpaceQuota(spaceQuota resources.SpaceQuota) (resources.SpaceQuota, ccv3.Warnings, error)
CreateUser(userGUID string) (resources.User, ccv3.Warnings, error)
DeleteRoutePolicy(guid string) (ccv3.JobURL, ccv3.Warnings, error)
DeleteApplication(guid string) (ccv3.JobURL, ccv3.Warnings, error)
DeleteApplicationProcessInstance(appGUID string, processType string, instanceIndex int) (ccv3.Warnings, error)
DeleteBuildpack(buildpackGUID string) (ccv3.JobURL, ccv3.Warnings, error)
Expand All @@ -63,6 +65,7 @@ type CloudControllerClient interface {
DeleteUser(userGUID string) (ccv3.JobURL, ccv3.Warnings, error)
DownloadDroplet(dropletGUID string) ([]byte, ccv3.Warnings, error)
EntitleIsolationSegmentToOrganizations(isoGUID string, orgGUIDs []string) (resources.RelationshipList, ccv3.Warnings, error)
GetRoutePolicies(query ...ccv3.Query) ([]resources.RoutePolicy, ccv3.IncludedResources, ccv3.Warnings, error)
GetApplicationByNameAndSpace(appName string, spaceGUID string) (resources.Application, ccv3.Warnings, error)
GetApplicationDropletCurrent(appGUID string) (resources.Droplet, ccv3.Warnings, error)
GetApplicationEnvironment(appGUID string) (ccv3.Environment, ccv3.Warnings, error)
Expand Down
29 changes: 23 additions & 6 deletions actor/v7action/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (actor Actor) CheckRoute(domainName string, hostname string, path string, p
return matches, allWarnings, err
}

func (actor Actor) CreateSharedDomain(domainName string, internal bool, routerGroupName string) (Warnings, error) {
func (actor Actor) CreateSharedDomain(domainName string, internal bool, routerGroupName string, enforceAccessRules bool, accessRulesScope string) (Warnings, error) {
allWarnings := Warnings{}
routerGroupGUID := ""

Expand All @@ -37,28 +37,45 @@ func (actor Actor) CreateSharedDomain(domainName string, internal bool, routerGr
routerGroupGUID = routerGroup.GUID
}

_, warnings, err := actor.CloudControllerClient.CreateDomain(resources.Domain{
domain := resources.Domain{
Name: domainName,
Internal: types.NullBool{IsSet: true, Value: internal},
RouterGroup: routerGroupGUID,
})
}

// Set enforce_route_policies if specified
if enforceAccessRules {
domain.EnforceRoutePolicies = types.NullBool{IsSet: true, Value: true}
domain.RoutePoliciesScope = accessRulesScope
}

_, warnings, err := actor.CloudControllerClient.CreateDomain(domain)
allWarnings = append(allWarnings, Warnings(warnings)...)

return allWarnings, err
}

func (actor Actor) CreatePrivateDomain(domainName string, orgName string) (Warnings, error) {
func (actor Actor) CreatePrivateDomain(domainName string, orgName string, enforceAccessRules bool, accessRulesScope string) (Warnings, error) {
allWarnings := Warnings{}
organization, warnings, err := actor.GetOrganizationByName(orgName)
allWarnings = append(allWarnings, warnings...)

if err != nil {
return allWarnings, err
}
_, apiWarnings, err := actor.CloudControllerClient.CreateDomain(resources.Domain{

domain := resources.Domain{
Name: domainName,
OrganizationGUID: organization.GUID,
})
}

// Set enforce_route_policies if specified
if enforceAccessRules {
domain.EnforceRoutePolicies = types.NullBool{IsSet: true, Value: true}
domain.RoutePoliciesScope = accessRulesScope
}

_, apiWarnings, err := actor.CloudControllerClient.CreateDomain(domain)

actorWarnings := Warnings(apiWarnings)
allWarnings = append(allWarnings, actorWarnings...)
Expand Down
59 changes: 54 additions & 5 deletions actor/v7action/domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,21 @@ var _ = Describe("Domain Actions", func() {

Describe("CreateSharedDomain", func() {
var (
warnings Warnings
executeErr error
routerGroup string
warnings Warnings
executeErr error
routerGroup string
enforceRules bool
scope string
)

JustBeforeEach(func() {
warnings, executeErr = actor.CreateSharedDomain("the-domain-name", true, routerGroup)
warnings, executeErr = actor.CreateSharedDomain("the-domain-name", true, routerGroup, enforceRules, scope)
})

BeforeEach(func() {
routerGroup = ""
enforceRules = false
scope = ""
fakeCloudControllerClient.CreateDomainReturns(resources.Domain{}, ccv3.Warnings{"create-warning-1", "create-warning-2"}, errors.New("create-error"))
})

Expand Down Expand Up @@ -170,11 +174,40 @@ var _ = Describe("Domain Actions", func() {
))
})
})

Context("when enforce route policies is enabled with a scope", func() {
BeforeEach(func() {
enforceRules = true
scope = "org"
fakeCloudControllerClient.CreateDomainReturns(resources.Domain{}, ccv3.Warnings{"create-warning-1"}, nil)
})

It("passes EnforceRoutePolicies and RoutePoliciesScope to the client", func() {
Expect(executeErr).NotTo(HaveOccurred())

Expect(fakeCloudControllerClient.CreateDomainCallCount()).To(Equal(1))
passedDomain := fakeCloudControllerClient.CreateDomainArgsForCall(0)
Expect(passedDomain.EnforceRoutePolicies).To(Equal(types.NullBool{IsSet: true, Value: true}))
Expect(passedDomain.RoutePoliciesScope).To(Equal("org"))
})
})
})

Describe("CreatePrivateDomain", func() {
var (
warnings Warnings
executeErr error
enforceRules bool
scope string
)

JustBeforeEach(func() {
warnings, executeErr = actor.CreatePrivateDomain("private-domain-name", "org-name", enforceRules, scope)
})

BeforeEach(func() {
enforceRules = false
scope = ""
fakeCloudControllerClient.GetOrganizationsReturns(
[]resources.Organization{
{GUID: "org-guid"},
Expand All @@ -191,7 +224,6 @@ var _ = Describe("Domain Actions", func() {
})

It("delegates to the cloud controller client", func() {
warnings, executeErr := actor.CreatePrivateDomain("private-domain-name", "org-name")
Expect(executeErr).To(MatchError("create-error"))
Expect(warnings).To(ConsistOf("get-orgs-warning", "create-warning-1", "create-warning-2"))

Expand All @@ -205,6 +237,23 @@ var _ = Describe("Domain Actions", func() {
},
))
})

Context("when enforce route policies is enabled with a scope", func() {
BeforeEach(func() {
enforceRules = true
scope = "org"
fakeCloudControllerClient.CreateDomainReturns(resources.Domain{}, ccv3.Warnings{"create-warning-1"}, nil)
})

It("passes EnforceRoutePolicies and RoutePoliciesScope to the client", func() {
Expect(executeErr).NotTo(HaveOccurred())

Expect(fakeCloudControllerClient.CreateDomainCallCount()).To(Equal(1))
passedDomain := fakeCloudControllerClient.CreateDomainArgsForCall(0)
Expect(passedDomain.EnforceRoutePolicies).To(Equal(types.NullBool{IsSet: true, Value: true}))
Expect(passedDomain.RoutePoliciesScope).To(Equal("org"))
})
})
})

Describe("delete domain", func() {
Expand Down
53 changes: 53 additions & 0 deletions actor/v7action/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v7action

import (
"code.cloudfoundry.org/cli/v9/actor/actionerror"
"code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3"
"code.cloudfoundry.org/cli/v9/resources"
"code.cloudfoundry.org/cli/v9/types"
)
Expand Down Expand Up @@ -178,3 +179,55 @@ func (actor *Actor) updateResourceMetadata(resourceType string, resourceGUID str

return warnings, nil
}

// resolveRoutePolicyGUID finds the GUID of the route policy to operate on.
// If source is non-empty, it finds the policy with that source.
// If source is empty, it requires exactly one policy (returns ambiguity or not-found error).
func (actor *Actor) resolveRoutePolicyGUID(routeURL, spaceGUID, source string) (string, *resources.Metadata, Warnings, error) {
route, routeWarnings, err := actor.GetRoute(routeURL, spaceGUID)
if err != nil {
return "", nil, routeWarnings, err
}

routePolicies, _, policyWarnings, err := actor.CloudControllerClient.GetRoutePolicies(
ccv3.Query{Key: ccv3.RouteGUIDFilter, Values: []string{route.GUID}},
)
allWarnings := append(routeWarnings, Warnings(policyWarnings)...)
if err != nil {
return "", nil, allWarnings, err
}

if source != "" {
for _, p := range routePolicies {
if p.Source == source {
return p.GUID, p.Metadata, allWarnings, nil
}
}
return "", nil, allWarnings, actionerror.RoutePolicyNotFoundError{Source: source}
}

if len(routePolicies) == 0 {
return "", nil, allWarnings, actionerror.RoutePolicyNotFoundError{Source: ""}
}
if len(routePolicies) > 1 {
return "", nil, allWarnings, actionerror.RoutePolicyAmbiguityError{RouteURL: routeURL, Count: len(routePolicies)}
}
p := routePolicies[0]
return p.GUID, p.Metadata, allWarnings, nil
}

func (actor *Actor) GetRoutePolicyLabels(routeURL, spaceGUID, source string) (map[string]types.NullString, Warnings, error) {
_, metadata, warnings, err := actor.resolveRoutePolicyGUID(routeURL, spaceGUID, source)
if err != nil {
return nil, warnings, err
}
return actor.extractLabels(metadata, warnings, nil)
}

func (actor *Actor) UpdateRoutePolicyLabels(routeURL, spaceGUID, source string, labels map[string]types.NullString) (Warnings, error) {
policyGUID, _, warnings, err := actor.resolveRoutePolicyGUID(routeURL, spaceGUID, source)
if err != nil {
return warnings, err
}
return actor.updateResourceMetadata("route-policy", policyGUID, resources.Metadata{Labels: labels}, warnings)
}
Loading
Loading