Skip to content

Commit 0ab3e02

Browse files
committed
Add name-based source flags to remove-route-policy
Extract source resolution flags (--source-app, --source-space, --source-org, --source-any, --source) into a shared RoutePolicySourceFlags struct embedded in both add-route-policy and remove-route-policy commands. Previously remove-route-policy only accepted --source with a raw GUID-format value (cf:app:<guid>, etc.), while add-route-policy supported name-based resolution. The two commands now have matching flag sets.
1 parent 9409dbb commit 0ab3e02

3 files changed

Lines changed: 191 additions & 192 deletions

File tree

command/v7/add_route_policy_command.go

Lines changed: 3 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ package v7
33
import (
44
"fmt"
55

6-
"code.cloudfoundry.org/cli/v9/actor/actionerror"
7-
"code.cloudfoundry.org/cli/v9/actor/v7action"
86
"code.cloudfoundry.org/cli/v9/command/flag"
9-
"code.cloudfoundry.org/cli/v9/command/translatableerror"
107
)
118

129
type AddRoutePolicyCommand struct {
@@ -15,23 +12,14 @@ type AddRoutePolicyCommand struct {
1512
RequiredArgs flag.AddRoutePolicyArgs `positional-args:"yes"`
1613
Hostname string `long:"hostname" required:"true" description:"Hostname for the route"`
1714
Path string `long:"path" description:"Path for the route"`
18-
19-
// Source resolution flags (mutually exclusive as primary source)
20-
SourceApp string `long:"source-app" description:"Allow access from this app (by name)"`
21-
SourceSpace string `long:"source-space" description:"Allow access from all apps in this space (by name) or specify the space for --source-app"`
22-
SourceOrg string `long:"source-org" description:"Allow access from all apps in this org (by name) or specify the org for --source-space/--source-app"`
23-
SourceAny bool `long:"source-any" description:"Allow access from any authenticated app"`
24-
25-
// Advanced: raw source flag
26-
Source string `long:"source" description:"Raw source (cf:app:<guid>, cf:space:<guid>, cf:org:<guid>, or cf:any)"`
15+
RoutePolicySourceFlags
2716

2817
usage interface{} `usage:"CF_NAME add-route-policy DOMAIN --hostname HOSTNAME [--source-app APP_NAME [--source-space SPACE_NAME] [--source-org ORG_NAME] | --source-space SPACE_NAME [--source-org ORG_NAME] | --source-org ORG_NAME | --source-any | --source SOURCE] [--path PATH]\n\nALLOW ACCESS TO A ROUTE:\n Create a route policy that allows specific apps, spaces, or orgs to access a route using mTLS authentication.\n\nEXAMPLES:\n # Allow the \"frontend-app\" (in current space) to access the backend route\n cf add-route-policy apps.identity --source-app frontend-app --hostname backend\n\n # Allow an app in a different space to access the route\n cf add-route-policy apps.identity --source-app api-client --source-space other-space --hostname backend\n\n # Allow an app in a different org to access the route\n cf add-route-policy apps.identity --source-app external-client --source-space external-space --source-org external-org --hostname backend\n\n # Allow all apps in the \"monitoring\" space to access the API metrics endpoint\n cf add-route-policy apps.identity --source-space monitoring --hostname api --path /metrics\n\n # Allow all apps in a space in a different org\n cf add-route-policy apps.identity --source-space prod-space --source-org prod-org --hostname api\n\n # Allow all apps in the \"platform\" org to access the route\n cf add-route-policy apps.identity --source-org platform --hostname shared-api\n\n # Allow any authenticated app to access the public API\n cf add-route-policy apps.identity --source-any --hostname public-api\n\n # Use raw source (advanced)\n cf add-route-policy apps.identity --source cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend"`
2918
relatedCommands interface{} `related_commands:"route-policies, remove-route-policy, create-shared-domain"`
3019
}
3120

3221
func (cmd AddRoutePolicyCommand) Execute(args []string) error {
33-
// Validate source flags
34-
if err := cmd.validateSourceFlags(); err != nil {
22+
if err := cmd.RoutePolicySourceFlags.validateSourceFlags(); err != nil {
3523
return err
3624
}
3725

@@ -45,14 +33,12 @@ func (cmd AddRoutePolicyCommand) Execute(args []string) error {
4533
return err
4634
}
4735

48-
// Resolve source from source flags
49-
source, scopeDisplay, warnings, err := cmd.resolveSource()
36+
source, scopeDisplay, warnings, err := resolveSource(cmd.RoutePolicySourceFlags, cmd.Actor, cmd.Config)
5037
cmd.UI.DisplayWarnings(warnings)
5138
if err != nil {
5239
return err
5340
}
5441

55-
// Validate source format
5642
if err := validateSource(source); err != nil {
5743
return err
5844
}
@@ -67,7 +53,6 @@ func (cmd AddRoutePolicyCommand) Execute(args []string) error {
6753
"User": user.Name,
6854
})
6955

70-
// Display resolved source (for transparency)
7156
cmd.UI.DisplayText(" {{.ScopeDisplay}}",
7257
map[string]interface{}{
7358
"ScopeDisplay": scopeDisplay,
@@ -88,169 +73,7 @@ func (cmd AddRoutePolicyCommand) Execute(args []string) error {
8873
return nil
8974
}
9075

91-
// validateSourceFlags ensures exactly one source target is specified and validates combinations
92-
func (cmd AddRoutePolicyCommand) validateSourceFlags() error {
93-
sourceFlags := []string{}
94-
95-
if cmd.Source != "" {
96-
sourceFlags = append(sourceFlags, "--source")
97-
}
98-
if cmd.SourceApp != "" {
99-
sourceFlags = append(sourceFlags, "--source-app")
100-
}
101-
if cmd.SourceSpace != "" && cmd.SourceApp == "" {
102-
// --source-space only counts as a primary source if --source-app is NOT provided
103-
sourceFlags = append(sourceFlags, "--source-space")
104-
}
105-
if cmd.SourceOrg != "" && cmd.SourceSpace == "" && cmd.SourceApp == "" {
106-
// --source-org only counts as a primary source if neither --source-space nor --source-app are provided
107-
sourceFlags = append(sourceFlags, "--source-org")
108-
}
109-
if cmd.SourceAny {
110-
sourceFlags = append(sourceFlags, "--source-any")
111-
}
112-
113-
if len(sourceFlags) == 0 {
114-
return translatableerror.RequiredArgumentError{
115-
ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --source",
116-
}
117-
}
118-
119-
if len(sourceFlags) > 1 {
120-
return translatableerror.ArgumentCombinationError{
121-
Args: sourceFlags,
122-
}
123-
}
124-
125-
return nil
126-
}
127-
128-
// resolveSource resolves source flags to a source string
129-
// Returns (source, scopeDisplay, warnings, error)
130-
// scopeDisplay is a human-readable description for output (e.g., "scope: app, source: frontend-app")
131-
func (cmd AddRoutePolicyCommand) resolveSource() (string, string, v7action.Warnings, error) {
132-
var allWarnings v7action.Warnings
133-
134-
// Priority: --source flag (raw source, no resolution needed)
135-
if cmd.Source != "" {
136-
return cmd.Source, fmt.Sprintf("source: %s", cmd.Source), allWarnings, nil
137-
}
138-
139-
// --source-any
140-
if cmd.SourceAny {
141-
return "cf:any", "scope: any, source: any authenticated app", allWarnings, nil
142-
}
143-
144-
// --source-app (with optional --source-space and --source-org for cross-space/org lookup)
145-
if cmd.SourceApp != "" {
146-
// Determine space GUID for app lookup
147-
spaceGUID := cmd.Config.TargetedSpace().GUID
148-
spaceName := cmd.Config.TargetedSpace().Name
149-
orgName := cmd.Config.TargetedOrganization().Name
150-
151-
if cmd.SourceSpace != "" {
152-
// Determine org GUID for space lookup
153-
orgGUID := cmd.Config.TargetedOrganization().GUID
154-
if cmd.SourceOrg != "" {
155-
org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg)
156-
allWarnings = append(allWarnings, warnings...)
157-
if err != nil {
158-
return "", "", allWarnings, err
159-
}
160-
orgGUID = org.GUID
161-
orgName = cmd.SourceOrg
162-
}
163-
164-
// Resolve space by name
165-
space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.SourceSpace, orgGUID)
166-
allWarnings = append(allWarnings, warnings...)
167-
if err != nil {
168-
return "", "", allWarnings, err
169-
}
170-
spaceGUID = space.GUID
171-
spaceName = cmd.SourceSpace
172-
}
173-
174-
// Resolve app by name in the determined space
175-
app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.SourceApp, spaceGUID)
176-
allWarnings = append(allWarnings, warnings...)
177-
if err != nil {
178-
// Enhanced error message for app not found
179-
if _, ok := err.(actionerror.ApplicationNotFoundError); ok {
180-
if cmd.SourceSpace == "" {
181-
// App not found in current space
182-
return "", "", allWarnings, fmt.Errorf(
183-
"App '%s' not found in space '%s' / org '%s'.\nTIP: If the app is in a different space or org, use --source-space and/or --source-org flags.",
184-
cmd.SourceApp,
185-
cmd.Config.TargetedSpace().Name,
186-
cmd.Config.TargetedOrganization().Name,
187-
)
188-
}
189-
}
190-
return "", "", allWarnings, err
191-
}
192-
193-
scopeDisplay := fmt.Sprintf("scope: app, source: %s", cmd.SourceApp)
194-
if cmd.SourceSpace != "" {
195-
scopeDisplay += fmt.Sprintf(" (space: %s", spaceName)
196-
if cmd.SourceOrg != "" {
197-
scopeDisplay += fmt.Sprintf(", org: %s", orgName)
198-
}
199-
scopeDisplay += ")"
200-
}
201-
202-
return fmt.Sprintf("cf:app:%s", app.GUID), scopeDisplay, allWarnings, nil
203-
}
204-
205-
// --source-space (without --source-app, so create space-level policy)
206-
if cmd.SourceSpace != "" {
207-
// Determine org GUID for space lookup
208-
orgGUID := cmd.Config.TargetedOrganization().GUID
209-
orgName := cmd.Config.TargetedOrganization().Name
210-
if cmd.SourceOrg != "" {
211-
org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg)
212-
allWarnings = append(allWarnings, warnings...)
213-
if err != nil {
214-
return "", "", allWarnings, err
215-
}
216-
orgGUID = org.GUID
217-
orgName = cmd.SourceOrg
218-
}
219-
220-
// Resolve space by name
221-
space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.SourceSpace, orgGUID)
222-
allWarnings = append(allWarnings, warnings...)
223-
if err != nil {
224-
return "", "", allWarnings, err
225-
}
226-
227-
scopeDisplay := fmt.Sprintf("scope: space, source: %s", cmd.SourceSpace)
228-
if cmd.SourceOrg != "" {
229-
scopeDisplay += fmt.Sprintf(" (org: %s)", orgName)
230-
}
231-
232-
return fmt.Sprintf("cf:space:%s", space.GUID), scopeDisplay, allWarnings, nil
233-
}
234-
235-
// --source-org (without --source-space or --source-app, so create org-level policy)
236-
if cmd.SourceOrg != "" {
237-
org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg)
238-
allWarnings = append(allWarnings, warnings...)
239-
if err != nil {
240-
return "", "", allWarnings, err
241-
}
242-
243-
scopeDisplay := fmt.Sprintf("scope: org, source: %s", cmd.SourceOrg)
244-
245-
return fmt.Sprintf("cf:org:%s", org.GUID), scopeDisplay, allWarnings, nil
246-
}
247-
248-
// Should never reach here due to validation
249-
return "", "", allWarnings, fmt.Errorf("no source specified")
250-
}
251-
25276
func validateSource(source string) error {
253-
// Basic validation - check for cf:app:, cf:space:, cf:org:, or cf:any prefix
25477
validPrefixes := []string{"cf:app:", "cf:space:", "cf:org:", "cf:any"}
25578
for _, prefix := range validPrefixes {
25679
if len(source) >= len(prefix) && source[:len(prefix)] == prefix {
@@ -260,7 +83,6 @@ func validateSource(source string) error {
26083
}
26184
return nil
26285
}
263-
// For other sources, ensure there's a GUID after the prefix
26486
if len(source) <= len(prefix) {
26587
return fmt.Errorf("source '%s' must include a GUID (e.g., %s<guid>)", source, prefix)
26688
}

command/v7/remove_route_policy_command.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import (
77
type RemoveRoutePolicyCommand struct {
88
BaseCommand
99

10-
RequiredArgs flag.RemoveRoutePolicyArgs `positional-args:"yes"`
11-
Source string `long:"source" required:"true" description:"Source to identify the route policy (cf:app:<guid>, cf:space:<guid>, cf:org:<guid>, or cf:any)"`
12-
Hostname string `long:"hostname" required:"true" description:"Hostname for the route"`
13-
Path string `long:"path" description:"Path for the route"`
14-
Force bool `short:"f" description:"Force deletion without confirmation"`
15-
usage interface{} `usage:"CF_NAME remove-route-policy DOMAIN --source SOURCE --hostname HOSTNAME [--path PATH] [-f]\n\nEXAMPLES:\n cf remove-route-policy apps.identity --source cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend\n cf remove-route-policy apps.identity --source cf:space:2b26e210-1b48-4e60-8432-f24bc5927789 --hostname api --path /metrics -f\n cf remove-route-policy apps.identity --source cf:any --hostname public-api -f"`
16-
relatedCommands interface{} `related_commands:"route-policies, add-route-policy"`
10+
RequiredArgs flag.RemoveRoutePolicyArgs `positional-args:"yes"`
11+
RoutePolicySourceFlags
12+
Hostname string `long:"hostname" required:"true" description:"Hostname for the route"`
13+
Path string `long:"path" description:"Path for the route"`
14+
Force bool `short:"f" description:"Force deletion without confirmation"`
15+
16+
usage interface{} `usage:"CF_NAME remove-route-policy DOMAIN --hostname HOSTNAME [--source-app APP_NAME [--source-space SPACE_NAME] [--source-org ORG_NAME] | --source-space SPACE_NAME [--source-org ORG_NAME] | --source-org ORG_NAME | --source-any | --source SOURCE] [--path PATH] [-f]\n\nEXAMPLES:\n # Remove by app name (mirrors add-route-policy)\n cf remove-route-policy apps.identity --source-app frontend-app --hostname backend\n\n # Remove by app in a different space\n cf remove-route-policy apps.identity --source-app api-client --source-space other-space --hostname backend\n\n # Remove a space-level policy\n cf remove-route-policy apps.identity --source-space monitoring --hostname api --path /metrics -f\n\n # Remove an org-level policy\n cf remove-route-policy apps.identity --source-org platform --hostname shared-api -f\n\n # Remove using raw source (advanced)\n cf remove-route-policy apps.identity --source cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend\n cf remove-route-policy apps.identity --source cf:any --hostname public-api -f"`
17+
relatedCommands interface{} `related_commands:"route-policies, add-route-policy"`
1718
}
1819

1920
func (cmd RemoveRoutePolicyCommand) Execute(args []string) error {
21+
if err := cmd.RoutePolicySourceFlags.validateSourceFlags(); err != nil {
22+
return err
23+
}
24+
2025
err := cmd.SharedActor.CheckTarget(true, true)
2126
if err != nil {
2227
return err
@@ -27,8 +32,13 @@ func (cmd RemoveRoutePolicyCommand) Execute(args []string) error {
2732
return err
2833
}
2934

30-
// Validate source format
31-
if err := validateSource(cmd.Source); err != nil {
35+
source, _, warnings, err := resolveSource(cmd.RoutePolicySourceFlags, cmd.Actor, cmd.Config)
36+
cmd.UI.DisplayWarnings(warnings)
37+
if err != nil {
38+
return err
39+
}
40+
41+
if err := validateSource(source); err != nil {
3242
return err
3343
}
3444

@@ -37,7 +47,7 @@ func (cmd RemoveRoutePolicyCommand) Execute(args []string) error {
3747
if !cmd.Force {
3848
prompt := "Really remove route policy with source {{.Source}} for route {{.Hostname}}.{{.Domain}}{{.Path}}?"
3949
response, promptErr := cmd.UI.DisplayBoolPrompt(false, prompt, map[string]interface{}{
40-
"Source": cmd.Source,
50+
"Source": source,
4151
"Hostname": cmd.Hostname,
4252
"Domain": domainName,
4353
"Path": formatPath(cmd.Path),
@@ -61,7 +71,7 @@ func (cmd RemoveRoutePolicyCommand) Execute(args []string) error {
6171
"User": user.Name,
6272
})
6373

64-
warnings, err := cmd.Actor.DeleteRoutePolicyBySource(domainName, cmd.Source, cmd.Hostname, cmd.Path)
74+
warnings, err = cmd.Actor.DeleteRoutePolicyBySource(domainName, source, cmd.Hostname, cmd.Path)
6575
cmd.UI.DisplayWarnings(warnings)
6676
if err != nil {
6777
return err

0 commit comments

Comments
 (0)