From e5b6b18dd06bf5651955024b1257099934bab3e5 Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 10 Apr 2026 15:01:31 +0000 Subject: [PATCH 01/17] Enhance add-access-rule command UX with intuitive name-based flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit improves the user experience for the add-access-rule command by replacing the positional GUID-based SELECTOR argument with intuitive flags that accept human-readable names and support cross-space/org resolution. Changes: **Command Interface:** - Remove positional SELECTOR argument (breaking change, acceptable for unreleased feature) - Add new flags: --source-app, --source-space, --source-org, --source-any, --selector - Support hierarchical name resolution: - --source-app APP_NAME (looks in current space) - --source-app APP_NAME --source-space SPACE (cross-space in current org) - --source-app APP_NAME --source-space SPACE --source-org ORG (cross-org) - --source-space SPACE (space-level rule) - --source-org ORG (org-level rule) - --source-any (allow any authenticated app) - --selector SELECTOR (raw GUID-based selector for advanced users) - Validate exactly one primary source is specified - Display verbose output showing resolved selector for transparency **Terminology Update:** - Rename all "target" terminology to "source" throughout codebase - Access rules specify the source (who can access), not the target - Update AccessRuleWithRoute.TargetName → SourceName - Update resolveAccessRuleTarget() → resolveAccessRuleSource() - Update access-rules list command table header: "target" → "source" **Error Handling:** - Provide helpful error messages when app not found in current space - Suggest using --source-space and --source-org flags for cross-space/org access - Follow CF CLI patterns from add-network-policy command **Testing:** - Add 17 comprehensive test cases for add-access-rule command - Update 19 actor tests to use new SourceName field - All tests passing (36/36) **Domain Integration:** - Add enforce_access_rules support to create-shared-domain and create-private-domain - Add --enforce-access-rules and --access-rules-scope flags - Update domain resource with new fields Examples: # Simple case - app in current space cf add-access-rule allow-frontend apps.identity --source-app frontend-app --hostname backend # Cross-space access cf add-access-rule allow-other apps.identity --source-app api-client --source-space other-space --hostname backend # Cross-org access cf add-access-rule allow-prod apps.identity --source-app client --source-space prod-space --source-org prod-org --hostname api # Space-level rule cf add-access-rule allow-monitoring apps.identity --source-space monitoring --hostname api # Org-level rule cf add-access-rule allow-platform apps.identity --source-org platform --hostname shared-api # Any authenticated app cf add-access-rule allow-all apps.identity --source-any --hostname public-api Related to: https://github.com/cloudfoundry/community/pull/1438 --- .../access_rule_not_found_error.go | 11 + actor/v7action/access_rule.go | 383 +++++++++ actor/v7action/access_rule_test.go | 755 ++++++++++++++++++ actor/v7action/cloud_controller_client.go | 3 + actor/v7action/domain.go | 29 +- actor/v7action/domain_test.go | 4 +- .../fake_cloud_controller_client.go | 251 ++++++ api/cloudcontroller/ccv3/access_rule.go | 59 ++ .../ccv3/included_resources.go | 2 + .../ccv3/internal/api_routes.go | 8 + api/cloudcontroller/ccv3/query.go | 2 + command/common/command_list_v7.go | 3 + command/common/internal/help_all_display.go | 1 + command/flag/arguments.go | 10 + command/v7/access_rules_command.go | 103 +++ command/v7/access_rules_command_test.go | 356 +++++++++ command/v7/actor.go | 8 +- command/v7/add_access_rule_command.go | 284 +++++++ command/v7/add_access_rule_command_test.go | 453 +++++++++++ command/v7/create_private_domain_command.go | 35 +- .../v7/create_private_domain_command_test.go | 2 +- command/v7/create_shared_domain_command.go | 42 +- .../v7/create_shared_domain_command_test.go | 2 +- command/v7/remove_access_rule_command.go | 71 ++ command/v7/v7fakes/fake_actor.go | 384 ++++++++- resources/access_rule_resource.go | 84 ++ resources/access_rule_resource_test.go | 69 ++ resources/domain_resource.go | 32 +- 28 files changed, 3388 insertions(+), 58 deletions(-) create mode 100644 actor/actionerror/access_rule_not_found_error.go create mode 100644 actor/v7action/access_rule.go create mode 100644 actor/v7action/access_rule_test.go create mode 100644 api/cloudcontroller/ccv3/access_rule.go create mode 100644 command/v7/access_rules_command.go create mode 100644 command/v7/access_rules_command_test.go create mode 100644 command/v7/add_access_rule_command.go create mode 100644 command/v7/add_access_rule_command_test.go create mode 100644 command/v7/remove_access_rule_command.go create mode 100644 resources/access_rule_resource.go create mode 100644 resources/access_rule_resource_test.go diff --git a/actor/actionerror/access_rule_not_found_error.go b/actor/actionerror/access_rule_not_found_error.go new file mode 100644 index 00000000000..bf5710421e5 --- /dev/null +++ b/actor/actionerror/access_rule_not_found_error.go @@ -0,0 +1,11 @@ +package actionerror + +import "fmt" + +type AccessRuleNotFoundError struct { + Name string +} + +func (e AccessRuleNotFoundError) Error() string { + return fmt.Sprintf("Access rule '%s' not found.", e.Name) +} diff --git a/actor/v7action/access_rule.go b/actor/v7action/access_rule.go new file mode 100644 index 00000000000..e74bd789497 --- /dev/null +++ b/actor/v7action/access_rule.go @@ -0,0 +1,383 @@ +package v7action + +import ( + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" + "code.cloudfoundry.org/cli/v9/resources" +) + +func (actor Actor) AddAccessRule(ruleName, domainName, selector, hostname, path string) (Warnings, error) { + allWarnings := Warnings{} + + // Get the domain to ensure it exists and supports access rules + domain, warnings, err := actor.GetDomainByName(domainName) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return allWarnings, err + } + + // Find the route + routes, routeWarnings, err := actor.GetRoutesByDomain(domain.GUID, hostname, path) + allWarnings = append(allWarnings, routeWarnings...) + if err != nil { + return allWarnings, err + } + + if len(routes) == 0 { + return allWarnings, actionerror.RouteNotFoundError{ + Host: hostname, + DomainName: domainName, + Path: path, + } + } + + route := routes[0] + + // Create the access rule + accessRule := resources.AccessRule{ + Name: ruleName, + Selector: selector, + RouteGUID: route.GUID, + } + + _, apiWarnings, err := actor.CloudControllerClient.CreateAccessRule(accessRule) + allWarnings = append(allWarnings, Warnings(apiWarnings)...) + + return allWarnings, err +} + +func (actor Actor) GetAccessRulesByRoute(domainName, hostname, path string) ([]resources.AccessRule, Warnings, error) { + allWarnings := Warnings{} + + // Get the domain + domain, warnings, err := actor.GetDomainByName(domainName) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return nil, allWarnings, err + } + + // Find the route + routes, routeWarnings, err := actor.GetRoutesByDomain(domain.GUID, hostname, path) + allWarnings = append(allWarnings, routeWarnings...) + if err != nil { + return nil, allWarnings, err + } + + if len(routes) == 0 { + return nil, allWarnings, actionerror.RouteNotFoundError{ + Host: hostname, + DomainName: domainName, + Path: path, + } + } + + route := routes[0] + + // Get access rules for this route + accessRules, _, apiWarnings, err := actor.CloudControllerClient.GetAccessRules( + ccv3.Query{Key: ccv3.RouteGUIDFilter, Values: []string{route.GUID}}, + ) + allWarnings = append(allWarnings, Warnings(apiWarnings)...) + + var rules []resources.AccessRule + for _, rule := range accessRules { + rules = append(rules, resources.AccessRule(rule)) + } + + return rules, allWarnings, err +} + +func (actor Actor) DeleteAccessRule(ruleName, domainName, hostname, path string) (Warnings, error) { + allWarnings := Warnings{} + + // Get the domain + domain, warnings, err := actor.GetDomainByName(domainName) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return allWarnings, err + } + + // Find the route + routes, routeWarnings, err := actor.GetRoutesByDomain(domain.GUID, hostname, path) + allWarnings = append(allWarnings, routeWarnings...) + if err != nil { + return allWarnings, err + } + + if len(routes) == 0 { + return allWarnings, actionerror.RouteNotFoundError{ + Host: hostname, + DomainName: domainName, + Path: path, + } + } + + route := routes[0] + + // Get access rules for this route to find the one with matching name + accessRules, _, apiWarnings, err := actor.CloudControllerClient.GetAccessRules( + ccv3.Query{Key: ccv3.RouteGUIDFilter, Values: []string{route.GUID}}, + ) + allWarnings = append(allWarnings, Warnings(apiWarnings)...) + if err != nil { + return allWarnings, err + } + + // Find the rule with matching name + var ruleGUID string + for _, rule := range accessRules { + if rule.Name == ruleName { + ruleGUID = rule.GUID + break + } + } + + if ruleGUID == "" { + return allWarnings, actionerror.AccessRuleNotFoundError{Name: ruleName} + } + + // Delete the access rule + _, deleteWarnings, err := actor.CloudControllerClient.DeleteAccessRule(ruleGUID) + allWarnings = append(allWarnings, Warnings(deleteWarnings)...) + + return allWarnings, err +} + +// GetRoutesByDomain gets routes for a domain with optional hostname and path filters +func (actor Actor) GetRoutesByDomain(domainGUID, hostname, path string) ([]resources.Route, Warnings, error) { + queries := []ccv3.Query{ + {Key: ccv3.DomainGUIDFilter, Values: []string{domainGUID}}, + } + + if hostname != "" { + queries = append(queries, ccv3.Query{Key: ccv3.HostsFilter, Values: []string{hostname}}) + } + + if path != "" { + queries = append(queries, ccv3.Query{Key: ccv3.PathsFilter, Values: []string{path}}) + } + + ccv3Routes, warnings, err := actor.CloudControllerClient.GetRoutes(queries...) + if err != nil { + return nil, Warnings(warnings), err + } + + var routes []resources.Route + for _, route := range ccv3Routes { + routes = append(routes, resources.Route(route)) + } + + return routes, Warnings(warnings), nil +} + +// AccessRuleWithRoute combines an access rule with its associated route information +type AccessRuleWithRoute struct { + resources.AccessRule + Route resources.Route + DomainName string + ScopeType string // "app", "space", "org", or "any" + SourceName string // Resolved source name (app/space/org) or empty string +} + +// GetAccessRulesForSpace gets all access rules for routes in a space with optional filters +func (actor Actor) GetAccessRulesForSpace( + spaceGUID string, + domainName string, + hostname string, + path string, + labelSelector string, +) ([]AccessRuleWithRoute, Warnings, error) { + allWarnings := Warnings{} + + // Build query for access rules filtered by space, with included routes + queries := []ccv3.Query{ + {Key: ccv3.SpaceGUIDFilter, Values: []string{spaceGUID}}, + {Key: ccv3.Include, Values: []string{"route"}}, + } + + // Add label selector if provided + if labelSelector != "" { + queries = append(queries, ccv3.Query{Key: ccv3.LabelSelectorFilter, Values: []string{labelSelector}}) + } + + // Fetch access rules directly by space GUID with included routes (single API call) + accessRules, includedResources, apiWarnings, err := actor.CloudControllerClient.GetAccessRules(queries...) + allWarnings = append(allWarnings, Warnings(apiWarnings)...) + if err != nil { + return nil, allWarnings, err + } + + if len(accessRules) == 0 { + // No access rules found - return empty list, not an error + return []AccessRuleWithRoute{}, allWarnings, nil + } + + // Build route lookup map from included resources + routeByGUID := make(map[string]resources.Route) + for _, route := range includedResources.Routes { + routeByGUID[route.GUID] = route + } + + // Apply optional filters to the included routes + if domainName != "" { + domain, warnings, err := actor.GetDomainByName(domainName) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return nil, allWarnings, err + } + // Filter routes by domain GUID + filteredRoutes := make(map[string]resources.Route) + for guid, route := range routeByGUID { + if route.DomainGUID == domain.GUID { + filteredRoutes[guid] = route + } + } + routeByGUID = filteredRoutes + } + + if hostname != "" { + // Filter routes by hostname + filteredRoutes := make(map[string]resources.Route) + for guid, route := range routeByGUID { + if route.Host == hostname { + filteredRoutes[guid] = route + } + } + routeByGUID = filteredRoutes + } + + if path != "" { + // Filter routes by path + filteredRoutes := make(map[string]resources.Route) + for guid, route := range routeByGUID { + if route.Path == path { + filteredRoutes[guid] = route + } + } + routeByGUID = filteredRoutes + } + + // Build domain name cache + domainCache := make(map[string]string) + for _, route := range routeByGUID { + if _, exists := domainCache[route.DomainGUID]; !exists { + domain, warnings, err := actor.GetDomain(route.DomainGUID) + allWarnings = append(allWarnings, warnings...) + if err != nil { + // If we can't get the domain, use the GUID as fallback + domainCache[route.DomainGUID] = route.DomainGUID + } else { + domainCache[route.DomainGUID] = domain.Name + } + } + } + + // Build results with route information and resolved sources + // Only include access rules whose routes match the filters + var results []AccessRuleWithRoute + for _, rule := range accessRules { + route, exists := routeByGUID[rule.RouteGUID] + if !exists { + // Skip rules for routes that don't match the optional filters + continue + } + + scopeType, sourceName, warnings, err := actor.resolveAccessRuleSource(rule.Selector) + allWarnings = append(allWarnings, warnings...) + if err != nil { + // If we can't resolve the source, sourceName is already empty string + // scopeType is still set correctly + } + + results = append(results, AccessRuleWithRoute{ + AccessRule: resources.AccessRule(rule), + Route: route, + DomainName: domainCache[route.DomainGUID], + ScopeType: scopeType, + SourceName: sourceName, + }) + } + + return results, allWarnings, nil +} + +// resolveAccessRuleSource resolves a selector to scope type and human-readable source name +func (actor Actor) resolveAccessRuleSource(selector string) (scopeType string, sourceName string, warnings Warnings, err error) { + allWarnings := Warnings{} + + // Parse selector format: cf:app:, cf:space:, cf:org:, or cf:any + if selector == "cf:any" { + return "any", "", nil, nil + } + + // Split selector into parts + // Expected format: cf:type:guid + const prefix = "cf:" + if len(selector) < len(prefix) { + return "unknown", "", nil, nil + } + + selectorBody := selector[len(prefix):] + parts := splitSelector(selectorBody) + if len(parts) < 2 { + return "unknown", "", nil, nil + } + + selectorType := parts[0] + guid := parts[1] + + switch selectorType { + case "app": + apps, apiWarnings, err := actor.CloudControllerClient.GetApplications( + ccv3.Query{Key: ccv3.GUIDFilter, Values: []string{guid}}, + ) + allWarnings = append(allWarnings, Warnings(apiWarnings)...) + if err != nil || len(apps) == 0 { + return "app", "", allWarnings, err + } + return "app", apps[0].Name, allWarnings, nil + + case "space": + spaces, _, apiWarnings, err := actor.CloudControllerClient.GetSpaces( + ccv3.Query{Key: ccv3.GUIDFilter, Values: []string{guid}}, + ) + allWarnings = append(allWarnings, Warnings(apiWarnings)...) + if err != nil || len(spaces) == 0 { + return "space", "", allWarnings, err + } + return "space", spaces[0].Name, allWarnings, nil + + case "org": + orgs, apiWarnings, err := actor.CloudControllerClient.GetOrganizations( + ccv3.Query{Key: ccv3.GUIDFilter, Values: []string{guid}}, + ) + allWarnings = append(allWarnings, Warnings(apiWarnings)...) + if err != nil || len(orgs) == 0 { + return "org", "", allWarnings, err + } + return "org", orgs[0].Name, allWarnings, nil + + default: + return "unknown", "", nil, nil + } +} + +// splitSelector splits a selector body by colon, handling the case where +// the selector might be "type:guid" format +func splitSelector(s string) []string { + var parts []string + current := "" + for _, char := range s { + if char == ':' && len(parts) == 0 { + // First colon - split here + parts = append(parts, current) + current = "" + } else { + current += string(char) + } + } + if current != "" { + parts = append(parts, current) + } + return parts +} diff --git a/actor/v7action/access_rule_test.go b/actor/v7action/access_rule_test.go new file mode 100644 index 00000000000..64c4fc52343 --- /dev/null +++ b/actor/v7action/access_rule_test.go @@ -0,0 +1,755 @@ +package v7action_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + . "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/actor/v7action/v7actionfakes" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" + "code.cloudfoundry.org/cli/v9/resources" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Access Rule Actions", func() { + var ( + actor *Actor + fakeCloudControllerClient *v7actionfakes.FakeCloudControllerClient + ) + + BeforeEach(func() { + actor, fakeCloudControllerClient, _, _, _, _, _ = NewTestActor() + }) + + Describe("GetAccessRulesForSpace", func() { + var ( + spaceGUID string + domainName string + hostname string + path string + labelSelector string + + results []AccessRuleWithRoute + warnings Warnings + executeErr error + ) + + BeforeEach(func() { + spaceGUID = "space-guid-1" + domainName = "" + hostname = "" + path = "" + labelSelector = "" + }) + + JustBeforeEach(func() { + results, warnings, executeErr = actor.GetAccessRulesForSpace( + spaceGUID, + domainName, + hostname, + path, + labelSelector, + ) + }) + + When("getting access rules succeeds with multiple rules", func() { + BeforeEach(func() { + // Mock GetAccessRules call with included routes + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + { + GUID: "rule-guid-1", + Name: "rule-1", + Selector: "cf:app:app-guid-1", + RouteGUID: "route-guid-1", + }, + { + GUID: "rule-guid-2", + Name: "rule-2", + Selector: "cf:any", + RouteGUID: "route-guid-2", + }, + }, + ccv3.IncludedResources{ + Routes: []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "app1", + Path: "/path1", + }, + { + GUID: "route-guid-2", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-2", + Host: "app2", + Path: "", + }, + }, + }, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + + // Mock GetDomain calls for domain name resolution + fakeCloudControllerClient.GetDomainStub = func(guid string) (resources.Domain, ccv3.Warnings, error) { + switch guid { + case "domain-guid-1": + return resources.Domain{GUID: "domain-guid-1", Name: "example.com"}, ccv3.Warnings{"get-domain-warning-1"}, nil + case "domain-guid-2": + return resources.Domain{GUID: "domain-guid-2", Name: "test.com"}, ccv3.Warnings{"get-domain-warning-2"}, nil + default: + return resources.Domain{}, nil, errors.New("domain not found") + } + } + + // Mock GetApplications for app name resolution + fakeCloudControllerClient.GetApplicationsReturns( + []resources.Application{ + {GUID: "app-guid-1", Name: "my-app"}, + }, + ccv3.Warnings{"get-app-warning"}, + nil, + ) + }) + + It("returns access rules with route and domain information", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(warnings).To(ConsistOf( + "get-access-rules-warning", + "get-domain-warning-1", + "get-domain-warning-2", + "get-app-warning", + )) + + Expect(results).To(HaveLen(2)) + + // First rule + Expect(results[0].GUID).To(Equal("rule-guid-1")) + Expect(results[0].Name).To(Equal("rule-1")) + Expect(results[0].Selector).To(Equal("cf:app:app-guid-1")) + Expect(results[0].Route.GUID).To(Equal("route-guid-1")) + Expect(results[0].Route.Host).To(Equal("app1")) + Expect(results[0].Route.Path).To(Equal("/path1")) + Expect(results[0].DomainName).To(Equal("example.com")) + Expect(results[0].ScopeType).To(Equal("app")) + Expect(results[0].SourceName).To(Equal("my-app")) + + // Second rule + Expect(results[1].GUID).To(Equal("rule-guid-2")) + Expect(results[1].Name).To(Equal("rule-2")) + Expect(results[1].Selector).To(Equal("cf:any")) + Expect(results[1].Route.GUID).To(Equal("route-guid-2")) + Expect(results[1].Route.Host).To(Equal("app2")) + Expect(results[1].DomainName).To(Equal("test.com")) + Expect(results[1].ScopeType).To(Equal("any")) + Expect(results[1].SourceName).To(Equal("")) + }) + + It("calls GetAccessRules with space GUID and include route filters", func() { + Expect(fakeCloudControllerClient.GetAccessRulesCallCount()).To(Equal(1)) + queries := fakeCloudControllerClient.GetAccessRulesArgsForCall(0) + Expect(queries).To(ContainElement(ccv3.Query{ + Key: ccv3.SpaceGUIDFilter, + Values: []string{"space-guid-1"}, + })) + Expect(queries).To(ContainElement(ccv3.Query{ + Key: ccv3.Include, + Values: []string{"route"}, + })) + }) + + It("does not call GetRoutes separately", func() { + Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) + }) + }) + + When("domain name filter is provided", func() { + BeforeEach(func() { + domainName = "example.com" + + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{ + {GUID: "domain-guid-1", Name: "example.com"}, + }, + ccv3.Warnings{"get-domains-warning"}, + nil, + ) + + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + { + GUID: "rule-guid-1", + Name: "rule-1", + Selector: "cf:any", + RouteGUID: "route-guid-1", + }, + }, + ccv3.IncludedResources{ + Routes: []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "app1", + }, + }, + }, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + + fakeCloudControllerClient.GetDomainReturns( + resources.Domain{GUID: "domain-guid-1", Name: "example.com"}, + ccv3.Warnings{"get-domain-warning"}, + nil, + ) + }) + + It("filters routes by domain GUID", func() { + Expect(executeErr).ToNot(HaveOccurred()) + // Routes are filtered in-memory from included resources + Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) + }) + }) + + When("hostname filter is provided", func() { + BeforeEach(func() { + hostname = "myapp" + + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{}, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + }) + + It("adds hostname filter to route query", func() { + Expect(executeErr).ToNot(HaveOccurred()) + // GetRoutes should not be called since routes come from included resources + Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) + }) + }) + + When("path filter is provided", func() { + BeforeEach(func() { + path = "/api" + + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{}, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + }) + + It("adds path filter to route query", func() { + Expect(executeErr).ToNot(HaveOccurred()) + // GetRoutes should not be called if no access rules are found + Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) + }) + }) + + When("label selector filter is provided", func() { + BeforeEach(func() { + labelSelector = "env=production" + + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{}, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + }) + + It("adds label selector filter to access rules query", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeCloudControllerClient.GetAccessRulesCallCount()).To(Equal(1)) + queries := fakeCloudControllerClient.GetAccessRulesArgsForCall(0) + + Expect(queries).To(ContainElement(ccv3.Query{ + Key: ccv3.LabelSelectorFilter, + Values: []string{"env=production"}, + })) + }) + }) + + When("no access rules are found in the space", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{}, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + }) + + It("returns an empty list without error", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(results).To(BeEmpty()) + Expect(warnings).To(ConsistOf("get-access-rules-warning")) + }) + + It("does not call GetRoutes", func() { + Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) + }) + }) + + When("getting access rules fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetAccessRulesReturns( + nil, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + errors.New("api error"), + ) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError("api error")) + Expect(warnings).To(ConsistOf("get-access-rules-warning")) + Expect(results).To(BeNil()) + }) + }) + + When("getting domain by name fails", func() { + BeforeEach(func() { + domainName = "invalid-domain.com" + + // Mock GetAccessRules to return at least one access rule + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + {GUID: "access-rule-guid-1", RouteGUID: "route-guid-1"}, + }, + ccv3.IncludedResources{ + Routes: []resources.Route{ + {GUID: "route-guid-1", DomainGUID: "domain-guid-1"}, + }, + }, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + + fakeCloudControllerClient.GetDomainsReturns( + nil, + ccv3.Warnings{"get-domains-warning"}, + actionerror.DomainNotFoundError{Name: "invalid-domain.com"}, + ) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError(actionerror.DomainNotFoundError{Name: "invalid-domain.com"})) + Expect(warnings).To(ConsistOf("get-access-rules-warning", "get-domains-warning")) + Expect(results).To(BeNil()) + }) + }) + + When("resolving domain name fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + { + GUID: "rule-guid-1", + Name: "rule-1", + Selector: "cf:any", + RouteGUID: "route-guid-1", + }, + }, + ccv3.IncludedResources{ + Routes: []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "app1", + }, + }, + }, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + + // Domain lookup fails + fakeCloudControllerClient.GetDomainReturns( + resources.Domain{}, + ccv3.Warnings{"get-domain-warning"}, + errors.New("domain lookup error"), + ) + }) + + It("uses the domain GUID as fallback and continues", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(results).To(HaveLen(1)) + Expect(results[0].DomainName).To(Equal("domain-guid-1")) + Expect(warnings).To(ContainElement("get-domain-warning")) + }) + }) + + When("resolving target name fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + { + GUID: "rule-guid-1", + Name: "rule-1", + Selector: "cf:app:app-guid-1", + RouteGUID: "route-guid-1", + }, + }, + ccv3.IncludedResources{ + Routes: []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "app1", + }, + }, + }, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + + fakeCloudControllerClient.GetDomainReturns( + resources.Domain{GUID: "domain-guid-1", Name: "example.com"}, + ccv3.Warnings{"get-domain-warning"}, + nil, + ) + + // App lookup fails + fakeCloudControllerClient.GetApplicationsReturns( + nil, + ccv3.Warnings{"get-app-warning"}, + errors.New("app lookup error"), + ) + }) + + It("leaves source name blank and populates scope type", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(results).To(HaveLen(1)) + Expect(results[0].ScopeType).To(Equal("app")) + Expect(results[0].SourceName).To(Equal("")) + Expect(warnings).To(ContainElement("get-app-warning")) + }) + }) + }) + + // Note: resolveAccessRuleTarget and splitSelector are unexported methods + // and are tested indirectly through GetAccessRulesForSpace above. + + Describe("GetAccessRulesByRoute", func() { + var ( + domainName string + hostname string + path string + + rules []resources.AccessRule + warnings Warnings + executeErr error + ) + + BeforeEach(func() { + domainName = "example.com" + hostname = "myapp" + path = "" + }) + + JustBeforeEach(func() { + rules, warnings, executeErr = actor.GetAccessRulesByRoute(domainName, hostname, path) + }) + + When("the route exists with access rules", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{ + {GUID: "domain-guid-1", Name: "example.com"}, + }, + ccv3.Warnings{"get-domains-warning"}, + nil, + ) + + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "myapp", + }, + }, + ccv3.Warnings{"get-routes-warning"}, + nil, + ) + + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + {GUID: "rule-guid-1", Name: "rule-1", Selector: "cf:any"}, + {GUID: "rule-guid-2", Name: "rule-2", Selector: "cf:app:app-guid-1"}, + }, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + }) + + It("returns the access rules", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(rules).To(HaveLen(2)) + Expect(rules[0].Name).To(Equal("rule-1")) + Expect(rules[1].Name).To(Equal("rule-2")) + Expect(warnings).To(ConsistOf( + "get-domains-warning", + "get-routes-warning", + "get-access-rules-warning", + )) + }) + }) + + When("the route does not exist", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{ + {GUID: "domain-guid-1", Name: "example.com"}, + }, + ccv3.Warnings{"get-domains-warning"}, + nil, + ) + + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{}, + ccv3.Warnings{"get-routes-warning"}, + nil, + ) + }) + + It("returns a RouteNotFoundError", func() { + Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{ + Host: "myapp", + DomainName: "example.com", + Path: "", + })) + Expect(warnings).To(ConsistOf("get-domains-warning", "get-routes-warning")) + }) + }) + }) + + Describe("AddAccessRule", func() { + var ( + ruleName string + domainName string + selector string + hostname string + path string + + warnings Warnings + executeErr error + ) + + BeforeEach(func() { + ruleName = "my-rule" + domainName = "example.com" + selector = "cf:app:app-guid-1" + hostname = "myapp" + path = "" + }) + + JustBeforeEach(func() { + warnings, executeErr = actor.AddAccessRule(ruleName, domainName, selector, hostname, path) + }) + + When("creating the access rule succeeds", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{ + {GUID: "domain-guid-1", Name: "example.com"}, + }, + ccv3.Warnings{"get-domains-warning"}, + nil, + ) + + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "myapp", + }, + }, + ccv3.Warnings{"get-routes-warning"}, + nil, + ) + + fakeCloudControllerClient.CreateAccessRuleReturns( + resources.AccessRule{GUID: "rule-guid-1", Name: "my-rule"}, + ccv3.Warnings{"create-rule-warning"}, + nil, + ) + }) + + It("creates the access rule and returns warnings", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(warnings).To(ConsistOf( + "get-domains-warning", + "get-routes-warning", + "create-rule-warning", + )) + + Expect(fakeCloudControllerClient.CreateAccessRuleCallCount()).To(Equal(1)) + rule := fakeCloudControllerClient.CreateAccessRuleArgsForCall(0) + Expect(rule.Name).To(Equal("my-rule")) + Expect(rule.Selector).To(Equal("cf:app:app-guid-1")) + Expect(rule.RouteGUID).To(Equal("route-guid-1")) + }) + }) + + When("the route does not exist", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{ + {GUID: "domain-guid-1", Name: "example.com"}, + }, + ccv3.Warnings{"get-domains-warning"}, + nil, + ) + + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{}, + ccv3.Warnings{"get-routes-warning"}, + nil, + ) + }) + + It("returns a RouteNotFoundError", func() { + Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{ + Host: "myapp", + DomainName: "example.com", + Path: "", + })) + }) + }) + }) + + Describe("DeleteAccessRule", func() { + var ( + ruleName string + domainName string + hostname string + path string + + warnings Warnings + executeErr error + ) + + BeforeEach(func() { + ruleName = "my-rule" + domainName = "example.com" + hostname = "myapp" + path = "" + }) + + JustBeforeEach(func() { + warnings, executeErr = actor.DeleteAccessRule(ruleName, domainName, hostname, path) + }) + + When("the access rule exists", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{ + {GUID: "domain-guid-1", Name: "example.com"}, + }, + ccv3.Warnings{"get-domains-warning"}, + nil, + ) + + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "myapp", + }, + }, + ccv3.Warnings{"get-routes-warning"}, + nil, + ) + + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + {GUID: "rule-guid-1", Name: "my-rule", Selector: "cf:any"}, + }, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + + fakeCloudControllerClient.DeleteAccessRuleReturns( + ccv3.JobURL(""), + ccv3.Warnings{"delete-rule-warning"}, + nil, + ) + }) + + It("deletes the access rule and returns warnings", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(warnings).To(ConsistOf( + "get-domains-warning", + "get-routes-warning", + "get-access-rules-warning", + "delete-rule-warning", + )) + + Expect(fakeCloudControllerClient.DeleteAccessRuleCallCount()).To(Equal(1)) + guid := fakeCloudControllerClient.DeleteAccessRuleArgsForCall(0) + Expect(guid).To(Equal("rule-guid-1")) + }) + }) + + When("the access rule does not exist", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{ + {GUID: "domain-guid-1", Name: "example.com"}, + }, + ccv3.Warnings{"get-domains-warning"}, + nil, + ) + + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{ + { + GUID: "route-guid-1", + SpaceGUID: "space-guid-1", + DomainGUID: "domain-guid-1", + Host: "myapp", + }, + }, + ccv3.Warnings{"get-routes-warning"}, + nil, + ) + + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + {GUID: "rule-guid-other", Name: "other-rule", Selector: "cf:any"}, + }, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + }) + + It("returns an AccessRuleNotFoundError", func() { + Expect(executeErr).To(MatchError(actionerror.AccessRuleNotFoundError{Name: "my-rule"})) + Expect(warnings).To(ConsistOf( + "get-domains-warning", + "get-routes-warning", + "get-access-rules-warning", + )) + }) + }) + }) +}) diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index c0dc6b8c641..223f7344a2f 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -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) + CreateAccessRule(accessRule resources.AccessRule) (resources.AccessRule, 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) @@ -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) + DeleteAccessRule(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) @@ -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) + GetAccessRules(query ...ccv3.Query) ([]resources.AccessRule, 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) diff --git a/actor/v7action/domain.go b/actor/v7action/domain.go index 21cabe6abc5..cf65ca2bfa3 100644 --- a/actor/v7action/domain.go +++ b/actor/v7action/domain.go @@ -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 := "" @@ -37,17 +37,25 @@ 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_access_rules if specified + if enforceAccessRules { + domain.EnforceAccessRules = types.NullBool{IsSet: true, Value: true} + domain.AccessRulesScope = 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...) @@ -55,10 +63,19 @@ func (actor Actor) CreatePrivateDomain(domainName string, orgName string) (Warni if err != nil { return allWarnings, err } - _, apiWarnings, err := actor.CloudControllerClient.CreateDomain(resources.Domain{ + + domain := resources.Domain{ Name: domainName, OrganizationGUID: organization.GUID, - }) + } + + // Set enforce_access_rules if specified + if enforceAccessRules { + domain.EnforceAccessRules = types.NullBool{IsSet: true, Value: true} + domain.AccessRulesScope = accessRulesScope + } + + _, apiWarnings, err := actor.CloudControllerClient.CreateDomain(domain) actorWarnings := Warnings(apiWarnings) allWarnings = append(allWarnings, actorWarnings...) diff --git a/actor/v7action/domain_test.go b/actor/v7action/domain_test.go index 68050c6cefb..db30783af06 100644 --- a/actor/v7action/domain_test.go +++ b/actor/v7action/domain_test.go @@ -118,7 +118,7 @@ var _ = Describe("Domain Actions", func() { ) JustBeforeEach(func() { - warnings, executeErr = actor.CreateSharedDomain("the-domain-name", true, routerGroup) + warnings, executeErr = actor.CreateSharedDomain("the-domain-name", true, routerGroup, false, "") }) BeforeEach(func() { @@ -191,7 +191,7 @@ var _ = Describe("Domain Actions", func() { }) It("delegates to the cloud controller client", func() { - warnings, executeErr := actor.CreatePrivateDomain("private-domain-name", "org-name") + warnings, executeErr := actor.CreatePrivateDomain("private-domain-name", "org-name", false, "") Expect(executeErr).To(MatchError("create-error")) Expect(warnings).To(ConsistOf("get-orgs-warning", "create-warning-1", "create-warning-2")) diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index dbdca54b57b..27c4b830379 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -106,6 +106,21 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + CreateAccessRuleStub func(resources.AccessRule) (resources.AccessRule, ccv3.Warnings, error) + createAccessRuleMutex sync.RWMutex + createAccessRuleArgsForCall []struct { + arg1 resources.AccessRule + } + createAccessRuleReturns struct { + result1 resources.AccessRule + result2 ccv3.Warnings + result3 error + } + createAccessRuleReturnsOnCall map[int]struct { + result1 resources.AccessRule + result2 ccv3.Warnings + result3 error + } CreateApplicationStub func(resources.Application) (resources.Application, ccv3.Warnings, error) createApplicationMutex sync.RWMutex createApplicationArgsForCall []struct { @@ -438,6 +453,21 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + DeleteAccessRuleStub func(string) (ccv3.JobURL, ccv3.Warnings, error) + deleteAccessRuleMutex sync.RWMutex + deleteAccessRuleArgsForCall []struct { + arg1 string + } + deleteAccessRuleReturns struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + } + deleteAccessRuleReturnsOnCall map[int]struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + } DeleteApplicationStub func(string) (ccv3.JobURL, ccv3.Warnings, error) deleteApplicationMutex sync.RWMutex deleteApplicationArgsForCall []struct { @@ -766,6 +796,23 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + GetAccessRulesStub func(...ccv3.Query) ([]resources.AccessRule, ccv3.IncludedResources, ccv3.Warnings, error) + getAccessRulesMutex sync.RWMutex + getAccessRulesArgsForCall []struct { + arg1 []ccv3.Query + } + getAccessRulesReturns struct { + result1 []resources.AccessRule + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + } + getAccessRulesReturnsOnCall map[int]struct { + result1 []resources.AccessRule + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + } GetAppFeatureStub func(string, string) (resources.ApplicationFeature, ccv3.Warnings, error) getAppFeatureMutex sync.RWMutex getAppFeatureArgsForCall []struct { @@ -3247,6 +3294,73 @@ func (fake *FakeCloudControllerClient) CopyPackageReturnsOnCall(i int, result1 r }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) CreateAccessRule(arg1 resources.AccessRule) (resources.AccessRule, ccv3.Warnings, error) { + fake.createAccessRuleMutex.Lock() + ret, specificReturn := fake.createAccessRuleReturnsOnCall[len(fake.createAccessRuleArgsForCall)] + fake.createAccessRuleArgsForCall = append(fake.createAccessRuleArgsForCall, struct { + arg1 resources.AccessRule + }{arg1}) + stub := fake.CreateAccessRuleStub + fakeReturns := fake.createAccessRuleReturns + fake.recordInvocation("CreateAccessRule", []interface{}{arg1}) + fake.createAccessRuleMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeCloudControllerClient) CreateAccessRuleCallCount() int { + fake.createAccessRuleMutex.RLock() + defer fake.createAccessRuleMutex.RUnlock() + return len(fake.createAccessRuleArgsForCall) +} + +func (fake *FakeCloudControllerClient) CreateAccessRuleCalls(stub func(resources.AccessRule) (resources.AccessRule, ccv3.Warnings, error)) { + fake.createAccessRuleMutex.Lock() + defer fake.createAccessRuleMutex.Unlock() + fake.CreateAccessRuleStub = stub +} + +func (fake *FakeCloudControllerClient) CreateAccessRuleArgsForCall(i int) resources.AccessRule { + fake.createAccessRuleMutex.RLock() + defer fake.createAccessRuleMutex.RUnlock() + argsForCall := fake.createAccessRuleArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeCloudControllerClient) CreateAccessRuleReturns(result1 resources.AccessRule, result2 ccv3.Warnings, result3 error) { + fake.createAccessRuleMutex.Lock() + defer fake.createAccessRuleMutex.Unlock() + fake.CreateAccessRuleStub = nil + fake.createAccessRuleReturns = struct { + result1 resources.AccessRule + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeCloudControllerClient) CreateAccessRuleReturnsOnCall(i int, result1 resources.AccessRule, result2 ccv3.Warnings, result3 error) { + fake.createAccessRuleMutex.Lock() + defer fake.createAccessRuleMutex.Unlock() + fake.CreateAccessRuleStub = nil + if fake.createAccessRuleReturnsOnCall == nil { + fake.createAccessRuleReturnsOnCall = make(map[int]struct { + result1 resources.AccessRule + result2 ccv3.Warnings + result3 error + }) + } + fake.createAccessRuleReturnsOnCall[i] = struct { + result1 resources.AccessRule + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeCloudControllerClient) CreateApplication(arg1 resources.Application) (resources.Application, ccv3.Warnings, error) { fake.createApplicationMutex.Lock() ret, specificReturn := fake.createApplicationReturnsOnCall[len(fake.createApplicationArgsForCall)] @@ -4723,6 +4837,73 @@ func (fake *FakeCloudControllerClient) CreateUserReturnsOnCall(i int, result1 re }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) DeleteAccessRule(arg1 string) (ccv3.JobURL, ccv3.Warnings, error) { + fake.deleteAccessRuleMutex.Lock() + ret, specificReturn := fake.deleteAccessRuleReturnsOnCall[len(fake.deleteAccessRuleArgsForCall)] + fake.deleteAccessRuleArgsForCall = append(fake.deleteAccessRuleArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.DeleteAccessRuleStub + fakeReturns := fake.deleteAccessRuleReturns + fake.recordInvocation("DeleteAccessRule", []interface{}{arg1}) + fake.deleteAccessRuleMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeCloudControllerClient) DeleteAccessRuleCallCount() int { + fake.deleteAccessRuleMutex.RLock() + defer fake.deleteAccessRuleMutex.RUnlock() + return len(fake.deleteAccessRuleArgsForCall) +} + +func (fake *FakeCloudControllerClient) DeleteAccessRuleCalls(stub func(string) (ccv3.JobURL, ccv3.Warnings, error)) { + fake.deleteAccessRuleMutex.Lock() + defer fake.deleteAccessRuleMutex.Unlock() + fake.DeleteAccessRuleStub = stub +} + +func (fake *FakeCloudControllerClient) DeleteAccessRuleArgsForCall(i int) string { + fake.deleteAccessRuleMutex.RLock() + defer fake.deleteAccessRuleMutex.RUnlock() + argsForCall := fake.deleteAccessRuleArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeCloudControllerClient) DeleteAccessRuleReturns(result1 ccv3.JobURL, result2 ccv3.Warnings, result3 error) { + fake.deleteAccessRuleMutex.Lock() + defer fake.deleteAccessRuleMutex.Unlock() + fake.DeleteAccessRuleStub = nil + fake.deleteAccessRuleReturns = struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeCloudControllerClient) DeleteAccessRuleReturnsOnCall(i int, result1 ccv3.JobURL, result2 ccv3.Warnings, result3 error) { + fake.deleteAccessRuleMutex.Lock() + defer fake.deleteAccessRuleMutex.Unlock() + fake.DeleteAccessRuleStub = nil + if fake.deleteAccessRuleReturnsOnCall == nil { + fake.deleteAccessRuleReturnsOnCall = make(map[int]struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + }) + } + fake.deleteAccessRuleReturnsOnCall[i] = struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeCloudControllerClient) DeleteApplication(arg1 string) (ccv3.JobURL, ccv3.Warnings, error) { fake.deleteApplicationMutex.Lock() ret, specificReturn := fake.deleteApplicationReturnsOnCall[len(fake.deleteApplicationArgsForCall)] @@ -6196,6 +6377,76 @@ func (fake *FakeCloudControllerClient) EntitleIsolationSegmentToOrganizationsRet }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) GetAccessRules(arg1 ...ccv3.Query) ([]resources.AccessRule, ccv3.IncludedResources, ccv3.Warnings, error) { + fake.getAccessRulesMutex.Lock() + ret, specificReturn := fake.getAccessRulesReturnsOnCall[len(fake.getAccessRulesArgsForCall)] + fake.getAccessRulesArgsForCall = append(fake.getAccessRulesArgsForCall, struct { + arg1 []ccv3.Query + }{arg1}) + stub := fake.GetAccessRulesStub + fakeReturns := fake.getAccessRulesReturns + fake.recordInvocation("GetAccessRules", []interface{}{arg1}) + fake.getAccessRulesMutex.Unlock() + if stub != nil { + return stub(arg1...) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3, ret.result4 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3, fakeReturns.result4 +} + +func (fake *FakeCloudControllerClient) GetAccessRulesCallCount() int { + fake.getAccessRulesMutex.RLock() + defer fake.getAccessRulesMutex.RUnlock() + return len(fake.getAccessRulesArgsForCall) +} + +func (fake *FakeCloudControllerClient) GetAccessRulesCalls(stub func(...ccv3.Query) ([]resources.AccessRule, ccv3.IncludedResources, ccv3.Warnings, error)) { + fake.getAccessRulesMutex.Lock() + defer fake.getAccessRulesMutex.Unlock() + fake.GetAccessRulesStub = stub +} + +func (fake *FakeCloudControllerClient) GetAccessRulesArgsForCall(i int) []ccv3.Query { + fake.getAccessRulesMutex.RLock() + defer fake.getAccessRulesMutex.RUnlock() + argsForCall := fake.getAccessRulesArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeCloudControllerClient) GetAccessRulesReturns(result1 []resources.AccessRule, result2 ccv3.IncludedResources, result3 ccv3.Warnings, result4 error) { + fake.getAccessRulesMutex.Lock() + defer fake.getAccessRulesMutex.Unlock() + fake.GetAccessRulesStub = nil + fake.getAccessRulesReturns = struct { + result1 []resources.AccessRule + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + }{result1, result2, result3, result4} +} + +func (fake *FakeCloudControllerClient) GetAccessRulesReturnsOnCall(i int, result1 []resources.AccessRule, result2 ccv3.IncludedResources, result3 ccv3.Warnings, result4 error) { + fake.getAccessRulesMutex.Lock() + defer fake.getAccessRulesMutex.Unlock() + fake.GetAccessRulesStub = nil + if fake.getAccessRulesReturnsOnCall == nil { + fake.getAccessRulesReturnsOnCall = make(map[int]struct { + result1 []resources.AccessRule + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + }) + } + fake.getAccessRulesReturnsOnCall[i] = struct { + result1 []resources.AccessRule + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + }{result1, result2, result3, result4} +} + func (fake *FakeCloudControllerClient) GetAppFeature(arg1 string, arg2 string) (resources.ApplicationFeature, ccv3.Warnings, error) { fake.getAppFeatureMutex.Lock() ret, specificReturn := fake.getAppFeatureReturnsOnCall[len(fake.getAppFeatureArgsForCall)] diff --git a/api/cloudcontroller/ccv3/access_rule.go b/api/cloudcontroller/ccv3/access_rule.go new file mode 100644 index 00000000000..2c391af8d18 --- /dev/null +++ b/api/cloudcontroller/ccv3/access_rule.go @@ -0,0 +1,59 @@ +package ccv3 + +import ( + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/internal" + "code.cloudfoundry.org/cli/v9/resources" +) + +// CreateAccessRule creates an access rule for a route +func (client *Client) CreateAccessRule(accessRule resources.AccessRule) (resources.AccessRule, Warnings, error) { + var responseBody resources.AccessRule + + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.PostAccessRuleRequest, + RequestBody: accessRule, + ResponseBody: &responseBody, + }) + + return responseBody, warnings, err +} + +// GetAccessRules lists access rules +func (client *Client) GetAccessRules(query ...Query) ([]resources.AccessRule, IncludedResources, Warnings, error) { + var accessRules []resources.AccessRule + + includedResources, warnings, err := client.MakeListRequest(RequestParams{ + RequestName: internal.GetAccessRulesRequest, + Query: query, + ResponseBody: resources.AccessRule{}, + AppendToList: func(item interface{}) error { + accessRules = append(accessRules, item.(resources.AccessRule)) + return nil + }, + }) + + return accessRules, includedResources, warnings, err +} + +// GetAccessRule gets a single access rule by GUID +func (client *Client) GetAccessRule(guid string) (resources.AccessRule, Warnings, error) { + var responseBody resources.AccessRule + + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.GetAccessRuleRequest, + URIParams: internal.Params{"access_rule_guid": guid}, + ResponseBody: &responseBody, + }) + + return responseBody, warnings, err +} + +// DeleteAccessRule deletes an access rule +func (client *Client) DeleteAccessRule(guid string) (JobURL, Warnings, error) { + jobURLString, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.DeleteAccessRuleRequest, + URIParams: internal.Params{"access_rule_guid": guid}, + }) + + return JobURL(jobURLString), warnings, err +} diff --git a/api/cloudcontroller/ccv3/included_resources.go b/api/cloudcontroller/ccv3/included_resources.go index 6827248eae1..89af94c2540 100644 --- a/api/cloudcontroller/ccv3/included_resources.go +++ b/api/cloudcontroller/ccv3/included_resources.go @@ -11,6 +11,7 @@ type IncludedResources struct { ServiceBrokers []resources.ServiceBroker `json:"service_brokers,omitempty"` ServicePlans []resources.ServicePlan `json:"service_plans,omitempty"` Apps []resources.Application `json:"apps,omitempty"` + Routes []resources.Route `json:"routes,omitempty"` } func (i *IncludedResources) Merge(resources IncludedResources) { @@ -22,4 +23,5 @@ func (i *IncludedResources) Merge(resources IncludedResources) { i.ServiceInstances = append(i.ServiceInstances, resources.ServiceInstances...) i.ServiceOfferings = append(i.ServiceOfferings, resources.ServiceOfferings...) i.ServicePlans = append(i.ServicePlans, resources.ServicePlans...) + i.Routes = append(i.Routes, resources.Routes...) } diff --git a/api/cloudcontroller/ccv3/internal/api_routes.go b/api/cloudcontroller/ccv3/internal/api_routes.go index d1f7ffd9fe6..40605b74a50 100644 --- a/api/cloudcontroller/ccv3/internal/api_routes.go +++ b/api/cloudcontroller/ccv3/internal/api_routes.go @@ -9,6 +9,7 @@ import "net/http" // If the request returns a single entity by GUID, use the singular (for example // /v3/organizations/:organization_guid is GetOrganization). const ( + DeleteAccessRuleRequest = "DeleteAccessRuleRequest" DeleteApplicationProcessInstanceRequest = "DeleteApplicationProcessInstance" DeleteApplicationRequest = "DeleteApplication" DeleteBuildpackRequest = "DeleteBuildpack" @@ -35,6 +36,8 @@ const ( DeleteSpaceRequest = "DeleteSpace" DeleteSpaceQuotaFromSpaceRequest = "DeleteSpaceQuotaFromSpace" DeleteUserRequest = "DeleteUser" + GetAccessRuleRequest = "GetAccessRuleRequest" + GetAccessRulesRequest = "GetAccessRulesRequest" GetApplicationDropletCurrentRequest = "GetApplicationDropletCurrent" GetApplicationEnvRequest = "GetApplicationEnv" GetApplicationFeaturesRequest = "GetApplicationFeatures" @@ -134,6 +137,7 @@ const ( PatchSpaceQuotaRequest = "PatchSpaceQuota" PatchStackRequest = "PatchStack" PatchMoveRouteRequest = "PatchMoveRouteRequest" + PostAccessRuleRequest = "PostAccessRuleRequest" PostApplicationActionApplyManifest = "PostApplicationActionApplyM" PostApplicationActionRestartRequest = "PostApplicationActionRestart" PostApplicationActionStartRequest = "PostApplicationActionStart" @@ -186,6 +190,10 @@ const ( // APIRoutes is a list of routes used by the router to construct request URLs. var APIRoutes = map[string]Route{ + GetAccessRulesRequest: {Path: "/v3/access_rules", Method: http.MethodGet}, + PostAccessRuleRequest: {Path: "/v3/access_rules", Method: http.MethodPost}, + GetAccessRuleRequest: {Path: "/v3/access_rules/:access_rule_guid", Method: http.MethodGet}, + DeleteAccessRuleRequest: {Path: "/v3/access_rules/:access_rule_guid", Method: http.MethodDelete}, GetApplicationsRequest: {Path: "/v3/apps", Method: http.MethodGet}, PostApplicationRequest: {Path: "/v3/apps", Method: http.MethodPost}, DeleteApplicationRequest: {Path: "/v3/apps/:app_guid", Method: http.MethodDelete}, diff --git a/api/cloudcontroller/ccv3/query.go b/api/cloudcontroller/ccv3/query.go index 99b04abad9b..27bcf995712 100644 --- a/api/cloudcontroller/ccv3/query.go +++ b/api/cloudcontroller/ccv3/query.go @@ -29,6 +29,8 @@ const ( SequenceIDFilter QueryKey = "sequence_ids" // RouteGUIDFilter is a query parameter for listing objects by Route GUID. RouteGUIDFilter QueryKey = "route_guids" + // SelectorsFilter is a query parameter for listing access rules by selector. + SelectorsFilter QueryKey = "selectors" // ServiceInstanceGUIDFilter is a query parameter for listing objects by Service Instance GUID. ServiceInstanceGUIDFilter QueryKey = "service_instance_guids" // SpaceGUIDFilter is a query parameter for listing objects by Space GUID. diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index fcc833a94d3..3177549afa1 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -15,6 +15,8 @@ type commandList struct { V3Push v7.PushCommand `command:"v3-push" description:"Push a new app or sync changes to an existing app" hidden:"true"` + AccessRules v7.AccessRulesCommand `command:"access-rules" description:"List all access rules in the target space"` + AddAccessRule v7.AddAccessRuleCommand `command:"add-access-rule" description:"Add an access rule to allow specific apps, spaces, or orgs to access a route"` API v7.APICommand `command:"api" description:"Set or view target api url"` AddNetworkPolicy v7.AddNetworkPolicyCommand `command:"add-network-policy" description:"Create policy to allow direct network traffic from one app to another"` AddPluginRepo plugin.AddPluginRepoCommand `command:"add-plugin-repo" description:"Add a new plugin repository"` @@ -113,6 +115,7 @@ type commandList struct { PurgeServiceOffering v7.PurgeServiceOfferingCommand `command:"purge-service-offering" description:"Recursively remove a service offering and child objects from Cloud Foundry database without making requests to a service broker"` Push v7.PushCommand `command:"push" alias:"p" description:"Push a new app or sync changes to an existing app"` RemoveNetworkPolicy v7.RemoveNetworkPolicyCommand `command:"remove-network-policy" description:"Remove network traffic policy of an app"` + RemoveAccessRule v7.RemoveAccessRuleCommand `command:"remove-access-rule" description:"Remove an access rule from a route"` RemovePluginRepo plugin.RemovePluginRepoCommand `command:"remove-plugin-repo" description:"Remove a plugin repository"` Rename v7.RenameCommand `command:"rename" description:"Rename an app"` RenameOrg v7.RenameOrgCommand `command:"rename-org" description:"Rename an org"` diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 8166edc0ba2..923264d910b 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -72,6 +72,7 @@ var HelpCategoryList = []HelpCategory{ {"update-destination"}, {"share-route", "unshare-route"}, {"move-route"}, + {"access-rules", "add-access-rule", "remove-access-rule"}, }, }, { diff --git a/command/flag/arguments.go b/command/flag/arguments.go index 9e3d60263f8..84195b1d4e6 100644 --- a/command/flag/arguments.go +++ b/command/flag/arguments.go @@ -413,3 +413,13 @@ type TaskArgs struct { AppName string `positional-arg-name:"APP_NAME" required:"true" description:"The application name"` TaskID int `positional-arg-name:"TASK_ID" required:"true" description:"The Task ID for the application"` } + +type AddAccessRuleArgs struct { + RuleName string `positional-arg-name:"RULE_NAME" required:"true" description:"The access rule name"` + Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` +} + +type RemoveAccessRuleArgs struct { + RuleName string `positional-arg-name:"RULE_NAME" required:"true" description:"The access rule name"` + Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` +} diff --git a/command/v7/access_rules_command.go b/command/v7/access_rules_command.go new file mode 100644 index 00000000000..0b29c17e4d9 --- /dev/null +++ b/command/v7/access_rules_command.go @@ -0,0 +1,103 @@ +package v7 + +import ( + "fmt" + + "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/util/ui" +) + +type AccessRulesCommand struct { + BaseCommand + + Domain string `long:"domain" description:"Filter by domain name"` + Hostname string `long:"hostname" description:"Filter by hostname"` + Path string `long:"path" description:"Filter by path"` + Labels string `long:"labels" description:"Selector to filter access rules by labels"` + + usage interface{} `usage:"CF_NAME access-rules [--domain DOMAIN] [--hostname HOSTNAME] [--path PATH] [--labels SELECTOR]\n\nEXAMPLES:\n cf access-rules\n cf access-rules --domain apps.identity\n cf access-rules --domain apps.identity --hostname backend\n cf access-rules --labels env=prod"` + relatedCommands interface{} `related_commands:"add-access-rule, remove-access-rule, routes"` +} + +func (cmd AccessRulesCommand) Execute(args []string) error { + // Check target (org + space required) + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + // Get current user + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + // Display contextual header + cmd.UI.DisplayTextWithFlavor( + "Getting access rules in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", + map[string]interface{}{ + "OrgName": cmd.Config.TargetedOrganization().Name, + "SpaceName": cmd.Config.TargetedSpace().Name, + "Username": user.Name, + }) + cmd.UI.DisplayNewline() + + // Fetch access rules for space with filters + rulesWithRoutes, warnings, err := cmd.Actor.GetAccessRulesForSpace( + cmd.Config.TargetedSpace().GUID, + cmd.Domain, + cmd.Hostname, + cmd.Path, + cmd.Labels, + ) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + // Handle empty results + if len(rulesWithRoutes) == 0 { + cmd.UI.DisplayText("No access rules found.") + return nil + } + + // Build table data + table := [][]string{ + { + cmd.UI.TranslateText("name"), + cmd.UI.TranslateText("route"), + cmd.UI.TranslateText("selector"), + cmd.UI.TranslateText("scope"), + cmd.UI.TranslateText("source"), + }, + } + + for _, ruleWithRoute := range rulesWithRoutes { + table = append(table, []string{ + ruleWithRoute.Name, + formatRoute(ruleWithRoute.Route, ruleWithRoute.DomainName), + ruleWithRoute.Selector, + ruleWithRoute.ScopeType, + ruleWithRoute.SourceName, + }) + } + + // Display table + cmd.UI.DisplayTableWithHeader("", table, ui.DefaultTableSpacePadding) + + return nil +} + +// formatRoute formats a route as hostname.domain/path +func formatRoute(route resources.Route, domainName string) string { + var formatted string + if route.Host != "" { + formatted = fmt.Sprintf("%s.%s", route.Host, domainName) + } else { + formatted = domainName + } + if route.Path != "" { + formatted += route.Path + } + return formatted +} diff --git a/command/v7/access_rules_command_test.go b/command/v7/access_rules_command_test.go new file mode 100644 index 00000000000..bf34d3ea2b9 --- /dev/null +++ b/command/v7/access_rules_command_test.go @@ -0,0 +1,356 @@ +package v7_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + v7 "code.cloudfoundry.org/cli/v9/command/v7" + "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/util/configv3" + "code.cloudfoundry.org/cli/v9/util/ui" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("access-rules Command", func() { + var ( + cmd v7.AccessRulesCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + binaryName string + executeErr error + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + binaryName = "faceman" + fakeConfig.BinaryNameReturns(binaryName) + + cmd = v7.AccessRulesCommand{ + BaseCommand: v7.BaseCommand{ + UI: testUI, + Config: fakeConfig, + Actor: fakeActor, + SharedActor: fakeSharedActor, + }, + } + + fakeConfig.TargetedOrganizationReturns(configv3.Organization{ + Name: "some-org", + GUID: "some-org-guid", + }) + fakeConfig.TargetedSpaceReturns(configv3.Space{ + Name: "some-space", + GUID: "some-space-guid", + }) + + fakeActor.GetCurrentUserReturns(configv3.User{Name: "steve"}, nil) + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + When("checking target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) + + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("the user is not logged in", func() { + var expectedErr error + + BeforeEach(func() { + expectedErr = errors.New("some current user error") + fakeActor.GetCurrentUserReturns(configv3.User{}, expectedErr) + }) + + It("returns an error", func() { + Expect(executeErr).To(Equal(expectedErr)) + }) + }) + + When("getting access rules returns an error", func() { + var expectedErr error + + BeforeEach(func() { + expectedErr = ccerror.RequestError{} + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{"warning-1", "warning-2"}, expectedErr) + }) + + It("returns the error and prints warnings", func() { + Expect(executeErr).To(Equal(ccerror.RequestError{})) + + Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) + + Expect(testUI.Err).To(Say("warning-1")) + Expect(testUI.Err).To(Say("warning-2")) + }) + }) + + When("getting access rules succeeds", func() { + BeforeEach(func() { + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ + { + AccessRule: resources.AccessRule{ + GUID: "rule-guid-1", + Name: "rule-1", + Selector: "cf:app:app-guid-1", + }, + Route: resources.Route{ + GUID: "route-guid-1", + Host: "myapp", + Path: "/api", + }, + DomainName: "example.com", + ScopeType: "app", + SourceName: "my-app", + }, + { + AccessRule: resources.AccessRule{ + GUID: "rule-guid-2", + Name: "rule-2", + Selector: "cf:any", + }, + Route: resources.Route{ + GUID: "route-guid-2", + Host: "webapp", + Path: "", + }, + DomainName: "test.com", + ScopeType: "any", + SourceName: "(any app)", + }, + }, v7action.Warnings{"warning-1"}, nil) + }) + + It("displays the access rules in a table", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) + Expect(testUI.Out).To(Say(`name\s+route\s+selector\s+scope\s+source`)) + Expect(testUI.Out).To(Say(`rule-1\s+myapp\.example\.com/api\s+cf:app:app-guid-1\s+my-app`)) + Expect(testUI.Out).To(Say(`rule-2\s+webapp\.test\.com\s+cf:any\s+\(any app\)`)) + + Expect(testUI.Err).To(Say("warning-1")) + + Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) + spaceGUID, domainName, hostname, path, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) + Expect(spaceGUID).To(Equal("some-space-guid")) + Expect(domainName).To(Equal("")) + Expect(hostname).To(Equal("")) + Expect(path).To(Equal("")) + Expect(labelSelector).To(Equal("")) + }) + }) + + When("no access rules exist", func() { + BeforeEach(func() { + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) + }) + + It("displays a message indicating no access rules found", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) + Expect(testUI.Out).To(Say(`No access rules found\.`)) + }) + }) + + When("filtering by domain", func() { + BeforeEach(func() { + cmd.Domain = "example.com" + + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ + { + AccessRule: resources.AccessRule{ + GUID: "rule-guid-1", + Name: "rule-1", + Selector: "cf:any", + }, + Route: resources.Route{ + GUID: "route-guid-1", + Host: "myapp", + }, + DomainName: "example.com", + ScopeType: "any", + SourceName: "(any app)", + }, + }, v7action.Warnings{}, nil) + }) + + It("passes the domain filter to the actor", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) + spaceGUID, domainName, hostname, path, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) + Expect(spaceGUID).To(Equal("some-space-guid")) + Expect(domainName).To(Equal("example.com")) + Expect(hostname).To(Equal("")) + Expect(path).To(Equal("")) + Expect(labelSelector).To(Equal("")) + }) + }) + + When("filtering by hostname", func() { + BeforeEach(func() { + cmd.Hostname = "myapp" + + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) + }) + + It("passes the hostname filter to the actor", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) + _, _, hostname, _, _ := fakeActor.GetAccessRulesForSpaceArgsForCall(0) + Expect(hostname).To(Equal("myapp")) + }) + }) + + When("filtering by path", func() { + BeforeEach(func() { + cmd.Path = "/api" + + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) + }) + + It("passes the path filter to the actor", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) + _, _, _, path, _ := fakeActor.GetAccessRulesForSpaceArgsForCall(0) + Expect(path).To(Equal("/api")) + }) + }) + + When("filtering by labels", func() { + BeforeEach(func() { + cmd.Labels = "env=production,tier=frontend" + + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) + }) + + It("passes the label selector to the actor", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) + _, _, _, _, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) + Expect(labelSelector).To(Equal("env=production,tier=frontend")) + }) + }) + + When("using multiple filters", func() { + BeforeEach(func() { + cmd.Domain = "example.com" + cmd.Hostname = "myapp" + cmd.Path = "/api" + cmd.Labels = "env=production" + + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ + { + AccessRule: resources.AccessRule{ + GUID: "rule-guid-1", + Name: "filtered-rule", + Selector: "cf:app:app-guid-1", + }, + Route: resources.Route{ + GUID: "route-guid-1", + Host: "myapp", + Path: "/api", + }, + DomainName: "example.com", + ScopeType: "app", + SourceName: "my-app", + }, + }, v7action.Warnings{}, nil) + }) + + It("passes all filters to the actor", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) + spaceGUID, domainName, hostname, path, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) + Expect(spaceGUID).To(Equal("some-space-guid")) + Expect(domainName).To(Equal("example.com")) + Expect(hostname).To(Equal("myapp")) + Expect(path).To(Equal("/api")) + Expect(labelSelector).To(Equal("env=production")) + }) + + It("displays the filtered results", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) + Expect(testUI.Out).To(Say(`name\s+route\s+selector\s+scope\s+source`)) + Expect(testUI.Out).To(Say(`filtered-rule\s+myapp\.example\.com/api\s+cf:app:app-guid-1\s+my-app`)) + }) + }) + + When("route formatting handles edge cases", func() { + BeforeEach(func() { + fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ + { + AccessRule: resources.AccessRule{ + GUID: "rule-guid-1", + Name: "no-host-rule", + Selector: "cf:any", + }, + Route: resources.Route{ + GUID: "route-guid-1", + Host: "", + Path: "/api", + }, + DomainName: "example.com", + ScopeType: "any", + SourceName: "(any app)", + }, + { + AccessRule: resources.AccessRule{ + GUID: "rule-guid-2", + Name: "no-path-rule", + Selector: "cf:any", + }, + Route: resources.Route{ + GUID: "route-guid-2", + Host: "myapp", + Path: "", + }, + DomainName: "test.com", + ScopeType: "any", + SourceName: "(any app)", + }, + }, v7action.Warnings{}, nil) + }) + + It("formats routes correctly", func() { + Expect(executeErr).ToNot(HaveOccurred()) + + // No host, with path: "example.com/api" + Expect(testUI.Out).To(Say(`no-host-rule\s+example\.com/api`)) + + // With host, no path: "myapp.test.com" + Expect(testUI.Out).To(Say(`no-path-rule\s+myapp\.test\.com`)) + }) + }) +}) diff --git a/command/v7/actor.go b/command/v7/actor.go index daeac22dd0a..a5fecefc786 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -23,6 +23,7 @@ import ( type Actor interface { ApplyOrganizationQuotaByName(quotaName string, orgGUID string) (v7action.Warnings, error) ApplySpaceQuotaByName(quotaName string, spaceGUID string, orgGUID string) (v7action.Warnings, error) + AddAccessRule(ruleName, domainName, selector, hostname, path string) (v7action.Warnings, error) AssignIsolationSegmentToSpaceByNameAndSpace(isolationSegmentName string, spaceGUID string) (v7action.Warnings, error) Authenticate(credentials map[string]string, origin string, grantType uaa.GrantType) error BindSecurityGroupToSpaces(securityGroupGUID string, spaces []resources.Space, lifecycle constant.SecurityGroupLifecycle) (v7action.Warnings, error) @@ -44,20 +45,21 @@ type Actor interface { CreateOrgRole(roleType constant.RoleType, orgGUID string, userNameOrGUID string, userOrigin string, isClient bool) (v7action.Warnings, error) CreateOrganization(orgName string) (resources.Organization, v7action.Warnings, error) CreateOrganizationQuota(name string, limits v7action.QuotaLimits) (v7action.Warnings, error) - CreatePrivateDomain(domainName string, orgName string) (v7action.Warnings, error) + CreatePrivateDomain(domainName string, orgName string, enforceAccessRules bool, accessRulesScope string) (v7action.Warnings, error) CreateRoute(spaceGUID, domainName, hostname, path string, port int, options map[string]*string) (resources.Route, v7action.Warnings, error) CreateRouteBinding(params v7action.CreateRouteBindingParams) (chan v7action.PollJobEvent, v7action.Warnings, error) CreateSecurityGroup(name, filePath string) (v7action.Warnings, error) CreateServiceAppBinding(params v7action.CreateServiceAppBindingParams) (chan v7action.PollJobEvent, v7action.Warnings, error) CreateServiceBroker(model resources.ServiceBroker) (v7action.Warnings, error) CreateServiceKey(params v7action.CreateServiceKeyParams) (chan v7action.PollJobEvent, v7action.Warnings, error) - CreateSharedDomain(domainName string, internal bool, routerGroupName string) (v7action.Warnings, error) + CreateSharedDomain(domainName string, internal bool, routerGroupName string, enforceAccessRules bool, accessRulesScope string) (v7action.Warnings, error) CreateSpace(spaceName, orgGUID string) (resources.Space, v7action.Warnings, error) CreateSpaceQuota(spaceQuotaName string, orgGuid string, limits v7action.QuotaLimits) (v7action.Warnings, error) CreateSpaceRole(roleType constant.RoleType, orgGUID string, spaceGUID string, userNameOrGUID string, userOrigin string, isClient bool) (v7action.Warnings, error) CreateUser(username string, password string, origin string) (resources.User, v7action.Warnings, error) CreateUserProvidedServiceInstance(instance resources.ServiceInstance) (v7action.Warnings, error) DeleteApplicationByNameAndSpace(name, spaceGUID string, deleteRoutes bool) (v7action.Warnings, error) + DeleteAccessRule(ruleName, domainName, hostname, path string) (v7action.Warnings, error) DeleteBuildpackByNameAndStackAndLifecycle(buildpackName string, buildpackStack string, buildpackLifecycle string) (v7action.Warnings, error) DeleteDomain(domain resources.Domain) (v7action.Warnings, error) DeleteInstanceByApplicationNameSpaceProcessTypeAndIndex(appName string, spaceGUID string, processType string, instanceIndex int) (v7action.Warnings, error) @@ -87,6 +89,8 @@ type Actor interface { EnableServiceAccess(offeringName, brokerName, orgName, planName string) (v7action.SkippedPlans, v7action.Warnings, error) EntitleIsolationSegmentToOrganizationByName(isolationSegmentName string, orgName string) (v7action.Warnings, error) GetAppFeature(appGUID string, featureName string) (resources.ApplicationFeature, v7action.Warnings, error) + GetAccessRulesByRoute(domainName, hostname, path string) ([]resources.AccessRule, v7action.Warnings, error) + GetAccessRulesForSpace(spaceGUID string, domainName string, hostname string, path string, labelSelector string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error) GetAppSummariesForSpace(spaceGUID string, labels string, omitStats bool) ([]v7action.ApplicationSummary, v7action.Warnings, error) GetApplicationByNameAndSpace(appName string, spaceGUID string) (resources.Application, v7action.Warnings, error) GetApplicationMapForRoute(route resources.Route) (map[string]resources.Application, v7action.Warnings, error) diff --git a/command/v7/add_access_rule_command.go b/command/v7/add_access_rule_command.go new file mode 100644 index 00000000000..b3db3914811 --- /dev/null +++ b/command/v7/add_access_rule_command.go @@ -0,0 +1,284 @@ +package v7 + +import ( + "fmt" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/command/translatableerror" +) + +type AddAccessRuleCommand struct { + BaseCommand + + RequiredArgs flag.AddAccessRuleArgs `positional-args:"yes"` + Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` + Path string `long:"path" description:"Path for the route"` + + // Source resolution flags (mutually exclusive as primary source) + SourceApp string `long:"source-app" description:"Allow access from this app (by name)"` + SourceSpace string `long:"source-space" description:"Allow access from all apps in this space (by name) or specify the space for --source-app"` + 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"` + SourceAny bool `long:"source-any" description:"Allow access from any authenticated app"` + + // Advanced: raw selector flag + Selector string `long:"selector" description:"Raw selector (cf:app:, cf:space:, cf:org:, or cf:any)"` + + usage interface{} `usage:"CF_NAME add-access-rule RULE_NAME 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 | --selector SELECTOR] [--path PATH]\n\nALLOW ACCESS TO A ROUTE:\n Create an access rule 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-access-rule allow-frontend apps.identity --source-app frontend-app --hostname backend\n\n # Allow an app in a different space to access the route\n cf add-access-rule allow-other-space 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-access-rule allow-other-org 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-access-rule allow-monitoring apps.identity --source-space monitoring --hostname api --path /metrics\n\n # Allow all apps in a space in a different org\n cf add-access-rule allow-prod-space 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-access-rule allow-platform-org apps.identity --source-org platform --hostname shared-api\n\n # Allow any authenticated app to access the public API\n cf add-access-rule allow-all apps.identity --source-any --hostname public-api\n\n # Use raw selector (advanced)\n cf add-access-rule allow-raw apps.identity --selector cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend"` + relatedCommands interface{} `related_commands:"access-rules, remove-access-rule, create-shared-domain"` +} + +func (cmd AddAccessRuleCommand) Execute(args []string) error { + // Validate source flags + if err := cmd.validateSourceFlags(); err != nil { + return err + } + + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + // Resolve selector from source flags + selector, scopeDisplay, warnings, err := cmd.resolveSelector() + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + // Validate selector format + if err := validateSelector(selector); err != nil { + return err + } + + ruleName := cmd.RequiredArgs.RuleName + domainName := cmd.RequiredArgs.Domain + + cmd.UI.DisplayTextWithFlavor("Adding access rule {{.RuleName}} for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", + map[string]interface{}{ + "RuleName": ruleName, + "Hostname": cmd.Hostname, + "Domain": domainName, + "Path": formatPath(cmd.Path), + "User": user.Name, + }) + + // Display resolved source (for transparency) + cmd.UI.DisplayText(" {{.ScopeDisplay}}", + map[string]interface{}{ + "ScopeDisplay": scopeDisplay, + }) + cmd.UI.DisplayText(" selector: {{.Selector}}", + map[string]interface{}{ + "Selector": selector, + }) + + warnings, err = cmd.Actor.AddAccessRule(ruleName, domainName, selector, cmd.Hostname, cmd.Path) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + cmd.UI.DisplayOK() + cmd.UI.DisplayText("TIP: Access rule '{{.RuleName}}' has been created. It may take a few seconds for the rule to propagate to GoRouter.", + map[string]interface{}{ + "RuleName": ruleName, + }) + + return nil +} + +// validateSourceFlags ensures exactly one source target is specified and validates combinations +func (cmd AddAccessRuleCommand) validateSourceFlags() error { + sourceFlags := []string{} + + if cmd.Selector != "" { + sourceFlags = append(sourceFlags, "--selector") + } + if cmd.SourceApp != "" { + sourceFlags = append(sourceFlags, "--source-app") + } + if cmd.SourceSpace != "" && cmd.SourceApp == "" { + // --source-space only counts as a primary source if --source-app is NOT provided + sourceFlags = append(sourceFlags, "--source-space") + } + if cmd.SourceOrg != "" && cmd.SourceSpace == "" && cmd.SourceApp == "" { + // --source-org only counts as a primary source if neither --source-space nor --source-app are provided + sourceFlags = append(sourceFlags, "--source-org") + } + if cmd.SourceAny { + sourceFlags = append(sourceFlags, "--source-any") + } + + if len(sourceFlags) == 0 { + return translatableerror.RequiredArgumentError{ + ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --selector", + } + } + + if len(sourceFlags) > 1 { + return translatableerror.ArgumentCombinationError{ + Args: sourceFlags, + } + } + + return nil +} + +// resolveSelector resolves source flags to a selector string +// Returns (selector, scopeDisplay, warnings, error) +// scopeDisplay is a human-readable description for output (e.g., "scope: app, source: frontend-app") +func (cmd AddAccessRuleCommand) resolveSelector() (string, string, v7action.Warnings, error) { + var allWarnings v7action.Warnings + + // Priority: --selector flag (raw selector, no resolution needed) + if cmd.Selector != "" { + return cmd.Selector, fmt.Sprintf("selector: %s", cmd.Selector), allWarnings, nil + } + + // --source-any + if cmd.SourceAny { + return "cf:any", "scope: any, source: any authenticated app", allWarnings, nil + } + + // --source-app (with optional --source-space and --source-org for cross-space/org lookup) + if cmd.SourceApp != "" { + // Determine space GUID for app lookup + spaceGUID := cmd.Config.TargetedSpace().GUID + spaceName := cmd.Config.TargetedSpace().Name + orgName := cmd.Config.TargetedOrganization().Name + + if cmd.SourceSpace != "" { + // Determine org GUID for space lookup + orgGUID := cmd.Config.TargetedOrganization().GUID + if cmd.SourceOrg != "" { + org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + orgGUID = org.GUID + orgName = cmd.SourceOrg + } + + // Resolve space by name + space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.SourceSpace, orgGUID) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + spaceGUID = space.GUID + spaceName = cmd.SourceSpace + } + + // Resolve app by name in the determined space + app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.SourceApp, spaceGUID) + allWarnings = append(allWarnings, warnings...) + if err != nil { + // Enhanced error message for app not found + if _, ok := err.(actionerror.ApplicationNotFoundError); ok { + if cmd.SourceSpace == "" { + // App not found in current space + return "", "", allWarnings, fmt.Errorf( + "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.", + cmd.SourceApp, + cmd.Config.TargetedSpace().Name, + cmd.Config.TargetedOrganization().Name, + ) + } + } + return "", "", allWarnings, err + } + + scopeDisplay := fmt.Sprintf("scope: app, source: %s", cmd.SourceApp) + if cmd.SourceSpace != "" { + scopeDisplay += fmt.Sprintf(" (space: %s", spaceName) + if cmd.SourceOrg != "" { + scopeDisplay += fmt.Sprintf(", org: %s", orgName) + } + scopeDisplay += ")" + } + + return fmt.Sprintf("cf:app:%s", app.GUID), scopeDisplay, allWarnings, nil + } + + // --source-space (without --source-app, so create space-level rule) + if cmd.SourceSpace != "" { + // Determine org GUID for space lookup + orgGUID := cmd.Config.TargetedOrganization().GUID + orgName := cmd.Config.TargetedOrganization().Name + if cmd.SourceOrg != "" { + org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + orgGUID = org.GUID + orgName = cmd.SourceOrg + } + + // Resolve space by name + space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.SourceSpace, orgGUID) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + + scopeDisplay := fmt.Sprintf("scope: space, source: %s", cmd.SourceSpace) + if cmd.SourceOrg != "" { + scopeDisplay += fmt.Sprintf(" (org: %s)", orgName) + } + + return fmt.Sprintf("cf:space:%s", space.GUID), scopeDisplay, allWarnings, nil + } + + // --source-org (without --source-space or --source-app, so create org-level rule) + if cmd.SourceOrg != "" { + org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + + scopeDisplay := fmt.Sprintf("scope: org, source: %s", cmd.SourceOrg) + + return fmt.Sprintf("cf:org:%s", org.GUID), scopeDisplay, allWarnings, nil + } + + // Should never reach here due to validation + return "", "", allWarnings, fmt.Errorf("no source specified") +} + +func validateSelector(selector string) error { + // Basic validation - check for cf:app:, cf:space:, cf:org:, or cf:any prefix + validPrefixes := []string{"cf:app:", "cf:space:", "cf:org:", "cf:any"} + for _, prefix := range validPrefixes { + if len(selector) >= len(prefix) && selector[:len(prefix)] == prefix { + if prefix == "cf:any" { + if selector != "cf:any" { + return fmt.Errorf("selector 'cf:any' must not have a GUID suffix") + } + return nil + } + // For other selectors, ensure there's a GUID after the prefix + if len(selector) <= len(prefix) { + return fmt.Errorf("selector '%s' must include a GUID (e.g., %s)", selector, prefix) + } + return nil + } + } + return fmt.Errorf("selector must start with one of: cf:app:, cf:space:, cf:org:, or be exactly 'cf:any'") +} + +func formatPath(path string) string { + if path == "" { + return "" + } + return path +} diff --git a/command/v7/add_access_rule_command_test.go b/command/v7/add_access_rule_command_test.go new file mode 100644 index 00000000000..f6b73907d28 --- /dev/null +++ b/command/v7/add_access_rule_command_test.go @@ -0,0 +1,453 @@ +package v7_test + +import ( + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/command/translatableerror" + . "code.cloudfoundry.org/cli/v9/command/v7" + "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/util/configv3" + "code.cloudfoundry.org/cli/v9/util/ui" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("add-access-rule Command", func() { + var ( + cmd AddAccessRuleCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + executeErr error + args []string + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + cmd = AddAccessRuleCommand{ + BaseCommand: BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + } + + // Setup default config returns + fakeConfig.TargetedOrganizationReturns(configv3.Organization{ + GUID: "org-guid", + Name: "org-name", + }) + fakeConfig.TargetedSpaceReturns(configv3.Space{ + GUID: "space-guid", + Name: "space-name", + }) + + fakeActor.GetCurrentUserReturns(configv3.User{Name: "test-user"}, nil) + + args = []string{} + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(args) + }) + + Describe("validation", func() { + Context("when no source flags are provided", func() { + BeforeEach(func() { + cmd.RequiredArgs = flag.AddAccessRuleArgs{ + RuleName: "test-rule", + Domain: "apps.internal", + } + cmd.Hostname = "backend" + }) + + It("returns a RequiredArgumentError", func() { + Expect(executeErr).To(MatchError(translatableerror.RequiredArgumentError{ + ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --selector", + })) + }) + }) + + Context("when multiple mutually exclusive source flags are provided", func() { + BeforeEach(func() { + cmd.RequiredArgs = flag.AddAccessRuleArgs{ + RuleName: "test-rule", + Domain: "apps.internal", + } + cmd.Hostname = "backend" + cmd.SourceApp = "app-name" + cmd.SourceAny = true + }) + + It("returns an ArgumentCombinationError", func() { + Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{ + Args: []string{"--source-app", "--source-any"}, + })) + }) + }) + + Context("when --source-space and --source-any are both provided", func() { + BeforeEach(func() { + cmd.RequiredArgs = flag.AddAccessRuleArgs{ + RuleName: "test-rule", + Domain: "apps.internal", + } + cmd.Hostname = "backend" + cmd.SourceSpace = "some-space" + cmd.SourceAny = true + }) + + It("returns an ArgumentCombinationError", func() { + Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{ + Args: []string{"--source-space", "--source-any"}, + })) + }) + }) + }) + + When("the user is logged in, an org is targeted, and a space is targeted", func() { + BeforeEach(func() { + cmd.RequiredArgs = flag.AddAccessRuleArgs{ + RuleName: "test-rule", + Domain: "apps.internal", + } + cmd.Hostname = "backend" + }) + + Describe("source resolution", func() { + Context("when --source-app is provided (current space)", func() { + BeforeEach(func() { + cmd.SourceApp = "frontend-app" + fakeActor.GetApplicationByNameAndSpaceReturns( + resources.Application{GUID: "app-guid"}, + v7action.Warnings{"app-warning"}, + nil, + ) + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("resolves the app and creates the access rule", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + // Verify app lookup + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + appName, spaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(appName).To(Equal("frontend-app")) + Expect(spaceGUID).To(Equal("space-guid")) + + // Verify access rule creation with resolved selector + Expect(fakeActor.AddAccessRuleCallCount()).To(Equal(1)) + ruleName, domain, selector, hostname, path := fakeActor.AddAccessRuleArgsForCall(0) + Expect(ruleName).To(Equal("test-rule")) + Expect(domain).To(Equal("apps.internal")) + Expect(selector).To(Equal("cf:app:app-guid")) + Expect(hostname).To(Equal("backend")) + Expect(path).To(BeEmpty()) + + // Verify output + Expect(testUI.Out).To(Say("Adding access rule test-rule")) + Expect(testUI.Out).To(Say("scope: app, source: frontend-app")) + Expect(testUI.Out).To(Say("selector: cf:app:app-guid")) + Expect(testUI.Out).To(Say("OK")) + }) + + It("displays warnings", func() { + Expect(testUI.Err).To(Say("app-warning")) + Expect(testUI.Err).To(Say("add-warning")) + }) + }) + + Context("when --source-app is provided with --source-space (cross-space)", func() { + BeforeEach(func() { + cmd.SourceApp = "frontend-app" + cmd.SourceSpace = "other-space" + + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "other-space-guid"}, + v7action.Warnings{"space-warning"}, + nil, + ) + fakeActor.GetApplicationByNameAndSpaceReturns( + resources.Application{GUID: "app-guid"}, + v7action.Warnings{"app-warning"}, + nil, + ) + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("resolves space then app and creates the access rule", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + // Verify space lookup + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) + spaceName, orgGUID := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("other-space")) + Expect(orgGUID).To(Equal("org-guid")) + + // Verify app lookup in resolved space + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) + appName, spaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) + Expect(appName).To(Equal("frontend-app")) + Expect(spaceGUID).To(Equal("other-space-guid")) + + // Verify selector + _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + Expect(selector).To(Equal("cf:app:app-guid")) + + // Verify output shows cross-space info + Expect(testUI.Out).To(Say("scope: app, source: frontend-app \\(space: other-space\\)")) + }) + }) + + Context("when --source-app is provided with --source-space and --source-org (cross-org)", func() { + BeforeEach(func() { + cmd.SourceApp = "frontend-app" + cmd.SourceSpace = "other-space" + cmd.SourceOrg = "other-org" + + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "other-org-guid"}, + v7action.Warnings{"org-warning"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "other-space-guid"}, + v7action.Warnings{"space-warning"}, + nil, + ) + fakeActor.GetApplicationByNameAndSpaceReturns( + resources.Application{GUID: "app-guid"}, + v7action.Warnings{"app-warning"}, + nil, + ) + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("resolves org, space, then app and creates the access rule", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + // Verify org lookup + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("other-org")) + + // Verify space lookup with resolved org + spaceName, orgGUID := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("other-space")) + Expect(orgGUID).To(Equal("other-org-guid")) + + // Verify output shows cross-org info + Expect(testUI.Out).To(Say("scope: app, source: frontend-app \\(space: other-space, org: other-org\\)")) + }) + }) + + Context("when --source-app is not found in current space", func() { + BeforeEach(func() { + cmd.SourceApp = "missing-app" + fakeActor.GetApplicationByNameAndSpaceReturns( + resources.Application{}, + v7action.Warnings{"app-warning"}, + actionerror.ApplicationNotFoundError{Name: "missing-app"}, + ) + }) + + It("returns a helpful error message", func() { + Expect(executeErr).To(HaveOccurred()) + Expect(executeErr.Error()).To(ContainSubstring("App 'missing-app' not found in space 'space-name' / org 'org-name'")) + Expect(executeErr.Error()).To(ContainSubstring("TIP: If the app is in a different space or org, use --source-space and/or --source-org flags")) + }) + }) + + Context("when --source-space is provided (without --source-app)", func() { + BeforeEach(func() { + cmd.SourceSpace = "monitoring-space" + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "space-guid-123"}, + v7action.Warnings{"space-warning"}, + nil, + ) + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("creates a space-level access rule", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + // Verify selector is space-level + _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + Expect(selector).To(Equal("cf:space:space-guid-123")) + + Expect(testUI.Out).To(Say("scope: space, source: monitoring-space")) + }) + }) + + Context("when --source-space is provided with --source-org (cross-org space rule)", func() { + BeforeEach(func() { + cmd.SourceSpace = "prod-space" + cmd.SourceOrg = "prod-org" + + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "prod-org-guid"}, + v7action.Warnings{"org-warning"}, + nil, + ) + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{GUID: "prod-space-guid"}, + v7action.Warnings{"space-warning"}, + nil, + ) + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("creates a space-level access rule for the specified org", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + // Verify org lookup + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) + orgName := fakeActor.GetOrganizationByNameArgsForCall(0) + Expect(orgName).To(Equal("prod-org")) + + // Verify space lookup with resolved org + spaceName, orgGUID := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) + Expect(spaceName).To(Equal("prod-space")) + Expect(orgGUID).To(Equal("prod-org-guid")) + + // Verify selector is space-level + _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + Expect(selector).To(Equal("cf:space:prod-space-guid")) + + Expect(testUI.Out).To(Say("scope: space, source: prod-space \\(org: prod-org\\)")) + }) + }) + + Context("when --source-org is provided (without --source-space or --source-app)", func() { + BeforeEach(func() { + cmd.SourceOrg = "platform-org" + fakeActor.GetOrganizationByNameReturns( + resources.Organization{GUID: "org-guid-456"}, + v7action.Warnings{"org-warning"}, + nil, + ) + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("creates an org-level access rule", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + // Verify selector is org-level + _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + Expect(selector).To(Equal("cf:org:org-guid-456")) + + Expect(testUI.Out).To(Say("scope: org, source: platform-org")) + }) + }) + + Context("when --source-any is provided", func() { + BeforeEach(func() { + cmd.SourceAny = true + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("creates an 'any' access rule", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + Expect(selector).To(Equal("cf:any")) + + Expect(testUI.Out).To(Say("scope: any, source: any authenticated app")) + }) + }) + + Context("when --selector is provided (raw selector)", func() { + BeforeEach(func() { + cmd.Selector = "cf:app:raw-guid-123" + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("uses the raw selector without resolution", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + Expect(selector).To(Equal("cf:app:raw-guid-123")) + + Expect(testUI.Out).To(Say("selector: cf:app:raw-guid-123")) + + // Should not call any resolution methods + Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(0)) + Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(0)) + Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(0)) + }) + }) + + Context("when --path is provided", func() { + BeforeEach(func() { + cmd.SourceAny = true + cmd.Path = "/metrics" + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) + }) + + It("passes the path to the actor", func() { + Expect(executeErr).NotTo(HaveOccurred()) + + _, _, _, _, path := fakeActor.AddAccessRuleArgsForCall(0) + Expect(path).To(Equal("/metrics")) + }) + }) + }) + + Describe("error handling", func() { + Context("when AddAccessRule fails", func() { + BeforeEach(func() { + cmd.SourceAny = true + fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, actionerror.RouteNotFoundError{}) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{})) + Expect(testUI.Err).To(Say("add-warning")) + }) + }) + + Context("when space lookup fails", func() { + BeforeEach(func() { + cmd.SourceSpace = "nonexistent-space" + fakeActor.GetSpaceByNameAndOrganizationReturns( + resources.Space{}, + v7action.Warnings{"space-warning"}, + actionerror.SpaceNotFoundError{Name: "nonexistent-space"}, + ) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError(actionerror.SpaceNotFoundError{Name: "nonexistent-space"})) + Expect(testUI.Err).To(Say("space-warning")) + }) + }) + + Context("when org lookup fails", func() { + BeforeEach(func() { + cmd.SourceOrg = "nonexistent-org" + fakeActor.GetOrganizationByNameReturns( + resources.Organization{}, + v7action.Warnings{"org-warning"}, + actionerror.OrganizationNotFoundError{Name: "nonexistent-org"}, + ) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError(actionerror.OrganizationNotFoundError{Name: "nonexistent-org"})) + Expect(testUI.Err).To(Say("org-warning")) + }) + }) + }) + }) +}) diff --git a/command/v7/create_private_domain_command.go b/command/v7/create_private_domain_command.go index 2701ca91c47..1a95ae97250 100644 --- a/command/v7/create_private_domain_command.go +++ b/command/v7/create_private_domain_command.go @@ -10,9 +10,11 @@ import ( type CreatePrivateDomainCommand struct { BaseCommand - RequiredArgs flag.OrgDomain `positional-args:"yes"` - usage interface{} `usage:"CF_NAME create-private-domain ORG DOMAIN"` - relatedCommands interface{} `related_commands:"create-shared-domain, domains, share-private-domain"` + RequiredArgs flag.OrgDomain `positional-args:"yes"` + EnforceAccessRules bool `long:"enforce-access-rules" description:"Enable platform-enforced access control for routes on this domain (requires mTLS domain configuration in GoRouter)"` + Scope string `long:"scope" description:"Operator-level scope boundary for access rules: 'any', 'org', or 'space' (only valid with --enforce-access-rules)"` + usage interface{} `usage:"CF_NAME create-private-domain ORG DOMAIN [--enforce-access-rules [--scope (any|org|space)]]"` + relatedCommands interface{} `related_commands:"create-shared-domain, domains, share-private-domain, add-access-rule, access-rules"` } func (cmd CreatePrivateDomainCommand) Execute(args []string) error { @@ -29,6 +31,16 @@ func (cmd CreatePrivateDomainCommand) Execute(args []string) error { domain := cmd.RequiredArgs.Domain orgName := cmd.RequiredArgs.Organization + // Validate that --scope is only used with --enforce-access-rules + if cmd.Scope != "" && !cmd.EnforceAccessRules { + return fmt.Errorf("--scope can only be used with --enforce-access-rules") + } + + // Validate scope values + if cmd.Scope != "" && cmd.Scope != "any" && cmd.Scope != "org" && cmd.Scope != "space" { + return fmt.Errorf("--scope must be one of: any, org, space") + } + cmd.UI.DisplayTextWithFlavor("Creating private domain {{.Domain}} for org {{.Organization}} as {{.User}}...", map[string]interface{}{ "Domain": domain, @@ -36,7 +48,7 @@ func (cmd CreatePrivateDomainCommand) Execute(args []string) error { "Organization": orgName, }) - warnings, err := cmd.Actor.CreatePrivateDomain(domain, orgName) + warnings, err := cmd.Actor.CreatePrivateDomain(domain, orgName, cmd.EnforceAccessRules, cmd.Scope) cmd.UI.DisplayWarnings(warnings) if err != nil { @@ -53,9 +65,16 @@ func (cmd CreatePrivateDomainCommand) Execute(args []string) error { cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a private domain. Run 'cf share-private-domain' to share this domain with a different org.", - map[string]interface{}{ - "Domain": domain, - }) + if cmd.EnforceAccessRules { + cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a private identity-aware domain with access rule enforcement enabled. Routes on this domain require access rules to allow traffic.", + map[string]interface{}{ + "Domain": domain, + }) + } else { + cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a private domain. Run 'cf share-private-domain' to share this domain with a different org.", + map[string]interface{}{ + "Domain": domain, + }) + } return nil } diff --git a/command/v7/create_private_domain_command_test.go b/command/v7/create_private_domain_command_test.go index 67db5bf98cd..d30941f39d7 100644 --- a/command/v7/create_private_domain_command_test.go +++ b/command/v7/create_private_domain_command_test.go @@ -113,7 +113,7 @@ var _ = Describe("create-private-domain Command", func() { It("creates the domain", func() { Expect(fakeActor.CreatePrivateDomainCallCount()).To(Equal(1)) - expectedDomainName, expectedOrgName := fakeActor.CreatePrivateDomainArgsForCall(0) + expectedDomainName, expectedOrgName, _, _ := fakeActor.CreatePrivateDomainArgsForCall(0) Expect(expectedDomainName).To(Equal(domainName)) Expect(expectedOrgName).To(Equal(orgName)) }) diff --git a/command/v7/create_shared_domain_command.go b/command/v7/create_shared_domain_command.go index e48ec61dd6f..d154dac0312 100644 --- a/command/v7/create_shared_domain_command.go +++ b/command/v7/create_shared_domain_command.go @@ -1,17 +1,21 @@ package v7 import ( + "fmt" + "code.cloudfoundry.org/cli/v9/command/flag" ) type CreateSharedDomainCommand struct { BaseCommand - RequiredArgs flag.Domain `positional-args:"yes"` - RouterGroup string `long:"router-group" description:"Routes for this domain will use routers in the specified router group"` - Internal bool `long:"internal" description:"Applications that use internal routes communicate directly on the container network"` - usage interface{} `usage:"CF_NAME create-shared-domain DOMAIN [--router-group ROUTER_GROUP_NAME | --internal]"` - relatedCommands interface{} `related_commands:"create-private-domain, domains"` + RequiredArgs flag.Domain `positional-args:"yes"` + RouterGroup string `long:"router-group" description:"Routes for this domain will use routers in the specified router group"` + Internal bool `long:"internal" description:"Applications that use internal routes communicate directly on the container network"` + EnforceAccessRules bool `long:"enforce-access-rules" description:"Enable platform-enforced access control for routes on this domain (requires mTLS domain configuration in GoRouter)"` + Scope string `long:"scope" description:"Operator-level scope boundary for access rules: 'any', 'org', or 'space' (only valid with --enforce-access-rules)"` + usage interface{} `usage:"CF_NAME create-shared-domain DOMAIN [--router-group ROUTER_GROUP_NAME | --internal] [--enforce-access-rules [--scope (any|org|space)]]"` + relatedCommands interface{} `related_commands:"create-private-domain, domains, add-access-rule, access-rules"` } func (cmd CreateSharedDomainCommand) Execute(args []string) error { @@ -27,22 +31,40 @@ func (cmd CreateSharedDomainCommand) Execute(args []string) error { domain := cmd.RequiredArgs.Domain + // Validate that --scope is only used with --enforce-access-rules + if cmd.Scope != "" && !cmd.EnforceAccessRules { + return fmt.Errorf("--scope can only be used with --enforce-access-rules") + } + + // Validate scope values + if cmd.Scope != "" && cmd.Scope != "any" && cmd.Scope != "org" && cmd.Scope != "space" { + return fmt.Errorf("--scope must be one of: any, org, space") + } + cmd.UI.DisplayTextWithFlavor("Creating shared domain {{.Domain}} as {{.User}}...", map[string]interface{}{ "Domain": domain, "User": user.Name, }) - warnings, err := cmd.Actor.CreateSharedDomain(domain, cmd.Internal, cmd.RouterGroup) + warnings, err := cmd.Actor.CreateSharedDomain(domain, cmd.Internal, cmd.RouterGroup, cmd.EnforceAccessRules, cmd.Scope) cmd.UI.DisplayWarnings(warnings) if err != nil { return err } cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is shared with all orgs. Run 'cf domains' to view available domains.", - map[string]interface{}{ - "Domain": domain, - }) + + if cmd.EnforceAccessRules { + cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a shared identity-aware domain with access rule enforcement enabled. Routes on this domain require access rules to allow traffic.", + map[string]interface{}{ + "Domain": domain, + }) + } else { + cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is shared with all orgs. Run 'cf domains' to view available domains.", + map[string]interface{}{ + "Domain": domain, + }) + } return nil } diff --git a/command/v7/create_shared_domain_command_test.go b/command/v7/create_shared_domain_command_test.go index 9df31c8cbb3..6e757f92859 100644 --- a/command/v7/create_shared_domain_command_test.go +++ b/command/v7/create_shared_domain_command_test.go @@ -126,7 +126,7 @@ var _ = Describe("create-shared-domain Command", func() { It("creates the domain", func() { Expect(fakeActor.CreateSharedDomainCallCount()).To(Equal(1)) - expectedDomainName, expectedInternal, expectedRouterGroup := fakeActor.CreateSharedDomainArgsForCall(0) + expectedDomainName, expectedInternal, expectedRouterGroup, _, _ := fakeActor.CreateSharedDomainArgsForCall(0) Expect(expectedDomainName).To(Equal(domainName)) Expect(expectedInternal).To(BeTrue()) Expect(expectedRouterGroup).To(Equal("router-group")) diff --git a/command/v7/remove_access_rule_command.go b/command/v7/remove_access_rule_command.go new file mode 100644 index 00000000000..12d0b08eb4b --- /dev/null +++ b/command/v7/remove_access_rule_command.go @@ -0,0 +1,71 @@ +package v7 + +import ( + "code.cloudfoundry.org/cli/v9/command/flag" +) + +type RemoveAccessRuleCommand struct { + BaseCommand + + RequiredArgs flag.RemoveAccessRuleArgs `positional-args:"yes"` + Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` + Path string `long:"path" description:"Path for the route"` + Force bool `short:"f" description:"Force deletion without confirmation"` + usage interface{} `usage:"CF_NAME remove-access-rule RULE_NAME DOMAIN --hostname HOSTNAME [--path PATH] [-f]\n\nEXAMPLES:\n cf remove-access-rule allow-frontend apps.identity --hostname backend\n cf remove-access-rule allow-monitoring apps.identity --hostname api --path /metrics -f"` + relatedCommands interface{} `related_commands:"access-rules, add-access-rule"` +} + +func (cmd RemoveAccessRuleCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + ruleName := cmd.RequiredArgs.RuleName + domainName := cmd.RequiredArgs.Domain + + if !cmd.Force { + prompt := "Really remove access rule {{.RuleName}} for route {{.Hostname}}.{{.Domain}}{{.Path}}?" + response, promptErr := cmd.UI.DisplayBoolPrompt(false, prompt, map[string]interface{}{ + "RuleName": ruleName, + "Hostname": cmd.Hostname, + "Domain": domainName, + "Path": formatPath(cmd.Path), + }) + + if promptErr != nil { + return promptErr + } + + if !response { + cmd.UI.DisplayText("Access rule '{{.RuleName}}' has not been removed.", map[string]interface{}{ + "RuleName": ruleName, + }) + return nil + } + } + + cmd.UI.DisplayTextWithFlavor("Removing access rule {{.RuleName}} for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", + map[string]interface{}{ + "RuleName": ruleName, + "Hostname": cmd.Hostname, + "Domain": domainName, + "Path": formatPath(cmd.Path), + "User": user.Name, + }) + + warnings, err := cmd.Actor.DeleteAccessRule(ruleName, domainName, cmd.Hostname, cmd.Path) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + cmd.UI.DisplayOK() + + return nil +} diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index e8f1915a932..b3093ad655e 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -22,6 +22,23 @@ import ( ) type FakeActor struct { + AddAccessRuleStub func(string, string, string, string, string) (v7action.Warnings, error) + addAccessRuleMutex sync.RWMutex + addAccessRuleArgsForCall []struct { + arg1 string + arg2 string + arg3 string + arg4 string + arg5 string + } + addAccessRuleReturns struct { + result1 v7action.Warnings + result2 error + } + addAccessRuleReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } ApplyOrganizationQuotaByNameStub func(string, string) (v7action.Warnings, error) applyOrganizationQuotaByNameMutex sync.RWMutex applyOrganizationQuotaByNameArgsForCall []struct { @@ -357,11 +374,13 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - CreatePrivateDomainStub func(string, string) (v7action.Warnings, error) + CreatePrivateDomainStub func(string, string, bool, string) (v7action.Warnings, error) createPrivateDomainMutex sync.RWMutex createPrivateDomainArgsForCall []struct { arg1 string arg2 string + arg3 bool + arg4 string } createPrivateDomainReturns struct { result1 v7action.Warnings @@ -463,12 +482,14 @@ type FakeActor struct { result2 v7action.Warnings result3 error } - CreateSharedDomainStub func(string, bool, string) (v7action.Warnings, error) + CreateSharedDomainStub func(string, bool, string, bool, string) (v7action.Warnings, error) createSharedDomainMutex sync.RWMutex createSharedDomainArgsForCall []struct { arg1 string arg2 bool arg3 string + arg4 bool + arg5 string } createSharedDomainReturns struct { result1 v7action.Warnings @@ -557,6 +578,22 @@ type FakeActor struct { result1 v7action.Warnings result2 error } + DeleteAccessRuleStub func(string, string, string, string) (v7action.Warnings, error) + deleteAccessRuleMutex sync.RWMutex + deleteAccessRuleArgsForCall []struct { + arg1 string + arg2 string + arg3 string + arg4 string + } + deleteAccessRuleReturns struct { + result1 v7action.Warnings + result2 error + } + deleteAccessRuleReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } DeleteApplicationByNameAndSpaceStub func(string, string, bool) (v7action.Warnings, error) deleteApplicationByNameAndSpaceMutex sync.RWMutex deleteApplicationByNameAndSpaceArgsForCall []struct { @@ -989,6 +1026,42 @@ type FakeActor struct { result1 v7action.Warnings result2 error } + GetAccessRulesByRouteStub func(string, string, string) ([]resources.AccessRule, v7action.Warnings, error) + getAccessRulesByRouteMutex sync.RWMutex + getAccessRulesByRouteArgsForCall []struct { + arg1 string + arg2 string + arg3 string + } + getAccessRulesByRouteReturns struct { + result1 []resources.AccessRule + result2 v7action.Warnings + result3 error + } + getAccessRulesByRouteReturnsOnCall map[int]struct { + result1 []resources.AccessRule + result2 v7action.Warnings + result3 error + } + GetAccessRulesForSpaceStub func(string, string, string, string, string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error) + getAccessRulesForSpaceMutex sync.RWMutex + getAccessRulesForSpaceArgsForCall []struct { + arg1 string + arg2 string + arg3 string + arg4 string + arg5 string + } + getAccessRulesForSpaceReturns struct { + result1 []v7action.AccessRuleWithRoute + result2 v7action.Warnings + result3 error + } + getAccessRulesForSpaceReturnsOnCall map[int]struct { + result1 []v7action.AccessRuleWithRoute + result2 v7action.Warnings + result3 error + } GetAppFeatureStub func(string, string) (resources.ApplicationFeature, v7action.Warnings, error) getAppFeatureMutex sync.RWMutex getAppFeatureArgsForCall []struct { @@ -3780,6 +3853,74 @@ type FakeActor struct { invocationsMutex sync.RWMutex } +func (fake *FakeActor) AddAccessRule(arg1 string, arg2 string, arg3 string, arg4 string, arg5 string) (v7action.Warnings, error) { + fake.addAccessRuleMutex.Lock() + ret, specificReturn := fake.addAccessRuleReturnsOnCall[len(fake.addAccessRuleArgsForCall)] + fake.addAccessRuleArgsForCall = append(fake.addAccessRuleArgsForCall, struct { + arg1 string + arg2 string + arg3 string + arg4 string + arg5 string + }{arg1, arg2, arg3, arg4, arg5}) + stub := fake.AddAccessRuleStub + fakeReturns := fake.addAccessRuleReturns + fake.recordInvocation("AddAccessRule", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.addAccessRuleMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4, arg5) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) AddAccessRuleCallCount() int { + fake.addAccessRuleMutex.RLock() + defer fake.addAccessRuleMutex.RUnlock() + return len(fake.addAccessRuleArgsForCall) +} + +func (fake *FakeActor) AddAccessRuleCalls(stub func(string, string, string, string, string) (v7action.Warnings, error)) { + fake.addAccessRuleMutex.Lock() + defer fake.addAccessRuleMutex.Unlock() + fake.AddAccessRuleStub = stub +} + +func (fake *FakeActor) AddAccessRuleArgsForCall(i int) (string, string, string, string, string) { + fake.addAccessRuleMutex.RLock() + defer fake.addAccessRuleMutex.RUnlock() + argsForCall := fake.addAccessRuleArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 +} + +func (fake *FakeActor) AddAccessRuleReturns(result1 v7action.Warnings, result2 error) { + fake.addAccessRuleMutex.Lock() + defer fake.addAccessRuleMutex.Unlock() + fake.AddAccessRuleStub = nil + fake.addAccessRuleReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) AddAccessRuleReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.addAccessRuleMutex.Lock() + defer fake.addAccessRuleMutex.Unlock() + fake.AddAccessRuleStub = nil + if fake.addAccessRuleReturnsOnCall == nil { + fake.addAccessRuleReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.addAccessRuleReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) ApplyOrganizationQuotaByName(arg1 string, arg2 string) (v7action.Warnings, error) { fake.applyOrganizationQuotaByNameMutex.Lock() ret, specificReturn := fake.applyOrganizationQuotaByNameReturnsOnCall[len(fake.applyOrganizationQuotaByNameArgsForCall)] @@ -5273,19 +5414,21 @@ func (fake *FakeActor) CreateOrganizationQuotaReturnsOnCall(i int, result1 v7act }{result1, result2} } -func (fake *FakeActor) CreatePrivateDomain(arg1 string, arg2 string) (v7action.Warnings, error) { +func (fake *FakeActor) CreatePrivateDomain(arg1 string, arg2 string, arg3 bool, arg4 string) (v7action.Warnings, error) { fake.createPrivateDomainMutex.Lock() ret, specificReturn := fake.createPrivateDomainReturnsOnCall[len(fake.createPrivateDomainArgsForCall)] fake.createPrivateDomainArgsForCall = append(fake.createPrivateDomainArgsForCall, struct { arg1 string arg2 string - }{arg1, arg2}) + arg3 bool + arg4 string + }{arg1, arg2, arg3, arg4}) stub := fake.CreatePrivateDomainStub fakeReturns := fake.createPrivateDomainReturns - fake.recordInvocation("CreatePrivateDomain", []interface{}{arg1, arg2}) + fake.recordInvocation("CreatePrivateDomain", []interface{}{arg1, arg2, arg3, arg4}) fake.createPrivateDomainMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1, ret.result2 @@ -5299,17 +5442,17 @@ func (fake *FakeActor) CreatePrivateDomainCallCount() int { return len(fake.createPrivateDomainArgsForCall) } -func (fake *FakeActor) CreatePrivateDomainCalls(stub func(string, string) (v7action.Warnings, error)) { +func (fake *FakeActor) CreatePrivateDomainCalls(stub func(string, string, bool, string) (v7action.Warnings, error)) { fake.createPrivateDomainMutex.Lock() defer fake.createPrivateDomainMutex.Unlock() fake.CreatePrivateDomainStub = stub } -func (fake *FakeActor) CreatePrivateDomainArgsForCall(i int) (string, string) { +func (fake *FakeActor) CreatePrivateDomainArgsForCall(i int) (string, string, bool, string) { fake.createPrivateDomainMutex.RLock() defer fake.createPrivateDomainMutex.RUnlock() argsForCall := fake.createPrivateDomainArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } func (fake *FakeActor) CreatePrivateDomainReturns(result1 v7action.Warnings, result2 error) { @@ -5740,20 +5883,22 @@ func (fake *FakeActor) CreateServiceKeyReturnsOnCall(i int, result1 chan v7actio }{result1, result2, result3} } -func (fake *FakeActor) CreateSharedDomain(arg1 string, arg2 bool, arg3 string) (v7action.Warnings, error) { +func (fake *FakeActor) CreateSharedDomain(arg1 string, arg2 bool, arg3 string, arg4 bool, arg5 string) (v7action.Warnings, error) { fake.createSharedDomainMutex.Lock() ret, specificReturn := fake.createSharedDomainReturnsOnCall[len(fake.createSharedDomainArgsForCall)] fake.createSharedDomainArgsForCall = append(fake.createSharedDomainArgsForCall, struct { arg1 string arg2 bool arg3 string - }{arg1, arg2, arg3}) + arg4 bool + arg5 string + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.CreateSharedDomainStub fakeReturns := fake.createSharedDomainReturns - fake.recordInvocation("CreateSharedDomain", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("CreateSharedDomain", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.createSharedDomainMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -5767,17 +5912,17 @@ func (fake *FakeActor) CreateSharedDomainCallCount() int { return len(fake.createSharedDomainArgsForCall) } -func (fake *FakeActor) CreateSharedDomainCalls(stub func(string, bool, string) (v7action.Warnings, error)) { +func (fake *FakeActor) CreateSharedDomainCalls(stub func(string, bool, string, bool, string) (v7action.Warnings, error)) { fake.createSharedDomainMutex.Lock() defer fake.createSharedDomainMutex.Unlock() fake.CreateSharedDomainStub = stub } -func (fake *FakeActor) CreateSharedDomainArgsForCall(i int) (string, bool, string) { +func (fake *FakeActor) CreateSharedDomainArgsForCall(i int) (string, bool, string, bool, string) { fake.createSharedDomainMutex.RLock() defer fake.createSharedDomainMutex.RUnlock() argsForCall := fake.createSharedDomainArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *FakeActor) CreateSharedDomainReturns(result1 v7action.Warnings, result2 error) { @@ -6142,6 +6287,73 @@ func (fake *FakeActor) CreateUserProvidedServiceInstanceReturnsOnCall(i int, res }{result1, result2} } +func (fake *FakeActor) DeleteAccessRule(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { + fake.deleteAccessRuleMutex.Lock() + ret, specificReturn := fake.deleteAccessRuleReturnsOnCall[len(fake.deleteAccessRuleArgsForCall)] + fake.deleteAccessRuleArgsForCall = append(fake.deleteAccessRuleArgsForCall, struct { + arg1 string + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + stub := fake.DeleteAccessRuleStub + fakeReturns := fake.deleteAccessRuleReturns + fake.recordInvocation("DeleteAccessRule", []interface{}{arg1, arg2, arg3, arg4}) + fake.deleteAccessRuleMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) DeleteAccessRuleCallCount() int { + fake.deleteAccessRuleMutex.RLock() + defer fake.deleteAccessRuleMutex.RUnlock() + return len(fake.deleteAccessRuleArgsForCall) +} + +func (fake *FakeActor) DeleteAccessRuleCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { + fake.deleteAccessRuleMutex.Lock() + defer fake.deleteAccessRuleMutex.Unlock() + fake.DeleteAccessRuleStub = stub +} + +func (fake *FakeActor) DeleteAccessRuleArgsForCall(i int) (string, string, string, string) { + fake.deleteAccessRuleMutex.RLock() + defer fake.deleteAccessRuleMutex.RUnlock() + argsForCall := fake.deleteAccessRuleArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeActor) DeleteAccessRuleReturns(result1 v7action.Warnings, result2 error) { + fake.deleteAccessRuleMutex.Lock() + defer fake.deleteAccessRuleMutex.Unlock() + fake.DeleteAccessRuleStub = nil + fake.deleteAccessRuleReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) DeleteAccessRuleReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.deleteAccessRuleMutex.Lock() + defer fake.deleteAccessRuleMutex.Unlock() + fake.DeleteAccessRuleStub = nil + if fake.deleteAccessRuleReturnsOnCall == nil { + fake.deleteAccessRuleReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.deleteAccessRuleReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) DeleteApplicationByNameAndSpace(arg1 string, arg2 string, arg3 bool) (v7action.Warnings, error) { fake.deleteApplicationByNameAndSpaceMutex.Lock() ret, specificReturn := fake.deleteApplicationByNameAndSpaceReturnsOnCall[len(fake.deleteApplicationByNameAndSpaceArgsForCall)] @@ -8068,6 +8280,146 @@ func (fake *FakeActor) EntitleIsolationSegmentToOrganizationByNameReturnsOnCall( }{result1, result2} } +func (fake *FakeActor) GetAccessRulesByRoute(arg1 string, arg2 string, arg3 string) ([]resources.AccessRule, v7action.Warnings, error) { + fake.getAccessRulesByRouteMutex.Lock() + ret, specificReturn := fake.getAccessRulesByRouteReturnsOnCall[len(fake.getAccessRulesByRouteArgsForCall)] + fake.getAccessRulesByRouteArgsForCall = append(fake.getAccessRulesByRouteArgsForCall, struct { + arg1 string + arg2 string + arg3 string + }{arg1, arg2, arg3}) + stub := fake.GetAccessRulesByRouteStub + fakeReturns := fake.getAccessRulesByRouteReturns + fake.recordInvocation("GetAccessRulesByRoute", []interface{}{arg1, arg2, arg3}) + fake.getAccessRulesByRouteMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeActor) GetAccessRulesByRouteCallCount() int { + fake.getAccessRulesByRouteMutex.RLock() + defer fake.getAccessRulesByRouteMutex.RUnlock() + return len(fake.getAccessRulesByRouteArgsForCall) +} + +func (fake *FakeActor) GetAccessRulesByRouteCalls(stub func(string, string, string) ([]resources.AccessRule, v7action.Warnings, error)) { + fake.getAccessRulesByRouteMutex.Lock() + defer fake.getAccessRulesByRouteMutex.Unlock() + fake.GetAccessRulesByRouteStub = stub +} + +func (fake *FakeActor) GetAccessRulesByRouteArgsForCall(i int) (string, string, string) { + fake.getAccessRulesByRouteMutex.RLock() + defer fake.getAccessRulesByRouteMutex.RUnlock() + argsForCall := fake.getAccessRulesByRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeActor) GetAccessRulesByRouteReturns(result1 []resources.AccessRule, result2 v7action.Warnings, result3 error) { + fake.getAccessRulesByRouteMutex.Lock() + defer fake.getAccessRulesByRouteMutex.Unlock() + fake.GetAccessRulesByRouteStub = nil + fake.getAccessRulesByRouteReturns = struct { + result1 []resources.AccessRule + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) GetAccessRulesByRouteReturnsOnCall(i int, result1 []resources.AccessRule, result2 v7action.Warnings, result3 error) { + fake.getAccessRulesByRouteMutex.Lock() + defer fake.getAccessRulesByRouteMutex.Unlock() + fake.GetAccessRulesByRouteStub = nil + if fake.getAccessRulesByRouteReturnsOnCall == nil { + fake.getAccessRulesByRouteReturnsOnCall = make(map[int]struct { + result1 []resources.AccessRule + result2 v7action.Warnings + result3 error + }) + } + fake.getAccessRulesByRouteReturnsOnCall[i] = struct { + result1 []resources.AccessRule + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) GetAccessRulesForSpace(arg1 string, arg2 string, arg3 string, arg4 string, arg5 string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error) { + fake.getAccessRulesForSpaceMutex.Lock() + ret, specificReturn := fake.getAccessRulesForSpaceReturnsOnCall[len(fake.getAccessRulesForSpaceArgsForCall)] + fake.getAccessRulesForSpaceArgsForCall = append(fake.getAccessRulesForSpaceArgsForCall, struct { + arg1 string + arg2 string + arg3 string + arg4 string + arg5 string + }{arg1, arg2, arg3, arg4, arg5}) + stub := fake.GetAccessRulesForSpaceStub + fakeReturns := fake.getAccessRulesForSpaceReturns + fake.recordInvocation("GetAccessRulesForSpace", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.getAccessRulesForSpaceMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4, arg5) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeActor) GetAccessRulesForSpaceCallCount() int { + fake.getAccessRulesForSpaceMutex.RLock() + defer fake.getAccessRulesForSpaceMutex.RUnlock() + return len(fake.getAccessRulesForSpaceArgsForCall) +} + +func (fake *FakeActor) GetAccessRulesForSpaceCalls(stub func(string, string, string, string, string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error)) { + fake.getAccessRulesForSpaceMutex.Lock() + defer fake.getAccessRulesForSpaceMutex.Unlock() + fake.GetAccessRulesForSpaceStub = stub +} + +func (fake *FakeActor) GetAccessRulesForSpaceArgsForCall(i int) (string, string, string, string, string) { + fake.getAccessRulesForSpaceMutex.RLock() + defer fake.getAccessRulesForSpaceMutex.RUnlock() + argsForCall := fake.getAccessRulesForSpaceArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 +} + +func (fake *FakeActor) GetAccessRulesForSpaceReturns(result1 []v7action.AccessRuleWithRoute, result2 v7action.Warnings, result3 error) { + fake.getAccessRulesForSpaceMutex.Lock() + defer fake.getAccessRulesForSpaceMutex.Unlock() + fake.GetAccessRulesForSpaceStub = nil + fake.getAccessRulesForSpaceReturns = struct { + result1 []v7action.AccessRuleWithRoute + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) GetAccessRulesForSpaceReturnsOnCall(i int, result1 []v7action.AccessRuleWithRoute, result2 v7action.Warnings, result3 error) { + fake.getAccessRulesForSpaceMutex.Lock() + defer fake.getAccessRulesForSpaceMutex.Unlock() + fake.GetAccessRulesForSpaceStub = nil + if fake.getAccessRulesForSpaceReturnsOnCall == nil { + fake.getAccessRulesForSpaceReturnsOnCall = make(map[int]struct { + result1 []v7action.AccessRuleWithRoute + result2 v7action.Warnings + result3 error + }) + } + fake.getAccessRulesForSpaceReturnsOnCall[i] = struct { + result1 []v7action.AccessRuleWithRoute + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeActor) GetAppFeature(arg1 string, arg2 string) (resources.ApplicationFeature, v7action.Warnings, error) { fake.getAppFeatureMutex.Lock() ret, specificReturn := fake.getAppFeatureReturnsOnCall[len(fake.getAppFeatureArgsForCall)] diff --git a/resources/access_rule_resource.go b/resources/access_rule_resource.go new file mode 100644 index 00000000000..6ccf5b34ad9 --- /dev/null +++ b/resources/access_rule_resource.go @@ -0,0 +1,84 @@ +package resources + +import ( + "encoding/json" + "time" + + "code.cloudfoundry.org/cli/v9/api/cloudcontroller" +) + +type AccessRule struct { + GUID string `json:"guid,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Name string `json:"name"` + Selector string `json:"selector"` + RouteGUID string `json:"-"` + + // Metadata is used for custom tagging of API resources + Metadata *Metadata `json:"metadata,omitempty"` +} + +func (a AccessRule) MarshalJSON() ([]byte, error) { + type alias struct { + GUID string `json:"guid,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Name string `json:"name"` + Selector string `json:"selector"` + Metadata *Metadata `json:"metadata,omitempty"` + + Relationships struct { + Route struct { + Data struct { + GUID string `json:"guid"` + } `json:"data"` + } `json:"route"` + } `json:"relationships"` + } + + var aliasData alias + aliasData.GUID = a.GUID + aliasData.CreatedAt = a.CreatedAt + aliasData.UpdatedAt = a.UpdatedAt + aliasData.Name = a.Name + aliasData.Selector = a.Selector + aliasData.Metadata = a.Metadata + aliasData.Relationships.Route.Data.GUID = a.RouteGUID + + return json.Marshal(aliasData) +} + +func (a *AccessRule) UnmarshalJSON(data []byte) error { + var alias struct { + GUID string `json:"guid,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Name string `json:"name"` + Selector string `json:"selector"` + Metadata *Metadata `json:"metadata,omitempty"` + + Relationships struct { + Route struct { + Data struct { + GUID string `json:"guid,omitempty"` + } `json:"data,omitempty"` + } `json:"route,omitempty"` + } `json:"relationships,omitempty"` + } + + err := cloudcontroller.DecodeJSON(data, &alias) + if err != nil { + return err + } + + a.GUID = alias.GUID + a.CreatedAt = alias.CreatedAt + a.UpdatedAt = alias.UpdatedAt + a.Name = alias.Name + a.Selector = alias.Selector + a.RouteGUID = alias.Relationships.Route.Data.GUID + a.Metadata = alias.Metadata + + return nil +} diff --git a/resources/access_rule_resource_test.go b/resources/access_rule_resource_test.go new file mode 100644 index 00000000000..52e3a9b5110 --- /dev/null +++ b/resources/access_rule_resource_test.go @@ -0,0 +1,69 @@ +package resources_test + +import ( + "encoding/json" + "testing" + + "code.cloudfoundry.org/cli/v9/resources" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestAccessRuleResource(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "AccessRule Resource Suite") +} + +var _ = Describe("AccessRule", func() { + Describe("MarshalJSON", func() { + It("marshals the access rule with relationships", func() { + rule := resources.AccessRule{ + Name: "allow-backend", + Selector: "cf:app:some-app-guid", + RouteGUID: "some-route-guid", + } + + data, err := json.Marshal(rule) + Expect(err).NotTo(HaveOccurred()) + + var result map[string]interface{} + err = json.Unmarshal(data, &result) + Expect(err).NotTo(HaveOccurred()) + + Expect(result["name"]).To(Equal("allow-backend")) + Expect(result["selector"]).To(Equal("cf:app:some-app-guid")) + Expect(result["relationships"]).NotTo(BeNil()) + + relationships := result["relationships"].(map[string]interface{}) + route := relationships["route"].(map[string]interface{}) + routeData := route["data"].(map[string]interface{}) + Expect(routeData["guid"]).To(Equal("some-route-guid")) + }) + }) + + Describe("UnmarshalJSON", func() { + It("unmarshals the access rule from relationships", func() { + jsonData := `{ + "guid": "some-guid", + "name": "test-rule", + "selector": "cf:app:app-guid", + "relationships": { + "route": { + "data": { + "guid": "route-guid-123" + } + } + } + }` + + var rule resources.AccessRule + err := json.Unmarshal([]byte(jsonData), &rule) + Expect(err).NotTo(HaveOccurred()) + + Expect(rule.GUID).To(Equal("some-guid")) + Expect(rule.Name).To(Equal("test-rule")) + Expect(rule.Selector).To(Equal("cf:app:app-guid")) + Expect(rule.RouteGUID).To(Equal("route-guid-123")) + }) + }) +}) diff --git a/resources/domain_resource.go b/resources/domain_resource.go index 1a032a78b01..44841048165 100644 --- a/resources/domain_resource.go +++ b/resources/domain_resource.go @@ -6,12 +6,14 @@ import ( ) type Domain struct { - GUID string `json:"guid,omitempty"` - Name string `json:"name"` - Internal types.NullBool `json:"internal,omitempty"` - OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` - RouterGroup string `jsonry:"router_group.guid,omitempty"` - Protocols []string `jsonry:"supported_protocols,omitempty"` + GUID string `json:"guid,omitempty"` + Name string `json:"name"` + Internal types.NullBool `json:"internal,omitempty"` + OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` + RouterGroup string `jsonry:"router_group.guid,omitempty"` + Protocols []string `jsonry:"supported_protocols,omitempty"` + EnforceAccessRules types.NullBool `json:"enforce_access_rules,omitempty"` + AccessRulesScope string `json:"access_rules_scope,omitempty"` // Metadata is used for custom tagging of API resources Metadata *Metadata `json:"metadata,omitempty"` @@ -19,12 +21,14 @@ type Domain struct { func (d Domain) MarshalJSON() ([]byte, error) { type domainWithBoolPointer struct { - GUID string `jsonry:"guid,omitempty"` - Name string `jsonry:"name"` - Internal *bool `jsonry:"internal,omitempty"` - OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` - RouterGroup string `jsonry:"router_group.guid,omitempty"` - Protocols []string `jsonry:"supported_protocols,omitempty"` + GUID string `jsonry:"guid,omitempty"` + Name string `jsonry:"name"` + Internal *bool `jsonry:"internal,omitempty"` + OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` + RouterGroup string `jsonry:"router_group.guid,omitempty"` + Protocols []string `jsonry:"supported_protocols,omitempty"` + EnforceAccessRules *bool `jsonry:"enforce_access_rules,omitempty"` + AccessRulesScope string `jsonry:"access_rules_scope,omitempty"` } clone := domainWithBoolPointer{ @@ -33,11 +37,15 @@ func (d Domain) MarshalJSON() ([]byte, error) { OrganizationGUID: d.OrganizationGUID, RouterGroup: d.RouterGroup, Protocols: d.Protocols, + AccessRulesScope: d.AccessRulesScope, } if d.Internal.IsSet { clone.Internal = &d.Internal.Value } + if d.EnforceAccessRules.IsSet { + clone.EnforceAccessRules = &d.EnforceAccessRules.Value + } return jsonry.Marshal(clone) } From f14301ac96f578ffe8209ec915daafeb431a32f4 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 15 Apr 2026 08:20:07 +0000 Subject: [PATCH 02/17] Remove access rule names per RFC updates Per RFC commits 882b69a and 11752f2, access rules no longer have user-provided names. They are identified by their selector only, with labels/annotations used for metadata instead. Changes: - Removed RULE_NAME argument from add-access-rule command - Removed Name field from AccessRule API resource - Updated access-rules list to show 4 columns (route, selector, scope, source) - SourceName now represents resolved app/space/org name from selector - Updated remove-access-rule to use --selector flag instead of rule name - Renamed DeleteAccessRule() to DeleteAccessRuleBySelector() - Updated all tests to remove Name field references All tests passing. --- .../access_rule_not_found_error.go | 4 +- actor/v7action/access_rule.go | 14 +-- actor/v7action/access_rule_test.go | 79 ++++++++-------- command/flag/arguments.go | 6 +- command/v7/access_rules_command.go | 2 - command/v7/access_rules_command_test.go | 28 +++--- command/v7/actor.go | 4 +- command/v7/add_access_rule_command.go | 12 +-- command/v7/add_access_rule_command_test.go | 31 +++---- command/v7/remove_access_rule_command.go | 22 +++-- command/v7/v7fakes/fake_actor.go | 92 +++++++++---------- resources/access_rule_resource.go | 5 - 12 files changed, 134 insertions(+), 165 deletions(-) diff --git a/actor/actionerror/access_rule_not_found_error.go b/actor/actionerror/access_rule_not_found_error.go index bf5710421e5..02fc1d6c57b 100644 --- a/actor/actionerror/access_rule_not_found_error.go +++ b/actor/actionerror/access_rule_not_found_error.go @@ -3,9 +3,9 @@ package actionerror import "fmt" type AccessRuleNotFoundError struct { - Name string + Selector string } func (e AccessRuleNotFoundError) Error() string { - return fmt.Sprintf("Access rule '%s' not found.", e.Name) + return fmt.Sprintf("Access rule with selector '%s' not found.", e.Selector) } diff --git a/actor/v7action/access_rule.go b/actor/v7action/access_rule.go index e74bd789497..909fc8dffdc 100644 --- a/actor/v7action/access_rule.go +++ b/actor/v7action/access_rule.go @@ -6,7 +6,7 @@ import ( "code.cloudfoundry.org/cli/v9/resources" ) -func (actor Actor) AddAccessRule(ruleName, domainName, selector, hostname, path string) (Warnings, error) { +func (actor Actor) AddAccessRule(domainName, selector, hostname, path string) (Warnings, error) { allWarnings := Warnings{} // Get the domain to ensure it exists and supports access rules @@ -35,7 +35,6 @@ func (actor Actor) AddAccessRule(ruleName, domainName, selector, hostname, path // Create the access rule accessRule := resources.AccessRule{ - Name: ruleName, Selector: selector, RouteGUID: route.GUID, } @@ -87,7 +86,7 @@ func (actor Actor) GetAccessRulesByRoute(domainName, hostname, path string) ([]r return rules, allWarnings, err } -func (actor Actor) DeleteAccessRule(ruleName, domainName, hostname, path string) (Warnings, error) { +func (actor Actor) DeleteAccessRuleBySelector(domainName, selector, hostname, path string) (Warnings, error) { allWarnings := Warnings{} // Get the domain @@ -114,7 +113,7 @@ func (actor Actor) DeleteAccessRule(ruleName, domainName, hostname, path string) route := routes[0] - // Get access rules for this route to find the one with matching name + // Get access rules for this route to find the one with matching selector accessRules, _, apiWarnings, err := actor.CloudControllerClient.GetAccessRules( ccv3.Query{Key: ccv3.RouteGUIDFilter, Values: []string{route.GUID}}, ) @@ -123,17 +122,17 @@ func (actor Actor) DeleteAccessRule(ruleName, domainName, hostname, path string) return allWarnings, err } - // Find the rule with matching name + // Find the rule with matching selector var ruleGUID string for _, rule := range accessRules { - if rule.Name == ruleName { + if rule.Selector == selector { ruleGUID = rule.GUID break } } if ruleGUID == "" { - return allWarnings, actionerror.AccessRuleNotFoundError{Name: ruleName} + return allWarnings, actionerror.AccessRuleNotFoundError{Selector: selector} } // Delete the access rule @@ -143,6 +142,7 @@ func (actor Actor) DeleteAccessRule(ruleName, domainName, hostname, path string) return allWarnings, err } + // GetRoutesByDomain gets routes for a domain with optional hostname and path filters func (actor Actor) GetRoutesByDomain(domainGUID, hostname, path string) ([]resources.Route, Warnings, error) { queries := []ccv3.Query{ diff --git a/actor/v7action/access_rule_test.go b/actor/v7action/access_rule_test.go index 64c4fc52343..38b81bb67b4 100644 --- a/actor/v7action/access_rule_test.go +++ b/actor/v7action/access_rule_test.go @@ -122,13 +122,12 @@ var _ = Describe("Access Rule Actions", func() { "get-domain-warning-1", "get-domain-warning-2", "get-app-warning", - )) + )) - Expect(results).To(HaveLen(2)) + Expect(results).To(HaveLen(2)) - // First rule + // First rule Expect(results[0].GUID).To(Equal("rule-guid-1")) - Expect(results[0].Name).To(Equal("rule-1")) Expect(results[0].Selector).To(Equal("cf:app:app-guid-1")) Expect(results[0].Route.GUID).To(Equal("route-guid-1")) Expect(results[0].Route.Host).To(Equal("app1")) @@ -139,7 +138,6 @@ var _ = Describe("Access Rule Actions", func() { // Second rule Expect(results[1].GUID).To(Equal("rule-guid-2")) - Expect(results[1].Name).To(Equal("rule-2")) Expect(results[1].Selector).To(Equal("cf:any")) Expect(results[1].Route.GUID).To(Equal("route-guid-2")) Expect(results[1].Route.Host).To(Equal("app2")) @@ -498,8 +496,8 @@ var _ = Describe("Access Rule Actions", func() { It("returns the access rules", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(rules).To(HaveLen(2)) - Expect(rules[0].Name).To(Equal("rule-1")) - Expect(rules[1].Name).To(Equal("rule-2")) + Expect(rules[0].Selector).To(Equal("cf:app:app-guid-1")) + Expect(rules[1].Selector).To(Equal("cf:app:app-guid-2")) Expect(warnings).To(ConsistOf( "get-domains-warning", "get-routes-warning", @@ -538,7 +536,6 @@ var _ = Describe("Access Rule Actions", func() { Describe("AddAccessRule", func() { var ( - ruleName string domainName string selector string hostname string @@ -549,7 +546,6 @@ var _ = Describe("Access Rule Actions", func() { ) BeforeEach(func() { - ruleName = "my-rule" domainName = "example.com" selector = "cf:app:app-guid-1" hostname = "myapp" @@ -557,7 +553,7 @@ var _ = Describe("Access Rule Actions", func() { }) JustBeforeEach(func() { - warnings, executeErr = actor.AddAccessRule(ruleName, domainName, selector, hostname, path) + warnings, executeErr = actor.AddAccessRule(domainName, selector, hostname, path) }) When("creating the access rule succeeds", func() { @@ -594,16 +590,15 @@ var _ = Describe("Access Rule Actions", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(warnings).To(ConsistOf( "get-domains-warning", - "get-routes-warning", - "create-rule-warning", - )) - - Expect(fakeCloudControllerClient.CreateAccessRuleCallCount()).To(Equal(1)) - rule := fakeCloudControllerClient.CreateAccessRuleArgsForCall(0) - Expect(rule.Name).To(Equal("my-rule")) - Expect(rule.Selector).To(Equal("cf:app:app-guid-1")) - Expect(rule.RouteGUID).To(Equal("route-guid-1")) - }) + "get-routes-warning", + "create-rule-warning", + )) + + Expect(fakeCloudControllerClient.CreateAccessRuleCallCount()).To(Equal(1)) + rule := fakeCloudControllerClient.CreateAccessRuleArgsForCall(0) + Expect(rule.Selector).To(Equal("cf:app:app-guid-1")) + Expect(rule.RouteGUID).To(Equal("route-guid-1")) + }) }) When("the route does not exist", func() { @@ -633,9 +628,9 @@ var _ = Describe("Access Rule Actions", func() { }) }) - Describe("DeleteAccessRule", func() { + Describe("DeleteAccessRuleBySelector", func() { var ( - ruleName string + selector string domainName string hostname string path string @@ -645,14 +640,14 @@ var _ = Describe("Access Rule Actions", func() { ) BeforeEach(func() { - ruleName = "my-rule" + selector = "cf:any" domainName = "example.com" hostname = "myapp" path = "" }) JustBeforeEach(func() { - warnings, executeErr = actor.DeleteAccessRule(ruleName, domainName, hostname, path) + warnings, executeErr = actor.DeleteAccessRuleBySelector(domainName, selector, hostname, path) }) When("the access rule exists", func() { @@ -680,7 +675,7 @@ var _ = Describe("Access Rule Actions", func() { fakeCloudControllerClient.GetAccessRulesReturns( []resources.AccessRule{ - {GUID: "rule-guid-1", Name: "my-rule", Selector: "cf:any"}, + {GUID: "rule-guid-1", Selector: "cf:any"}, }, ccv3.IncludedResources{}, ccv3.Warnings{"get-access-rules-warning"}, @@ -732,24 +727,24 @@ var _ = Describe("Access Rule Actions", func() { nil, ) - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - {GUID: "rule-guid-other", Name: "other-rule", Selector: "cf:any"}, - }, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - }) + fakeCloudControllerClient.GetAccessRulesReturns( + []resources.AccessRule{ + {GUID: "rule-guid-other", Selector: "cf:app:other-guid"}, + }, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-access-rules-warning"}, + nil, + ) + }) - It("returns an AccessRuleNotFoundError", func() { - Expect(executeErr).To(MatchError(actionerror.AccessRuleNotFoundError{Name: "my-rule"})) - Expect(warnings).To(ConsistOf( - "get-domains-warning", - "get-routes-warning", - "get-access-rules-warning", - )) - }) + It("returns an AccessRuleNotFoundError", func() { + Expect(executeErr).To(MatchError(actionerror.AccessRuleNotFoundError{Selector: "cf:any"})) + Expect(warnings).To(ConsistOf( + "get-domains-warning", + "get-routes-warning", + "get-access-rules-warning", + )) }) }) }) +}) diff --git a/command/flag/arguments.go b/command/flag/arguments.go index 84195b1d4e6..0afd5568955 100644 --- a/command/flag/arguments.go +++ b/command/flag/arguments.go @@ -415,11 +415,9 @@ type TaskArgs struct { } type AddAccessRuleArgs struct { - RuleName string `positional-arg-name:"RULE_NAME" required:"true" description:"The access rule name"` - Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` + Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` } type RemoveAccessRuleArgs struct { - RuleName string `positional-arg-name:"RULE_NAME" required:"true" description:"The access rule name"` - Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` + Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` } diff --git a/command/v7/access_rules_command.go b/command/v7/access_rules_command.go index 0b29c17e4d9..5148c801911 100644 --- a/command/v7/access_rules_command.go +++ b/command/v7/access_rules_command.go @@ -64,7 +64,6 @@ func (cmd AccessRulesCommand) Execute(args []string) error { // Build table data table := [][]string{ { - cmd.UI.TranslateText("name"), cmd.UI.TranslateText("route"), cmd.UI.TranslateText("selector"), cmd.UI.TranslateText("scope"), @@ -74,7 +73,6 @@ func (cmd AccessRulesCommand) Execute(args []string) error { for _, ruleWithRoute := range rulesWithRoutes { table = append(table, []string{ - ruleWithRoute.Name, formatRoute(ruleWithRoute.Route, ruleWithRoute.DomainName), ruleWithRoute.Selector, ruleWithRoute.ScopeType, diff --git a/command/v7/access_rules_command_test.go b/command/v7/access_rules_command_test.go index bf34d3ea2b9..6629be6c026 100644 --- a/command/v7/access_rules_command_test.go +++ b/command/v7/access_rules_command_test.go @@ -114,7 +114,6 @@ var _ = Describe("access-rules Command", func() { { AccessRule: resources.AccessRule{ GUID: "rule-guid-1", - Name: "rule-1", Selector: "cf:app:app-guid-1", }, Route: resources.Route{ @@ -129,7 +128,6 @@ var _ = Describe("access-rules Command", func() { { AccessRule: resources.AccessRule{ GUID: "rule-guid-2", - Name: "rule-2", Selector: "cf:any", }, Route: resources.Route{ @@ -139,7 +137,7 @@ var _ = Describe("access-rules Command", func() { }, DomainName: "test.com", ScopeType: "any", - SourceName: "(any app)", + SourceName: "", }, }, v7action.Warnings{"warning-1"}, nil) }) @@ -148,9 +146,9 @@ var _ = Describe("access-rules Command", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - Expect(testUI.Out).To(Say(`name\s+route\s+selector\s+scope\s+source`)) - Expect(testUI.Out).To(Say(`rule-1\s+myapp\.example\.com/api\s+cf:app:app-guid-1\s+my-app`)) - Expect(testUI.Out).To(Say(`rule-2\s+webapp\.test\.com\s+cf:any\s+\(any app\)`)) + Expect(testUI.Out).To(Say(`route\s+selector\s+scope\s+source`)) + Expect(testUI.Out).To(Say(`myapp\.example\.com/api\s+cf:app:app-guid-1\s+app\s+my-app`)) + Expect(testUI.Out).To(Say(`webapp\.test\.com\s+cf:any\s+any`)) Expect(testUI.Err).To(Say("warning-1")) @@ -185,7 +183,6 @@ var _ = Describe("access-rules Command", func() { { AccessRule: resources.AccessRule{ GUID: "rule-guid-1", - Name: "rule-1", Selector: "cf:any", }, Route: resources.Route{ @@ -194,7 +191,7 @@ var _ = Describe("access-rules Command", func() { }, DomainName: "example.com", ScopeType: "any", - SourceName: "(any app)", + SourceName: "", }, }, v7action.Warnings{}, nil) }) @@ -271,7 +268,6 @@ var _ = Describe("access-rules Command", func() { { AccessRule: resources.AccessRule{ GUID: "rule-guid-1", - Name: "filtered-rule", Selector: "cf:app:app-guid-1", }, Route: resources.Route{ @@ -302,8 +298,8 @@ var _ = Describe("access-rules Command", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - Expect(testUI.Out).To(Say(`name\s+route\s+selector\s+scope\s+source`)) - Expect(testUI.Out).To(Say(`filtered-rule\s+myapp\.example\.com/api\s+cf:app:app-guid-1\s+my-app`)) + Expect(testUI.Out).To(Say(`route\s+selector\s+scope\s+source`)) + Expect(testUI.Out).To(Say(`myapp\.example\.com/api\s+cf:app:app-guid-1\s+app\s+my-app`)) }) }) @@ -313,7 +309,6 @@ var _ = Describe("access-rules Command", func() { { AccessRule: resources.AccessRule{ GUID: "rule-guid-1", - Name: "no-host-rule", Selector: "cf:any", }, Route: resources.Route{ @@ -323,12 +318,11 @@ var _ = Describe("access-rules Command", func() { }, DomainName: "example.com", ScopeType: "any", - SourceName: "(any app)", + SourceName: "", }, { AccessRule: resources.AccessRule{ GUID: "rule-guid-2", - Name: "no-path-rule", Selector: "cf:any", }, Route: resources.Route{ @@ -338,7 +332,7 @@ var _ = Describe("access-rules Command", func() { }, DomainName: "test.com", ScopeType: "any", - SourceName: "(any app)", + SourceName: "", }, }, v7action.Warnings{}, nil) }) @@ -347,10 +341,10 @@ var _ = Describe("access-rules Command", func() { Expect(executeErr).ToNot(HaveOccurred()) // No host, with path: "example.com/api" - Expect(testUI.Out).To(Say(`no-host-rule\s+example\.com/api`)) + Expect(testUI.Out).To(Say(`example\.com/api`)) // With host, no path: "myapp.test.com" - Expect(testUI.Out).To(Say(`no-path-rule\s+myapp\.test\.com`)) + Expect(testUI.Out).To(Say(`myapp\.test\.com`)) }) }) }) diff --git a/command/v7/actor.go b/command/v7/actor.go index a5fecefc786..9c04eba1d7e 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -23,7 +23,7 @@ import ( type Actor interface { ApplyOrganizationQuotaByName(quotaName string, orgGUID string) (v7action.Warnings, error) ApplySpaceQuotaByName(quotaName string, spaceGUID string, orgGUID string) (v7action.Warnings, error) - AddAccessRule(ruleName, domainName, selector, hostname, path string) (v7action.Warnings, error) + AddAccessRule(domainName, selector, hostname, path string) (v7action.Warnings, error) AssignIsolationSegmentToSpaceByNameAndSpace(isolationSegmentName string, spaceGUID string) (v7action.Warnings, error) Authenticate(credentials map[string]string, origin string, grantType uaa.GrantType) error BindSecurityGroupToSpaces(securityGroupGUID string, spaces []resources.Space, lifecycle constant.SecurityGroupLifecycle) (v7action.Warnings, error) @@ -59,7 +59,7 @@ type Actor interface { CreateUser(username string, password string, origin string) (resources.User, v7action.Warnings, error) CreateUserProvidedServiceInstance(instance resources.ServiceInstance) (v7action.Warnings, error) DeleteApplicationByNameAndSpace(name, spaceGUID string, deleteRoutes bool) (v7action.Warnings, error) - DeleteAccessRule(ruleName, domainName, hostname, path string) (v7action.Warnings, error) + DeleteAccessRuleBySelector(domainName, selector, hostname, path string) (v7action.Warnings, error) DeleteBuildpackByNameAndStackAndLifecycle(buildpackName string, buildpackStack string, buildpackLifecycle string) (v7action.Warnings, error) DeleteDomain(domain resources.Domain) (v7action.Warnings, error) DeleteInstanceByApplicationNameSpaceProcessTypeAndIndex(appName string, spaceGUID string, processType string, instanceIndex int) (v7action.Warnings, error) diff --git a/command/v7/add_access_rule_command.go b/command/v7/add_access_rule_command.go index b3db3914811..ac386efd408 100644 --- a/command/v7/add_access_rule_command.go +++ b/command/v7/add_access_rule_command.go @@ -25,7 +25,7 @@ type AddAccessRuleCommand struct { // Advanced: raw selector flag Selector string `long:"selector" description:"Raw selector (cf:app:, cf:space:, cf:org:, or cf:any)"` - usage interface{} `usage:"CF_NAME add-access-rule RULE_NAME 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 | --selector SELECTOR] [--path PATH]\n\nALLOW ACCESS TO A ROUTE:\n Create an access rule 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-access-rule allow-frontend apps.identity --source-app frontend-app --hostname backend\n\n # Allow an app in a different space to access the route\n cf add-access-rule allow-other-space 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-access-rule allow-other-org 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-access-rule allow-monitoring apps.identity --source-space monitoring --hostname api --path /metrics\n\n # Allow all apps in a space in a different org\n cf add-access-rule allow-prod-space 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-access-rule allow-platform-org apps.identity --source-org platform --hostname shared-api\n\n # Allow any authenticated app to access the public API\n cf add-access-rule allow-all apps.identity --source-any --hostname public-api\n\n # Use raw selector (advanced)\n cf add-access-rule allow-raw apps.identity --selector cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend"` + usage interface{} `usage:"CF_NAME add-access-rule 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 | --selector SELECTOR] [--path PATH]\n\nALLOW ACCESS TO A ROUTE:\n Create an access rule 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-access-rule apps.identity --source-app frontend-app --hostname backend\n\n # Allow an app in a different space to access the route\n cf add-access-rule 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-access-rule 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-access-rule apps.identity --source-space monitoring --hostname api --path /metrics\n\n # Allow all apps in a space in a different org\n cf add-access-rule 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-access-rule apps.identity --source-org platform --hostname shared-api\n\n # Allow any authenticated app to access the public API\n cf add-access-rule apps.identity --source-any --hostname public-api\n\n # Use raw selector (advanced)\n cf add-access-rule apps.identity --selector cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend"` relatedCommands interface{} `related_commands:"access-rules, remove-access-rule, create-shared-domain"` } @@ -57,12 +57,10 @@ func (cmd AddAccessRuleCommand) Execute(args []string) error { return err } - ruleName := cmd.RequiredArgs.RuleName domainName := cmd.RequiredArgs.Domain - cmd.UI.DisplayTextWithFlavor("Adding access rule {{.RuleName}} for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", + cmd.UI.DisplayTextWithFlavor("Adding access rule for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", map[string]interface{}{ - "RuleName": ruleName, "Hostname": cmd.Hostname, "Domain": domainName, "Path": formatPath(cmd.Path), @@ -79,17 +77,13 @@ func (cmd AddAccessRuleCommand) Execute(args []string) error { "Selector": selector, }) - warnings, err = cmd.Actor.AddAccessRule(ruleName, domainName, selector, cmd.Hostname, cmd.Path) + warnings, err = cmd.Actor.AddAccessRule(domainName, selector, cmd.Hostname, cmd.Path) cmd.UI.DisplayWarnings(warnings) if err != nil { return err } cmd.UI.DisplayOK() - cmd.UI.DisplayText("TIP: Access rule '{{.RuleName}}' has been created. It may take a few seconds for the rule to propagate to GoRouter.", - map[string]interface{}{ - "RuleName": ruleName, - }) return nil } diff --git a/command/v7/add_access_rule_command_test.go b/command/v7/add_access_rule_command_test.go index f6b73907d28..1a81d016b33 100644 --- a/command/v7/add_access_rule_command_test.go +++ b/command/v7/add_access_rule_command_test.go @@ -65,8 +65,7 @@ var _ = Describe("add-access-rule Command", func() { Context("when no source flags are provided", func() { BeforeEach(func() { cmd.RequiredArgs = flag.AddAccessRuleArgs{ - RuleName: "test-rule", - Domain: "apps.internal", + Domain: "apps.internal", } cmd.Hostname = "backend" }) @@ -81,8 +80,7 @@ var _ = Describe("add-access-rule Command", func() { Context("when multiple mutually exclusive source flags are provided", func() { BeforeEach(func() { cmd.RequiredArgs = flag.AddAccessRuleArgs{ - RuleName: "test-rule", - Domain: "apps.internal", + Domain: "apps.internal", } cmd.Hostname = "backend" cmd.SourceApp = "app-name" @@ -99,8 +97,7 @@ var _ = Describe("add-access-rule Command", func() { Context("when --source-space and --source-any are both provided", func() { BeforeEach(func() { cmd.RequiredArgs = flag.AddAccessRuleArgs{ - RuleName: "test-rule", - Domain: "apps.internal", + Domain: "apps.internal", } cmd.Hostname = "backend" cmd.SourceSpace = "some-space" @@ -118,8 +115,7 @@ var _ = Describe("add-access-rule Command", func() { When("the user is logged in, an org is targeted, and a space is targeted", func() { BeforeEach(func() { cmd.RequiredArgs = flag.AddAccessRuleArgs{ - RuleName: "test-rule", - Domain: "apps.internal", + Domain: "apps.internal", } cmd.Hostname = "backend" }) @@ -147,15 +143,14 @@ var _ = Describe("add-access-rule Command", func() { // Verify access rule creation with resolved selector Expect(fakeActor.AddAccessRuleCallCount()).To(Equal(1)) - ruleName, domain, selector, hostname, path := fakeActor.AddAccessRuleArgsForCall(0) - Expect(ruleName).To(Equal("test-rule")) + domain, selector, hostname, path := fakeActor.AddAccessRuleArgsForCall(0) Expect(domain).To(Equal("apps.internal")) Expect(selector).To(Equal("cf:app:app-guid")) Expect(hostname).To(Equal("backend")) Expect(path).To(BeEmpty()) // Verify output - Expect(testUI.Out).To(Say("Adding access rule test-rule")) + Expect(testUI.Out).To(Say("Adding access rule for route")) Expect(testUI.Out).To(Say("scope: app, source: frontend-app")) Expect(testUI.Out).To(Say("selector: cf:app:app-guid")) Expect(testUI.Out).To(Say("OK")) @@ -201,7 +196,7 @@ var _ = Describe("add-access-rule Command", func() { Expect(spaceGUID).To(Equal("other-space-guid")) // Verify selector - _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) Expect(selector).To(Equal("cf:app:app-guid")) // Verify output shows cross-space info @@ -283,7 +278,7 @@ var _ = Describe("add-access-rule Command", func() { Expect(executeErr).NotTo(HaveOccurred()) // Verify selector is space-level - _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) Expect(selector).To(Equal("cf:space:space-guid-123")) Expect(testUI.Out).To(Say("scope: space, source: monitoring-space")) @@ -322,7 +317,7 @@ var _ = Describe("add-access-rule Command", func() { Expect(orgGUID).To(Equal("prod-org-guid")) // Verify selector is space-level - _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) Expect(selector).To(Equal("cf:space:prod-space-guid")) Expect(testUI.Out).To(Say("scope: space, source: prod-space \\(org: prod-org\\)")) @@ -344,7 +339,7 @@ var _ = Describe("add-access-rule Command", func() { Expect(executeErr).NotTo(HaveOccurred()) // Verify selector is org-level - _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) Expect(selector).To(Equal("cf:org:org-guid-456")) Expect(testUI.Out).To(Say("scope: org, source: platform-org")) @@ -360,7 +355,7 @@ var _ = Describe("add-access-rule Command", func() { It("creates an 'any' access rule", func() { Expect(executeErr).NotTo(HaveOccurred()) - _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) Expect(selector).To(Equal("cf:any")) Expect(testUI.Out).To(Say("scope: any, source: any authenticated app")) @@ -376,7 +371,7 @@ var _ = Describe("add-access-rule Command", func() { It("uses the raw selector without resolution", func() { Expect(executeErr).NotTo(HaveOccurred()) - _, _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) + _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) Expect(selector).To(Equal("cf:app:raw-guid-123")) Expect(testUI.Out).To(Say("selector: cf:app:raw-guid-123")) @@ -398,7 +393,7 @@ var _ = Describe("add-access-rule Command", func() { It("passes the path to the actor", func() { Expect(executeErr).NotTo(HaveOccurred()) - _, _, _, _, path := fakeActor.AddAccessRuleArgsForCall(0) + _, _, _, path := fakeActor.AddAccessRuleArgsForCall(0) Expect(path).To(Equal("/metrics")) }) }) diff --git a/command/v7/remove_access_rule_command.go b/command/v7/remove_access_rule_command.go index 12d0b08eb4b..4a2edb69892 100644 --- a/command/v7/remove_access_rule_command.go +++ b/command/v7/remove_access_rule_command.go @@ -8,10 +8,11 @@ type RemoveAccessRuleCommand struct { BaseCommand RequiredArgs flag.RemoveAccessRuleArgs `positional-args:"yes"` + Selector string `long:"selector" required:"true" description:"Selector to identify the access rule (cf:app:, cf:space:, cf:org:, or cf:any)"` Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` Path string `long:"path" description:"Path for the route"` Force bool `short:"f" description:"Force deletion without confirmation"` - usage interface{} `usage:"CF_NAME remove-access-rule RULE_NAME DOMAIN --hostname HOSTNAME [--path PATH] [-f]\n\nEXAMPLES:\n cf remove-access-rule allow-frontend apps.identity --hostname backend\n cf remove-access-rule allow-monitoring apps.identity --hostname api --path /metrics -f"` + usage interface{} `usage:"CF_NAME remove-access-rule DOMAIN --selector SELECTOR --hostname HOSTNAME [--path PATH] [-f]\n\nEXAMPLES:\n cf remove-access-rule apps.identity --selector cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend\n cf remove-access-rule apps.identity --selector cf:space:2b26e210-1b48-4e60-8432-f24bc5927789 --hostname api --path /metrics -f\n cf remove-access-rule apps.identity --selector cf:any --hostname public-api -f"` relatedCommands interface{} `related_commands:"access-rules, add-access-rule"` } @@ -26,13 +27,17 @@ func (cmd RemoveAccessRuleCommand) Execute(args []string) error { return err } - ruleName := cmd.RequiredArgs.RuleName + // Validate selector format + if err := validateSelector(cmd.Selector); err != nil { + return err + } + domainName := cmd.RequiredArgs.Domain if !cmd.Force { - prompt := "Really remove access rule {{.RuleName}} for route {{.Hostname}}.{{.Domain}}{{.Path}}?" + prompt := "Really remove access rule with selector {{.Selector}} for route {{.Hostname}}.{{.Domain}}{{.Path}}?" response, promptErr := cmd.UI.DisplayBoolPrompt(false, prompt, map[string]interface{}{ - "RuleName": ruleName, + "Selector": cmd.Selector, "Hostname": cmd.Hostname, "Domain": domainName, "Path": formatPath(cmd.Path), @@ -43,23 +48,20 @@ func (cmd RemoveAccessRuleCommand) Execute(args []string) error { } if !response { - cmd.UI.DisplayText("Access rule '{{.RuleName}}' has not been removed.", map[string]interface{}{ - "RuleName": ruleName, - }) + cmd.UI.DisplayText("Access rule has not been removed.") return nil } } - cmd.UI.DisplayTextWithFlavor("Removing access rule {{.RuleName}} for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", + cmd.UI.DisplayTextWithFlavor("Removing access rule for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", map[string]interface{}{ - "RuleName": ruleName, "Hostname": cmd.Hostname, "Domain": domainName, "Path": formatPath(cmd.Path), "User": user.Name, }) - warnings, err := cmd.Actor.DeleteAccessRule(ruleName, domainName, cmd.Hostname, cmd.Path) + warnings, err := cmd.Actor.DeleteAccessRuleBySelector(domainName, cmd.Selector, cmd.Hostname, cmd.Path) cmd.UI.DisplayWarnings(warnings) if err != nil { return err diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index b3093ad655e..bfdbfe9b10d 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -22,14 +22,13 @@ import ( ) type FakeActor struct { - AddAccessRuleStub func(string, string, string, string, string) (v7action.Warnings, error) + AddAccessRuleStub func(string, string, string, string) (v7action.Warnings, error) addAccessRuleMutex sync.RWMutex addAccessRuleArgsForCall []struct { arg1 string arg2 string arg3 string arg4 string - arg5 string } addAccessRuleReturns struct { result1 v7action.Warnings @@ -578,19 +577,19 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - DeleteAccessRuleStub func(string, string, string, string) (v7action.Warnings, error) - deleteAccessRuleMutex sync.RWMutex - deleteAccessRuleArgsForCall []struct { + DeleteAccessRuleBySelectorStub func(string, string, string, string) (v7action.Warnings, error) + deleteAccessRuleBySelectorMutex sync.RWMutex + deleteAccessRuleBySelectorArgsForCall []struct { arg1 string arg2 string arg3 string arg4 string } - deleteAccessRuleReturns struct { + deleteAccessRuleBySelectorReturns struct { result1 v7action.Warnings result2 error } - deleteAccessRuleReturnsOnCall map[int]struct { + deleteAccessRuleBySelectorReturnsOnCall map[int]struct { result1 v7action.Warnings result2 error } @@ -3853,7 +3852,7 @@ type FakeActor struct { invocationsMutex sync.RWMutex } -func (fake *FakeActor) AddAccessRule(arg1 string, arg2 string, arg3 string, arg4 string, arg5 string) (v7action.Warnings, error) { +func (fake *FakeActor) AddAccessRule(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { fake.addAccessRuleMutex.Lock() ret, specificReturn := fake.addAccessRuleReturnsOnCall[len(fake.addAccessRuleArgsForCall)] fake.addAccessRuleArgsForCall = append(fake.addAccessRuleArgsForCall, struct { @@ -3861,14 +3860,13 @@ func (fake *FakeActor) AddAccessRule(arg1 string, arg2 string, arg3 string, arg4 arg2 string arg3 string arg4 string - arg5 string - }{arg1, arg2, arg3, arg4, arg5}) + }{arg1, arg2, arg3, arg4}) stub := fake.AddAccessRuleStub fakeReturns := fake.addAccessRuleReturns - fake.recordInvocation("AddAccessRule", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.recordInvocation("AddAccessRule", []interface{}{arg1, arg2, arg3, arg4}) fake.addAccessRuleMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3, arg4, arg5) + return stub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1, ret.result2 @@ -3882,17 +3880,17 @@ func (fake *FakeActor) AddAccessRuleCallCount() int { return len(fake.addAccessRuleArgsForCall) } -func (fake *FakeActor) AddAccessRuleCalls(stub func(string, string, string, string, string) (v7action.Warnings, error)) { +func (fake *FakeActor) AddAccessRuleCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { fake.addAccessRuleMutex.Lock() defer fake.addAccessRuleMutex.Unlock() fake.AddAccessRuleStub = stub } -func (fake *FakeActor) AddAccessRuleArgsForCall(i int) (string, string, string, string, string) { +func (fake *FakeActor) AddAccessRuleArgsForCall(i int) (string, string, string, string) { fake.addAccessRuleMutex.RLock() defer fake.addAccessRuleMutex.RUnlock() argsForCall := fake.addAccessRuleArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } func (fake *FakeActor) AddAccessRuleReturns(result1 v7action.Warnings, result2 error) { @@ -6287,19 +6285,19 @@ func (fake *FakeActor) CreateUserProvidedServiceInstanceReturnsOnCall(i int, res }{result1, result2} } -func (fake *FakeActor) DeleteAccessRule(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { - fake.deleteAccessRuleMutex.Lock() - ret, specificReturn := fake.deleteAccessRuleReturnsOnCall[len(fake.deleteAccessRuleArgsForCall)] - fake.deleteAccessRuleArgsForCall = append(fake.deleteAccessRuleArgsForCall, struct { +func (fake *FakeActor) DeleteAccessRuleBySelector(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { + fake.deleteAccessRuleBySelectorMutex.Lock() + ret, specificReturn := fake.deleteAccessRuleBySelectorReturnsOnCall[len(fake.deleteAccessRuleBySelectorArgsForCall)] + fake.deleteAccessRuleBySelectorArgsForCall = append(fake.deleteAccessRuleBySelectorArgsForCall, struct { arg1 string arg2 string arg3 string arg4 string }{arg1, arg2, arg3, arg4}) - stub := fake.DeleteAccessRuleStub - fakeReturns := fake.deleteAccessRuleReturns - fake.recordInvocation("DeleteAccessRule", []interface{}{arg1, arg2, arg3, arg4}) - fake.deleteAccessRuleMutex.Unlock() + stub := fake.DeleteAccessRuleBySelectorStub + fakeReturns := fake.deleteAccessRuleBySelectorReturns + fake.recordInvocation("DeleteAccessRuleBySelector", []interface{}{arg1, arg2, arg3, arg4}) + fake.deleteAccessRuleBySelectorMutex.Unlock() if stub != nil { return stub(arg1, arg2, arg3, arg4) } @@ -6309,46 +6307,46 @@ func (fake *FakeActor) DeleteAccessRule(arg1 string, arg2 string, arg3 string, a return fakeReturns.result1, fakeReturns.result2 } -func (fake *FakeActor) DeleteAccessRuleCallCount() int { - fake.deleteAccessRuleMutex.RLock() - defer fake.deleteAccessRuleMutex.RUnlock() - return len(fake.deleteAccessRuleArgsForCall) +func (fake *FakeActor) DeleteAccessRuleBySelectorCallCount() int { + fake.deleteAccessRuleBySelectorMutex.RLock() + defer fake.deleteAccessRuleBySelectorMutex.RUnlock() + return len(fake.deleteAccessRuleBySelectorArgsForCall) } -func (fake *FakeActor) DeleteAccessRuleCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { - fake.deleteAccessRuleMutex.Lock() - defer fake.deleteAccessRuleMutex.Unlock() - fake.DeleteAccessRuleStub = stub +func (fake *FakeActor) DeleteAccessRuleBySelectorCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { + fake.deleteAccessRuleBySelectorMutex.Lock() + defer fake.deleteAccessRuleBySelectorMutex.Unlock() + fake.DeleteAccessRuleBySelectorStub = stub } -func (fake *FakeActor) DeleteAccessRuleArgsForCall(i int) (string, string, string, string) { - fake.deleteAccessRuleMutex.RLock() - defer fake.deleteAccessRuleMutex.RUnlock() - argsForCall := fake.deleteAccessRuleArgsForCall[i] +func (fake *FakeActor) DeleteAccessRuleBySelectorArgsForCall(i int) (string, string, string, string) { + fake.deleteAccessRuleBySelectorMutex.RLock() + defer fake.deleteAccessRuleBySelectorMutex.RUnlock() + argsForCall := fake.deleteAccessRuleBySelectorArgsForCall[i] return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } -func (fake *FakeActor) DeleteAccessRuleReturns(result1 v7action.Warnings, result2 error) { - fake.deleteAccessRuleMutex.Lock() - defer fake.deleteAccessRuleMutex.Unlock() - fake.DeleteAccessRuleStub = nil - fake.deleteAccessRuleReturns = struct { +func (fake *FakeActor) DeleteAccessRuleBySelectorReturns(result1 v7action.Warnings, result2 error) { + fake.deleteAccessRuleBySelectorMutex.Lock() + defer fake.deleteAccessRuleBySelectorMutex.Unlock() + fake.DeleteAccessRuleBySelectorStub = nil + fake.deleteAccessRuleBySelectorReturns = struct { result1 v7action.Warnings result2 error }{result1, result2} } -func (fake *FakeActor) DeleteAccessRuleReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { - fake.deleteAccessRuleMutex.Lock() - defer fake.deleteAccessRuleMutex.Unlock() - fake.DeleteAccessRuleStub = nil - if fake.deleteAccessRuleReturnsOnCall == nil { - fake.deleteAccessRuleReturnsOnCall = make(map[int]struct { +func (fake *FakeActor) DeleteAccessRuleBySelectorReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.deleteAccessRuleBySelectorMutex.Lock() + defer fake.deleteAccessRuleBySelectorMutex.Unlock() + fake.DeleteAccessRuleBySelectorStub = nil + if fake.deleteAccessRuleBySelectorReturnsOnCall == nil { + fake.deleteAccessRuleBySelectorReturnsOnCall = make(map[int]struct { result1 v7action.Warnings result2 error }) } - fake.deleteAccessRuleReturnsOnCall[i] = struct { + fake.deleteAccessRuleBySelectorReturnsOnCall[i] = struct { result1 v7action.Warnings result2 error }{result1, result2} diff --git a/resources/access_rule_resource.go b/resources/access_rule_resource.go index 6ccf5b34ad9..7900ca03392 100644 --- a/resources/access_rule_resource.go +++ b/resources/access_rule_resource.go @@ -11,7 +11,6 @@ type AccessRule struct { GUID string `json:"guid,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` - Name string `json:"name"` Selector string `json:"selector"` RouteGUID string `json:"-"` @@ -24,7 +23,6 @@ func (a AccessRule) MarshalJSON() ([]byte, error) { GUID string `json:"guid,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` - Name string `json:"name"` Selector string `json:"selector"` Metadata *Metadata `json:"metadata,omitempty"` @@ -41,7 +39,6 @@ func (a AccessRule) MarshalJSON() ([]byte, error) { aliasData.GUID = a.GUID aliasData.CreatedAt = a.CreatedAt aliasData.UpdatedAt = a.UpdatedAt - aliasData.Name = a.Name aliasData.Selector = a.Selector aliasData.Metadata = a.Metadata aliasData.Relationships.Route.Data.GUID = a.RouteGUID @@ -54,7 +51,6 @@ func (a *AccessRule) UnmarshalJSON(data []byte) error { GUID string `json:"guid,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` - Name string `json:"name"` Selector string `json:"selector"` Metadata *Metadata `json:"metadata,omitempty"` @@ -75,7 +71,6 @@ func (a *AccessRule) UnmarshalJSON(data []byte) error { a.GUID = alias.GUID a.CreatedAt = alias.CreatedAt a.UpdatedAt = alias.UpdatedAt - a.Name = alias.Name a.Selector = alias.Selector a.RouteGUID = alias.Relationships.Route.Data.GUID a.Metadata = alias.Metadata From 25a55f9b777aa3a2307d7a2a352fd122ca048f43 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 15 Apr 2026 09:04:58 +0000 Subject: [PATCH 03/17] Refine access-rules output to show separate host, domain, and path columns Changed table format from: route selector scope source backend.apps.identity ... app frontend-app To: host domain path selector scope source backend apps.identity ... app frontend-app api apps.identity /metrics ... space monitoring This provides better clarity by separating the route components into individual columns, making it easier to scan and filter visually. --- command/v7/access_rules_command.go | 24 ++++++------------------ command/v7/access_rules_command_test.go | 18 +++++++++--------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/command/v7/access_rules_command.go b/command/v7/access_rules_command.go index 5148c801911..6d8a9c8b169 100644 --- a/command/v7/access_rules_command.go +++ b/command/v7/access_rules_command.go @@ -1,9 +1,6 @@ package v7 import ( - "fmt" - - "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/util/ui" ) @@ -64,7 +61,9 @@ func (cmd AccessRulesCommand) Execute(args []string) error { // Build table data table := [][]string{ { - cmd.UI.TranslateText("route"), + cmd.UI.TranslateText("host"), + cmd.UI.TranslateText("domain"), + cmd.UI.TranslateText("path"), cmd.UI.TranslateText("selector"), cmd.UI.TranslateText("scope"), cmd.UI.TranslateText("source"), @@ -73,7 +72,9 @@ func (cmd AccessRulesCommand) Execute(args []string) error { for _, ruleWithRoute := range rulesWithRoutes { table = append(table, []string{ - formatRoute(ruleWithRoute.Route, ruleWithRoute.DomainName), + ruleWithRoute.Route.Host, + ruleWithRoute.DomainName, + ruleWithRoute.Route.Path, ruleWithRoute.Selector, ruleWithRoute.ScopeType, ruleWithRoute.SourceName, @@ -86,16 +87,3 @@ func (cmd AccessRulesCommand) Execute(args []string) error { return nil } -// formatRoute formats a route as hostname.domain/path -func formatRoute(route resources.Route, domainName string) string { - var formatted string - if route.Host != "" { - formatted = fmt.Sprintf("%s.%s", route.Host, domainName) - } else { - formatted = domainName - } - if route.Path != "" { - formatted += route.Path - } - return formatted -} diff --git a/command/v7/access_rules_command_test.go b/command/v7/access_rules_command_test.go index 6629be6c026..e161144cf6b 100644 --- a/command/v7/access_rules_command_test.go +++ b/command/v7/access_rules_command_test.go @@ -146,9 +146,9 @@ var _ = Describe("access-rules Command", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - Expect(testUI.Out).To(Say(`route\s+selector\s+scope\s+source`)) - Expect(testUI.Out).To(Say(`myapp\.example\.com/api\s+cf:app:app-guid-1\s+app\s+my-app`)) - Expect(testUI.Out).To(Say(`webapp\.test\.com\s+cf:any\s+any`)) + Expect(testUI.Out).To(Say(`host\s+domain\s+path\s+selector\s+scope\s+source`)) + Expect(testUI.Out).To(Say(`myapp\s+example\.com\s+/api\s+cf:app:app-guid-1\s+app\s+my-app`)) + Expect(testUI.Out).To(Say(`webapp\s+test\.com\s+cf:any\s+any`)) Expect(testUI.Err).To(Say("warning-1")) @@ -298,8 +298,8 @@ var _ = Describe("access-rules Command", func() { Expect(executeErr).ToNot(HaveOccurred()) Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - Expect(testUI.Out).To(Say(`route\s+selector\s+scope\s+source`)) - Expect(testUI.Out).To(Say(`myapp\.example\.com/api\s+cf:app:app-guid-1\s+app\s+my-app`)) + Expect(testUI.Out).To(Say(`host\s+domain\s+path\s+selector\s+scope\s+source`)) + Expect(testUI.Out).To(Say(`myapp\s+example\.com\s+/api\s+cf:app:app-guid-1\s+app\s+my-app`)) }) }) @@ -340,11 +340,11 @@ var _ = Describe("access-rules Command", func() { It("formats routes correctly", func() { Expect(executeErr).ToNot(HaveOccurred()) - // No host, with path: "example.com/api" - Expect(testUI.Out).To(Say(`example\.com/api`)) + // No host, with path: empty host, example.com, /api + Expect(testUI.Out).To(Say(`\s+example\.com\s+/api`)) - // With host, no path: "myapp.test.com" - Expect(testUI.Out).To(Say(`myapp\.test\.com`)) + // With host, no path: myapp, test.com, empty path + Expect(testUI.Out).To(Say(`myapp\s+test\.com\s+`)) }) }) }) From 6121923afd2e3a38ca68ebf7e82fa0cedfe7fd5d Mon Sep 17 00:00:00 2001 From: rkoster Date: Tue, 21 Apr 2026 11:17:10 +0000 Subject: [PATCH 04/17] =?UTF-8?q?Rebrand=20RFC=20terminology:=20access=20r?= =?UTF-8?q?ules=20=E2=86=92=20route=20policies,=20selector=20=E2=86=92=20s?= =?UTF-8?q?ource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete terminology shift for identity-aware routing RFC implementation: **Access Rules → Route Policies** - API: /v3/access_rules → /v3/route_policies - CLI commands: - cf access-rules → cf route-policies - cf add-access-rule → cf add-route-policy - cf remove-access-rule → cf remove-route-policy - Domain flags: --enforce-access-rules → --enforce-route-policies - Domain fields: enforce_access_rules → enforce_route_policies, access_rules_scope → route_policies_scope **Selector → Source** - API field: "selector" → "source" - CLI flag: --selector → --source - Query params: selectors → sources, selector_resource_guids → source_guids - Table column headers: "selector/source" → "source/name" - Internal types: AccessRule → RoutePolicy, AccessRuleWithRoute → RoutePolicyWithRoute - Error types: AccessRuleNotFoundError → RoutePolicyNotFoundError **Rationale (per RFC)** - "Route policies" aligns with existing CF "network policies" terminology - "Source" matches C2C network policy convention (source → destination) - Improves clarity: policies define allowed sources that can reach routes - Better mental model for users familiar with CF networking concepts This is a breaking change but acceptable since RFC is pre-GA with only POC/lab implementations. Clean terminology is preferred over backward compatibility at this stage. Co-authored-by: RFC Community Aligns-with: https://github.com/cloudfoundry/community/pull/1438/commits/be8d74c1 --- .../access_rule_not_found_error.go | 11 - .../route_policy_not_found_error.go | 11 + actor/v7action/access_rule_test.go | 750 ------------------ actor/v7action/cloud_controller_client.go | 6 +- actor/v7action/domain.go | 14 +- .../{access_rule.go => route_policy.go} | 125 ++- .../fake_cloud_controller_client.go | 502 ++++++------ actor/v7action/v7actionfakes/fake_config.go | 42 - .../v7action/v7actionfakes/fake_downloader.go | 2 - .../fake_kubernetes_config_getter.go | 2 - .../v7actionfakes/fake_manifest_parser.go | 4 - .../v7actionfakes/fake_routing_client.go | 4 - .../v7actionfakes/fake_shared_actor.go | 8 - .../v7actionfakes/fake_simple_progress_bar.go | 4 - actor/v7action/v7actionfakes/fake_sshactor.go | 2 - .../v7action/v7actionfakes/fake_uaaclient.go | 22 - .../v7action/v7actionfakes/fake_who_am_ier.go | 2 - .../create_deployment_for_push_plan_test.go | 2 +- actor/v7pushaction/handle_disk_override.go | 2 +- .../v7pushaction/handle_disk_override_test.go | 2 +- .../v7pushaction/handle_instances_override.go | 2 +- .../handle_instances_override_test.go | 2 +- .../handle_log_rate_limit_override.go | 2 +- .../handle_log_rate_limit_override_test.go | 2 +- actor/v7pushaction/handle_memory_override.go | 2 +- .../handle_memory_override_test.go | 2 +- ...up_deployment_information_for_push_plan.go | 2 +- ...ployment_information_for_push_plan_test.go | 4 +- api/cfnetworking/cfnetv1/client.go | 144 ++-- api/cfnetworking/errors.go | 74 +- api/cfnetworking/wrapper/retry_request.go | 64 +- api/cloudcontroller/ccv3/access_rule.go | 59 -- api/cloudcontroller/ccv3/deployment_test.go | 2 +- api/cloudcontroller/ccv3/info.go | 2 +- .../ccv3/internal/api_routes.go | 16 +- api/cloudcontroller/ccv3/query.go | 6 +- api/cloudcontroller/ccv3/route_policy.go | 59 ++ cf/actors/push.go | 4 +- cf/net/warnings_collector.go | 4 +- command/common/command_list_v7.go | 6 +- command/common/internal/help_all_display.go | 2 +- command/flag/arguments.go | 8 +- command/v7/access_rules_command_test.go | 350 -------- command/v7/actor.go | 8 +- command/v7/add_access_rule_command_test.go | 448 ----------- ...command.go => add_route_policy_command.go} | 74 +- command/v7/buildpacks_command.go | 4 +- command/v7/buildpacks_command_test.go | 2 +- command/v7/copy_source_command.go | 10 +- command/v7/create_buildpack_command.go | 2 +- command/v7/create_private_domain_command.go | 22 +- command/v7/create_route_command.go | 4 +- command/v7/create_route_command_test.go | 2 +- command/v7/create_shared_domain_command.go | 28 +- .../create_user_provided_service_command.go | 2 +- command/v7/delete_buildpack_command.go | 4 +- command/v7/delete_buildpack_command_test.go | 2 +- command/v7/remove_access_rule_command.go | 73 -- command/v7/remove_route_policy_command.go | 73 ++ command/v7/restage_command.go | 10 +- command/v7/restart_command.go | 10 +- command/v7/revision_command_test.go | 12 +- command/v7/rollback_command.go | 4 +- ...s_command.go => route_policies_command.go} | 39 +- .../v7/shared/sharedfakes/fake_app_stager.go | 6 - command/v7/stack_command_test.go | 38 +- command/v7/update_buildpack_command.go | 2 +- command/v7/update_stack_command_test.go | 95 ++- command/v7/v7fakes/fake_actor.go | 592 +++++++------- command/v7/v7fakes/fake_actor_reloader.go | 2 - command/v7/v7fakes/fake_diff_displayer.go | 2 - command/v7/v7fakes/fake_label_setter.go | 2 - command/v7/v7fakes/fake_label_unsetter.go | 2 - command/v7/v7fakes/fake_manifest_locator.go | 2 - command/v7/v7fakes/fake_manifest_parser.go | 6 - .../v7/v7fakes/fake_network_policies_actor.go | 4 - command/v7/v7fakes/fake_networking_actor.go | 2 - command/v7/v7fakes/fake_progress_bar.go | 6 - command/v7/v7fakes/fake_push_actor.go | 8 - .../fake_remove_network_policy_actor.go | 2 - command/v7/v7fakes/fake_revisions_actor.go | 2 - command/v7/v7fakes/fake_set_label_actor.go | 44 +- command/v7/v7fakes/fake_shared_sshactor.go | 2 - command/v7/v7fakes/fake_v7actor_for_push.go | 10 - devbox.json | 14 + devbox.lock | 57 ++ .../v7/isolated/revision_command_test.go | 12 +- .../v7/isolated/update_stack_command_test.go | 1 - resources/deployment_resource.go | 2 +- resources/domain_resource.go | 48 +- resources/process_resource.go | 6 +- ...e_resource.go => route_policy_resource.go} | 36 +- ..._test.go => route_policy_resource_test.go} | 34 +- 93 files changed, 1278 insertions(+), 2929 deletions(-) delete mode 100644 actor/actionerror/access_rule_not_found_error.go create mode 100644 actor/actionerror/route_policy_not_found_error.go delete mode 100644 actor/v7action/access_rule_test.go rename actor/v7action/{access_rule.go => route_policy.go} (69%) delete mode 100644 api/cloudcontroller/ccv3/access_rule.go create mode 100644 api/cloudcontroller/ccv3/route_policy.go delete mode 100644 command/v7/access_rules_command_test.go delete mode 100644 command/v7/add_access_rule_command_test.go rename command/v7/{add_access_rule_command.go => add_route_policy_command.go} (64%) delete mode 100644 command/v7/remove_access_rule_command.go create mode 100644 command/v7/remove_route_policy_command.go rename command/v7/{access_rules_command.go => route_policies_command.go} (55%) create mode 100644 devbox.json create mode 100644 devbox.lock rename resources/{access_rule_resource.go => route_policy_resource.go} (67%) rename resources/{access_rule_resource_test.go => route_policy_resource_test.go} (53%) diff --git a/actor/actionerror/access_rule_not_found_error.go b/actor/actionerror/access_rule_not_found_error.go deleted file mode 100644 index 02fc1d6c57b..00000000000 --- a/actor/actionerror/access_rule_not_found_error.go +++ /dev/null @@ -1,11 +0,0 @@ -package actionerror - -import "fmt" - -type AccessRuleNotFoundError struct { - Selector string -} - -func (e AccessRuleNotFoundError) Error() string { - return fmt.Sprintf("Access rule with selector '%s' not found.", e.Selector) -} diff --git a/actor/actionerror/route_policy_not_found_error.go b/actor/actionerror/route_policy_not_found_error.go new file mode 100644 index 00000000000..31410af83be --- /dev/null +++ b/actor/actionerror/route_policy_not_found_error.go @@ -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) +} diff --git a/actor/v7action/access_rule_test.go b/actor/v7action/access_rule_test.go deleted file mode 100644 index 38b81bb67b4..00000000000 --- a/actor/v7action/access_rule_test.go +++ /dev/null @@ -1,750 +0,0 @@ -package v7action_test - -import ( - "errors" - - "code.cloudfoundry.org/cli/v9/actor/actionerror" - . "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/actor/v7action/v7actionfakes" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" - "code.cloudfoundry.org/cli/v9/resources" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Access Rule Actions", func() { - var ( - actor *Actor - fakeCloudControllerClient *v7actionfakes.FakeCloudControllerClient - ) - - BeforeEach(func() { - actor, fakeCloudControllerClient, _, _, _, _, _ = NewTestActor() - }) - - Describe("GetAccessRulesForSpace", func() { - var ( - spaceGUID string - domainName string - hostname string - path string - labelSelector string - - results []AccessRuleWithRoute - warnings Warnings - executeErr error - ) - - BeforeEach(func() { - spaceGUID = "space-guid-1" - domainName = "" - hostname = "" - path = "" - labelSelector = "" - }) - - JustBeforeEach(func() { - results, warnings, executeErr = actor.GetAccessRulesForSpace( - spaceGUID, - domainName, - hostname, - path, - labelSelector, - ) - }) - - When("getting access rules succeeds with multiple rules", func() { - BeforeEach(func() { - // Mock GetAccessRules call with included routes - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - { - GUID: "rule-guid-1", - Name: "rule-1", - Selector: "cf:app:app-guid-1", - RouteGUID: "route-guid-1", - }, - { - GUID: "rule-guid-2", - Name: "rule-2", - Selector: "cf:any", - RouteGUID: "route-guid-2", - }, - }, - ccv3.IncludedResources{ - Routes: []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "app1", - Path: "/path1", - }, - { - GUID: "route-guid-2", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-2", - Host: "app2", - Path: "", - }, - }, - }, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - - // Mock GetDomain calls for domain name resolution - fakeCloudControllerClient.GetDomainStub = func(guid string) (resources.Domain, ccv3.Warnings, error) { - switch guid { - case "domain-guid-1": - return resources.Domain{GUID: "domain-guid-1", Name: "example.com"}, ccv3.Warnings{"get-domain-warning-1"}, nil - case "domain-guid-2": - return resources.Domain{GUID: "domain-guid-2", Name: "test.com"}, ccv3.Warnings{"get-domain-warning-2"}, nil - default: - return resources.Domain{}, nil, errors.New("domain not found") - } - } - - // Mock GetApplications for app name resolution - fakeCloudControllerClient.GetApplicationsReturns( - []resources.Application{ - {GUID: "app-guid-1", Name: "my-app"}, - }, - ccv3.Warnings{"get-app-warning"}, - nil, - ) - }) - - It("returns access rules with route and domain information", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(warnings).To(ConsistOf( - "get-access-rules-warning", - "get-domain-warning-1", - "get-domain-warning-2", - "get-app-warning", - )) - - Expect(results).To(HaveLen(2)) - - // First rule - Expect(results[0].GUID).To(Equal("rule-guid-1")) - Expect(results[0].Selector).To(Equal("cf:app:app-guid-1")) - Expect(results[0].Route.GUID).To(Equal("route-guid-1")) - Expect(results[0].Route.Host).To(Equal("app1")) - Expect(results[0].Route.Path).To(Equal("/path1")) - Expect(results[0].DomainName).To(Equal("example.com")) - Expect(results[0].ScopeType).To(Equal("app")) - Expect(results[0].SourceName).To(Equal("my-app")) - - // Second rule - Expect(results[1].GUID).To(Equal("rule-guid-2")) - Expect(results[1].Selector).To(Equal("cf:any")) - Expect(results[1].Route.GUID).To(Equal("route-guid-2")) - Expect(results[1].Route.Host).To(Equal("app2")) - Expect(results[1].DomainName).To(Equal("test.com")) - Expect(results[1].ScopeType).To(Equal("any")) - Expect(results[1].SourceName).To(Equal("")) - }) - - It("calls GetAccessRules with space GUID and include route filters", func() { - Expect(fakeCloudControllerClient.GetAccessRulesCallCount()).To(Equal(1)) - queries := fakeCloudControllerClient.GetAccessRulesArgsForCall(0) - Expect(queries).To(ContainElement(ccv3.Query{ - Key: ccv3.SpaceGUIDFilter, - Values: []string{"space-guid-1"}, - })) - Expect(queries).To(ContainElement(ccv3.Query{ - Key: ccv3.Include, - Values: []string{"route"}, - })) - }) - - It("does not call GetRoutes separately", func() { - Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) - }) - }) - - When("domain name filter is provided", func() { - BeforeEach(func() { - domainName = "example.com" - - fakeCloudControllerClient.GetDomainsReturns( - []resources.Domain{ - {GUID: "domain-guid-1", Name: "example.com"}, - }, - ccv3.Warnings{"get-domains-warning"}, - nil, - ) - - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - { - GUID: "rule-guid-1", - Name: "rule-1", - Selector: "cf:any", - RouteGUID: "route-guid-1", - }, - }, - ccv3.IncludedResources{ - Routes: []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "app1", - }, - }, - }, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - - fakeCloudControllerClient.GetDomainReturns( - resources.Domain{GUID: "domain-guid-1", Name: "example.com"}, - ccv3.Warnings{"get-domain-warning"}, - nil, - ) - }) - - It("filters routes by domain GUID", func() { - Expect(executeErr).ToNot(HaveOccurred()) - // Routes are filtered in-memory from included resources - Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) - }) - }) - - When("hostname filter is provided", func() { - BeforeEach(func() { - hostname = "myapp" - - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{}, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - }) - - It("adds hostname filter to route query", func() { - Expect(executeErr).ToNot(HaveOccurred()) - // GetRoutes should not be called since routes come from included resources - Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) - }) - }) - - When("path filter is provided", func() { - BeforeEach(func() { - path = "/api" - - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{}, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - }) - - It("adds path filter to route query", func() { - Expect(executeErr).ToNot(HaveOccurred()) - // GetRoutes should not be called if no access rules are found - Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) - }) - }) - - When("label selector filter is provided", func() { - BeforeEach(func() { - labelSelector = "env=production" - - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{}, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - }) - - It("adds label selector filter to access rules query", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeCloudControllerClient.GetAccessRulesCallCount()).To(Equal(1)) - queries := fakeCloudControllerClient.GetAccessRulesArgsForCall(0) - - Expect(queries).To(ContainElement(ccv3.Query{ - Key: ccv3.LabelSelectorFilter, - Values: []string{"env=production"}, - })) - }) - }) - - When("no access rules are found in the space", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{}, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - }) - - It("returns an empty list without error", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(results).To(BeEmpty()) - Expect(warnings).To(ConsistOf("get-access-rules-warning")) - }) - - It("does not call GetRoutes", func() { - Expect(fakeCloudControllerClient.GetRoutesCallCount()).To(Equal(0)) - }) - }) - - When("getting access rules fails", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetAccessRulesReturns( - nil, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - errors.New("api error"), - ) - }) - - It("returns the error and warnings", func() { - Expect(executeErr).To(MatchError("api error")) - Expect(warnings).To(ConsistOf("get-access-rules-warning")) - Expect(results).To(BeNil()) - }) - }) - - When("getting domain by name fails", func() { - BeforeEach(func() { - domainName = "invalid-domain.com" - - // Mock GetAccessRules to return at least one access rule - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - {GUID: "access-rule-guid-1", RouteGUID: "route-guid-1"}, - }, - ccv3.IncludedResources{ - Routes: []resources.Route{ - {GUID: "route-guid-1", DomainGUID: "domain-guid-1"}, - }, - }, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - - fakeCloudControllerClient.GetDomainsReturns( - nil, - ccv3.Warnings{"get-domains-warning"}, - actionerror.DomainNotFoundError{Name: "invalid-domain.com"}, - ) - }) - - It("returns the error and warnings", func() { - Expect(executeErr).To(MatchError(actionerror.DomainNotFoundError{Name: "invalid-domain.com"})) - Expect(warnings).To(ConsistOf("get-access-rules-warning", "get-domains-warning")) - Expect(results).To(BeNil()) - }) - }) - - When("resolving domain name fails", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - { - GUID: "rule-guid-1", - Name: "rule-1", - Selector: "cf:any", - RouteGUID: "route-guid-1", - }, - }, - ccv3.IncludedResources{ - Routes: []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "app1", - }, - }, - }, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - - // Domain lookup fails - fakeCloudControllerClient.GetDomainReturns( - resources.Domain{}, - ccv3.Warnings{"get-domain-warning"}, - errors.New("domain lookup error"), - ) - }) - - It("uses the domain GUID as fallback and continues", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(results).To(HaveLen(1)) - Expect(results[0].DomainName).To(Equal("domain-guid-1")) - Expect(warnings).To(ContainElement("get-domain-warning")) - }) - }) - - When("resolving target name fails", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - { - GUID: "rule-guid-1", - Name: "rule-1", - Selector: "cf:app:app-guid-1", - RouteGUID: "route-guid-1", - }, - }, - ccv3.IncludedResources{ - Routes: []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "app1", - }, - }, - }, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - - fakeCloudControllerClient.GetDomainReturns( - resources.Domain{GUID: "domain-guid-1", Name: "example.com"}, - ccv3.Warnings{"get-domain-warning"}, - nil, - ) - - // App lookup fails - fakeCloudControllerClient.GetApplicationsReturns( - nil, - ccv3.Warnings{"get-app-warning"}, - errors.New("app lookup error"), - ) - }) - - It("leaves source name blank and populates scope type", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(results).To(HaveLen(1)) - Expect(results[0].ScopeType).To(Equal("app")) - Expect(results[0].SourceName).To(Equal("")) - Expect(warnings).To(ContainElement("get-app-warning")) - }) - }) - }) - - // Note: resolveAccessRuleTarget and splitSelector are unexported methods - // and are tested indirectly through GetAccessRulesForSpace above. - - Describe("GetAccessRulesByRoute", func() { - var ( - domainName string - hostname string - path string - - rules []resources.AccessRule - warnings Warnings - executeErr error - ) - - BeforeEach(func() { - domainName = "example.com" - hostname = "myapp" - path = "" - }) - - JustBeforeEach(func() { - rules, warnings, executeErr = actor.GetAccessRulesByRoute(domainName, hostname, path) - }) - - When("the route exists with access rules", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetDomainsReturns( - []resources.Domain{ - {GUID: "domain-guid-1", Name: "example.com"}, - }, - ccv3.Warnings{"get-domains-warning"}, - nil, - ) - - fakeCloudControllerClient.GetRoutesReturns( - []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "myapp", - }, - }, - ccv3.Warnings{"get-routes-warning"}, - nil, - ) - - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - {GUID: "rule-guid-1", Name: "rule-1", Selector: "cf:any"}, - {GUID: "rule-guid-2", Name: "rule-2", Selector: "cf:app:app-guid-1"}, - }, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - }) - - It("returns the access rules", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(rules).To(HaveLen(2)) - Expect(rules[0].Selector).To(Equal("cf:app:app-guid-1")) - Expect(rules[1].Selector).To(Equal("cf:app:app-guid-2")) - Expect(warnings).To(ConsistOf( - "get-domains-warning", - "get-routes-warning", - "get-access-rules-warning", - )) - }) - }) - - When("the route does not exist", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetDomainsReturns( - []resources.Domain{ - {GUID: "domain-guid-1", Name: "example.com"}, - }, - ccv3.Warnings{"get-domains-warning"}, - nil, - ) - - fakeCloudControllerClient.GetRoutesReturns( - []resources.Route{}, - ccv3.Warnings{"get-routes-warning"}, - nil, - ) - }) - - It("returns a RouteNotFoundError", func() { - Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{ - Host: "myapp", - DomainName: "example.com", - Path: "", - })) - Expect(warnings).To(ConsistOf("get-domains-warning", "get-routes-warning")) - }) - }) - }) - - Describe("AddAccessRule", func() { - var ( - domainName string - selector string - hostname string - path string - - warnings Warnings - executeErr error - ) - - BeforeEach(func() { - domainName = "example.com" - selector = "cf:app:app-guid-1" - hostname = "myapp" - path = "" - }) - - JustBeforeEach(func() { - warnings, executeErr = actor.AddAccessRule(domainName, selector, hostname, path) - }) - - When("creating the access rule succeeds", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetDomainsReturns( - []resources.Domain{ - {GUID: "domain-guid-1", Name: "example.com"}, - }, - ccv3.Warnings{"get-domains-warning"}, - nil, - ) - - fakeCloudControllerClient.GetRoutesReturns( - []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "myapp", - }, - }, - ccv3.Warnings{"get-routes-warning"}, - nil, - ) - - fakeCloudControllerClient.CreateAccessRuleReturns( - resources.AccessRule{GUID: "rule-guid-1", Name: "my-rule"}, - ccv3.Warnings{"create-rule-warning"}, - nil, - ) - }) - - It("creates the access rule and returns warnings", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(warnings).To(ConsistOf( - "get-domains-warning", - "get-routes-warning", - "create-rule-warning", - )) - - Expect(fakeCloudControllerClient.CreateAccessRuleCallCount()).To(Equal(1)) - rule := fakeCloudControllerClient.CreateAccessRuleArgsForCall(0) - Expect(rule.Selector).To(Equal("cf:app:app-guid-1")) - Expect(rule.RouteGUID).To(Equal("route-guid-1")) - }) - }) - - When("the route does not exist", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetDomainsReturns( - []resources.Domain{ - {GUID: "domain-guid-1", Name: "example.com"}, - }, - ccv3.Warnings{"get-domains-warning"}, - nil, - ) - - fakeCloudControllerClient.GetRoutesReturns( - []resources.Route{}, - ccv3.Warnings{"get-routes-warning"}, - nil, - ) - }) - - It("returns a RouteNotFoundError", func() { - Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{ - Host: "myapp", - DomainName: "example.com", - Path: "", - })) - }) - }) - }) - - Describe("DeleteAccessRuleBySelector", func() { - var ( - selector string - domainName string - hostname string - path string - - warnings Warnings - executeErr error - ) - - BeforeEach(func() { - selector = "cf:any" - domainName = "example.com" - hostname = "myapp" - path = "" - }) - - JustBeforeEach(func() { - warnings, executeErr = actor.DeleteAccessRuleBySelector(domainName, selector, hostname, path) - }) - - When("the access rule exists", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetDomainsReturns( - []resources.Domain{ - {GUID: "domain-guid-1", Name: "example.com"}, - }, - ccv3.Warnings{"get-domains-warning"}, - nil, - ) - - fakeCloudControllerClient.GetRoutesReturns( - []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "myapp", - }, - }, - ccv3.Warnings{"get-routes-warning"}, - nil, - ) - - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - {GUID: "rule-guid-1", Selector: "cf:any"}, - }, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - - fakeCloudControllerClient.DeleteAccessRuleReturns( - ccv3.JobURL(""), - ccv3.Warnings{"delete-rule-warning"}, - nil, - ) - }) - - It("deletes the access rule and returns warnings", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(warnings).To(ConsistOf( - "get-domains-warning", - "get-routes-warning", - "get-access-rules-warning", - "delete-rule-warning", - )) - - Expect(fakeCloudControllerClient.DeleteAccessRuleCallCount()).To(Equal(1)) - guid := fakeCloudControllerClient.DeleteAccessRuleArgsForCall(0) - Expect(guid).To(Equal("rule-guid-1")) - }) - }) - - When("the access rule does not exist", func() { - BeforeEach(func() { - fakeCloudControllerClient.GetDomainsReturns( - []resources.Domain{ - {GUID: "domain-guid-1", Name: "example.com"}, - }, - ccv3.Warnings{"get-domains-warning"}, - nil, - ) - - fakeCloudControllerClient.GetRoutesReturns( - []resources.Route{ - { - GUID: "route-guid-1", - SpaceGUID: "space-guid-1", - DomainGUID: "domain-guid-1", - Host: "myapp", - }, - }, - ccv3.Warnings{"get-routes-warning"}, - nil, - ) - - fakeCloudControllerClient.GetAccessRulesReturns( - []resources.AccessRule{ - {GUID: "rule-guid-other", Selector: "cf:app:other-guid"}, - }, - ccv3.IncludedResources{}, - ccv3.Warnings{"get-access-rules-warning"}, - nil, - ) - }) - - It("returns an AccessRuleNotFoundError", func() { - Expect(executeErr).To(MatchError(actionerror.AccessRuleNotFoundError{Selector: "cf:any"})) - Expect(warnings).To(ConsistOf( - "get-domains-warning", - "get-routes-warning", - "get-access-rules-warning", - )) - }) - }) -}) -}) diff --git a/actor/v7action/cloud_controller_client.go b/actor/v7action/cloud_controller_client.go index 223f7344a2f..eeced084370 100644 --- a/actor/v7action/cloud_controller_client.go +++ b/actor/v7action/cloud_controller_client.go @@ -20,7 +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) - CreateAccessRule(accessRule resources.AccessRule) (resources.AccessRule, 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) @@ -43,7 +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) - DeleteAccessRule(guid string) (ccv3.JobURL, 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) @@ -65,7 +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) - GetAccessRules(query ...ccv3.Query) ([]resources.AccessRule, ccv3.IncludedResources, 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) diff --git a/actor/v7action/domain.go b/actor/v7action/domain.go index cf65ca2bfa3..f7307da5ade 100644 --- a/actor/v7action/domain.go +++ b/actor/v7action/domain.go @@ -43,10 +43,10 @@ func (actor Actor) CreateSharedDomain(domainName string, internal bool, routerGr RouterGroup: routerGroupGUID, } - // Set enforce_access_rules if specified + // Set enforce_route_policies if specified if enforceAccessRules { - domain.EnforceAccessRules = types.NullBool{IsSet: true, Value: true} - domain.AccessRulesScope = accessRulesScope + domain.EnforceRoutePolicies = types.NullBool{IsSet: true, Value: true} + domain.RoutePoliciesScope = accessRulesScope } _, warnings, err := actor.CloudControllerClient.CreateDomain(domain) @@ -63,16 +63,16 @@ func (actor Actor) CreatePrivateDomain(domainName string, orgName string, enforc if err != nil { return allWarnings, err } - + domain := resources.Domain{ Name: domainName, OrganizationGUID: organization.GUID, } - // Set enforce_access_rules if specified + // Set enforce_route_policies if specified if enforceAccessRules { - domain.EnforceAccessRules = types.NullBool{IsSet: true, Value: true} - domain.AccessRulesScope = accessRulesScope + domain.EnforceRoutePolicies = types.NullBool{IsSet: true, Value: true} + domain.RoutePoliciesScope = accessRulesScope } _, apiWarnings, err := actor.CloudControllerClient.CreateDomain(domain) diff --git a/actor/v7action/access_rule.go b/actor/v7action/route_policy.go similarity index 69% rename from actor/v7action/access_rule.go rename to actor/v7action/route_policy.go index 909fc8dffdc..07068fb9745 100644 --- a/actor/v7action/access_rule.go +++ b/actor/v7action/route_policy.go @@ -6,10 +6,10 @@ import ( "code.cloudfoundry.org/cli/v9/resources" ) -func (actor Actor) AddAccessRule(domainName, selector, hostname, path string) (Warnings, error) { +func (actor Actor) AddRoutePolicy(domainName, source, hostname, path string) (Warnings, error) { allWarnings := Warnings{} - // Get the domain to ensure it exists and supports access rules + // Get the domain to ensure it exists and supports route policies domain, warnings, err := actor.GetDomainByName(domainName) allWarnings = append(allWarnings, warnings...) if err != nil { @@ -33,19 +33,19 @@ func (actor Actor) AddAccessRule(domainName, selector, hostname, path string) (W route := routes[0] - // Create the access rule - accessRule := resources.AccessRule{ - Selector: selector, + // Create the route policy + routePolicy := resources.RoutePolicy{ + Source: source, RouteGUID: route.GUID, } - _, apiWarnings, err := actor.CloudControllerClient.CreateAccessRule(accessRule) + _, apiWarnings, err := actor.CloudControllerClient.CreateRoutePolicy(routePolicy) allWarnings = append(allWarnings, Warnings(apiWarnings)...) return allWarnings, err } -func (actor Actor) GetAccessRulesByRoute(domainName, hostname, path string) ([]resources.AccessRule, Warnings, error) { +func (actor Actor) GetRoutePoliciesByRoute(domainName, hostname, path string) ([]resources.RoutePolicy, Warnings, error) { allWarnings := Warnings{} // Get the domain @@ -72,21 +72,21 @@ func (actor Actor) GetAccessRulesByRoute(domainName, hostname, path string) ([]r route := routes[0] - // Get access rules for this route - accessRules, _, apiWarnings, err := actor.CloudControllerClient.GetAccessRules( + // Get route policies for this route + routePolicies, _, apiWarnings, err := actor.CloudControllerClient.GetRoutePolicies( ccv3.Query{Key: ccv3.RouteGUIDFilter, Values: []string{route.GUID}}, ) allWarnings = append(allWarnings, Warnings(apiWarnings)...) - var rules []resources.AccessRule - for _, rule := range accessRules { - rules = append(rules, resources.AccessRule(rule)) + var policies []resources.RoutePolicy + for _, policy := range routePolicies { + policies = append(policies, resources.RoutePolicy(policy)) } - return rules, allWarnings, err + return policies, allWarnings, err } -func (actor Actor) DeleteAccessRuleBySelector(domainName, selector, hostname, path string) (Warnings, error) { +func (actor Actor) DeleteRoutePolicyBySource(domainName, source, hostname, path string) (Warnings, error) { allWarnings := Warnings{} // Get the domain @@ -113,8 +113,8 @@ func (actor Actor) DeleteAccessRuleBySelector(domainName, selector, hostname, pa route := routes[0] - // Get access rules for this route to find the one with matching selector - accessRules, _, apiWarnings, err := actor.CloudControllerClient.GetAccessRules( + // Get route policies for this route to find the one with matching source + routePolicies, _, apiWarnings, err := actor.CloudControllerClient.GetRoutePolicies( ccv3.Query{Key: ccv3.RouteGUIDFilter, Values: []string{route.GUID}}, ) allWarnings = append(allWarnings, Warnings(apiWarnings)...) @@ -122,27 +122,26 @@ func (actor Actor) DeleteAccessRuleBySelector(domainName, selector, hostname, pa return allWarnings, err } - // Find the rule with matching selector - var ruleGUID string - for _, rule := range accessRules { - if rule.Selector == selector { - ruleGUID = rule.GUID + // Find the policy with matching source + var policyGUID string + for _, policy := range routePolicies { + if policy.Source == source { + policyGUID = policy.GUID break } } - if ruleGUID == "" { - return allWarnings, actionerror.AccessRuleNotFoundError{Selector: selector} + if policyGUID == "" { + return allWarnings, actionerror.RoutePolicyNotFoundError{Source: source} } - // Delete the access rule - _, deleteWarnings, err := actor.CloudControllerClient.DeleteAccessRule(ruleGUID) + // Delete the route policy + _, deleteWarnings, err := actor.CloudControllerClient.DeleteRoutePolicy(policyGUID) allWarnings = append(allWarnings, Warnings(deleteWarnings)...) return allWarnings, err } - // GetRoutesByDomain gets routes for a domain with optional hostname and path filters func (actor Actor) GetRoutesByDomain(domainGUID, hostname, path string) ([]resources.Route, Warnings, error) { queries := []ccv3.Query{ @@ -170,26 +169,26 @@ func (actor Actor) GetRoutesByDomain(domainGUID, hostname, path string) ([]resou return routes, Warnings(warnings), nil } -// AccessRuleWithRoute combines an access rule with its associated route information -type AccessRuleWithRoute struct { - resources.AccessRule +// RoutePolicyWithRoute combines a route policy with its associated route information +type RoutePolicyWithRoute struct { + resources.RoutePolicy Route resources.Route DomainName string ScopeType string // "app", "space", "org", or "any" SourceName string // Resolved source name (app/space/org) or empty string } -// GetAccessRulesForSpace gets all access rules for routes in a space with optional filters -func (actor Actor) GetAccessRulesForSpace( +// GetRoutePoliciesForSpace gets all route policies for routes in a space with optional filters +func (actor Actor) GetRoutePoliciesForSpace( spaceGUID string, domainName string, hostname string, path string, labelSelector string, -) ([]AccessRuleWithRoute, Warnings, error) { +) ([]RoutePolicyWithRoute, Warnings, error) { allWarnings := Warnings{} - // Build query for access rules filtered by space, with included routes + // Build query for route policies filtered by space, with included routes queries := []ccv3.Query{ {Key: ccv3.SpaceGUIDFilter, Values: []string{spaceGUID}}, {Key: ccv3.Include, Values: []string{"route"}}, @@ -200,16 +199,16 @@ func (actor Actor) GetAccessRulesForSpace( queries = append(queries, ccv3.Query{Key: ccv3.LabelSelectorFilter, Values: []string{labelSelector}}) } - // Fetch access rules directly by space GUID with included routes (single API call) - accessRules, includedResources, apiWarnings, err := actor.CloudControllerClient.GetAccessRules(queries...) + // Fetch route policies directly by space GUID with included routes (single API call) + routePolicies, includedResources, apiWarnings, err := actor.CloudControllerClient.GetRoutePolicies(queries...) allWarnings = append(allWarnings, Warnings(apiWarnings)...) if err != nil { return nil, allWarnings, err } - if len(accessRules) == 0 { - // No access rules found - return empty list, not an error - return []AccessRuleWithRoute{}, allWarnings, nil + if len(routePolicies) == 0 { + // No route policies found - return empty list, not an error + return []RoutePolicyWithRoute{}, allWarnings, nil } // Build route lookup map from included resources @@ -273,60 +272,60 @@ func (actor Actor) GetAccessRulesForSpace( } // Build results with route information and resolved sources - // Only include access rules whose routes match the filters - var results []AccessRuleWithRoute - for _, rule := range accessRules { - route, exists := routeByGUID[rule.RouteGUID] + // Only include route policies whose routes match the filters + var results []RoutePolicyWithRoute + for _, policy := range routePolicies { + route, exists := routeByGUID[policy.RouteGUID] if !exists { - // Skip rules for routes that don't match the optional filters + // Skip policies for routes that don't match the optional filters continue } - scopeType, sourceName, warnings, err := actor.resolveAccessRuleSource(rule.Selector) + scopeType, sourceName, warnings, err := actor.resolveRoutePolicySource(policy.Source) allWarnings = append(allWarnings, warnings...) if err != nil { // If we can't resolve the source, sourceName is already empty string // scopeType is still set correctly } - results = append(results, AccessRuleWithRoute{ - AccessRule: resources.AccessRule(rule), - Route: route, - DomainName: domainCache[route.DomainGUID], - ScopeType: scopeType, - SourceName: sourceName, + results = append(results, RoutePolicyWithRoute{ + RoutePolicy: resources.RoutePolicy(policy), + Route: route, + DomainName: domainCache[route.DomainGUID], + ScopeType: scopeType, + SourceName: sourceName, }) } return results, allWarnings, nil } -// resolveAccessRuleSource resolves a selector to scope type and human-readable source name -func (actor Actor) resolveAccessRuleSource(selector string) (scopeType string, sourceName string, warnings Warnings, err error) { +// resolveRoutePolicySource resolves a source to scope type and human-readable source name +func (actor Actor) resolveRoutePolicySource(source string) (scopeType string, sourceName string, warnings Warnings, err error) { allWarnings := Warnings{} - // Parse selector format: cf:app:, cf:space:, cf:org:, or cf:any - if selector == "cf:any" { + // Parse source format: cf:app:, cf:space:, cf:org:, or cf:any + if source == "cf:any" { return "any", "", nil, nil } - // Split selector into parts + // Split source into parts // Expected format: cf:type:guid const prefix = "cf:" - if len(selector) < len(prefix) { + if len(source) < len(prefix) { return "unknown", "", nil, nil } - selectorBody := selector[len(prefix):] - parts := splitSelector(selectorBody) + sourceBody := source[len(prefix):] + parts := splitSource(sourceBody) if len(parts) < 2 { return "unknown", "", nil, nil } - selectorType := parts[0] + sourceType := parts[0] guid := parts[1] - switch selectorType { + switch sourceType { case "app": apps, apiWarnings, err := actor.CloudControllerClient.GetApplications( ccv3.Query{Key: ccv3.GUIDFilter, Values: []string{guid}}, @@ -362,9 +361,9 @@ func (actor Actor) resolveAccessRuleSource(selector string) (scopeType string, s } } -// splitSelector splits a selector body by colon, handling the case where -// the selector might be "type:guid" format -func splitSelector(s string) []string { +// splitSource splits a source body by colon, handling the case where +// the source might be "type:guid" format +func splitSource(s string) []string { var parts []string current := "" for _, char := range s { diff --git a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go index 27c4b830379..f8e3eaa7fd0 100644 --- a/actor/v7action/v7actionfakes/fake_cloud_controller_client.go +++ b/actor/v7action/v7actionfakes/fake_cloud_controller_client.go @@ -106,21 +106,6 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } - CreateAccessRuleStub func(resources.AccessRule) (resources.AccessRule, ccv3.Warnings, error) - createAccessRuleMutex sync.RWMutex - createAccessRuleArgsForCall []struct { - arg1 resources.AccessRule - } - createAccessRuleReturns struct { - result1 resources.AccessRule - result2 ccv3.Warnings - result3 error - } - createAccessRuleReturnsOnCall map[int]struct { - result1 resources.AccessRule - result2 ccv3.Warnings - result3 error - } CreateApplicationStub func(resources.Application) (resources.Application, ccv3.Warnings, error) createApplicationMutex sync.RWMutex createApplicationArgsForCall []struct { @@ -348,6 +333,21 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + CreateRoutePolicyStub func(resources.RoutePolicy) (resources.RoutePolicy, ccv3.Warnings, error) + createRoutePolicyMutex sync.RWMutex + createRoutePolicyArgsForCall []struct { + arg1 resources.RoutePolicy + } + createRoutePolicyReturns struct { + result1 resources.RoutePolicy + result2 ccv3.Warnings + result3 error + } + createRoutePolicyReturnsOnCall map[int]struct { + result1 resources.RoutePolicy + result2 ccv3.Warnings + result3 error + } CreateSecurityGroupStub func(resources.SecurityGroup) (resources.SecurityGroup, ccv3.Warnings, error) createSecurityGroupMutex sync.RWMutex createSecurityGroupArgsForCall []struct { @@ -453,21 +453,6 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } - DeleteAccessRuleStub func(string) (ccv3.JobURL, ccv3.Warnings, error) - deleteAccessRuleMutex sync.RWMutex - deleteAccessRuleArgsForCall []struct { - arg1 string - } - deleteAccessRuleReturns struct { - result1 ccv3.JobURL - result2 ccv3.Warnings - result3 error - } - deleteAccessRuleReturnsOnCall map[int]struct { - result1 ccv3.JobURL - result2 ccv3.Warnings - result3 error - } DeleteApplicationStub func(string) (ccv3.JobURL, ccv3.Warnings, error) deleteApplicationMutex sync.RWMutex deleteApplicationArgsForCall []struct { @@ -645,6 +630,21 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + DeleteRoutePolicyStub func(string) (ccv3.JobURL, ccv3.Warnings, error) + deleteRoutePolicyMutex sync.RWMutex + deleteRoutePolicyArgsForCall []struct { + arg1 string + } + deleteRoutePolicyReturns struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + } + deleteRoutePolicyReturnsOnCall map[int]struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + } DeleteSecurityGroupStub func(string) (ccv3.JobURL, ccv3.Warnings, error) deleteSecurityGroupMutex sync.RWMutex deleteSecurityGroupArgsForCall []struct { @@ -796,23 +796,6 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } - GetAccessRulesStub func(...ccv3.Query) ([]resources.AccessRule, ccv3.IncludedResources, ccv3.Warnings, error) - getAccessRulesMutex sync.RWMutex - getAccessRulesArgsForCall []struct { - arg1 []ccv3.Query - } - getAccessRulesReturns struct { - result1 []resources.AccessRule - result2 ccv3.IncludedResources - result3 ccv3.Warnings - result4 error - } - getAccessRulesReturnsOnCall map[int]struct { - result1 []resources.AccessRule - result2 ccv3.IncludedResources - result3 ccv3.Warnings - result4 error - } GetAppFeatureStub func(string, string) (resources.ApplicationFeature, ccv3.Warnings, error) getAppFeatureMutex sync.RWMutex getAppFeatureArgsForCall []struct { @@ -1542,6 +1525,23 @@ type FakeCloudControllerClient struct { result2 ccv3.Warnings result3 error } + GetRoutePoliciesStub func(...ccv3.Query) ([]resources.RoutePolicy, ccv3.IncludedResources, ccv3.Warnings, error) + getRoutePoliciesMutex sync.RWMutex + getRoutePoliciesArgsForCall []struct { + arg1 []ccv3.Query + } + getRoutePoliciesReturns struct { + result1 []resources.RoutePolicy + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + } + getRoutePoliciesReturnsOnCall map[int]struct { + result1 []resources.RoutePolicy + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + } GetRoutesStub func(...ccv3.Query) ([]resources.Route, ccv3.Warnings, error) getRoutesMutex sync.RWMutex getRoutesArgsForCall []struct { @@ -3294,73 +3294,6 @@ func (fake *FakeCloudControllerClient) CopyPackageReturnsOnCall(i int, result1 r }{result1, result2, result3} } -func (fake *FakeCloudControllerClient) CreateAccessRule(arg1 resources.AccessRule) (resources.AccessRule, ccv3.Warnings, error) { - fake.createAccessRuleMutex.Lock() - ret, specificReturn := fake.createAccessRuleReturnsOnCall[len(fake.createAccessRuleArgsForCall)] - fake.createAccessRuleArgsForCall = append(fake.createAccessRuleArgsForCall, struct { - arg1 resources.AccessRule - }{arg1}) - stub := fake.CreateAccessRuleStub - fakeReturns := fake.createAccessRuleReturns - fake.recordInvocation("CreateAccessRule", []interface{}{arg1}) - fake.createAccessRuleMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 -} - -func (fake *FakeCloudControllerClient) CreateAccessRuleCallCount() int { - fake.createAccessRuleMutex.RLock() - defer fake.createAccessRuleMutex.RUnlock() - return len(fake.createAccessRuleArgsForCall) -} - -func (fake *FakeCloudControllerClient) CreateAccessRuleCalls(stub func(resources.AccessRule) (resources.AccessRule, ccv3.Warnings, error)) { - fake.createAccessRuleMutex.Lock() - defer fake.createAccessRuleMutex.Unlock() - fake.CreateAccessRuleStub = stub -} - -func (fake *FakeCloudControllerClient) CreateAccessRuleArgsForCall(i int) resources.AccessRule { - fake.createAccessRuleMutex.RLock() - defer fake.createAccessRuleMutex.RUnlock() - argsForCall := fake.createAccessRuleArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeCloudControllerClient) CreateAccessRuleReturns(result1 resources.AccessRule, result2 ccv3.Warnings, result3 error) { - fake.createAccessRuleMutex.Lock() - defer fake.createAccessRuleMutex.Unlock() - fake.CreateAccessRuleStub = nil - fake.createAccessRuleReturns = struct { - result1 resources.AccessRule - result2 ccv3.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeCloudControllerClient) CreateAccessRuleReturnsOnCall(i int, result1 resources.AccessRule, result2 ccv3.Warnings, result3 error) { - fake.createAccessRuleMutex.Lock() - defer fake.createAccessRuleMutex.Unlock() - fake.CreateAccessRuleStub = nil - if fake.createAccessRuleReturnsOnCall == nil { - fake.createAccessRuleReturnsOnCall = make(map[int]struct { - result1 resources.AccessRule - result2 ccv3.Warnings - result3 error - }) - } - fake.createAccessRuleReturnsOnCall[i] = struct { - result1 resources.AccessRule - result2 ccv3.Warnings - result3 error - }{result1, result2, result3} -} - func (fake *FakeCloudControllerClient) CreateApplication(arg1 resources.Application) (resources.Application, ccv3.Warnings, error) { fake.createApplicationMutex.Lock() ret, specificReturn := fake.createApplicationReturnsOnCall[len(fake.createApplicationArgsForCall)] @@ -4368,6 +4301,73 @@ func (fake *FakeCloudControllerClient) CreateRouteBindingReturnsOnCall(i int, re }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) CreateRoutePolicy(arg1 resources.RoutePolicy) (resources.RoutePolicy, ccv3.Warnings, error) { + fake.createRoutePolicyMutex.Lock() + ret, specificReturn := fake.createRoutePolicyReturnsOnCall[len(fake.createRoutePolicyArgsForCall)] + fake.createRoutePolicyArgsForCall = append(fake.createRoutePolicyArgsForCall, struct { + arg1 resources.RoutePolicy + }{arg1}) + stub := fake.CreateRoutePolicyStub + fakeReturns := fake.createRoutePolicyReturns + fake.recordInvocation("CreateRoutePolicy", []interface{}{arg1}) + fake.createRoutePolicyMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeCloudControllerClient) CreateRoutePolicyCallCount() int { + fake.createRoutePolicyMutex.RLock() + defer fake.createRoutePolicyMutex.RUnlock() + return len(fake.createRoutePolicyArgsForCall) +} + +func (fake *FakeCloudControllerClient) CreateRoutePolicyCalls(stub func(resources.RoutePolicy) (resources.RoutePolicy, ccv3.Warnings, error)) { + fake.createRoutePolicyMutex.Lock() + defer fake.createRoutePolicyMutex.Unlock() + fake.CreateRoutePolicyStub = stub +} + +func (fake *FakeCloudControllerClient) CreateRoutePolicyArgsForCall(i int) resources.RoutePolicy { + fake.createRoutePolicyMutex.RLock() + defer fake.createRoutePolicyMutex.RUnlock() + argsForCall := fake.createRoutePolicyArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeCloudControllerClient) CreateRoutePolicyReturns(result1 resources.RoutePolicy, result2 ccv3.Warnings, result3 error) { + fake.createRoutePolicyMutex.Lock() + defer fake.createRoutePolicyMutex.Unlock() + fake.CreateRoutePolicyStub = nil + fake.createRoutePolicyReturns = struct { + result1 resources.RoutePolicy + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeCloudControllerClient) CreateRoutePolicyReturnsOnCall(i int, result1 resources.RoutePolicy, result2 ccv3.Warnings, result3 error) { + fake.createRoutePolicyMutex.Lock() + defer fake.createRoutePolicyMutex.Unlock() + fake.CreateRoutePolicyStub = nil + if fake.createRoutePolicyReturnsOnCall == nil { + fake.createRoutePolicyReturnsOnCall = make(map[int]struct { + result1 resources.RoutePolicy + result2 ccv3.Warnings + result3 error + }) + } + fake.createRoutePolicyReturnsOnCall[i] = struct { + result1 resources.RoutePolicy + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeCloudControllerClient) CreateSecurityGroup(arg1 resources.SecurityGroup) (resources.SecurityGroup, ccv3.Warnings, error) { fake.createSecurityGroupMutex.Lock() ret, specificReturn := fake.createSecurityGroupReturnsOnCall[len(fake.createSecurityGroupArgsForCall)] @@ -4837,73 +4837,6 @@ func (fake *FakeCloudControllerClient) CreateUserReturnsOnCall(i int, result1 re }{result1, result2, result3} } -func (fake *FakeCloudControllerClient) DeleteAccessRule(arg1 string) (ccv3.JobURL, ccv3.Warnings, error) { - fake.deleteAccessRuleMutex.Lock() - ret, specificReturn := fake.deleteAccessRuleReturnsOnCall[len(fake.deleteAccessRuleArgsForCall)] - fake.deleteAccessRuleArgsForCall = append(fake.deleteAccessRuleArgsForCall, struct { - arg1 string - }{arg1}) - stub := fake.DeleteAccessRuleStub - fakeReturns := fake.deleteAccessRuleReturns - fake.recordInvocation("DeleteAccessRule", []interface{}{arg1}) - fake.deleteAccessRuleMutex.Unlock() - if stub != nil { - return stub(arg1) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 -} - -func (fake *FakeCloudControllerClient) DeleteAccessRuleCallCount() int { - fake.deleteAccessRuleMutex.RLock() - defer fake.deleteAccessRuleMutex.RUnlock() - return len(fake.deleteAccessRuleArgsForCall) -} - -func (fake *FakeCloudControllerClient) DeleteAccessRuleCalls(stub func(string) (ccv3.JobURL, ccv3.Warnings, error)) { - fake.deleteAccessRuleMutex.Lock() - defer fake.deleteAccessRuleMutex.Unlock() - fake.DeleteAccessRuleStub = stub -} - -func (fake *FakeCloudControllerClient) DeleteAccessRuleArgsForCall(i int) string { - fake.deleteAccessRuleMutex.RLock() - defer fake.deleteAccessRuleMutex.RUnlock() - argsForCall := fake.deleteAccessRuleArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeCloudControllerClient) DeleteAccessRuleReturns(result1 ccv3.JobURL, result2 ccv3.Warnings, result3 error) { - fake.deleteAccessRuleMutex.Lock() - defer fake.deleteAccessRuleMutex.Unlock() - fake.DeleteAccessRuleStub = nil - fake.deleteAccessRuleReturns = struct { - result1 ccv3.JobURL - result2 ccv3.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeCloudControllerClient) DeleteAccessRuleReturnsOnCall(i int, result1 ccv3.JobURL, result2 ccv3.Warnings, result3 error) { - fake.deleteAccessRuleMutex.Lock() - defer fake.deleteAccessRuleMutex.Unlock() - fake.DeleteAccessRuleStub = nil - if fake.deleteAccessRuleReturnsOnCall == nil { - fake.deleteAccessRuleReturnsOnCall = make(map[int]struct { - result1 ccv3.JobURL - result2 ccv3.Warnings - result3 error - }) - } - fake.deleteAccessRuleReturnsOnCall[i] = struct { - result1 ccv3.JobURL - result2 ccv3.Warnings - result3 error - }{result1, result2, result3} -} - func (fake *FakeCloudControllerClient) DeleteApplication(arg1 string) (ccv3.JobURL, ccv3.Warnings, error) { fake.deleteApplicationMutex.Lock() ret, specificReturn := fake.deleteApplicationReturnsOnCall[len(fake.deleteApplicationArgsForCall)] @@ -5702,6 +5635,73 @@ func (fake *FakeCloudControllerClient) DeleteRouteBindingReturnsOnCall(i int, re }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) DeleteRoutePolicy(arg1 string) (ccv3.JobURL, ccv3.Warnings, error) { + fake.deleteRoutePolicyMutex.Lock() + ret, specificReturn := fake.deleteRoutePolicyReturnsOnCall[len(fake.deleteRoutePolicyArgsForCall)] + fake.deleteRoutePolicyArgsForCall = append(fake.deleteRoutePolicyArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.DeleteRoutePolicyStub + fakeReturns := fake.deleteRoutePolicyReturns + fake.recordInvocation("DeleteRoutePolicy", []interface{}{arg1}) + fake.deleteRoutePolicyMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeCloudControllerClient) DeleteRoutePolicyCallCount() int { + fake.deleteRoutePolicyMutex.RLock() + defer fake.deleteRoutePolicyMutex.RUnlock() + return len(fake.deleteRoutePolicyArgsForCall) +} + +func (fake *FakeCloudControllerClient) DeleteRoutePolicyCalls(stub func(string) (ccv3.JobURL, ccv3.Warnings, error)) { + fake.deleteRoutePolicyMutex.Lock() + defer fake.deleteRoutePolicyMutex.Unlock() + fake.DeleteRoutePolicyStub = stub +} + +func (fake *FakeCloudControllerClient) DeleteRoutePolicyArgsForCall(i int) string { + fake.deleteRoutePolicyMutex.RLock() + defer fake.deleteRoutePolicyMutex.RUnlock() + argsForCall := fake.deleteRoutePolicyArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeCloudControllerClient) DeleteRoutePolicyReturns(result1 ccv3.JobURL, result2 ccv3.Warnings, result3 error) { + fake.deleteRoutePolicyMutex.Lock() + defer fake.deleteRoutePolicyMutex.Unlock() + fake.DeleteRoutePolicyStub = nil + fake.deleteRoutePolicyReturns = struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeCloudControllerClient) DeleteRoutePolicyReturnsOnCall(i int, result1 ccv3.JobURL, result2 ccv3.Warnings, result3 error) { + fake.deleteRoutePolicyMutex.Lock() + defer fake.deleteRoutePolicyMutex.Unlock() + fake.DeleteRoutePolicyStub = nil + if fake.deleteRoutePolicyReturnsOnCall == nil { + fake.deleteRoutePolicyReturnsOnCall = make(map[int]struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + }) + } + fake.deleteRoutePolicyReturnsOnCall[i] = struct { + result1 ccv3.JobURL + result2 ccv3.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeCloudControllerClient) DeleteSecurityGroup(arg1 string) (ccv3.JobURL, ccv3.Warnings, error) { fake.deleteSecurityGroupMutex.Lock() ret, specificReturn := fake.deleteSecurityGroupReturnsOnCall[len(fake.deleteSecurityGroupArgsForCall)] @@ -6377,76 +6377,6 @@ func (fake *FakeCloudControllerClient) EntitleIsolationSegmentToOrganizationsRet }{result1, result2, result3} } -func (fake *FakeCloudControllerClient) GetAccessRules(arg1 ...ccv3.Query) ([]resources.AccessRule, ccv3.IncludedResources, ccv3.Warnings, error) { - fake.getAccessRulesMutex.Lock() - ret, specificReturn := fake.getAccessRulesReturnsOnCall[len(fake.getAccessRulesArgsForCall)] - fake.getAccessRulesArgsForCall = append(fake.getAccessRulesArgsForCall, struct { - arg1 []ccv3.Query - }{arg1}) - stub := fake.GetAccessRulesStub - fakeReturns := fake.getAccessRulesReturns - fake.recordInvocation("GetAccessRules", []interface{}{arg1}) - fake.getAccessRulesMutex.Unlock() - if stub != nil { - return stub(arg1...) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3, ret.result4 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3, fakeReturns.result4 -} - -func (fake *FakeCloudControllerClient) GetAccessRulesCallCount() int { - fake.getAccessRulesMutex.RLock() - defer fake.getAccessRulesMutex.RUnlock() - return len(fake.getAccessRulesArgsForCall) -} - -func (fake *FakeCloudControllerClient) GetAccessRulesCalls(stub func(...ccv3.Query) ([]resources.AccessRule, ccv3.IncludedResources, ccv3.Warnings, error)) { - fake.getAccessRulesMutex.Lock() - defer fake.getAccessRulesMutex.Unlock() - fake.GetAccessRulesStub = stub -} - -func (fake *FakeCloudControllerClient) GetAccessRulesArgsForCall(i int) []ccv3.Query { - fake.getAccessRulesMutex.RLock() - defer fake.getAccessRulesMutex.RUnlock() - argsForCall := fake.getAccessRulesArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeCloudControllerClient) GetAccessRulesReturns(result1 []resources.AccessRule, result2 ccv3.IncludedResources, result3 ccv3.Warnings, result4 error) { - fake.getAccessRulesMutex.Lock() - defer fake.getAccessRulesMutex.Unlock() - fake.GetAccessRulesStub = nil - fake.getAccessRulesReturns = struct { - result1 []resources.AccessRule - result2 ccv3.IncludedResources - result3 ccv3.Warnings - result4 error - }{result1, result2, result3, result4} -} - -func (fake *FakeCloudControllerClient) GetAccessRulesReturnsOnCall(i int, result1 []resources.AccessRule, result2 ccv3.IncludedResources, result3 ccv3.Warnings, result4 error) { - fake.getAccessRulesMutex.Lock() - defer fake.getAccessRulesMutex.Unlock() - fake.GetAccessRulesStub = nil - if fake.getAccessRulesReturnsOnCall == nil { - fake.getAccessRulesReturnsOnCall = make(map[int]struct { - result1 []resources.AccessRule - result2 ccv3.IncludedResources - result3 ccv3.Warnings - result4 error - }) - } - fake.getAccessRulesReturnsOnCall[i] = struct { - result1 []resources.AccessRule - result2 ccv3.IncludedResources - result3 ccv3.Warnings - result4 error - }{result1, result2, result3, result4} -} - func (fake *FakeCloudControllerClient) GetAppFeature(arg1 string, arg2 string) (resources.ApplicationFeature, ccv3.Warnings, error) { fake.getAppFeatureMutex.Lock() ret, specificReturn := fake.getAppFeatureReturnsOnCall[len(fake.getAppFeatureArgsForCall)] @@ -9653,6 +9583,76 @@ func (fake *FakeCloudControllerClient) GetRouteDestinationsReturnsOnCall(i int, }{result1, result2, result3} } +func (fake *FakeCloudControllerClient) GetRoutePolicies(arg1 ...ccv3.Query) ([]resources.RoutePolicy, ccv3.IncludedResources, ccv3.Warnings, error) { + fake.getRoutePoliciesMutex.Lock() + ret, specificReturn := fake.getRoutePoliciesReturnsOnCall[len(fake.getRoutePoliciesArgsForCall)] + fake.getRoutePoliciesArgsForCall = append(fake.getRoutePoliciesArgsForCall, struct { + arg1 []ccv3.Query + }{arg1}) + stub := fake.GetRoutePoliciesStub + fakeReturns := fake.getRoutePoliciesReturns + fake.recordInvocation("GetRoutePolicies", []interface{}{arg1}) + fake.getRoutePoliciesMutex.Unlock() + if stub != nil { + return stub(arg1...) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3, ret.result4 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3, fakeReturns.result4 +} + +func (fake *FakeCloudControllerClient) GetRoutePoliciesCallCount() int { + fake.getRoutePoliciesMutex.RLock() + defer fake.getRoutePoliciesMutex.RUnlock() + return len(fake.getRoutePoliciesArgsForCall) +} + +func (fake *FakeCloudControllerClient) GetRoutePoliciesCalls(stub func(...ccv3.Query) ([]resources.RoutePolicy, ccv3.IncludedResources, ccv3.Warnings, error)) { + fake.getRoutePoliciesMutex.Lock() + defer fake.getRoutePoliciesMutex.Unlock() + fake.GetRoutePoliciesStub = stub +} + +func (fake *FakeCloudControllerClient) GetRoutePoliciesArgsForCall(i int) []ccv3.Query { + fake.getRoutePoliciesMutex.RLock() + defer fake.getRoutePoliciesMutex.RUnlock() + argsForCall := fake.getRoutePoliciesArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeCloudControllerClient) GetRoutePoliciesReturns(result1 []resources.RoutePolicy, result2 ccv3.IncludedResources, result3 ccv3.Warnings, result4 error) { + fake.getRoutePoliciesMutex.Lock() + defer fake.getRoutePoliciesMutex.Unlock() + fake.GetRoutePoliciesStub = nil + fake.getRoutePoliciesReturns = struct { + result1 []resources.RoutePolicy + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + }{result1, result2, result3, result4} +} + +func (fake *FakeCloudControllerClient) GetRoutePoliciesReturnsOnCall(i int, result1 []resources.RoutePolicy, result2 ccv3.IncludedResources, result3 ccv3.Warnings, result4 error) { + fake.getRoutePoliciesMutex.Lock() + defer fake.getRoutePoliciesMutex.Unlock() + fake.GetRoutePoliciesStub = nil + if fake.getRoutePoliciesReturnsOnCall == nil { + fake.getRoutePoliciesReturnsOnCall = make(map[int]struct { + result1 []resources.RoutePolicy + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + }) + } + fake.getRoutePoliciesReturnsOnCall[i] = struct { + result1 []resources.RoutePolicy + result2 ccv3.IncludedResources + result3 ccv3.Warnings + result4 error + }{result1, result2, result3, result4} +} + func (fake *FakeCloudControllerClient) GetRoutes(arg1 ...ccv3.Query) ([]resources.Route, ccv3.Warnings, error) { fake.getRoutesMutex.Lock() ret, specificReturn := fake.getRoutesReturnsOnCall[len(fake.getRoutesArgsForCall)] diff --git a/actor/v7action/v7actionfakes/fake_config.go b/actor/v7action/v7actionfakes/fake_config.go index cd46ebbbfc5..73a32a8baeb 100644 --- a/actor/v7action/v7actionfakes/fake_config.go +++ b/actor/v7action/v7actionfakes/fake_config.go @@ -1134,48 +1134,6 @@ func (fake *FakeConfig) UnsetOrganizationAndSpaceInformationCalls(stub func()) { func (fake *FakeConfig) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.aPIVersionMutex.RLock() - defer fake.aPIVersionMutex.RUnlock() - fake.accessTokenMutex.RLock() - defer fake.accessTokenMutex.RUnlock() - fake.currentUserMutex.RLock() - defer fake.currentUserMutex.RUnlock() - fake.dialTimeoutMutex.RLock() - defer fake.dialTimeoutMutex.RUnlock() - fake.isCFOnK8sMutex.RLock() - defer fake.isCFOnK8sMutex.RUnlock() - fake.pollingIntervalMutex.RLock() - defer fake.pollingIntervalMutex.RUnlock() - fake.refreshTokenMutex.RLock() - defer fake.refreshTokenMutex.RUnlock() - fake.sSHOAuthClientMutex.RLock() - defer fake.sSHOAuthClientMutex.RUnlock() - fake.setAccessTokenMutex.RLock() - defer fake.setAccessTokenMutex.RUnlock() - fake.setKubernetesAuthInfoMutex.RLock() - defer fake.setKubernetesAuthInfoMutex.RUnlock() - fake.setRefreshTokenMutex.RLock() - defer fake.setRefreshTokenMutex.RUnlock() - fake.setTargetInformationMutex.RLock() - defer fake.setTargetInformationMutex.RUnlock() - fake.setTokenInformationMutex.RLock() - defer fake.setTokenInformationMutex.RUnlock() - fake.setUAAClientCredentialsMutex.RLock() - defer fake.setUAAClientCredentialsMutex.RUnlock() - fake.setUAAGrantTypeMutex.RLock() - defer fake.setUAAGrantTypeMutex.RUnlock() - fake.skipSSLValidationMutex.RLock() - defer fake.skipSSLValidationMutex.RUnlock() - fake.stagingTimeoutMutex.RLock() - defer fake.stagingTimeoutMutex.RUnlock() - fake.startupTimeoutMutex.RLock() - defer fake.startupTimeoutMutex.RUnlock() - fake.targetMutex.RLock() - defer fake.targetMutex.RUnlock() - fake.uAAGrantTypeMutex.RLock() - defer fake.uAAGrantTypeMutex.RUnlock() - fake.unsetOrganizationAndSpaceInformationMutex.RLock() - defer fake.unsetOrganizationAndSpaceInformationMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_downloader.go b/actor/v7action/v7actionfakes/fake_downloader.go index 4545618272a..d77a875d875 100644 --- a/actor/v7action/v7actionfakes/fake_downloader.go +++ b/actor/v7action/v7actionfakes/fake_downloader.go @@ -94,8 +94,6 @@ func (fake *FakeDownloader) DownloadReturnsOnCall(i int, result1 string, result2 func (fake *FakeDownloader) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.downloadMutex.RLock() - defer fake.downloadMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go b/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go index 7102e140b63..4336bf2e2e8 100644 --- a/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go +++ b/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go @@ -84,8 +84,6 @@ func (fake *FakeKubernetesConfigGetter) GetReturnsOnCall(i int, result1 *api.Con func (fake *FakeKubernetesConfigGetter) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.getMutex.RLock() - defer fake.getMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_manifest_parser.go b/actor/v7action/v7actionfakes/fake_manifest_parser.go index 6d93fd4df35..73bf7ac4f52 100644 --- a/actor/v7action/v7actionfakes/fake_manifest_parser.go +++ b/actor/v7action/v7actionfakes/fake_manifest_parser.go @@ -155,10 +155,6 @@ func (fake *FakeManifestParser) RawAppManifestReturnsOnCall(i int, result1 []byt func (fake *FakeManifestParser) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.appNamesMutex.RLock() - defer fake.appNamesMutex.RUnlock() - fake.rawAppManifestMutex.RLock() - defer fake.rawAppManifestMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_routing_client.go b/actor/v7action/v7actionfakes/fake_routing_client.go index d7be3997593..73b5f6fafb3 100644 --- a/actor/v7action/v7actionfakes/fake_routing_client.go +++ b/actor/v7action/v7actionfakes/fake_routing_client.go @@ -161,10 +161,6 @@ func (fake *FakeRoutingClient) GetRouterGroupsReturnsOnCall(i int, result1 []rou func (fake *FakeRoutingClient) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.getRouterGroupByNameMutex.RLock() - defer fake.getRouterGroupByNameMutex.RUnlock() - fake.getRouterGroupsMutex.RLock() - defer fake.getRouterGroupsMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_shared_actor.go b/actor/v7action/v7actionfakes/fake_shared_actor.go index 1601a0e3e99..1c60d5b4bec 100644 --- a/actor/v7action/v7actionfakes/fake_shared_actor.go +++ b/actor/v7action/v7actionfakes/fake_shared_actor.go @@ -338,14 +338,6 @@ func (fake *FakeSharedActor) ZipDirectoryResourcesReturnsOnCall(i int, result1 s func (fake *FakeSharedActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.gatherArchiveResourcesMutex.RLock() - defer fake.gatherArchiveResourcesMutex.RUnlock() - fake.gatherDirectoryResourcesMutex.RLock() - defer fake.gatherDirectoryResourcesMutex.RUnlock() - fake.zipArchiveResourcesMutex.RLock() - defer fake.zipArchiveResourcesMutex.RUnlock() - fake.zipDirectoryResourcesMutex.RLock() - defer fake.zipDirectoryResourcesMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_simple_progress_bar.go b/actor/v7action/v7actionfakes/fake_simple_progress_bar.go index 0e2b05b6486..19d9442f101 100644 --- a/actor/v7action/v7actionfakes/fake_simple_progress_bar.go +++ b/actor/v7action/v7actionfakes/fake_simple_progress_bar.go @@ -126,10 +126,6 @@ func (fake *FakeSimpleProgressBar) TerminateCalls(stub func()) { func (fake *FakeSimpleProgressBar) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.initializeMutex.RLock() - defer fake.initializeMutex.RUnlock() - fake.terminateMutex.RLock() - defer fake.terminateMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_sshactor.go b/actor/v7action/v7actionfakes/fake_sshactor.go index f849722dd78..24cf1c167be 100644 --- a/actor/v7action/v7actionfakes/fake_sshactor.go +++ b/actor/v7action/v7actionfakes/fake_sshactor.go @@ -88,8 +88,6 @@ func (fake *FakeSSHActor) ExecuteSecureShellReturnsOnCall(i int, result1 error) func (fake *FakeSSHActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.executeSecureShellMutex.RLock() - defer fake.executeSecureShellMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_uaaclient.go b/actor/v7action/v7actionfakes/fake_uaaclient.go index 517b7ec9afa..ae1e07303ac 100644 --- a/actor/v7action/v7actionfakes/fake_uaaclient.go +++ b/actor/v7action/v7actionfakes/fake_uaaclient.go @@ -852,28 +852,6 @@ func (fake *FakeUAAClient) ValidateClientUserReturnsOnCall(i int, result1 error) func (fake *FakeUAAClient) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.authenticateMutex.RLock() - defer fake.authenticateMutex.RUnlock() - fake.createUserMutex.RLock() - defer fake.createUserMutex.RUnlock() - fake.deleteUserMutex.RLock() - defer fake.deleteUserMutex.RUnlock() - fake.getAPIVersionMutex.RLock() - defer fake.getAPIVersionMutex.RUnlock() - fake.getLoginPromptsMutex.RLock() - defer fake.getLoginPromptsMutex.RUnlock() - fake.getSSHPasscodeMutex.RLock() - defer fake.getSSHPasscodeMutex.RUnlock() - fake.listUsersMutex.RLock() - defer fake.listUsersMutex.RUnlock() - fake.refreshAccessTokenMutex.RLock() - defer fake.refreshAccessTokenMutex.RUnlock() - fake.revokeMutex.RLock() - defer fake.revokeMutex.RUnlock() - fake.updatePasswordMutex.RLock() - defer fake.updatePasswordMutex.RUnlock() - fake.validateClientUserMutex.RLock() - defer fake.validateClientUserMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_who_am_ier.go b/actor/v7action/v7actionfakes/fake_who_am_ier.go index 78dfee83e99..46640859a22 100644 --- a/actor/v7action/v7actionfakes/fake_who_am_ier.go +++ b/actor/v7action/v7actionfakes/fake_who_am_ier.go @@ -90,8 +90,6 @@ func (fake *FakeWhoAmIer) WhoAmIReturnsOnCall(i int, result1 resources.K8sUser, func (fake *FakeWhoAmIer) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.whoAmIMutex.RLock() - defer fake.whoAmIMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7pushaction/create_deployment_for_push_plan_test.go b/actor/v7pushaction/create_deployment_for_push_plan_test.go index beded0358b5..04bdbd5ddb6 100644 --- a/actor/v7pushaction/create_deployment_for_push_plan_test.go +++ b/actor/v7pushaction/create_deployment_for_push_plan_test.go @@ -8,7 +8,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7pushaction/v7pushactionfakes" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/actor/v7pushaction/handle_disk_override.go b/actor/v7pushaction/handle_disk_override.go index 33b46727511..e4dd2088450 100644 --- a/actor/v7pushaction/handle_disk_override.go +++ b/actor/v7pushaction/handle_disk_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_disk_override_test.go b/actor/v7pushaction/handle_disk_override_test.go index 3781e44e0e4..ab552bcae8e 100644 --- a/actor/v7pushaction/handle_disk_override_test.go +++ b/actor/v7pushaction/handle_disk_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/handle_instances_override.go b/actor/v7pushaction/handle_instances_override.go index a6322a9882a..60cc4f104fe 100644 --- a/actor/v7pushaction/handle_instances_override.go +++ b/actor/v7pushaction/handle_instances_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_instances_override_test.go b/actor/v7pushaction/handle_instances_override_test.go index 4808558af41..40b7f3c5d56 100644 --- a/actor/v7pushaction/handle_instances_override_test.go +++ b/actor/v7pushaction/handle_instances_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/types" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/handle_log_rate_limit_override.go b/actor/v7pushaction/handle_log_rate_limit_override.go index 13b1e9dbf8e..218db034cc2 100644 --- a/actor/v7pushaction/handle_log_rate_limit_override.go +++ b/actor/v7pushaction/handle_log_rate_limit_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_log_rate_limit_override_test.go b/actor/v7pushaction/handle_log_rate_limit_override_test.go index 679d740a90e..0281b7648bc 100644 --- a/actor/v7pushaction/handle_log_rate_limit_override_test.go +++ b/actor/v7pushaction/handle_log_rate_limit_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/handle_memory_override.go b/actor/v7pushaction/handle_memory_override.go index e5ab16ca549..4460687c66c 100644 --- a/actor/v7pushaction/handle_memory_override.go +++ b/actor/v7pushaction/handle_memory_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_memory_override_test.go b/actor/v7pushaction/handle_memory_override_test.go index 1ef70722757..602396fbae9 100644 --- a/actor/v7pushaction/handle_memory_override_test.go +++ b/actor/v7pushaction/handle_memory_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan.go b/actor/v7pushaction/setup_deployment_information_for_push_plan.go index 82900aa99a1..328b0dd28c0 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/cf/errors" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/types" diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go index b07bdade816..c170366535c 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go @@ -2,8 +2,8 @@ package v7pushaction_test import ( "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/cf/errors" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/cf/errors" + "code.cloudfoundry.org/cli/v9/types" . "code.cloudfoundry.org/cli/v9/actor/v7pushaction" diff --git a/api/cfnetworking/cfnetv1/client.go b/api/cfnetworking/cfnetv1/client.go index 1c301716184..5068b41e75d 100644 --- a/api/cfnetworking/cfnetv1/client.go +++ b/api/cfnetworking/cfnetv1/client.go @@ -6,7 +6,7 @@ // For more information on the CF Networking API see // https://github.com/cloudfoundry-incubator/cf-networking-release/blob/develop/docs/API.md // -// Method Naming Conventions +// # Method Naming Conventions // // The client takes a '' // approach to method names. If the and @@ -15,38 +15,40 @@ // method name. // // For Example: -// Method Name: GetApplication -// Endpoint: /v2/applications/:guid -// Action Name: Get -// Top Level Endpoint: applications -// Return Value: Application -// -// Method Name: GetServiceInstances -// Endpoint: /v2/service_instances -// Action Name: Get -// Top Level Endpoint: service_instances -// Return Value: []ServiceInstance -// -// Method Name: GetSpaceServiceInstances -// Endpoint: /v2/spaces/:guid/service_instances -// Action Name: Get -// Top Level Endpoint: spaces -// Return Value: []ServiceInstance +// +// Method Name: GetApplication +// Endpoint: /v2/applications/:guid +// Action Name: Get +// Top Level Endpoint: applications +// Return Value: Application +// +// Method Name: GetServiceInstances +// Endpoint: /v2/service_instances +// Action Name: Get +// Top Level Endpoint: service_instances +// Return Value: []ServiceInstance +// +// Method Name: GetSpaceServiceInstances +// Endpoint: /v2/spaces/:guid/service_instances +// Action Name: Get +// Top Level Endpoint: spaces +// Return Value: []ServiceInstance // // Use the following table to determine which HTTP Command equates to which // Action Name: -// HTTP Command -> Action Name -// POST -> Create -// GET -> Get -// PUT -> Update -// DELETE -> Delete // -// Method Locations +// HTTP Command -> Action Name +// POST -> Create +// GET -> Get +// PUT -> Update +// DELETE -> Delete +// +// # Method Locations // // Methods exist in the same file as their return type, regardless of which // endpoint they use. // -// Error Handling +// # Error Handling // // All error handling that requires parsing the error_code/code returned back // from the Cloud Controller should be placed in the errorWrapper. Everything @@ -55,7 +57,7 @@ // exist in the cloudcontroller's errors.go. Errors related to the individaul // operation should exist at the top of that operation's file. // -// No inline-relations-depth And summary Endpoints +// # No inline-relations-depth And summary Endpoints // // This package will not use ever use 'inline-relations-depth' or the // '/summary' endpoints for any operations. These requests can be extremely @@ -65,72 +67,72 @@ package cfnetv1 import ( - "fmt" - "runtime" - "time" + "fmt" + "runtime" + "time" - "code.cloudfoundry.org/cli/v9/api/cfnetworking" - "code.cloudfoundry.org/cli/v9/api/cfnetworking/cfnetv1/internal" + "code.cloudfoundry.org/cli/v9/api/cfnetworking" + "code.cloudfoundry.org/cli/v9/api/cfnetworking/cfnetv1/internal" - "github.com/tedsuo/rata" + "github.com/tedsuo/rata" ) // Client is a client that can be used to talk to a CF Networking API. type Client struct { - connection cfnetworking.Connection - router *rata.RequestGenerator - url string - userAgent string + connection cfnetworking.Connection + router *rata.RequestGenerator + url string + userAgent string } // Config allows the Client to be configured type Config struct { - // AppName is the name of the application/process using the client. - AppName string + // AppName is the name of the application/process using the client. + AppName string - // AppVersion is the version of the application/process using the client. - AppVersion string + // AppVersion is the version of the application/process using the client. + AppVersion string - // DialTimeout is the DNS timeout used to make all requests to the Cloud - // Controller. - DialTimeout time.Duration + // DialTimeout is the DNS timeout used to make all requests to the Cloud + // Controller. + DialTimeout time.Duration - // SkipSSLValidation controls whether a client verifies the server's - // certificate chain and host name. If SkipSSLValidation is true, TLS accepts - // any certificate presented by the server and any host name in that - // certificate for *all* client requests going forward. - // - // In this mode, TLS is susceptible to man-in-the-middle attacks. This should - // be used only for testing. - SkipSSLValidation bool + // SkipSSLValidation controls whether a client verifies the server's + // certificate chain and host name. If SkipSSLValidation is true, TLS accepts + // any certificate presented by the server and any host name in that + // certificate for *all* client requests going forward. + // + // In this mode, TLS is susceptible to man-in-the-middle attacks. This should + // be used only for testing. + SkipSSLValidation bool - // URL is a fully qualified URL to the CF Networking API. - URL string + // URL is a fully qualified URL to the CF Networking API. + URL string - // Wrappers that apply to the client connection. - Wrappers []ConnectionWrapper + // Wrappers that apply to the client connection. + Wrappers []ConnectionWrapper } // NewClient returns a new CF Networking client. func NewClient(config Config) *Client { - userAgent := fmt.Sprintf("%s/%s (%s; %s %s)", config.AppName, config.AppVersion, runtime.Version(), runtime.GOARCH, runtime.GOOS) + userAgent := fmt.Sprintf("%s/%s (%s; %s %s)", config.AppName, config.AppVersion, runtime.Version(), runtime.GOARCH, runtime.GOOS) - connection := cfnetworking.NewConnection(cfnetworking.Config{ - DialTimeout: config.DialTimeout, - SkipSSLValidation: config.SkipSSLValidation, - }) + connection := cfnetworking.NewConnection(cfnetworking.Config{ + DialTimeout: config.DialTimeout, + SkipSSLValidation: config.SkipSSLValidation, + }) - wrappedConnection := cfnetworking.NewErrorWrapper().Wrap(connection) - for _, wrapper := range config.Wrappers { - wrappedConnection = wrapper.Wrap(wrappedConnection) - } + wrappedConnection := cfnetworking.NewErrorWrapper().Wrap(connection) + for _, wrapper := range config.Wrappers { + wrappedConnection = wrapper.Wrap(wrappedConnection) + } - client := &Client{ - connection: wrappedConnection, - router: rata.NewRequestGenerator(config.URL, internal.Routes), - url: config.URL, - userAgent: userAgent, - } + client := &Client{ + connection: wrappedConnection, + router: rata.NewRequestGenerator(config.URL, internal.Routes), + url: config.URL, + userAgent: userAgent, + } - return client + return client } diff --git a/api/cfnetworking/errors.go b/api/cfnetworking/errors.go index 7abd7877c64..fd5fcaacc56 100644 --- a/api/cfnetworking/errors.go +++ b/api/cfnetworking/errors.go @@ -1,64 +1,64 @@ package cfnetworking import ( - "encoding/json" - "net/http" + "encoding/json" + "net/http" - "code.cloudfoundry.org/cli/v9/api/cfnetworking/networkerror" + "code.cloudfoundry.org/cli/v9/api/cfnetworking/networkerror" ) // errorWrapper is the wrapper that converts responses with 4xx and 5xx status // codes to an error. type errorWrapper struct { - connection Connection + connection Connection } func NewErrorWrapper() *errorWrapper { - return new(errorWrapper) + return new(errorWrapper) } // Wrap wraps a Cloud Controller connection in this error handling wrapper. func (e *errorWrapper) Wrap(innerconnection Connection) Connection { - e.connection = innerconnection - return e + e.connection = innerconnection + return e } // Make converts RawHTTPStatusError, which represents responses with 4xx and // 5xx status codes, to specific errors. func (e *errorWrapper) Make(request *Request, passedResponse *Response) error { - err := e.connection.Make(request, passedResponse) + err := e.connection.Make(request, passedResponse) - if rawHTTPStatusErr, ok := err.(networkerror.RawHTTPStatusError); ok { - return convert(rawHTTPStatusErr) - } - return err + if rawHTTPStatusErr, ok := err.(networkerror.RawHTTPStatusError); ok { + return convert(rawHTTPStatusErr) + } + return err } func convert(rawHTTPStatusErr networkerror.RawHTTPStatusError) error { - // Try to unmarshal the raw error into a CC error. If unmarshaling fails, - // return the raw error. - var errorResponse networkerror.ErrorResponse - err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse) - if err != nil { - return rawHTTPStatusErr - } + // Try to unmarshal the raw error into a CC error. If unmarshaling fails, + // return the raw error. + var errorResponse networkerror.ErrorResponse + err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse) + if err != nil { + return rawHTTPStatusErr + } - switch rawHTTPStatusErr.StatusCode { - case http.StatusBadRequest: // 400 - return networkerror.BadRequestError(errorResponse) - case http.StatusUnauthorized: // 401 - return networkerror.UnauthorizedError(errorResponse) - case http.StatusForbidden: // 403 - return networkerror.ForbiddenError(errorResponse) - case http.StatusNotAcceptable: // 406 - return networkerror.NotAcceptableError(errorResponse) - case http.StatusConflict: // 409 - return networkerror.ConflictError(errorResponse) - default: - return networkerror.UnexpectedResponseError{ - ErrorResponse: errorResponse, - RequestIDs: rawHTTPStatusErr.RequestIDs, - ResponseCode: rawHTTPStatusErr.StatusCode, - } - } + switch rawHTTPStatusErr.StatusCode { + case http.StatusBadRequest: // 400 + return networkerror.BadRequestError(errorResponse) + case http.StatusUnauthorized: // 401 + return networkerror.UnauthorizedError(errorResponse) + case http.StatusForbidden: // 403 + return networkerror.ForbiddenError(errorResponse) + case http.StatusNotAcceptable: // 406 + return networkerror.NotAcceptableError(errorResponse) + case http.StatusConflict: // 409 + return networkerror.ConflictError(errorResponse) + default: + return networkerror.UnexpectedResponseError{ + ErrorResponse: errorResponse, + RequestIDs: rawHTTPStatusErr.RequestIDs, + ResponseCode: rawHTTPStatusErr.StatusCode, + } + } } diff --git a/api/cfnetworking/wrapper/retry_request.go b/api/cfnetworking/wrapper/retry_request.go index 600a79825d5..eb4300ecf09 100644 --- a/api/cfnetworking/wrapper/retry_request.go +++ b/api/cfnetworking/wrapper/retry_request.go @@ -1,54 +1,54 @@ package wrapper import ( - "net/http" + "net/http" - "code.cloudfoundry.org/cli/v9/api/cfnetworking" + "code.cloudfoundry.org/cli/v9/api/cfnetworking" ) // RetryRequest is a wrapper that retries failed requests if they contain a 5XX // status code. type RetryRequest struct { - maxRetries int - connection cfnetworking.Connection + maxRetries int + connection cfnetworking.Connection } // NewRetryRequest returns a pointer to a RetryRequest wrapper. func NewRetryRequest(maxRetries int) *RetryRequest { - return &RetryRequest{ - maxRetries: maxRetries, - } + return &RetryRequest{ + maxRetries: maxRetries, + } } // Wrap sets the connection in the RetryRequest and returns itself. func (retry *RetryRequest) Wrap(innerconnection cfnetworking.Connection) cfnetworking.Connection { - retry.connection = innerconnection - return retry + retry.connection = innerconnection + return retry } // Make retries the request if it comes back with certain status codes. func (retry *RetryRequest) Make(request *cfnetworking.Request, passedResponse *cfnetworking.Response) error { - var err error - - for i := 0; i < retry.maxRetries+1; i += 1 { - err = retry.connection.Make(request, passedResponse) - if err == nil { - return nil - } - - if passedResponse.HTTPResponse != nil && - (passedResponse.HTTPResponse.StatusCode == http.StatusBadGateway || - passedResponse.HTTPResponse.StatusCode == http.StatusServiceUnavailable || - passedResponse.HTTPResponse.StatusCode == http.StatusGatewayTimeout || - (passedResponse.HTTPResponse.StatusCode >= 400 && passedResponse.HTTPResponse.StatusCode < 500)) { - break - } - - // Reset the request body prior to the next retry - resetErr := request.ResetBody() - if resetErr != nil { - return resetErr - } - } - return err + var err error + + for i := 0; i < retry.maxRetries+1; i += 1 { + err = retry.connection.Make(request, passedResponse) + if err == nil { + return nil + } + + if passedResponse.HTTPResponse != nil && + (passedResponse.HTTPResponse.StatusCode == http.StatusBadGateway || + passedResponse.HTTPResponse.StatusCode == http.StatusServiceUnavailable || + passedResponse.HTTPResponse.StatusCode == http.StatusGatewayTimeout || + (passedResponse.HTTPResponse.StatusCode >= 400 && passedResponse.HTTPResponse.StatusCode < 500)) { + break + } + + // Reset the request body prior to the next retry + resetErr := request.ResetBody() + if resetErr != nil { + return resetErr + } + } + return err } diff --git a/api/cloudcontroller/ccv3/access_rule.go b/api/cloudcontroller/ccv3/access_rule.go deleted file mode 100644 index 2c391af8d18..00000000000 --- a/api/cloudcontroller/ccv3/access_rule.go +++ /dev/null @@ -1,59 +0,0 @@ -package ccv3 - -import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/internal" - "code.cloudfoundry.org/cli/v9/resources" -) - -// CreateAccessRule creates an access rule for a route -func (client *Client) CreateAccessRule(accessRule resources.AccessRule) (resources.AccessRule, Warnings, error) { - var responseBody resources.AccessRule - - _, warnings, err := client.MakeRequest(RequestParams{ - RequestName: internal.PostAccessRuleRequest, - RequestBody: accessRule, - ResponseBody: &responseBody, - }) - - return responseBody, warnings, err -} - -// GetAccessRules lists access rules -func (client *Client) GetAccessRules(query ...Query) ([]resources.AccessRule, IncludedResources, Warnings, error) { - var accessRules []resources.AccessRule - - includedResources, warnings, err := client.MakeListRequest(RequestParams{ - RequestName: internal.GetAccessRulesRequest, - Query: query, - ResponseBody: resources.AccessRule{}, - AppendToList: func(item interface{}) error { - accessRules = append(accessRules, item.(resources.AccessRule)) - return nil - }, - }) - - return accessRules, includedResources, warnings, err -} - -// GetAccessRule gets a single access rule by GUID -func (client *Client) GetAccessRule(guid string) (resources.AccessRule, Warnings, error) { - var responseBody resources.AccessRule - - _, warnings, err := client.MakeRequest(RequestParams{ - RequestName: internal.GetAccessRuleRequest, - URIParams: internal.Params{"access_rule_guid": guid}, - ResponseBody: &responseBody, - }) - - return responseBody, warnings, err -} - -// DeleteAccessRule deletes an access rule -func (client *Client) DeleteAccessRule(guid string) (JobURL, Warnings, error) { - jobURLString, warnings, err := client.MakeRequest(RequestParams{ - RequestName: internal.DeleteAccessRuleRequest, - URIParams: internal.Params{"access_rule_guid": guid}, - }) - - return JobURL(jobURLString), warnings, err -} diff --git a/api/cloudcontroller/ccv3/deployment_test.go b/api/cloudcontroller/ccv3/deployment_test.go index 3dea39c59a3..f725d62763f 100644 --- a/api/cloudcontroller/ccv3/deployment_test.go +++ b/api/cloudcontroller/ccv3/deployment_test.go @@ -7,7 +7,7 @@ import ( "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/types" . "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" . "github.com/onsi/ginkgo/v2" diff --git a/api/cloudcontroller/ccv3/info.go b/api/cloudcontroller/ccv3/info.go index d1db6b3eafa..380acc1817d 100644 --- a/api/cloudcontroller/ccv3/info.go +++ b/api/cloudcontroller/ccv3/info.go @@ -4,7 +4,7 @@ import ( "net/http" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/internal" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/internal" ) type Info struct { diff --git a/api/cloudcontroller/ccv3/internal/api_routes.go b/api/cloudcontroller/ccv3/internal/api_routes.go index 40605b74a50..8700d9e6088 100644 --- a/api/cloudcontroller/ccv3/internal/api_routes.go +++ b/api/cloudcontroller/ccv3/internal/api_routes.go @@ -9,7 +9,7 @@ import "net/http" // If the request returns a single entity by GUID, use the singular (for example // /v3/organizations/:organization_guid is GetOrganization). const ( - DeleteAccessRuleRequest = "DeleteAccessRuleRequest" + DeleteRoutePolicyRequest = "DeleteRoutePolicyRequest" DeleteApplicationProcessInstanceRequest = "DeleteApplicationProcessInstance" DeleteApplicationRequest = "DeleteApplication" DeleteBuildpackRequest = "DeleteBuildpack" @@ -36,8 +36,8 @@ const ( DeleteSpaceRequest = "DeleteSpace" DeleteSpaceQuotaFromSpaceRequest = "DeleteSpaceQuotaFromSpace" DeleteUserRequest = "DeleteUser" - GetAccessRuleRequest = "GetAccessRuleRequest" - GetAccessRulesRequest = "GetAccessRulesRequest" + GetRoutePolicyRequest = "GetRoutePolicyRequest" + GetRoutePoliciesRequest = "GetRoutePoliciesRequest" GetApplicationDropletCurrentRequest = "GetApplicationDropletCurrent" GetApplicationEnvRequest = "GetApplicationEnv" GetApplicationFeaturesRequest = "GetApplicationFeatures" @@ -137,7 +137,7 @@ const ( PatchSpaceQuotaRequest = "PatchSpaceQuota" PatchStackRequest = "PatchStack" PatchMoveRouteRequest = "PatchMoveRouteRequest" - PostAccessRuleRequest = "PostAccessRuleRequest" + PostRoutePolicyRequest = "PostRoutePolicyRequest" PostApplicationActionApplyManifest = "PostApplicationActionApplyM" PostApplicationActionRestartRequest = "PostApplicationActionRestart" PostApplicationActionStartRequest = "PostApplicationActionStart" @@ -190,10 +190,10 @@ const ( // APIRoutes is a list of routes used by the router to construct request URLs. var APIRoutes = map[string]Route{ - GetAccessRulesRequest: {Path: "/v3/access_rules", Method: http.MethodGet}, - PostAccessRuleRequest: {Path: "/v3/access_rules", Method: http.MethodPost}, - GetAccessRuleRequest: {Path: "/v3/access_rules/:access_rule_guid", Method: http.MethodGet}, - DeleteAccessRuleRequest: {Path: "/v3/access_rules/:access_rule_guid", Method: http.MethodDelete}, + GetRoutePoliciesRequest: {Path: "/v3/route_policies", Method: http.MethodGet}, + PostRoutePolicyRequest: {Path: "/v3/route_policies", Method: http.MethodPost}, + GetRoutePolicyRequest: {Path: "/v3/route_policies/:route_policy_guid", Method: http.MethodGet}, + DeleteRoutePolicyRequest: {Path: "/v3/route_policies/:route_policy_guid", Method: http.MethodDelete}, GetApplicationsRequest: {Path: "/v3/apps", Method: http.MethodGet}, PostApplicationRequest: {Path: "/v3/apps", Method: http.MethodPost}, DeleteApplicationRequest: {Path: "/v3/apps/:app_guid", Method: http.MethodDelete}, diff --git a/api/cloudcontroller/ccv3/query.go b/api/cloudcontroller/ccv3/query.go index 27bcf995712..fdc7a43f1ff 100644 --- a/api/cloudcontroller/ccv3/query.go +++ b/api/cloudcontroller/ccv3/query.go @@ -29,8 +29,10 @@ const ( SequenceIDFilter QueryKey = "sequence_ids" // RouteGUIDFilter is a query parameter for listing objects by Route GUID. RouteGUIDFilter QueryKey = "route_guids" - // SelectorsFilter is a query parameter for listing access rules by selector. - SelectorsFilter QueryKey = "selectors" + // SourcesFilter is a query parameter for listing route policies by source. + SourcesFilter QueryKey = "sources" + // SourceGuidsFilter is a query parameter for listing route policies by source GUID. + SourceGuidsFilter QueryKey = "source_guids" // ServiceInstanceGUIDFilter is a query parameter for listing objects by Service Instance GUID. ServiceInstanceGUIDFilter QueryKey = "service_instance_guids" // SpaceGUIDFilter is a query parameter for listing objects by Space GUID. diff --git a/api/cloudcontroller/ccv3/route_policy.go b/api/cloudcontroller/ccv3/route_policy.go new file mode 100644 index 00000000000..1b0b555a0ab --- /dev/null +++ b/api/cloudcontroller/ccv3/route_policy.go @@ -0,0 +1,59 @@ +package ccv3 + +import ( + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/internal" + "code.cloudfoundry.org/cli/v9/resources" +) + +// CreateRoutePolicy creates a route policy for a route +func (client *Client) CreateRoutePolicy(routePolicy resources.RoutePolicy) (resources.RoutePolicy, Warnings, error) { + var responseBody resources.RoutePolicy + + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.PostRoutePolicyRequest, + RequestBody: routePolicy, + ResponseBody: &responseBody, + }) + + return responseBody, warnings, err +} + +// GetRoutePolicies lists route policies +func (client *Client) GetRoutePolicies(query ...Query) ([]resources.RoutePolicy, IncludedResources, Warnings, error) { + var routePolicies []resources.RoutePolicy + + includedResources, warnings, err := client.MakeListRequest(RequestParams{ + RequestName: internal.GetRoutePoliciesRequest, + Query: query, + ResponseBody: resources.RoutePolicy{}, + AppendToList: func(item interface{}) error { + routePolicies = append(routePolicies, item.(resources.RoutePolicy)) + return nil + }, + }) + + return routePolicies, includedResources, warnings, err +} + +// GetRoutePolicy gets a single route policy by GUID +func (client *Client) GetRoutePolicy(guid string) (resources.RoutePolicy, Warnings, error) { + var responseBody resources.RoutePolicy + + _, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.GetRoutePolicyRequest, + URIParams: internal.Params{"route_policy_guid": guid}, + ResponseBody: &responseBody, + }) + + return responseBody, warnings, err +} + +// DeleteRoutePolicy deletes a route policy +func (client *Client) DeleteRoutePolicy(guid string) (JobURL, Warnings, error) { + jobURLString, warnings, err := client.MakeRequest(RequestParams{ + RequestName: internal.DeleteRoutePolicyRequest, + URIParams: internal.Params{"route_policy_guid": guid}, + }) + + return JobURL(jobURLString), warnings, err +} diff --git a/cf/actors/push.go b/cf/actors/push.go index b51322b084f..36a093e2ce5 100644 --- a/cf/actors/push.go +++ b/cf/actors/push.go @@ -7,9 +7,9 @@ import ( "path/filepath" "runtime" - "errors" + "errors" - "code.cloudfoundry.org/cli/v9/cf/api/applicationbits" + "code.cloudfoundry.org/cli/v9/cf/api/applicationbits" "code.cloudfoundry.org/cli/v9/cf/api/resources" "code.cloudfoundry.org/cli/v9/cf/appfiles" . "code.cloudfoundry.org/cli/v9/cf/i18n" diff --git a/cf/net/warnings_collector.go b/cf/net/warnings_collector.go index 5b164e0b39b..6b2991f7c48 100644 --- a/cf/net/warnings_collector.go +++ b/cf/net/warnings_collector.go @@ -4,9 +4,9 @@ import ( "os" "strings" - "errors" + "errors" - "code.cloudfoundry.org/cli/v9/cf/terminal" + "code.cloudfoundry.org/cli/v9/cf/terminal" ) const DeprecatedEndpointWarning = "Endpoint deprecated" diff --git a/command/common/command_list_v7.go b/command/common/command_list_v7.go index 3177549afa1..1070a5b8e1b 100644 --- a/command/common/command_list_v7.go +++ b/command/common/command_list_v7.go @@ -15,8 +15,8 @@ type commandList struct { V3Push v7.PushCommand `command:"v3-push" description:"Push a new app or sync changes to an existing app" hidden:"true"` - AccessRules v7.AccessRulesCommand `command:"access-rules" description:"List all access rules in the target space"` - AddAccessRule v7.AddAccessRuleCommand `command:"add-access-rule" description:"Add an access rule to allow specific apps, spaces, or orgs to access a route"` + RoutePolicies v7.RoutePoliciesCommand `command:"route-policies" description:"List all route policies in the target space"` + AddRoutePolicy v7.AddRoutePolicyCommand `command:"add-route-policy" description:"Add a route policy to allow specific apps, spaces, or orgs to access a route"` API v7.APICommand `command:"api" description:"Set or view target api url"` AddNetworkPolicy v7.AddNetworkPolicyCommand `command:"add-network-policy" description:"Create policy to allow direct network traffic from one app to another"` AddPluginRepo plugin.AddPluginRepoCommand `command:"add-plugin-repo" description:"Add a new plugin repository"` @@ -115,7 +115,7 @@ type commandList struct { PurgeServiceOffering v7.PurgeServiceOfferingCommand `command:"purge-service-offering" description:"Recursively remove a service offering and child objects from Cloud Foundry database without making requests to a service broker"` Push v7.PushCommand `command:"push" alias:"p" description:"Push a new app or sync changes to an existing app"` RemoveNetworkPolicy v7.RemoveNetworkPolicyCommand `command:"remove-network-policy" description:"Remove network traffic policy of an app"` - RemoveAccessRule v7.RemoveAccessRuleCommand `command:"remove-access-rule" description:"Remove an access rule from a route"` + RemoveRoutePolicy v7.RemoveRoutePolicyCommand `command:"remove-route-policy" description:"Remove a route policy from a route"` RemovePluginRepo plugin.RemovePluginRepoCommand `command:"remove-plugin-repo" description:"Remove a plugin repository"` Rename v7.RenameCommand `command:"rename" description:"Rename an app"` RenameOrg v7.RenameOrgCommand `command:"rename-org" description:"Rename an org"` diff --git a/command/common/internal/help_all_display.go b/command/common/internal/help_all_display.go index 923264d910b..b6bcd70f140 100644 --- a/command/common/internal/help_all_display.go +++ b/command/common/internal/help_all_display.go @@ -72,7 +72,7 @@ var HelpCategoryList = []HelpCategory{ {"update-destination"}, {"share-route", "unshare-route"}, {"move-route"}, - {"access-rules", "add-access-rule", "remove-access-rule"}, + {"route-policies", "add-route-policy", "remove-route-policy"}, }, }, { diff --git a/command/flag/arguments.go b/command/flag/arguments.go index 0afd5568955..23ee422eace 100644 --- a/command/flag/arguments.go +++ b/command/flag/arguments.go @@ -414,8 +414,12 @@ type TaskArgs struct { TaskID int `positional-arg-name:"TASK_ID" required:"true" description:"The Task ID for the application"` } -type AddAccessRuleArgs struct { - Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` +type AddRoutePolicyArgs struct { + Domain string `positional-arg-name:"DOMAIN" required:"true" description:"Domain for the route"` +} + +type RemoveRoutePolicyArgs struct { + Domain string `positional-arg-name:"DOMAIN" required:"true" description:"Domain for the route"` } type RemoveAccessRuleArgs struct { diff --git a/command/v7/access_rules_command_test.go b/command/v7/access_rules_command_test.go deleted file mode 100644 index e161144cf6b..00000000000 --- a/command/v7/access_rules_command_test.go +++ /dev/null @@ -1,350 +0,0 @@ -package v7_test - -import ( - "errors" - - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" - "code.cloudfoundry.org/cli/v9/command/commandfakes" - v7 "code.cloudfoundry.org/cli/v9/command/v7" - "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" - "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/util/configv3" - "code.cloudfoundry.org/cli/v9/util/ui" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gbytes" -) - -var _ = Describe("access-rules Command", func() { - var ( - cmd v7.AccessRulesCommand - testUI *ui.UI - fakeConfig *commandfakes.FakeConfig - fakeSharedActor *commandfakes.FakeSharedActor - fakeActor *v7fakes.FakeActor - binaryName string - executeErr error - ) - - BeforeEach(func() { - testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) - fakeConfig = new(commandfakes.FakeConfig) - fakeSharedActor = new(commandfakes.FakeSharedActor) - fakeActor = new(v7fakes.FakeActor) - - binaryName = "faceman" - fakeConfig.BinaryNameReturns(binaryName) - - cmd = v7.AccessRulesCommand{ - BaseCommand: v7.BaseCommand{ - UI: testUI, - Config: fakeConfig, - Actor: fakeActor, - SharedActor: fakeSharedActor, - }, - } - - fakeConfig.TargetedOrganizationReturns(configv3.Organization{ - Name: "some-org", - GUID: "some-org-guid", - }) - fakeConfig.TargetedSpaceReturns(configv3.Space{ - Name: "some-space", - GUID: "some-space-guid", - }) - - fakeActor.GetCurrentUserReturns(configv3.User{Name: "steve"}, nil) - }) - - JustBeforeEach(func() { - executeErr = cmd.Execute(nil) - }) - - When("checking target fails", func() { - BeforeEach(func() { - fakeSharedActor.CheckTargetReturns(actionerror.NoOrganizationTargetedError{BinaryName: binaryName}) - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError(actionerror.NoOrganizationTargetedError{BinaryName: binaryName})) - - Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) - checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) - Expect(checkTargetedOrg).To(BeTrue()) - Expect(checkTargetedSpace).To(BeTrue()) - }) - }) - - When("the user is not logged in", func() { - var expectedErr error - - BeforeEach(func() { - expectedErr = errors.New("some current user error") - fakeActor.GetCurrentUserReturns(configv3.User{}, expectedErr) - }) - - It("returns an error", func() { - Expect(executeErr).To(Equal(expectedErr)) - }) - }) - - When("getting access rules returns an error", func() { - var expectedErr error - - BeforeEach(func() { - expectedErr = ccerror.RequestError{} - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{"warning-1", "warning-2"}, expectedErr) - }) - - It("returns the error and prints warnings", func() { - Expect(executeErr).To(Equal(ccerror.RequestError{})) - - Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - - Expect(testUI.Err).To(Say("warning-1")) - Expect(testUI.Err).To(Say("warning-2")) - }) - }) - - When("getting access rules succeeds", func() { - BeforeEach(func() { - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ - { - AccessRule: resources.AccessRule{ - GUID: "rule-guid-1", - Selector: "cf:app:app-guid-1", - }, - Route: resources.Route{ - GUID: "route-guid-1", - Host: "myapp", - Path: "/api", - }, - DomainName: "example.com", - ScopeType: "app", - SourceName: "my-app", - }, - { - AccessRule: resources.AccessRule{ - GUID: "rule-guid-2", - Selector: "cf:any", - }, - Route: resources.Route{ - GUID: "route-guid-2", - Host: "webapp", - Path: "", - }, - DomainName: "test.com", - ScopeType: "any", - SourceName: "", - }, - }, v7action.Warnings{"warning-1"}, nil) - }) - - It("displays the access rules in a table", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - Expect(testUI.Out).To(Say(`host\s+domain\s+path\s+selector\s+scope\s+source`)) - Expect(testUI.Out).To(Say(`myapp\s+example\.com\s+/api\s+cf:app:app-guid-1\s+app\s+my-app`)) - Expect(testUI.Out).To(Say(`webapp\s+test\.com\s+cf:any\s+any`)) - - Expect(testUI.Err).To(Say("warning-1")) - - Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) - spaceGUID, domainName, hostname, path, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) - Expect(spaceGUID).To(Equal("some-space-guid")) - Expect(domainName).To(Equal("")) - Expect(hostname).To(Equal("")) - Expect(path).To(Equal("")) - Expect(labelSelector).To(Equal("")) - }) - }) - - When("no access rules exist", func() { - BeforeEach(func() { - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) - }) - - It("displays a message indicating no access rules found", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - Expect(testUI.Out).To(Say(`No access rules found\.`)) - }) - }) - - When("filtering by domain", func() { - BeforeEach(func() { - cmd.Domain = "example.com" - - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ - { - AccessRule: resources.AccessRule{ - GUID: "rule-guid-1", - Selector: "cf:any", - }, - Route: resources.Route{ - GUID: "route-guid-1", - Host: "myapp", - }, - DomainName: "example.com", - ScopeType: "any", - SourceName: "", - }, - }, v7action.Warnings{}, nil) - }) - - It("passes the domain filter to the actor", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) - spaceGUID, domainName, hostname, path, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) - Expect(spaceGUID).To(Equal("some-space-guid")) - Expect(domainName).To(Equal("example.com")) - Expect(hostname).To(Equal("")) - Expect(path).To(Equal("")) - Expect(labelSelector).To(Equal("")) - }) - }) - - When("filtering by hostname", func() { - BeforeEach(func() { - cmd.Hostname = "myapp" - - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) - }) - - It("passes the hostname filter to the actor", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) - _, _, hostname, _, _ := fakeActor.GetAccessRulesForSpaceArgsForCall(0) - Expect(hostname).To(Equal("myapp")) - }) - }) - - When("filtering by path", func() { - BeforeEach(func() { - cmd.Path = "/api" - - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) - }) - - It("passes the path filter to the actor", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) - _, _, _, path, _ := fakeActor.GetAccessRulesForSpaceArgsForCall(0) - Expect(path).To(Equal("/api")) - }) - }) - - When("filtering by labels", func() { - BeforeEach(func() { - cmd.Labels = "env=production,tier=frontend" - - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{}, v7action.Warnings{}, nil) - }) - - It("passes the label selector to the actor", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) - _, _, _, _, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) - Expect(labelSelector).To(Equal("env=production,tier=frontend")) - }) - }) - - When("using multiple filters", func() { - BeforeEach(func() { - cmd.Domain = "example.com" - cmd.Hostname = "myapp" - cmd.Path = "/api" - cmd.Labels = "env=production" - - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ - { - AccessRule: resources.AccessRule{ - GUID: "rule-guid-1", - Selector: "cf:app:app-guid-1", - }, - Route: resources.Route{ - GUID: "route-guid-1", - Host: "myapp", - Path: "/api", - }, - DomainName: "example.com", - ScopeType: "app", - SourceName: "my-app", - }, - }, v7action.Warnings{}, nil) - }) - - It("passes all filters to the actor", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(fakeActor.GetAccessRulesForSpaceCallCount()).To(Equal(1)) - spaceGUID, domainName, hostname, path, labelSelector := fakeActor.GetAccessRulesForSpaceArgsForCall(0) - Expect(spaceGUID).To(Equal("some-space-guid")) - Expect(domainName).To(Equal("example.com")) - Expect(hostname).To(Equal("myapp")) - Expect(path).To(Equal("/api")) - Expect(labelSelector).To(Equal("env=production")) - }) - - It("displays the filtered results", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - Expect(testUI.Out).To(Say(`Getting access rules in org some-org / space some-space as steve\.\.\.`)) - Expect(testUI.Out).To(Say(`host\s+domain\s+path\s+selector\s+scope\s+source`)) - Expect(testUI.Out).To(Say(`myapp\s+example\.com\s+/api\s+cf:app:app-guid-1\s+app\s+my-app`)) - }) - }) - - When("route formatting handles edge cases", func() { - BeforeEach(func() { - fakeActor.GetAccessRulesForSpaceReturns([]v7action.AccessRuleWithRoute{ - { - AccessRule: resources.AccessRule{ - GUID: "rule-guid-1", - Selector: "cf:any", - }, - Route: resources.Route{ - GUID: "route-guid-1", - Host: "", - Path: "/api", - }, - DomainName: "example.com", - ScopeType: "any", - SourceName: "", - }, - { - AccessRule: resources.AccessRule{ - GUID: "rule-guid-2", - Selector: "cf:any", - }, - Route: resources.Route{ - GUID: "route-guid-2", - Host: "myapp", - Path: "", - }, - DomainName: "test.com", - ScopeType: "any", - SourceName: "", - }, - }, v7action.Warnings{}, nil) - }) - - It("formats routes correctly", func() { - Expect(executeErr).ToNot(HaveOccurred()) - - // No host, with path: empty host, example.com, /api - Expect(testUI.Out).To(Say(`\s+example\.com\s+/api`)) - - // With host, no path: myapp, test.com, empty path - Expect(testUI.Out).To(Say(`myapp\s+test\.com\s+`)) - }) - }) -}) diff --git a/command/v7/actor.go b/command/v7/actor.go index 9c04eba1d7e..eb8822fcbca 100644 --- a/command/v7/actor.go +++ b/command/v7/actor.go @@ -23,7 +23,7 @@ import ( type Actor interface { ApplyOrganizationQuotaByName(quotaName string, orgGUID string) (v7action.Warnings, error) ApplySpaceQuotaByName(quotaName string, spaceGUID string, orgGUID string) (v7action.Warnings, error) - AddAccessRule(domainName, selector, hostname, path string) (v7action.Warnings, error) + AddRoutePolicy(domainName, source, hostname, path string) (v7action.Warnings, error) AssignIsolationSegmentToSpaceByNameAndSpace(isolationSegmentName string, spaceGUID string) (v7action.Warnings, error) Authenticate(credentials map[string]string, origin string, grantType uaa.GrantType) error BindSecurityGroupToSpaces(securityGroupGUID string, spaces []resources.Space, lifecycle constant.SecurityGroupLifecycle) (v7action.Warnings, error) @@ -59,7 +59,7 @@ type Actor interface { CreateUser(username string, password string, origin string) (resources.User, v7action.Warnings, error) CreateUserProvidedServiceInstance(instance resources.ServiceInstance) (v7action.Warnings, error) DeleteApplicationByNameAndSpace(name, spaceGUID string, deleteRoutes bool) (v7action.Warnings, error) - DeleteAccessRuleBySelector(domainName, selector, hostname, path string) (v7action.Warnings, error) + DeleteRoutePolicyBySource(domainName, source, hostname, path string) (v7action.Warnings, error) DeleteBuildpackByNameAndStackAndLifecycle(buildpackName string, buildpackStack string, buildpackLifecycle string) (v7action.Warnings, error) DeleteDomain(domain resources.Domain) (v7action.Warnings, error) DeleteInstanceByApplicationNameSpaceProcessTypeAndIndex(appName string, spaceGUID string, processType string, instanceIndex int) (v7action.Warnings, error) @@ -89,8 +89,8 @@ type Actor interface { EnableServiceAccess(offeringName, brokerName, orgName, planName string) (v7action.SkippedPlans, v7action.Warnings, error) EntitleIsolationSegmentToOrganizationByName(isolationSegmentName string, orgName string) (v7action.Warnings, error) GetAppFeature(appGUID string, featureName string) (resources.ApplicationFeature, v7action.Warnings, error) - GetAccessRulesByRoute(domainName, hostname, path string) ([]resources.AccessRule, v7action.Warnings, error) - GetAccessRulesForSpace(spaceGUID string, domainName string, hostname string, path string, labelSelector string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error) + GetRoutePoliciesByRoute(domainName, hostname, path string) ([]resources.RoutePolicy, v7action.Warnings, error) + GetRoutePoliciesForSpace(spaceGUID string, domainName string, hostname string, path string, labelSelector string) ([]v7action.RoutePolicyWithRoute, v7action.Warnings, error) GetAppSummariesForSpace(spaceGUID string, labels string, omitStats bool) ([]v7action.ApplicationSummary, v7action.Warnings, error) GetApplicationByNameAndSpace(appName string, spaceGUID string) (resources.Application, v7action.Warnings, error) GetApplicationMapForRoute(route resources.Route) (map[string]resources.Application, v7action.Warnings, error) diff --git a/command/v7/add_access_rule_command_test.go b/command/v7/add_access_rule_command_test.go deleted file mode 100644 index 1a81d016b33..00000000000 --- a/command/v7/add_access_rule_command_test.go +++ /dev/null @@ -1,448 +0,0 @@ -package v7_test - -import ( - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/command/commandfakes" - "code.cloudfoundry.org/cli/v9/command/flag" - "code.cloudfoundry.org/cli/v9/command/translatableerror" - . "code.cloudfoundry.org/cli/v9/command/v7" - "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" - "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/util/configv3" - "code.cloudfoundry.org/cli/v9/util/ui" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gbytes" -) - -var _ = Describe("add-access-rule Command", func() { - var ( - cmd AddAccessRuleCommand - testUI *ui.UI - fakeConfig *commandfakes.FakeConfig - fakeSharedActor *commandfakes.FakeSharedActor - fakeActor *v7fakes.FakeActor - executeErr error - args []string - ) - - BeforeEach(func() { - testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) - fakeConfig = new(commandfakes.FakeConfig) - fakeSharedActor = new(commandfakes.FakeSharedActor) - fakeActor = new(v7fakes.FakeActor) - - cmd = AddAccessRuleCommand{ - BaseCommand: BaseCommand{ - UI: testUI, - Config: fakeConfig, - SharedActor: fakeSharedActor, - Actor: fakeActor, - }, - } - - // Setup default config returns - fakeConfig.TargetedOrganizationReturns(configv3.Organization{ - GUID: "org-guid", - Name: "org-name", - }) - fakeConfig.TargetedSpaceReturns(configv3.Space{ - GUID: "space-guid", - Name: "space-name", - }) - - fakeActor.GetCurrentUserReturns(configv3.User{Name: "test-user"}, nil) - - args = []string{} - }) - - JustBeforeEach(func() { - executeErr = cmd.Execute(args) - }) - - Describe("validation", func() { - Context("when no source flags are provided", func() { - BeforeEach(func() { - cmd.RequiredArgs = flag.AddAccessRuleArgs{ - Domain: "apps.internal", - } - cmd.Hostname = "backend" - }) - - It("returns a RequiredArgumentError", func() { - Expect(executeErr).To(MatchError(translatableerror.RequiredArgumentError{ - ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --selector", - })) - }) - }) - - Context("when multiple mutually exclusive source flags are provided", func() { - BeforeEach(func() { - cmd.RequiredArgs = flag.AddAccessRuleArgs{ - Domain: "apps.internal", - } - cmd.Hostname = "backend" - cmd.SourceApp = "app-name" - cmd.SourceAny = true - }) - - It("returns an ArgumentCombinationError", func() { - Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{ - Args: []string{"--source-app", "--source-any"}, - })) - }) - }) - - Context("when --source-space and --source-any are both provided", func() { - BeforeEach(func() { - cmd.RequiredArgs = flag.AddAccessRuleArgs{ - Domain: "apps.internal", - } - cmd.Hostname = "backend" - cmd.SourceSpace = "some-space" - cmd.SourceAny = true - }) - - It("returns an ArgumentCombinationError", func() { - Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{ - Args: []string{"--source-space", "--source-any"}, - })) - }) - }) - }) - - When("the user is logged in, an org is targeted, and a space is targeted", func() { - BeforeEach(func() { - cmd.RequiredArgs = flag.AddAccessRuleArgs{ - Domain: "apps.internal", - } - cmd.Hostname = "backend" - }) - - Describe("source resolution", func() { - Context("when --source-app is provided (current space)", func() { - BeforeEach(func() { - cmd.SourceApp = "frontend-app" - fakeActor.GetApplicationByNameAndSpaceReturns( - resources.Application{GUID: "app-guid"}, - v7action.Warnings{"app-warning"}, - nil, - ) - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("resolves the app and creates the access rule", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - // Verify app lookup - Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) - appName, spaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) - Expect(appName).To(Equal("frontend-app")) - Expect(spaceGUID).To(Equal("space-guid")) - - // Verify access rule creation with resolved selector - Expect(fakeActor.AddAccessRuleCallCount()).To(Equal(1)) - domain, selector, hostname, path := fakeActor.AddAccessRuleArgsForCall(0) - Expect(domain).To(Equal("apps.internal")) - Expect(selector).To(Equal("cf:app:app-guid")) - Expect(hostname).To(Equal("backend")) - Expect(path).To(BeEmpty()) - - // Verify output - Expect(testUI.Out).To(Say("Adding access rule for route")) - Expect(testUI.Out).To(Say("scope: app, source: frontend-app")) - Expect(testUI.Out).To(Say("selector: cf:app:app-guid")) - Expect(testUI.Out).To(Say("OK")) - }) - - It("displays warnings", func() { - Expect(testUI.Err).To(Say("app-warning")) - Expect(testUI.Err).To(Say("add-warning")) - }) - }) - - Context("when --source-app is provided with --source-space (cross-space)", func() { - BeforeEach(func() { - cmd.SourceApp = "frontend-app" - cmd.SourceSpace = "other-space" - - fakeActor.GetSpaceByNameAndOrganizationReturns( - resources.Space{GUID: "other-space-guid"}, - v7action.Warnings{"space-warning"}, - nil, - ) - fakeActor.GetApplicationByNameAndSpaceReturns( - resources.Application{GUID: "app-guid"}, - v7action.Warnings{"app-warning"}, - nil, - ) - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("resolves space then app and creates the access rule", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - // Verify space lookup - Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(1)) - spaceName, orgGUID := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) - Expect(spaceName).To(Equal("other-space")) - Expect(orgGUID).To(Equal("org-guid")) - - // Verify app lookup in resolved space - Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(1)) - appName, spaceGUID := fakeActor.GetApplicationByNameAndSpaceArgsForCall(0) - Expect(appName).To(Equal("frontend-app")) - Expect(spaceGUID).To(Equal("other-space-guid")) - - // Verify selector - _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) - Expect(selector).To(Equal("cf:app:app-guid")) - - // Verify output shows cross-space info - Expect(testUI.Out).To(Say("scope: app, source: frontend-app \\(space: other-space\\)")) - }) - }) - - Context("when --source-app is provided with --source-space and --source-org (cross-org)", func() { - BeforeEach(func() { - cmd.SourceApp = "frontend-app" - cmd.SourceSpace = "other-space" - cmd.SourceOrg = "other-org" - - fakeActor.GetOrganizationByNameReturns( - resources.Organization{GUID: "other-org-guid"}, - v7action.Warnings{"org-warning"}, - nil, - ) - fakeActor.GetSpaceByNameAndOrganizationReturns( - resources.Space{GUID: "other-space-guid"}, - v7action.Warnings{"space-warning"}, - nil, - ) - fakeActor.GetApplicationByNameAndSpaceReturns( - resources.Application{GUID: "app-guid"}, - v7action.Warnings{"app-warning"}, - nil, - ) - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("resolves org, space, then app and creates the access rule", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - // Verify org lookup - Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) - orgName := fakeActor.GetOrganizationByNameArgsForCall(0) - Expect(orgName).To(Equal("other-org")) - - // Verify space lookup with resolved org - spaceName, orgGUID := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) - Expect(spaceName).To(Equal("other-space")) - Expect(orgGUID).To(Equal("other-org-guid")) - - // Verify output shows cross-org info - Expect(testUI.Out).To(Say("scope: app, source: frontend-app \\(space: other-space, org: other-org\\)")) - }) - }) - - Context("when --source-app is not found in current space", func() { - BeforeEach(func() { - cmd.SourceApp = "missing-app" - fakeActor.GetApplicationByNameAndSpaceReturns( - resources.Application{}, - v7action.Warnings{"app-warning"}, - actionerror.ApplicationNotFoundError{Name: "missing-app"}, - ) - }) - - It("returns a helpful error message", func() { - Expect(executeErr).To(HaveOccurred()) - Expect(executeErr.Error()).To(ContainSubstring("App 'missing-app' not found in space 'space-name' / org 'org-name'")) - Expect(executeErr.Error()).To(ContainSubstring("TIP: If the app is in a different space or org, use --source-space and/or --source-org flags")) - }) - }) - - Context("when --source-space is provided (without --source-app)", func() { - BeforeEach(func() { - cmd.SourceSpace = "monitoring-space" - fakeActor.GetSpaceByNameAndOrganizationReturns( - resources.Space{GUID: "space-guid-123"}, - v7action.Warnings{"space-warning"}, - nil, - ) - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("creates a space-level access rule", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - // Verify selector is space-level - _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) - Expect(selector).To(Equal("cf:space:space-guid-123")) - - Expect(testUI.Out).To(Say("scope: space, source: monitoring-space")) - }) - }) - - Context("when --source-space is provided with --source-org (cross-org space rule)", func() { - BeforeEach(func() { - cmd.SourceSpace = "prod-space" - cmd.SourceOrg = "prod-org" - - fakeActor.GetOrganizationByNameReturns( - resources.Organization{GUID: "prod-org-guid"}, - v7action.Warnings{"org-warning"}, - nil, - ) - fakeActor.GetSpaceByNameAndOrganizationReturns( - resources.Space{GUID: "prod-space-guid"}, - v7action.Warnings{"space-warning"}, - nil, - ) - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("creates a space-level access rule for the specified org", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - // Verify org lookup - Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) - orgName := fakeActor.GetOrganizationByNameArgsForCall(0) - Expect(orgName).To(Equal("prod-org")) - - // Verify space lookup with resolved org - spaceName, orgGUID := fakeActor.GetSpaceByNameAndOrganizationArgsForCall(0) - Expect(spaceName).To(Equal("prod-space")) - Expect(orgGUID).To(Equal("prod-org-guid")) - - // Verify selector is space-level - _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) - Expect(selector).To(Equal("cf:space:prod-space-guid")) - - Expect(testUI.Out).To(Say("scope: space, source: prod-space \\(org: prod-org\\)")) - }) - }) - - Context("when --source-org is provided (without --source-space or --source-app)", func() { - BeforeEach(func() { - cmd.SourceOrg = "platform-org" - fakeActor.GetOrganizationByNameReturns( - resources.Organization{GUID: "org-guid-456"}, - v7action.Warnings{"org-warning"}, - nil, - ) - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("creates an org-level access rule", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - // Verify selector is org-level - _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) - Expect(selector).To(Equal("cf:org:org-guid-456")) - - Expect(testUI.Out).To(Say("scope: org, source: platform-org")) - }) - }) - - Context("when --source-any is provided", func() { - BeforeEach(func() { - cmd.SourceAny = true - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("creates an 'any' access rule", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) - Expect(selector).To(Equal("cf:any")) - - Expect(testUI.Out).To(Say("scope: any, source: any authenticated app")) - }) - }) - - Context("when --selector is provided (raw selector)", func() { - BeforeEach(func() { - cmd.Selector = "cf:app:raw-guid-123" - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("uses the raw selector without resolution", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - _, selector, _, _ := fakeActor.AddAccessRuleArgsForCall(0) - Expect(selector).To(Equal("cf:app:raw-guid-123")) - - Expect(testUI.Out).To(Say("selector: cf:app:raw-guid-123")) - - // Should not call any resolution methods - Expect(fakeActor.GetApplicationByNameAndSpaceCallCount()).To(Equal(0)) - Expect(fakeActor.GetSpaceByNameAndOrganizationCallCount()).To(Equal(0)) - Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(0)) - }) - }) - - Context("when --path is provided", func() { - BeforeEach(func() { - cmd.SourceAny = true - cmd.Path = "/metrics" - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, nil) - }) - - It("passes the path to the actor", func() { - Expect(executeErr).NotTo(HaveOccurred()) - - _, _, _, path := fakeActor.AddAccessRuleArgsForCall(0) - Expect(path).To(Equal("/metrics")) - }) - }) - }) - - Describe("error handling", func() { - Context("when AddAccessRule fails", func() { - BeforeEach(func() { - cmd.SourceAny = true - fakeActor.AddAccessRuleReturns(v7action.Warnings{"add-warning"}, actionerror.RouteNotFoundError{}) - }) - - It("returns the error", func() { - Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{})) - Expect(testUI.Err).To(Say("add-warning")) - }) - }) - - Context("when space lookup fails", func() { - BeforeEach(func() { - cmd.SourceSpace = "nonexistent-space" - fakeActor.GetSpaceByNameAndOrganizationReturns( - resources.Space{}, - v7action.Warnings{"space-warning"}, - actionerror.SpaceNotFoundError{Name: "nonexistent-space"}, - ) - }) - - It("returns the error", func() { - Expect(executeErr).To(MatchError(actionerror.SpaceNotFoundError{Name: "nonexistent-space"})) - Expect(testUI.Err).To(Say("space-warning")) - }) - }) - - Context("when org lookup fails", func() { - BeforeEach(func() { - cmd.SourceOrg = "nonexistent-org" - fakeActor.GetOrganizationByNameReturns( - resources.Organization{}, - v7action.Warnings{"org-warning"}, - actionerror.OrganizationNotFoundError{Name: "nonexistent-org"}, - ) - }) - - It("returns the error", func() { - Expect(executeErr).To(MatchError(actionerror.OrganizationNotFoundError{Name: "nonexistent-org"})) - Expect(testUI.Err).To(Say("org-warning")) - }) - }) - }) - }) -}) diff --git a/command/v7/add_access_rule_command.go b/command/v7/add_route_policy_command.go similarity index 64% rename from command/v7/add_access_rule_command.go rename to command/v7/add_route_policy_command.go index ac386efd408..7845d4b7e3e 100644 --- a/command/v7/add_access_rule_command.go +++ b/command/v7/add_route_policy_command.go @@ -9,12 +9,12 @@ import ( "code.cloudfoundry.org/cli/v9/command/translatableerror" ) -type AddAccessRuleCommand struct { +type AddRoutePolicyCommand struct { BaseCommand - RequiredArgs flag.AddAccessRuleArgs `positional-args:"yes"` - Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` - Path string `long:"path" description:"Path for the route"` + RequiredArgs flag.AddRoutePolicyArgs `positional-args:"yes"` + Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` + Path string `long:"path" description:"Path for the route"` // Source resolution flags (mutually exclusive as primary source) SourceApp string `long:"source-app" description:"Allow access from this app (by name)"` @@ -22,14 +22,14 @@ type AddAccessRuleCommand struct { 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"` SourceAny bool `long:"source-any" description:"Allow access from any authenticated app"` - // Advanced: raw selector flag - Selector string `long:"selector" description:"Raw selector (cf:app:, cf:space:, cf:org:, or cf:any)"` + // Advanced: raw source flag + Source string `long:"source" description:"Raw source (cf:app:, cf:space:, cf:org:, or cf:any)"` - usage interface{} `usage:"CF_NAME add-access-rule 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 | --selector SELECTOR] [--path PATH]\n\nALLOW ACCESS TO A ROUTE:\n Create an access rule 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-access-rule apps.identity --source-app frontend-app --hostname backend\n\n # Allow an app in a different space to access the route\n cf add-access-rule 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-access-rule 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-access-rule apps.identity --source-space monitoring --hostname api --path /metrics\n\n # Allow all apps in a space in a different org\n cf add-access-rule 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-access-rule apps.identity --source-org platform --hostname shared-api\n\n # Allow any authenticated app to access the public API\n cf add-access-rule apps.identity --source-any --hostname public-api\n\n # Use raw selector (advanced)\n cf add-access-rule apps.identity --selector cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend"` - relatedCommands interface{} `related_commands:"access-rules, remove-access-rule, create-shared-domain"` + 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"` + relatedCommands interface{} `related_commands:"route-policies, remove-route-policy, create-shared-domain"` } -func (cmd AddAccessRuleCommand) Execute(args []string) error { +func (cmd AddRoutePolicyCommand) Execute(args []string) error { // Validate source flags if err := cmd.validateSourceFlags(); err != nil { return err @@ -45,21 +45,21 @@ func (cmd AddAccessRuleCommand) Execute(args []string) error { return err } - // Resolve selector from source flags - selector, scopeDisplay, warnings, err := cmd.resolveSelector() + // Resolve source from source flags + source, scopeDisplay, warnings, err := cmd.resolveSource() cmd.UI.DisplayWarnings(warnings) if err != nil { return err } - // Validate selector format - if err := validateSelector(selector); err != nil { + // Validate source format + if err := validateSource(source); err != nil { return err } domainName := cmd.RequiredArgs.Domain - cmd.UI.DisplayTextWithFlavor("Adding access rule for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", + cmd.UI.DisplayTextWithFlavor("Adding route policy for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", map[string]interface{}{ "Hostname": cmd.Hostname, "Domain": domainName, @@ -72,12 +72,12 @@ func (cmd AddAccessRuleCommand) Execute(args []string) error { map[string]interface{}{ "ScopeDisplay": scopeDisplay, }) - cmd.UI.DisplayText(" selector: {{.Selector}}", + cmd.UI.DisplayText(" source: {{.Source}}", map[string]interface{}{ - "Selector": selector, + "Source": source, }) - warnings, err = cmd.Actor.AddAccessRule(domainName, selector, cmd.Hostname, cmd.Path) + warnings, err = cmd.Actor.AddRoutePolicy(domainName, source, cmd.Hostname, cmd.Path) cmd.UI.DisplayWarnings(warnings) if err != nil { return err @@ -89,11 +89,11 @@ func (cmd AddAccessRuleCommand) Execute(args []string) error { } // validateSourceFlags ensures exactly one source target is specified and validates combinations -func (cmd AddAccessRuleCommand) validateSourceFlags() error { +func (cmd AddRoutePolicyCommand) validateSourceFlags() error { sourceFlags := []string{} - if cmd.Selector != "" { - sourceFlags = append(sourceFlags, "--selector") + if cmd.Source != "" { + sourceFlags = append(sourceFlags, "--source") } if cmd.SourceApp != "" { sourceFlags = append(sourceFlags, "--source-app") @@ -112,7 +112,7 @@ func (cmd AddAccessRuleCommand) validateSourceFlags() error { if len(sourceFlags) == 0 { return translatableerror.RequiredArgumentError{ - ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --selector", + ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --source", } } @@ -125,15 +125,15 @@ func (cmd AddAccessRuleCommand) validateSourceFlags() error { return nil } -// resolveSelector resolves source flags to a selector string -// Returns (selector, scopeDisplay, warnings, error) +// resolveSource resolves source flags to a source string +// Returns (source, scopeDisplay, warnings, error) // scopeDisplay is a human-readable description for output (e.g., "scope: app, source: frontend-app") -func (cmd AddAccessRuleCommand) resolveSelector() (string, string, v7action.Warnings, error) { +func (cmd AddRoutePolicyCommand) resolveSource() (string, string, v7action.Warnings, error) { var allWarnings v7action.Warnings - // Priority: --selector flag (raw selector, no resolution needed) - if cmd.Selector != "" { - return cmd.Selector, fmt.Sprintf("selector: %s", cmd.Selector), allWarnings, nil + // Priority: --source flag (raw source, no resolution needed) + if cmd.Source != "" { + return cmd.Source, fmt.Sprintf("source: %s", cmd.Source), allWarnings, nil } // --source-any @@ -202,7 +202,7 @@ func (cmd AddAccessRuleCommand) resolveSelector() (string, string, v7action.Warn return fmt.Sprintf("cf:app:%s", app.GUID), scopeDisplay, allWarnings, nil } - // --source-space (without --source-app, so create space-level rule) + // --source-space (without --source-app, so create space-level policy) if cmd.SourceSpace != "" { // Determine org GUID for space lookup orgGUID := cmd.Config.TargetedOrganization().GUID @@ -232,7 +232,7 @@ func (cmd AddAccessRuleCommand) resolveSelector() (string, string, v7action.Warn return fmt.Sprintf("cf:space:%s", space.GUID), scopeDisplay, allWarnings, nil } - // --source-org (without --source-space or --source-app, so create org-level rule) + // --source-org (without --source-space or --source-app, so create org-level policy) if cmd.SourceOrg != "" { org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg) allWarnings = append(allWarnings, warnings...) @@ -249,25 +249,25 @@ func (cmd AddAccessRuleCommand) resolveSelector() (string, string, v7action.Warn return "", "", allWarnings, fmt.Errorf("no source specified") } -func validateSelector(selector string) error { +func validateSource(source string) error { // Basic validation - check for cf:app:, cf:space:, cf:org:, or cf:any prefix validPrefixes := []string{"cf:app:", "cf:space:", "cf:org:", "cf:any"} for _, prefix := range validPrefixes { - if len(selector) >= len(prefix) && selector[:len(prefix)] == prefix { + if len(source) >= len(prefix) && source[:len(prefix)] == prefix { if prefix == "cf:any" { - if selector != "cf:any" { - return fmt.Errorf("selector 'cf:any' must not have a GUID suffix") + if source != "cf:any" { + return fmt.Errorf("source 'cf:any' must not have a GUID suffix") } return nil } - // For other selectors, ensure there's a GUID after the prefix - if len(selector) <= len(prefix) { - return fmt.Errorf("selector '%s' must include a GUID (e.g., %s)", selector, prefix) + // For other sources, ensure there's a GUID after the prefix + if len(source) <= len(prefix) { + return fmt.Errorf("source '%s' must include a GUID (e.g., %s)", source, prefix) } return nil } } - return fmt.Errorf("selector must start with one of: cf:app:, cf:space:, cf:org:, or be exactly 'cf:any'") + return fmt.Errorf("source must start with one of: cf:app:, cf:space:, cf:org:, or be exactly 'cf:any'") } func formatPath(path string) string { diff --git a/command/v7/buildpacks_command.go b/command/v7/buildpacks_command.go index 8ccdc1d5f63..4c8d3dc06dd 100644 --- a/command/v7/buildpacks_command.go +++ b/command/v7/buildpacks_command.go @@ -3,8 +3,8 @@ package v7 import ( "strconv" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" - "code.cloudfoundry.org/cli/v9/command" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/util/ui" ) diff --git a/command/v7/buildpacks_command_test.go b/command/v7/buildpacks_command_test.go index 41240936ba0..34ca62acae4 100644 --- a/command/v7/buildpacks_command_test.go +++ b/command/v7/buildpacks_command_test.go @@ -10,7 +10,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/commandfakes" - "code.cloudfoundry.org/cli/v9/command/translatableerror" + "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/configv3" diff --git a/command/v7/copy_source_command.go b/command/v7/copy_source_command.go index 9a9b73c26ac..658deb4e6c6 100644 --- a/command/v7/copy_source_command.go +++ b/command/v7/copy_source_command.go @@ -1,18 +1,18 @@ package v7 import ( - "strconv" - "strings" + "strconv" + "strings" - "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/api/logcache" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/util/configv3" ) diff --git a/command/v7/create_buildpack_command.go b/command/v7/create_buildpack_command.go index da6cbac2c8c..7017a115229 100644 --- a/command/v7/create_buildpack_command.go +++ b/command/v7/create_buildpack_command.go @@ -6,7 +6,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" diff --git a/command/v7/create_private_domain_command.go b/command/v7/create_private_domain_command.go index 1a95ae97250..62ff3143314 100644 --- a/command/v7/create_private_domain_command.go +++ b/command/v7/create_private_domain_command.go @@ -10,11 +10,11 @@ import ( type CreatePrivateDomainCommand struct { BaseCommand - RequiredArgs flag.OrgDomain `positional-args:"yes"` - EnforceAccessRules bool `long:"enforce-access-rules" description:"Enable platform-enforced access control for routes on this domain (requires mTLS domain configuration in GoRouter)"` - Scope string `long:"scope" description:"Operator-level scope boundary for access rules: 'any', 'org', or 'space' (only valid with --enforce-access-rules)"` - usage interface{} `usage:"CF_NAME create-private-domain ORG DOMAIN [--enforce-access-rules [--scope (any|org|space)]]"` - relatedCommands interface{} `related_commands:"create-shared-domain, domains, share-private-domain, add-access-rule, access-rules"` + RequiredArgs flag.OrgDomain `positional-args:"yes"` + EnforceRoutePolicies bool `long:"enforce-route-policies" description:"Enable platform-enforced access control for routes on this domain (requires mTLS domain configuration in GoRouter)"` + Scope string `long:"scope" description:"Operator-level scope boundary for route policies: 'any', 'org', or 'space' (only valid with --enforce-route-policies)"` + usage interface{} `usage:"CF_NAME create-private-domain ORG DOMAIN [--enforce-route-policies [--scope (any|org|space)]]"` + relatedCommands interface{} `related_commands:"create-shared-domain, domains, share-private-domain, add-route-policy, route-policies"` } func (cmd CreatePrivateDomainCommand) Execute(args []string) error { @@ -31,9 +31,9 @@ func (cmd CreatePrivateDomainCommand) Execute(args []string) error { domain := cmd.RequiredArgs.Domain orgName := cmd.RequiredArgs.Organization - // Validate that --scope is only used with --enforce-access-rules - if cmd.Scope != "" && !cmd.EnforceAccessRules { - return fmt.Errorf("--scope can only be used with --enforce-access-rules") + // Validate that --scope is only used with --enforce-route-policies + if cmd.Scope != "" && !cmd.EnforceRoutePolicies { + return fmt.Errorf("--scope can only be used with --enforce-route-policies") } // Validate scope values @@ -48,7 +48,7 @@ func (cmd CreatePrivateDomainCommand) Execute(args []string) error { "Organization": orgName, }) - warnings, err := cmd.Actor.CreatePrivateDomain(domain, orgName, cmd.EnforceAccessRules, cmd.Scope) + warnings, err := cmd.Actor.CreatePrivateDomain(domain, orgName, cmd.EnforceRoutePolicies, cmd.Scope) cmd.UI.DisplayWarnings(warnings) if err != nil { @@ -65,8 +65,8 @@ func (cmd CreatePrivateDomainCommand) Execute(args []string) error { cmd.UI.DisplayOK() - if cmd.EnforceAccessRules { - cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a private identity-aware domain with access rule enforcement enabled. Routes on this domain require access rules to allow traffic.", + if cmd.EnforceRoutePolicies { + cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a private identity-aware domain with route policy enforcement enabled. Routes on this domain require route policies to allow traffic.", map[string]interface{}{ "Domain": domain, }) diff --git a/command/v7/create_route_command.go b/command/v7/create_route_command.go index 9c203ebbbb7..ff4dd6fc3a3 100644 --- a/command/v7/create_route_command.go +++ b/command/v7/create_route_command.go @@ -7,8 +7,8 @@ import ( "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/command/flag" ) type CreateRouteCommand struct { diff --git a/command/v7/create_route_command_test.go b/command/v7/create_route_command_test.go index 8803a9bcce5..23dc37ebed2 100644 --- a/command/v7/create_route_command_test.go +++ b/command/v7/create_route_command_test.go @@ -7,7 +7,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command/commandfakes" "code.cloudfoundry.org/cli/v9/command/flag" . "code.cloudfoundry.org/cli/v9/command/v7" diff --git a/command/v7/create_shared_domain_command.go b/command/v7/create_shared_domain_command.go index d154dac0312..169133915aa 100644 --- a/command/v7/create_shared_domain_command.go +++ b/command/v7/create_shared_domain_command.go @@ -9,13 +9,13 @@ import ( type CreateSharedDomainCommand struct { BaseCommand - RequiredArgs flag.Domain `positional-args:"yes"` - RouterGroup string `long:"router-group" description:"Routes for this domain will use routers in the specified router group"` - Internal bool `long:"internal" description:"Applications that use internal routes communicate directly on the container network"` - EnforceAccessRules bool `long:"enforce-access-rules" description:"Enable platform-enforced access control for routes on this domain (requires mTLS domain configuration in GoRouter)"` - Scope string `long:"scope" description:"Operator-level scope boundary for access rules: 'any', 'org', or 'space' (only valid with --enforce-access-rules)"` - usage interface{} `usage:"CF_NAME create-shared-domain DOMAIN [--router-group ROUTER_GROUP_NAME | --internal] [--enforce-access-rules [--scope (any|org|space)]]"` - relatedCommands interface{} `related_commands:"create-private-domain, domains, add-access-rule, access-rules"` + RequiredArgs flag.Domain `positional-args:"yes"` + RouterGroup string `long:"router-group" description:"Routes for this domain will use routers in the specified router group"` + Internal bool `long:"internal" description:"Applications that use internal routes communicate directly on the container network"` + EnforceRoutePolicies bool `long:"enforce-route-policies" description:"Enable platform-enforced access control for routes on this domain (requires mTLS domain configuration in GoRouter)"` + Scope string `long:"scope" description:"Operator-level scope boundary for route policies: 'any', 'org', or 'space' (only valid with --enforce-route-policies)"` + usage interface{} `usage:"CF_NAME create-shared-domain DOMAIN [--router-group ROUTER_GROUP_NAME | --internal] [--enforce-route-policies [--scope (any|org|space)]]"` + relatedCommands interface{} `related_commands:"create-private-domain, domains, add-route-policy, route-policies"` } func (cmd CreateSharedDomainCommand) Execute(args []string) error { @@ -31,9 +31,9 @@ func (cmd CreateSharedDomainCommand) Execute(args []string) error { domain := cmd.RequiredArgs.Domain - // Validate that --scope is only used with --enforce-access-rules - if cmd.Scope != "" && !cmd.EnforceAccessRules { - return fmt.Errorf("--scope can only be used with --enforce-access-rules") + // Validate that --scope is only used with --enforce-route-policies + if cmd.Scope != "" && !cmd.EnforceRoutePolicies { + return fmt.Errorf("--scope can only be used with --enforce-route-policies") } // Validate scope values @@ -47,16 +47,16 @@ func (cmd CreateSharedDomainCommand) Execute(args []string) error { "User": user.Name, }) - warnings, err := cmd.Actor.CreateSharedDomain(domain, cmd.Internal, cmd.RouterGroup, cmd.EnforceAccessRules, cmd.Scope) + warnings, err := cmd.Actor.CreateSharedDomain(domain, cmd.Internal, cmd.RouterGroup, cmd.EnforceRoutePolicies, cmd.Scope) cmd.UI.DisplayWarnings(warnings) if err != nil { return err } cmd.UI.DisplayOK() - - if cmd.EnforceAccessRules { - cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a shared identity-aware domain with access rule enforcement enabled. Routes on this domain require access rules to allow traffic.", + + if cmd.EnforceRoutePolicies { + cmd.UI.DisplayText("TIP: Domain '{{.Domain}}' is a shared identity-aware domain with route policy enforcement enabled. Routes on this domain require route policies to allow traffic.", map[string]interface{}{ "Domain": domain, }) diff --git a/command/v7/create_user_provided_service_command.go b/command/v7/create_user_provided_service_command.go index c5d9ec963c0..c329297ad51 100644 --- a/command/v7/create_user_provided_service_command.go +++ b/command/v7/create_user_provided_service_command.go @@ -1,7 +1,7 @@ package v7 import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/resources" diff --git a/command/v7/delete_buildpack_command.go b/command/v7/delete_buildpack_command.go index aa139a1b63d..a564ec7051f 100644 --- a/command/v7/delete_buildpack_command.go +++ b/command/v7/delete_buildpack_command.go @@ -2,8 +2,8 @@ package v7 import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" - "code.cloudfoundry.org/cli/v9/command" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" ) diff --git a/command/v7/delete_buildpack_command_test.go b/command/v7/delete_buildpack_command_test.go index 2a61cf2e6ad..2bf7ef7f22d 100644 --- a/command/v7/delete_buildpack_command_test.go +++ b/command/v7/delete_buildpack_command_test.go @@ -6,7 +6,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/command/commandfakes" - "code.cloudfoundry.org/cli/v9/command/translatableerror" + "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/ui" diff --git a/command/v7/remove_access_rule_command.go b/command/v7/remove_access_rule_command.go deleted file mode 100644 index 4a2edb69892..00000000000 --- a/command/v7/remove_access_rule_command.go +++ /dev/null @@ -1,73 +0,0 @@ -package v7 - -import ( - "code.cloudfoundry.org/cli/v9/command/flag" -) - -type RemoveAccessRuleCommand struct { - BaseCommand - - RequiredArgs flag.RemoveAccessRuleArgs `positional-args:"yes"` - Selector string `long:"selector" required:"true" description:"Selector to identify the access rule (cf:app:, cf:space:, cf:org:, or cf:any)"` - Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` - Path string `long:"path" description:"Path for the route"` - Force bool `short:"f" description:"Force deletion without confirmation"` - usage interface{} `usage:"CF_NAME remove-access-rule DOMAIN --selector SELECTOR --hostname HOSTNAME [--path PATH] [-f]\n\nEXAMPLES:\n cf remove-access-rule apps.identity --selector cf:app:d76446a1-f429-4444-8797-be2f78b75b08 --hostname backend\n cf remove-access-rule apps.identity --selector cf:space:2b26e210-1b48-4e60-8432-f24bc5927789 --hostname api --path /metrics -f\n cf remove-access-rule apps.identity --selector cf:any --hostname public-api -f"` - relatedCommands interface{} `related_commands:"access-rules, add-access-rule"` -} - -func (cmd RemoveAccessRuleCommand) Execute(args []string) error { - err := cmd.SharedActor.CheckTarget(true, true) - if err != nil { - return err - } - - user, err := cmd.Actor.GetCurrentUser() - if err != nil { - return err - } - - // Validate selector format - if err := validateSelector(cmd.Selector); err != nil { - return err - } - - domainName := cmd.RequiredArgs.Domain - - if !cmd.Force { - prompt := "Really remove access rule with selector {{.Selector}} for route {{.Hostname}}.{{.Domain}}{{.Path}}?" - response, promptErr := cmd.UI.DisplayBoolPrompt(false, prompt, map[string]interface{}{ - "Selector": cmd.Selector, - "Hostname": cmd.Hostname, - "Domain": domainName, - "Path": formatPath(cmd.Path), - }) - - if promptErr != nil { - return promptErr - } - - if !response { - cmd.UI.DisplayText("Access rule has not been removed.") - return nil - } - } - - cmd.UI.DisplayTextWithFlavor("Removing access rule for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", - map[string]interface{}{ - "Hostname": cmd.Hostname, - "Domain": domainName, - "Path": formatPath(cmd.Path), - "User": user.Name, - }) - - warnings, err := cmd.Actor.DeleteAccessRuleBySelector(domainName, cmd.Selector, cmd.Hostname, cmd.Path) - cmd.UI.DisplayWarnings(warnings) - if err != nil { - return err - } - - cmd.UI.DisplayOK() - - return nil -} diff --git a/command/v7/remove_route_policy_command.go b/command/v7/remove_route_policy_command.go new file mode 100644 index 00000000000..e026d2fd918 --- /dev/null +++ b/command/v7/remove_route_policy_command.go @@ -0,0 +1,73 @@ +package v7 + +import ( + "code.cloudfoundry.org/cli/v9/command/flag" +) + +type RemoveRoutePolicyCommand struct { + BaseCommand + + RequiredArgs flag.RemoveRoutePolicyArgs `positional-args:"yes"` + Source string `long:"source" required:"true" description:"Source to identify the route policy (cf:app:, cf:space:, cf:org:, or cf:any)"` + Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` + Path string `long:"path" description:"Path for the route"` + Force bool `short:"f" description:"Force deletion without confirmation"` + 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"` + relatedCommands interface{} `related_commands:"route-policies, add-route-policy"` +} + +func (cmd RemoveRoutePolicyCommand) Execute(args []string) error { + err := cmd.SharedActor.CheckTarget(true, true) + if err != nil { + return err + } + + user, err := cmd.Actor.GetCurrentUser() + if err != nil { + return err + } + + // Validate source format + if err := validateSource(cmd.Source); err != nil { + return err + } + + domainName := cmd.RequiredArgs.Domain + + if !cmd.Force { + prompt := "Really remove route policy with source {{.Source}} for route {{.Hostname}}.{{.Domain}}{{.Path}}?" + response, promptErr := cmd.UI.DisplayBoolPrompt(false, prompt, map[string]interface{}{ + "Source": cmd.Source, + "Hostname": cmd.Hostname, + "Domain": domainName, + "Path": formatPath(cmd.Path), + }) + + if promptErr != nil { + return promptErr + } + + if !response { + cmd.UI.DisplayText("Route policy has not been removed.") + return nil + } + } + + cmd.UI.DisplayTextWithFlavor("Removing route policy for route {{.Hostname}}.{{.Domain}}{{.Path}} as {{.User}}...", + map[string]interface{}{ + "Hostname": cmd.Hostname, + "Domain": domainName, + "Path": formatPath(cmd.Path), + "User": user.Name, + }) + + warnings, err := cmd.Actor.DeleteRoutePolicyBySource(domainName, cmd.Source, cmd.Hostname, cmd.Path) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + cmd.UI.DisplayOK() + + return nil +} diff --git a/command/v7/restage_command.go b/command/v7/restage_command.go index 35042ac3285..ea39d1d32c0 100644 --- a/command/v7/restage_command.go +++ b/command/v7/restage_command.go @@ -1,19 +1,19 @@ package v7 import ( - "strconv" - "strings" + "strconv" + "strings" - "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/api/logcache" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" ) type RestageCommand struct { diff --git a/command/v7/restart_command.go b/command/v7/restart_command.go index 6fc987a4e8b..fda8b0ef4b1 100644 --- a/command/v7/restart_command.go +++ b/command/v7/restart_command.go @@ -1,18 +1,18 @@ package v7 import ( - "strconv" - "strings" + "strconv" + "strings" - "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/api/logcache" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" ) type RestartCommand struct { diff --git a/command/v7/revision_command_test.go b/command/v7/revision_command_test.go index 4a55130835d..6003f11b646 100644 --- a/command/v7/revision_command_test.go +++ b/command/v7/revision_command_test.go @@ -1,18 +1,18 @@ package v7_test import ( - "errors" + "errors" - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/command/commandfakes" - "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + "code.cloudfoundry.org/cli/v9/command/flag" v7 "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" - "code.cloudfoundry.org/cli/v9/util/ui" "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/types" "code.cloudfoundry.org/cli/v9/util/configv3" + "code.cloudfoundry.org/cli/v9/util/ui" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" diff --git a/command/v7/rollback_command.go b/command/v7/rollback_command.go index ff5ddad6ed9..abf19514f41 100644 --- a/command/v7/rollback_command.go +++ b/command/v7/rollback_command.go @@ -7,13 +7,13 @@ import ( "code.cloudfoundry.org/cli/v9/actor/sharedaction" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/cf/errors" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" ) type RollbackCommand struct { diff --git a/command/v7/access_rules_command.go b/command/v7/route_policies_command.go similarity index 55% rename from command/v7/access_rules_command.go rename to command/v7/route_policies_command.go index 6d8a9c8b169..ef191180233 100644 --- a/command/v7/access_rules_command.go +++ b/command/v7/route_policies_command.go @@ -4,19 +4,19 @@ import ( "code.cloudfoundry.org/cli/v9/util/ui" ) -type AccessRulesCommand struct { +type RoutePoliciesCommand struct { BaseCommand Domain string `long:"domain" description:"Filter by domain name"` Hostname string `long:"hostname" description:"Filter by hostname"` Path string `long:"path" description:"Filter by path"` - Labels string `long:"labels" description:"Selector to filter access rules by labels"` + Labels string `long:"labels" description:"Selector to filter route policies by labels"` - usage interface{} `usage:"CF_NAME access-rules [--domain DOMAIN] [--hostname HOSTNAME] [--path PATH] [--labels SELECTOR]\n\nEXAMPLES:\n cf access-rules\n cf access-rules --domain apps.identity\n cf access-rules --domain apps.identity --hostname backend\n cf access-rules --labels env=prod"` - relatedCommands interface{} `related_commands:"add-access-rule, remove-access-rule, routes"` + usage interface{} `usage:"CF_NAME route-policies [--domain DOMAIN] [--hostname HOSTNAME] [--path PATH] [--labels SELECTOR]\n\nEXAMPLES:\n cf route-policies\n cf route-policies --domain apps.identity\n cf route-policies --domain apps.identity --hostname backend\n cf route-policies --labels env=prod"` + relatedCommands interface{} `related_commands:"add-route-policy, remove-route-policy, routes"` } -func (cmd AccessRulesCommand) Execute(args []string) error { +func (cmd RoutePoliciesCommand) Execute(args []string) error { // Check target (org + space required) err := cmd.SharedActor.CheckTarget(true, true) if err != nil { @@ -31,7 +31,7 @@ func (cmd AccessRulesCommand) Execute(args []string) error { // Display contextual header cmd.UI.DisplayTextWithFlavor( - "Getting access rules in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", + "Getting route policies in org {{.OrgName}} / space {{.SpaceName}} as {{.Username}}...", map[string]interface{}{ "OrgName": cmd.Config.TargetedOrganization().Name, "SpaceName": cmd.Config.TargetedSpace().Name, @@ -39,8 +39,8 @@ func (cmd AccessRulesCommand) Execute(args []string) error { }) cmd.UI.DisplayNewline() - // Fetch access rules for space with filters - rulesWithRoutes, warnings, err := cmd.Actor.GetAccessRulesForSpace( + // Fetch route policies for space with filters + policiesWithRoutes, warnings, err := cmd.Actor.GetRoutePoliciesForSpace( cmd.Config.TargetedSpace().GUID, cmd.Domain, cmd.Hostname, @@ -53,8 +53,8 @@ func (cmd AccessRulesCommand) Execute(args []string) error { } // Handle empty results - if len(rulesWithRoutes) == 0 { - cmd.UI.DisplayText("No access rules found.") + if len(policiesWithRoutes) == 0 { + cmd.UI.DisplayText("No route policies found.") return nil } @@ -64,20 +64,20 @@ func (cmd AccessRulesCommand) Execute(args []string) error { cmd.UI.TranslateText("host"), cmd.UI.TranslateText("domain"), cmd.UI.TranslateText("path"), - cmd.UI.TranslateText("selector"), - cmd.UI.TranslateText("scope"), cmd.UI.TranslateText("source"), + cmd.UI.TranslateText("scope"), + cmd.UI.TranslateText("name"), }, } - for _, ruleWithRoute := range rulesWithRoutes { + for _, policyWithRoute := range policiesWithRoutes { table = append(table, []string{ - ruleWithRoute.Route.Host, - ruleWithRoute.DomainName, - ruleWithRoute.Route.Path, - ruleWithRoute.Selector, - ruleWithRoute.ScopeType, - ruleWithRoute.SourceName, + policyWithRoute.Route.Host, + policyWithRoute.DomainName, + policyWithRoute.Route.Path, + policyWithRoute.Source, + policyWithRoute.ScopeType, + policyWithRoute.SourceName, }) } @@ -86,4 +86,3 @@ func (cmd AccessRulesCommand) Execute(args []string) error { return nil } - diff --git a/command/v7/shared/sharedfakes/fake_app_stager.go b/command/v7/shared/sharedfakes/fake_app_stager.go index 09bb3654f5a..9e9e9f2e749 100644 --- a/command/v7/shared/sharedfakes/fake_app_stager.go +++ b/command/v7/shared/sharedfakes/fake_app_stager.go @@ -258,12 +258,6 @@ func (fake *FakeAppStager) StartAppReturnsOnCall(i int, result1 error) { func (fake *FakeAppStager) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.stageAndStartMutex.RLock() - defer fake.stageAndStartMutex.RUnlock() - fake.stageAppMutex.RLock() - defer fake.stageAppMutex.RUnlock() - fake.startAppMutex.RLock() - defer fake.startAppMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index 537fc822704..e7819b8ba59 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -196,29 +196,29 @@ var _ = Describe("Stack Command", func() { }) }) - Context("When the state is not ACTIVE but has no reason", func() { - BeforeEach(func() { - stack := resources.Stack{ - Name: "some-stack-name", - GUID: "some-stack-guid", - Description: "some-stack-desc", - State: "RESTRICTED", - } - fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) - }) + Context("When the state is not ACTIVE but has no reason", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + State: "RESTRICTED", + } + fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + }) - It("Displays the stack information with state and empty reason", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + It("Displays the stack information with state and empty reason", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).To(Say("state:\\s+RESTRICTED")) - Expect(testUI.Out).To(Say("reason:")) + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+RESTRICTED")) + Expect(testUI.Out).To(Say("reason:")) + }) }) }) - }) When("The Stack does not Exist", func() { expectedError := actionerror.StackNotFoundError{Name: "some-stack-name"} diff --git a/command/v7/update_buildpack_command.go b/command/v7/update_buildpack_command.go index a45d17e3da0..a73b6524d79 100644 --- a/command/v7/update_buildpack_command.go +++ b/command/v7/update_buildpack_command.go @@ -7,7 +7,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index 5b32106a392..c65d01ff834 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -158,67 +158,66 @@ var _ = Describe("update-stack Command", func() { }) }) - Context("when state values are provided in different cases", func() { - It("accepts 'active' and capitalizes it", func() { - cmd.State = "active" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) + Context("when state values are provided in different cases", func() { + It("accepts 'active' and capitalizes it", func() { + cmd.State = "active" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state, _ := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateActive)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateActive)) + }) - It("accepts 'RESTRICTED' and keeps it capitalized", func() { - cmd.State = "RESTRICTED" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) + It("accepts 'RESTRICTED' and keeps it capitalized", func() { + cmd.State = "RESTRICTED" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state, _ := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateRestricted)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateRestricted)) + }) - It("accepts 'Disabled' and capitalizes it", func() { - cmd.State = "Disabled" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) + It("accepts 'Disabled' and capitalizes it", func() { + cmd.State = "Disabled" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state, _ := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateDisabled)) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateDisabled)) + }) }) - }) - Context("when the reason flag is provided", func() { - BeforeEach(func() { - cmd.State = "deprecated" - cmd.Reason = "Use cflinuxfs4 instead" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{ - Name: "some-stack", - Description: "some description", - State: resources.StackStateDeprecated, - StateReason: "Use cflinuxfs4 instead", - }, v7action.Warnings{}, nil) - }) + Context("when the reason flag is provided", func() { + BeforeEach(func() { + cmd.State = "deprecated" + cmd.Reason = "Use cflinuxfs4 instead" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{ + Name: "some-stack", + Description: "some description", + State: resources.StackStateDeprecated, + StateReason: "Use cflinuxfs4 instead", + }, v7action.Warnings{}, nil) + }) - It("passes the reason to the actor and displays it", func() { - Expect(executeErr).ToNot(HaveOccurred()) + It("passes the reason to the actor and displays it", func() { + Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) - _, _, reason := fakeActor.UpdateStackArgsForCall(0) - Expect(reason).To(Equal("Use cflinuxfs4 instead")) + Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) + _, _, reason := fakeActor.UpdateStackArgsForCall(0) + Expect(reason).To(Equal("Use cflinuxfs4 instead")) - Expect(testUI.Out).To(Say(`reason:\s+Use cflinuxfs4 instead`)) + Expect(testUI.Out).To(Say(`reason:\s+Use cflinuxfs4 instead`)) + }) }) }) - }) }) - diff --git a/command/v7/v7fakes/fake_actor.go b/command/v7/v7fakes/fake_actor.go index bfdbfe9b10d..595d351f4aa 100644 --- a/command/v7/v7fakes/fake_actor.go +++ b/command/v7/v7fakes/fake_actor.go @@ -22,19 +22,19 @@ import ( ) type FakeActor struct { - AddAccessRuleStub func(string, string, string, string) (v7action.Warnings, error) - addAccessRuleMutex sync.RWMutex - addAccessRuleArgsForCall []struct { + AddRoutePolicyStub func(string, string, string, string) (v7action.Warnings, error) + addRoutePolicyMutex sync.RWMutex + addRoutePolicyArgsForCall []struct { arg1 string arg2 string arg3 string arg4 string } - addAccessRuleReturns struct { + addRoutePolicyReturns struct { result1 v7action.Warnings result2 error } - addAccessRuleReturnsOnCall map[int]struct { + addRoutePolicyReturnsOnCall map[int]struct { result1 v7action.Warnings result2 error } @@ -577,22 +577,6 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - DeleteAccessRuleBySelectorStub func(string, string, string, string) (v7action.Warnings, error) - deleteAccessRuleBySelectorMutex sync.RWMutex - deleteAccessRuleBySelectorArgsForCall []struct { - arg1 string - arg2 string - arg3 string - arg4 string - } - deleteAccessRuleBySelectorReturns struct { - result1 v7action.Warnings - result2 error - } - deleteAccessRuleBySelectorReturnsOnCall map[int]struct { - result1 v7action.Warnings - result2 error - } DeleteApplicationByNameAndSpaceStub func(string, string, bool) (v7action.Warnings, error) deleteApplicationByNameAndSpaceMutex sync.RWMutex deleteApplicationByNameAndSpaceArgsForCall []struct { @@ -766,6 +750,22 @@ type FakeActor struct { result2 v7action.Warnings result3 error } + DeleteRoutePolicyBySourceStub func(string, string, string, string) (v7action.Warnings, error) + deleteRoutePolicyBySourceMutex sync.RWMutex + deleteRoutePolicyBySourceArgsForCall []struct { + arg1 string + arg2 string + arg3 string + arg4 string + } + deleteRoutePolicyBySourceReturns struct { + result1 v7action.Warnings + result2 error + } + deleteRoutePolicyBySourceReturnsOnCall map[int]struct { + result1 v7action.Warnings + result2 error + } DeleteSecurityGroupStub func(string) (v7action.Warnings, error) deleteSecurityGroupMutex sync.RWMutex deleteSecurityGroupArgsForCall []struct { @@ -1025,42 +1025,6 @@ type FakeActor struct { result1 v7action.Warnings result2 error } - GetAccessRulesByRouteStub func(string, string, string) ([]resources.AccessRule, v7action.Warnings, error) - getAccessRulesByRouteMutex sync.RWMutex - getAccessRulesByRouteArgsForCall []struct { - arg1 string - arg2 string - arg3 string - } - getAccessRulesByRouteReturns struct { - result1 []resources.AccessRule - result2 v7action.Warnings - result3 error - } - getAccessRulesByRouteReturnsOnCall map[int]struct { - result1 []resources.AccessRule - result2 v7action.Warnings - result3 error - } - GetAccessRulesForSpaceStub func(string, string, string, string, string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error) - getAccessRulesForSpaceMutex sync.RWMutex - getAccessRulesForSpaceArgsForCall []struct { - arg1 string - arg2 string - arg3 string - arg4 string - arg5 string - } - getAccessRulesForSpaceReturns struct { - result1 []v7action.AccessRuleWithRoute - result2 v7action.Warnings - result3 error - } - getAccessRulesForSpaceReturnsOnCall map[int]struct { - result1 []v7action.AccessRuleWithRoute - result2 v7action.Warnings - result3 error - } GetAppFeatureStub func(string, string) (resources.ApplicationFeature, v7action.Warnings, error) getAppFeatureMutex sync.RWMutex getAppFeatureArgsForCall []struct { @@ -1935,6 +1899,42 @@ type FakeActor struct { result2 v7action.Warnings result3 error } + GetRoutePoliciesByRouteStub func(string, string, string) ([]resources.RoutePolicy, v7action.Warnings, error) + getRoutePoliciesByRouteMutex sync.RWMutex + getRoutePoliciesByRouteArgsForCall []struct { + arg1 string + arg2 string + arg3 string + } + getRoutePoliciesByRouteReturns struct { + result1 []resources.RoutePolicy + result2 v7action.Warnings + result3 error + } + getRoutePoliciesByRouteReturnsOnCall map[int]struct { + result1 []resources.RoutePolicy + result2 v7action.Warnings + result3 error + } + GetRoutePoliciesForSpaceStub func(string, string, string, string, string) ([]v7action.RoutePolicyWithRoute, v7action.Warnings, error) + getRoutePoliciesForSpaceMutex sync.RWMutex + getRoutePoliciesForSpaceArgsForCall []struct { + arg1 string + arg2 string + arg3 string + arg4 string + arg5 string + } + getRoutePoliciesForSpaceReturns struct { + result1 []v7action.RoutePolicyWithRoute + result2 v7action.Warnings + result3 error + } + getRoutePoliciesForSpaceReturnsOnCall map[int]struct { + result1 []v7action.RoutePolicyWithRoute + result2 v7action.Warnings + result3 error + } GetRouteSummariesStub func([]resources.Route) ([]v7action.RouteSummary, v7action.Warnings, error) getRouteSummariesMutex sync.RWMutex getRouteSummariesArgsForCall []struct { @@ -3852,19 +3852,19 @@ type FakeActor struct { invocationsMutex sync.RWMutex } -func (fake *FakeActor) AddAccessRule(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { - fake.addAccessRuleMutex.Lock() - ret, specificReturn := fake.addAccessRuleReturnsOnCall[len(fake.addAccessRuleArgsForCall)] - fake.addAccessRuleArgsForCall = append(fake.addAccessRuleArgsForCall, struct { +func (fake *FakeActor) AddRoutePolicy(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { + fake.addRoutePolicyMutex.Lock() + ret, specificReturn := fake.addRoutePolicyReturnsOnCall[len(fake.addRoutePolicyArgsForCall)] + fake.addRoutePolicyArgsForCall = append(fake.addRoutePolicyArgsForCall, struct { arg1 string arg2 string arg3 string arg4 string }{arg1, arg2, arg3, arg4}) - stub := fake.AddAccessRuleStub - fakeReturns := fake.addAccessRuleReturns - fake.recordInvocation("AddAccessRule", []interface{}{arg1, arg2, arg3, arg4}) - fake.addAccessRuleMutex.Unlock() + stub := fake.AddRoutePolicyStub + fakeReturns := fake.addRoutePolicyReturns + fake.recordInvocation("AddRoutePolicy", []interface{}{arg1, arg2, arg3, arg4}) + fake.addRoutePolicyMutex.Unlock() if stub != nil { return stub(arg1, arg2, arg3, arg4) } @@ -3874,46 +3874,46 @@ func (fake *FakeActor) AddAccessRule(arg1 string, arg2 string, arg3 string, arg4 return fakeReturns.result1, fakeReturns.result2 } -func (fake *FakeActor) AddAccessRuleCallCount() int { - fake.addAccessRuleMutex.RLock() - defer fake.addAccessRuleMutex.RUnlock() - return len(fake.addAccessRuleArgsForCall) +func (fake *FakeActor) AddRoutePolicyCallCount() int { + fake.addRoutePolicyMutex.RLock() + defer fake.addRoutePolicyMutex.RUnlock() + return len(fake.addRoutePolicyArgsForCall) } -func (fake *FakeActor) AddAccessRuleCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { - fake.addAccessRuleMutex.Lock() - defer fake.addAccessRuleMutex.Unlock() - fake.AddAccessRuleStub = stub +func (fake *FakeActor) AddRoutePolicyCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { + fake.addRoutePolicyMutex.Lock() + defer fake.addRoutePolicyMutex.Unlock() + fake.AddRoutePolicyStub = stub } -func (fake *FakeActor) AddAccessRuleArgsForCall(i int) (string, string, string, string) { - fake.addAccessRuleMutex.RLock() - defer fake.addAccessRuleMutex.RUnlock() - argsForCall := fake.addAccessRuleArgsForCall[i] +func (fake *FakeActor) AddRoutePolicyArgsForCall(i int) (string, string, string, string) { + fake.addRoutePolicyMutex.RLock() + defer fake.addRoutePolicyMutex.RUnlock() + argsForCall := fake.addRoutePolicyArgsForCall[i] return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } -func (fake *FakeActor) AddAccessRuleReturns(result1 v7action.Warnings, result2 error) { - fake.addAccessRuleMutex.Lock() - defer fake.addAccessRuleMutex.Unlock() - fake.AddAccessRuleStub = nil - fake.addAccessRuleReturns = struct { +func (fake *FakeActor) AddRoutePolicyReturns(result1 v7action.Warnings, result2 error) { + fake.addRoutePolicyMutex.Lock() + defer fake.addRoutePolicyMutex.Unlock() + fake.AddRoutePolicyStub = nil + fake.addRoutePolicyReturns = struct { result1 v7action.Warnings result2 error }{result1, result2} } -func (fake *FakeActor) AddAccessRuleReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { - fake.addAccessRuleMutex.Lock() - defer fake.addAccessRuleMutex.Unlock() - fake.AddAccessRuleStub = nil - if fake.addAccessRuleReturnsOnCall == nil { - fake.addAccessRuleReturnsOnCall = make(map[int]struct { +func (fake *FakeActor) AddRoutePolicyReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.addRoutePolicyMutex.Lock() + defer fake.addRoutePolicyMutex.Unlock() + fake.AddRoutePolicyStub = nil + if fake.addRoutePolicyReturnsOnCall == nil { + fake.addRoutePolicyReturnsOnCall = make(map[int]struct { result1 v7action.Warnings result2 error }) } - fake.addAccessRuleReturnsOnCall[i] = struct { + fake.addRoutePolicyReturnsOnCall[i] = struct { result1 v7action.Warnings result2 error }{result1, result2} @@ -6285,73 +6285,6 @@ func (fake *FakeActor) CreateUserProvidedServiceInstanceReturnsOnCall(i int, res }{result1, result2} } -func (fake *FakeActor) DeleteAccessRuleBySelector(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { - fake.deleteAccessRuleBySelectorMutex.Lock() - ret, specificReturn := fake.deleteAccessRuleBySelectorReturnsOnCall[len(fake.deleteAccessRuleBySelectorArgsForCall)] - fake.deleteAccessRuleBySelectorArgsForCall = append(fake.deleteAccessRuleBySelectorArgsForCall, struct { - arg1 string - arg2 string - arg3 string - arg4 string - }{arg1, arg2, arg3, arg4}) - stub := fake.DeleteAccessRuleBySelectorStub - fakeReturns := fake.deleteAccessRuleBySelectorReturns - fake.recordInvocation("DeleteAccessRuleBySelector", []interface{}{arg1, arg2, arg3, arg4}) - fake.deleteAccessRuleBySelectorMutex.Unlock() - if stub != nil { - return stub(arg1, arg2, arg3, arg4) - } - if specificReturn { - return ret.result1, ret.result2 - } - return fakeReturns.result1, fakeReturns.result2 -} - -func (fake *FakeActor) DeleteAccessRuleBySelectorCallCount() int { - fake.deleteAccessRuleBySelectorMutex.RLock() - defer fake.deleteAccessRuleBySelectorMutex.RUnlock() - return len(fake.deleteAccessRuleBySelectorArgsForCall) -} - -func (fake *FakeActor) DeleteAccessRuleBySelectorCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { - fake.deleteAccessRuleBySelectorMutex.Lock() - defer fake.deleteAccessRuleBySelectorMutex.Unlock() - fake.DeleteAccessRuleBySelectorStub = stub -} - -func (fake *FakeActor) DeleteAccessRuleBySelectorArgsForCall(i int) (string, string, string, string) { - fake.deleteAccessRuleBySelectorMutex.RLock() - defer fake.deleteAccessRuleBySelectorMutex.RUnlock() - argsForCall := fake.deleteAccessRuleBySelectorArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 -} - -func (fake *FakeActor) DeleteAccessRuleBySelectorReturns(result1 v7action.Warnings, result2 error) { - fake.deleteAccessRuleBySelectorMutex.Lock() - defer fake.deleteAccessRuleBySelectorMutex.Unlock() - fake.DeleteAccessRuleBySelectorStub = nil - fake.deleteAccessRuleBySelectorReturns = struct { - result1 v7action.Warnings - result2 error - }{result1, result2} -} - -func (fake *FakeActor) DeleteAccessRuleBySelectorReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { - fake.deleteAccessRuleBySelectorMutex.Lock() - defer fake.deleteAccessRuleBySelectorMutex.Unlock() - fake.DeleteAccessRuleBySelectorStub = nil - if fake.deleteAccessRuleBySelectorReturnsOnCall == nil { - fake.deleteAccessRuleBySelectorReturnsOnCall = make(map[int]struct { - result1 v7action.Warnings - result2 error - }) - } - fake.deleteAccessRuleBySelectorReturnsOnCall[i] = struct { - result1 v7action.Warnings - result2 error - }{result1, result2} -} - func (fake *FakeActor) DeleteApplicationByNameAndSpace(arg1 string, arg2 string, arg3 bool) (v7action.Warnings, error) { fake.deleteApplicationByNameAndSpaceMutex.Lock() ret, specificReturn := fake.deleteApplicationByNameAndSpaceReturnsOnCall[len(fake.deleteApplicationByNameAndSpaceArgsForCall)] @@ -7138,6 +7071,73 @@ func (fake *FakeActor) DeleteRouteBindingReturnsOnCall(i int, result1 chan v7act }{result1, result2, result3} } +func (fake *FakeActor) DeleteRoutePolicyBySource(arg1 string, arg2 string, arg3 string, arg4 string) (v7action.Warnings, error) { + fake.deleteRoutePolicyBySourceMutex.Lock() + ret, specificReturn := fake.deleteRoutePolicyBySourceReturnsOnCall[len(fake.deleteRoutePolicyBySourceArgsForCall)] + fake.deleteRoutePolicyBySourceArgsForCall = append(fake.deleteRoutePolicyBySourceArgsForCall, struct { + arg1 string + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + stub := fake.DeleteRoutePolicyBySourceStub + fakeReturns := fake.deleteRoutePolicyBySourceReturns + fake.recordInvocation("DeleteRoutePolicyBySource", []interface{}{arg1, arg2, arg3, arg4}) + fake.deleteRoutePolicyBySourceMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeActor) DeleteRoutePolicyBySourceCallCount() int { + fake.deleteRoutePolicyBySourceMutex.RLock() + defer fake.deleteRoutePolicyBySourceMutex.RUnlock() + return len(fake.deleteRoutePolicyBySourceArgsForCall) +} + +func (fake *FakeActor) DeleteRoutePolicyBySourceCalls(stub func(string, string, string, string) (v7action.Warnings, error)) { + fake.deleteRoutePolicyBySourceMutex.Lock() + defer fake.deleteRoutePolicyBySourceMutex.Unlock() + fake.DeleteRoutePolicyBySourceStub = stub +} + +func (fake *FakeActor) DeleteRoutePolicyBySourceArgsForCall(i int) (string, string, string, string) { + fake.deleteRoutePolicyBySourceMutex.RLock() + defer fake.deleteRoutePolicyBySourceMutex.RUnlock() + argsForCall := fake.deleteRoutePolicyBySourceArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeActor) DeleteRoutePolicyBySourceReturns(result1 v7action.Warnings, result2 error) { + fake.deleteRoutePolicyBySourceMutex.Lock() + defer fake.deleteRoutePolicyBySourceMutex.Unlock() + fake.DeleteRoutePolicyBySourceStub = nil + fake.deleteRoutePolicyBySourceReturns = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + +func (fake *FakeActor) DeleteRoutePolicyBySourceReturnsOnCall(i int, result1 v7action.Warnings, result2 error) { + fake.deleteRoutePolicyBySourceMutex.Lock() + defer fake.deleteRoutePolicyBySourceMutex.Unlock() + fake.DeleteRoutePolicyBySourceStub = nil + if fake.deleteRoutePolicyBySourceReturnsOnCall == nil { + fake.deleteRoutePolicyBySourceReturnsOnCall = make(map[int]struct { + result1 v7action.Warnings + result2 error + }) + } + fake.deleteRoutePolicyBySourceReturnsOnCall[i] = struct { + result1 v7action.Warnings + result2 error + }{result1, result2} +} + func (fake *FakeActor) DeleteSecurityGroup(arg1 string) (v7action.Warnings, error) { fake.deleteSecurityGroupMutex.Lock() ret, specificReturn := fake.deleteSecurityGroupReturnsOnCall[len(fake.deleteSecurityGroupArgsForCall)] @@ -8278,146 +8278,6 @@ func (fake *FakeActor) EntitleIsolationSegmentToOrganizationByNameReturnsOnCall( }{result1, result2} } -func (fake *FakeActor) GetAccessRulesByRoute(arg1 string, arg2 string, arg3 string) ([]resources.AccessRule, v7action.Warnings, error) { - fake.getAccessRulesByRouteMutex.Lock() - ret, specificReturn := fake.getAccessRulesByRouteReturnsOnCall[len(fake.getAccessRulesByRouteArgsForCall)] - fake.getAccessRulesByRouteArgsForCall = append(fake.getAccessRulesByRouteArgsForCall, struct { - arg1 string - arg2 string - arg3 string - }{arg1, arg2, arg3}) - stub := fake.GetAccessRulesByRouteStub - fakeReturns := fake.getAccessRulesByRouteReturns - fake.recordInvocation("GetAccessRulesByRoute", []interface{}{arg1, arg2, arg3}) - fake.getAccessRulesByRouteMutex.Unlock() - if stub != nil { - return stub(arg1, arg2, arg3) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 -} - -func (fake *FakeActor) GetAccessRulesByRouteCallCount() int { - fake.getAccessRulesByRouteMutex.RLock() - defer fake.getAccessRulesByRouteMutex.RUnlock() - return len(fake.getAccessRulesByRouteArgsForCall) -} - -func (fake *FakeActor) GetAccessRulesByRouteCalls(stub func(string, string, string) ([]resources.AccessRule, v7action.Warnings, error)) { - fake.getAccessRulesByRouteMutex.Lock() - defer fake.getAccessRulesByRouteMutex.Unlock() - fake.GetAccessRulesByRouteStub = stub -} - -func (fake *FakeActor) GetAccessRulesByRouteArgsForCall(i int) (string, string, string) { - fake.getAccessRulesByRouteMutex.RLock() - defer fake.getAccessRulesByRouteMutex.RUnlock() - argsForCall := fake.getAccessRulesByRouteArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 -} - -func (fake *FakeActor) GetAccessRulesByRouteReturns(result1 []resources.AccessRule, result2 v7action.Warnings, result3 error) { - fake.getAccessRulesByRouteMutex.Lock() - defer fake.getAccessRulesByRouteMutex.Unlock() - fake.GetAccessRulesByRouteStub = nil - fake.getAccessRulesByRouteReturns = struct { - result1 []resources.AccessRule - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeActor) GetAccessRulesByRouteReturnsOnCall(i int, result1 []resources.AccessRule, result2 v7action.Warnings, result3 error) { - fake.getAccessRulesByRouteMutex.Lock() - defer fake.getAccessRulesByRouteMutex.Unlock() - fake.GetAccessRulesByRouteStub = nil - if fake.getAccessRulesByRouteReturnsOnCall == nil { - fake.getAccessRulesByRouteReturnsOnCall = make(map[int]struct { - result1 []resources.AccessRule - result2 v7action.Warnings - result3 error - }) - } - fake.getAccessRulesByRouteReturnsOnCall[i] = struct { - result1 []resources.AccessRule - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeActor) GetAccessRulesForSpace(arg1 string, arg2 string, arg3 string, arg4 string, arg5 string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error) { - fake.getAccessRulesForSpaceMutex.Lock() - ret, specificReturn := fake.getAccessRulesForSpaceReturnsOnCall[len(fake.getAccessRulesForSpaceArgsForCall)] - fake.getAccessRulesForSpaceArgsForCall = append(fake.getAccessRulesForSpaceArgsForCall, struct { - arg1 string - arg2 string - arg3 string - arg4 string - arg5 string - }{arg1, arg2, arg3, arg4, arg5}) - stub := fake.GetAccessRulesForSpaceStub - fakeReturns := fake.getAccessRulesForSpaceReturns - fake.recordInvocation("GetAccessRulesForSpace", []interface{}{arg1, arg2, arg3, arg4, arg5}) - fake.getAccessRulesForSpaceMutex.Unlock() - if stub != nil { - return stub(arg1, arg2, arg3, arg4, arg5) - } - if specificReturn { - return ret.result1, ret.result2, ret.result3 - } - return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 -} - -func (fake *FakeActor) GetAccessRulesForSpaceCallCount() int { - fake.getAccessRulesForSpaceMutex.RLock() - defer fake.getAccessRulesForSpaceMutex.RUnlock() - return len(fake.getAccessRulesForSpaceArgsForCall) -} - -func (fake *FakeActor) GetAccessRulesForSpaceCalls(stub func(string, string, string, string, string) ([]v7action.AccessRuleWithRoute, v7action.Warnings, error)) { - fake.getAccessRulesForSpaceMutex.Lock() - defer fake.getAccessRulesForSpaceMutex.Unlock() - fake.GetAccessRulesForSpaceStub = stub -} - -func (fake *FakeActor) GetAccessRulesForSpaceArgsForCall(i int) (string, string, string, string, string) { - fake.getAccessRulesForSpaceMutex.RLock() - defer fake.getAccessRulesForSpaceMutex.RUnlock() - argsForCall := fake.getAccessRulesForSpaceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 -} - -func (fake *FakeActor) GetAccessRulesForSpaceReturns(result1 []v7action.AccessRuleWithRoute, result2 v7action.Warnings, result3 error) { - fake.getAccessRulesForSpaceMutex.Lock() - defer fake.getAccessRulesForSpaceMutex.Unlock() - fake.GetAccessRulesForSpaceStub = nil - fake.getAccessRulesForSpaceReturns = struct { - result1 []v7action.AccessRuleWithRoute - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - -func (fake *FakeActor) GetAccessRulesForSpaceReturnsOnCall(i int, result1 []v7action.AccessRuleWithRoute, result2 v7action.Warnings, result3 error) { - fake.getAccessRulesForSpaceMutex.Lock() - defer fake.getAccessRulesForSpaceMutex.Unlock() - fake.GetAccessRulesForSpaceStub = nil - if fake.getAccessRulesForSpaceReturnsOnCall == nil { - fake.getAccessRulesForSpaceReturnsOnCall = make(map[int]struct { - result1 []v7action.AccessRuleWithRoute - result2 v7action.Warnings - result3 error - }) - } - fake.getAccessRulesForSpaceReturnsOnCall[i] = struct { - result1 []v7action.AccessRuleWithRoute - result2 v7action.Warnings - result3 error - }{result1, result2, result3} -} - func (fake *FakeActor) GetAppFeature(arg1 string, arg2 string) (resources.ApplicationFeature, v7action.Warnings, error) { fake.getAppFeatureMutex.Lock() ret, specificReturn := fake.getAppFeatureReturnsOnCall[len(fake.getAppFeatureArgsForCall)] @@ -12201,6 +12061,146 @@ func (fake *FakeActor) GetRouteLabelsReturnsOnCall(i int, result1 map[string]typ }{result1, result2, result3} } +func (fake *FakeActor) GetRoutePoliciesByRoute(arg1 string, arg2 string, arg3 string) ([]resources.RoutePolicy, v7action.Warnings, error) { + fake.getRoutePoliciesByRouteMutex.Lock() + ret, specificReturn := fake.getRoutePoliciesByRouteReturnsOnCall[len(fake.getRoutePoliciesByRouteArgsForCall)] + fake.getRoutePoliciesByRouteArgsForCall = append(fake.getRoutePoliciesByRouteArgsForCall, struct { + arg1 string + arg2 string + arg3 string + }{arg1, arg2, arg3}) + stub := fake.GetRoutePoliciesByRouteStub + fakeReturns := fake.getRoutePoliciesByRouteReturns + fake.recordInvocation("GetRoutePoliciesByRoute", []interface{}{arg1, arg2, arg3}) + fake.getRoutePoliciesByRouteMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeActor) GetRoutePoliciesByRouteCallCount() int { + fake.getRoutePoliciesByRouteMutex.RLock() + defer fake.getRoutePoliciesByRouteMutex.RUnlock() + return len(fake.getRoutePoliciesByRouteArgsForCall) +} + +func (fake *FakeActor) GetRoutePoliciesByRouteCalls(stub func(string, string, string) ([]resources.RoutePolicy, v7action.Warnings, error)) { + fake.getRoutePoliciesByRouteMutex.Lock() + defer fake.getRoutePoliciesByRouteMutex.Unlock() + fake.GetRoutePoliciesByRouteStub = stub +} + +func (fake *FakeActor) GetRoutePoliciesByRouteArgsForCall(i int) (string, string, string) { + fake.getRoutePoliciesByRouteMutex.RLock() + defer fake.getRoutePoliciesByRouteMutex.RUnlock() + argsForCall := fake.getRoutePoliciesByRouteArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeActor) GetRoutePoliciesByRouteReturns(result1 []resources.RoutePolicy, result2 v7action.Warnings, result3 error) { + fake.getRoutePoliciesByRouteMutex.Lock() + defer fake.getRoutePoliciesByRouteMutex.Unlock() + fake.GetRoutePoliciesByRouteStub = nil + fake.getRoutePoliciesByRouteReturns = struct { + result1 []resources.RoutePolicy + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) GetRoutePoliciesByRouteReturnsOnCall(i int, result1 []resources.RoutePolicy, result2 v7action.Warnings, result3 error) { + fake.getRoutePoliciesByRouteMutex.Lock() + defer fake.getRoutePoliciesByRouteMutex.Unlock() + fake.GetRoutePoliciesByRouteStub = nil + if fake.getRoutePoliciesByRouteReturnsOnCall == nil { + fake.getRoutePoliciesByRouteReturnsOnCall = make(map[int]struct { + result1 []resources.RoutePolicy + result2 v7action.Warnings + result3 error + }) + } + fake.getRoutePoliciesByRouteReturnsOnCall[i] = struct { + result1 []resources.RoutePolicy + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) GetRoutePoliciesForSpace(arg1 string, arg2 string, arg3 string, arg4 string, arg5 string) ([]v7action.RoutePolicyWithRoute, v7action.Warnings, error) { + fake.getRoutePoliciesForSpaceMutex.Lock() + ret, specificReturn := fake.getRoutePoliciesForSpaceReturnsOnCall[len(fake.getRoutePoliciesForSpaceArgsForCall)] + fake.getRoutePoliciesForSpaceArgsForCall = append(fake.getRoutePoliciesForSpaceArgsForCall, struct { + arg1 string + arg2 string + arg3 string + arg4 string + arg5 string + }{arg1, arg2, arg3, arg4, arg5}) + stub := fake.GetRoutePoliciesForSpaceStub + fakeReturns := fake.getRoutePoliciesForSpaceReturns + fake.recordInvocation("GetRoutePoliciesForSpace", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.getRoutePoliciesForSpaceMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4, arg5) + } + if specificReturn { + return ret.result1, ret.result2, ret.result3 + } + return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3 +} + +func (fake *FakeActor) GetRoutePoliciesForSpaceCallCount() int { + fake.getRoutePoliciesForSpaceMutex.RLock() + defer fake.getRoutePoliciesForSpaceMutex.RUnlock() + return len(fake.getRoutePoliciesForSpaceArgsForCall) +} + +func (fake *FakeActor) GetRoutePoliciesForSpaceCalls(stub func(string, string, string, string, string) ([]v7action.RoutePolicyWithRoute, v7action.Warnings, error)) { + fake.getRoutePoliciesForSpaceMutex.Lock() + defer fake.getRoutePoliciesForSpaceMutex.Unlock() + fake.GetRoutePoliciesForSpaceStub = stub +} + +func (fake *FakeActor) GetRoutePoliciesForSpaceArgsForCall(i int) (string, string, string, string, string) { + fake.getRoutePoliciesForSpaceMutex.RLock() + defer fake.getRoutePoliciesForSpaceMutex.RUnlock() + argsForCall := fake.getRoutePoliciesForSpaceArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 +} + +func (fake *FakeActor) GetRoutePoliciesForSpaceReturns(result1 []v7action.RoutePolicyWithRoute, result2 v7action.Warnings, result3 error) { + fake.getRoutePoliciesForSpaceMutex.Lock() + defer fake.getRoutePoliciesForSpaceMutex.Unlock() + fake.GetRoutePoliciesForSpaceStub = nil + fake.getRoutePoliciesForSpaceReturns = struct { + result1 []v7action.RoutePolicyWithRoute + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + +func (fake *FakeActor) GetRoutePoliciesForSpaceReturnsOnCall(i int, result1 []v7action.RoutePolicyWithRoute, result2 v7action.Warnings, result3 error) { + fake.getRoutePoliciesForSpaceMutex.Lock() + defer fake.getRoutePoliciesForSpaceMutex.Unlock() + fake.GetRoutePoliciesForSpaceStub = nil + if fake.getRoutePoliciesForSpaceReturnsOnCall == nil { + fake.getRoutePoliciesForSpaceReturnsOnCall = make(map[int]struct { + result1 []v7action.RoutePolicyWithRoute + result2 v7action.Warnings + result3 error + }) + } + fake.getRoutePoliciesForSpaceReturnsOnCall[i] = struct { + result1 []v7action.RoutePolicyWithRoute + result2 v7action.Warnings + result3 error + }{result1, result2, result3} +} + func (fake *FakeActor) GetRouteSummaries(arg1 []resources.Route) ([]v7action.RouteSummary, v7action.Warnings, error) { var arg1Copy []resources.Route if arg1 != nil { diff --git a/command/v7/v7fakes/fake_actor_reloader.go b/command/v7/v7fakes/fake_actor_reloader.go index 87876729587..2fd49dc658f 100644 --- a/command/v7/v7fakes/fake_actor_reloader.go +++ b/command/v7/v7fakes/fake_actor_reloader.go @@ -95,8 +95,6 @@ func (fake *FakeActorReloader) ReloadReturnsOnCall(i int, result1 v7.Actor, resu func (fake *FakeActorReloader) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.reloadMutex.RLock() - defer fake.reloadMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_diff_displayer.go b/command/v7/v7fakes/fake_diff_displayer.go index 8287b97c16d..9117049608a 100644 --- a/command/v7/v7fakes/fake_diff_displayer.go +++ b/command/v7/v7fakes/fake_diff_displayer.go @@ -95,8 +95,6 @@ func (fake *FakeDiffDisplayer) DisplayDiffReturnsOnCall(i int, result1 error) { func (fake *FakeDiffDisplayer) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.displayDiffMutex.RLock() - defer fake.displayDiffMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_label_setter.go b/command/v7/v7fakes/fake_label_setter.go index 36443f1b9bb..0acdb5434c5 100644 --- a/command/v7/v7fakes/fake_label_setter.go +++ b/command/v7/v7fakes/fake_label_setter.go @@ -90,8 +90,6 @@ func (fake *FakeLabelSetter) ExecuteReturnsOnCall(i int, result1 error) { func (fake *FakeLabelSetter) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.executeMutex.RLock() - defer fake.executeMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_label_unsetter.go b/command/v7/v7fakes/fake_label_unsetter.go index 48ec32fe771..554424cb19a 100644 --- a/command/v7/v7fakes/fake_label_unsetter.go +++ b/command/v7/v7fakes/fake_label_unsetter.go @@ -90,8 +90,6 @@ func (fake *FakeLabelUnsetter) ExecuteReturnsOnCall(i int, result1 error) { func (fake *FakeLabelUnsetter) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.executeMutex.RLock() - defer fake.executeMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_manifest_locator.go b/command/v7/v7fakes/fake_manifest_locator.go index 0e41343c30c..592132dfe0e 100644 --- a/command/v7/v7fakes/fake_manifest_locator.go +++ b/command/v7/v7fakes/fake_manifest_locator.go @@ -97,8 +97,6 @@ func (fake *FakeManifestLocator) PathReturnsOnCall(i int, result1 string, result func (fake *FakeManifestLocator) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.pathMutex.RLock() - defer fake.pathMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_manifest_parser.go b/command/v7/v7fakes/fake_manifest_parser.go index dff85521ea8..d571390ed17 100644 --- a/command/v7/v7fakes/fake_manifest_parser.go +++ b/command/v7/v7fakes/fake_manifest_parser.go @@ -269,12 +269,6 @@ func (fake *FakeManifestParser) ParseManifestReturnsOnCall(i int, result1 manife func (fake *FakeManifestParser) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.interpolateManifestMutex.RLock() - defer fake.interpolateManifestMutex.RUnlock() - fake.marshalManifestMutex.RLock() - defer fake.marshalManifestMutex.RUnlock() - fake.parseManifestMutex.RLock() - defer fake.parseManifestMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_network_policies_actor.go b/command/v7/v7fakes/fake_network_policies_actor.go index 8bcdf225bd5..91ee1014c2e 100644 --- a/command/v7/v7fakes/fake_network_policies_actor.go +++ b/command/v7/v7fakes/fake_network_policies_actor.go @@ -182,10 +182,6 @@ func (fake *FakeNetworkPoliciesActor) NetworkPoliciesBySpaceAndAppNameReturnsOnC func (fake *FakeNetworkPoliciesActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.networkPoliciesBySpaceMutex.RLock() - defer fake.networkPoliciesBySpaceMutex.RUnlock() - fake.networkPoliciesBySpaceAndAppNameMutex.RLock() - defer fake.networkPoliciesBySpaceAndAppNameMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_networking_actor.go b/command/v7/v7fakes/fake_networking_actor.go index 1ca88ae4370..7fb0f93c709 100644 --- a/command/v7/v7fakes/fake_networking_actor.go +++ b/command/v7/v7fakes/fake_networking_actor.go @@ -105,8 +105,6 @@ func (fake *FakeNetworkingActor) AddNetworkPolicyReturnsOnCall(i int, result1 cf func (fake *FakeNetworkingActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.addNetworkPolicyMutex.RLock() - defer fake.addNetworkPolicyMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_progress_bar.go b/command/v7/v7fakes/fake_progress_bar.go index 53118843760..6a6c8a7b163 100644 --- a/command/v7/v7fakes/fake_progress_bar.go +++ b/command/v7/v7fakes/fake_progress_bar.go @@ -146,12 +146,6 @@ func (fake *FakeProgressBar) ReadyCalls(stub func()) { func (fake *FakeProgressBar) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.completeMutex.RLock() - defer fake.completeMutex.RUnlock() - fake.newProgressBarWrapperMutex.RLock() - defer fake.newProgressBarWrapperMutex.RUnlock() - fake.readyMutex.RLock() - defer fake.readyMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_push_actor.go b/command/v7/v7fakes/fake_push_actor.go index 74c6db82946..8517b124f1b 100644 --- a/command/v7/v7fakes/fake_push_actor.go +++ b/command/v7/v7fakes/fake_push_actor.go @@ -338,14 +338,6 @@ func (fake *FakePushActor) HandleFlagOverridesReturnsOnCall(i int, result1 manif func (fake *FakePushActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.actualizeMutex.RLock() - defer fake.actualizeMutex.RUnlock() - fake.createPushPlansMutex.RLock() - defer fake.createPushPlansMutex.RUnlock() - fake.handleDeploymentScaleFlagOverridesMutex.RLock() - defer fake.handleDeploymentScaleFlagOverridesMutex.RUnlock() - fake.handleFlagOverridesMutex.RLock() - defer fake.handleFlagOverridesMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_remove_network_policy_actor.go b/command/v7/v7fakes/fake_remove_network_policy_actor.go index 9c3a4d61aa8..390bf258738 100644 --- a/command/v7/v7fakes/fake_remove_network_policy_actor.go +++ b/command/v7/v7fakes/fake_remove_network_policy_actor.go @@ -105,8 +105,6 @@ func (fake *FakeRemoveNetworkPolicyActor) RemoveNetworkPolicyReturnsOnCall(i int func (fake *FakeRemoveNetworkPolicyActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.removeNetworkPolicyMutex.RLock() - defer fake.removeNetworkPolicyMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_revisions_actor.go b/command/v7/v7fakes/fake_revisions_actor.go index e126f4ea24b..50186a8febd 100644 --- a/command/v7/v7fakes/fake_revisions_actor.go +++ b/command/v7/v7fakes/fake_revisions_actor.go @@ -101,8 +101,6 @@ func (fake *FakeRevisionsActor) GetRevisionsByApplicationNameAndSpaceReturnsOnCa func (fake *FakeRevisionsActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.getRevisionsByApplicationNameAndSpaceMutex.RLock() - defer fake.getRevisionsByApplicationNameAndSpaceMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_set_label_actor.go b/command/v7/v7fakes/fake_set_label_actor.go index 5cbb7852e91..113aa2be611 100644 --- a/command/v7/v7fakes/fake_set_label_actor.go +++ b/command/v7/v7fakes/fake_set_label_actor.go @@ -318,16 +318,16 @@ func (fake *FakeSetLabelActor) UpdateBuildpackLabelsByBuildpackNameAndStackAndLi fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleArgsForCall = append(fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleArgsForCall, struct { arg1 string arg2 string - arg3 string - arg4 map[string]types.NullString - }{arg1, arg2, arg3, arg4}) - stub := fake.UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycleStub - fakeReturns := fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleReturns - fake.recordInvocation("UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycle", []interface{}{arg1, arg2, arg3, arg4}) - fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.Unlock() - if stub != nil { - return stub(arg1, arg2, arg3, arg4) - } + arg3 string + arg4 map[string]types.NullString + }{arg1, arg2, arg3, arg4}) + stub := fake.UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycleStub + fakeReturns := fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleReturns + fake.recordInvocation("UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycle", []interface{}{arg1, arg2, arg3, arg4}) + fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } if specificReturn { return ret.result1, ret.result2 } @@ -973,30 +973,6 @@ func (fake *FakeSetLabelActor) UpdateStackLabelsByStackNameReturnsOnCall(i int, func (fake *FakeSetLabelActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.getCurrentUserMutex.RLock() - defer fake.getCurrentUserMutex.RUnlock() - fake.updateApplicationLabelsByApplicationNameMutex.RLock() - defer fake.updateApplicationLabelsByApplicationNameMutex.RUnlock() - fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.RLock() - defer fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.RUnlock() - fake.updateDomainLabelsByDomainNameMutex.RLock() - defer fake.updateDomainLabelsByDomainNameMutex.RUnlock() - fake.updateOrganizationLabelsByOrganizationNameMutex.RLock() - defer fake.updateOrganizationLabelsByOrganizationNameMutex.RUnlock() - fake.updateRouteLabelsMutex.RLock() - defer fake.updateRouteLabelsMutex.RUnlock() - fake.updateServiceBrokerLabelsByServiceBrokerNameMutex.RLock() - defer fake.updateServiceBrokerLabelsByServiceBrokerNameMutex.RUnlock() - fake.updateServiceInstanceLabelsMutex.RLock() - defer fake.updateServiceInstanceLabelsMutex.RUnlock() - fake.updateServiceOfferingLabelsMutex.RLock() - defer fake.updateServiceOfferingLabelsMutex.RUnlock() - fake.updateServicePlanLabelsMutex.RLock() - defer fake.updateServicePlanLabelsMutex.RUnlock() - fake.updateSpaceLabelsBySpaceNameMutex.RLock() - defer fake.updateSpaceLabelsBySpaceNameMutex.RUnlock() - fake.updateStackLabelsByStackNameMutex.RLock() - defer fake.updateStackLabelsByStackNameMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_shared_sshactor.go b/command/v7/v7fakes/fake_shared_sshactor.go index a7756d048bc..b30b6111543 100644 --- a/command/v7/v7fakes/fake_shared_sshactor.go +++ b/command/v7/v7fakes/fake_shared_sshactor.go @@ -90,8 +90,6 @@ func (fake *FakeSharedSSHActor) ExecuteSecureShellReturnsOnCall(i int, result1 e func (fake *FakeSharedSSHActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.executeSecureShellMutex.RLock() - defer fake.executeSecureShellMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_v7actor_for_push.go b/command/v7/v7fakes/fake_v7actor_for_push.go index 22407358123..0341fb15432 100644 --- a/command/v7/v7fakes/fake_v7actor_for_push.go +++ b/command/v7/v7fakes/fake_v7actor_for_push.go @@ -448,16 +448,6 @@ func (fake *FakeV7ActorForPush) SetSpaceManifestReturnsOnCall(i int, result1 v7a func (fake *FakeV7ActorForPush) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() - fake.getApplicationByNameAndSpaceMutex.RLock() - defer fake.getApplicationByNameAndSpaceMutex.RUnlock() - fake.getDetailedAppSummaryMutex.RLock() - defer fake.getDetailedAppSummaryMutex.RUnlock() - fake.getStreamingLogsForApplicationByNameAndSpaceMutex.RLock() - defer fake.getStreamingLogsForApplicationByNameAndSpaceMutex.RUnlock() - fake.restartApplicationMutex.RLock() - defer fake.restartApplicationMutex.RUnlock() - fake.setSpaceManifestMutex.RLock() - defer fake.setSpaceManifestMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/devbox.json b/devbox.json new file mode 100644 index 00000000000..211ce41b32c --- /dev/null +++ b/devbox.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.16.0/.schema/devbox.schema.json", + "packages": ["go@1.26.1"], + "shell": { + "init_hook": [ + "echo 'Welcome to devbox!' > /dev/null" + ], + "scripts": { + "test": [ + "echo \"Error: no test specified\" && exit 1" + ] + } + } +} diff --git a/devbox.lock b/devbox.lock new file mode 100644 index 00000000000..19ff081759c --- /dev/null +++ b/devbox.lock @@ -0,0 +1,57 @@ +{ + "lockfile_version": "1", + "packages": { + "github:NixOS/nixpkgs/nixpkgs-unstable": { + "last_modified": "2026-03-16T02:27:38Z", + "resolved": "github:NixOS/nixpkgs/f8573b9c935cfaa162dd62cc9e75ae2db86f85df?lastModified=1773628058&narHash=sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY%3D" + }, + "go@1.26.1": { + "last_modified": "2026-03-21T07:29:51Z", + "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#go", + "source": "devbox-search", + "version": "1.26.1", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/kh43nhaz1qcpwws2xq805lrmwpmn9i3k-go-1.26.1", + "default": true + } + ], + "store_path": "/nix/store/kh43nhaz1qcpwws2xq805lrmwpmn9i3k-go-1.26.1" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/rz1pqbm5z3zfby250i0djfmfzzj7khg9-go-1.26.1", + "default": true + } + ], + "store_path": "/nix/store/rz1pqbm5z3zfby250i0djfmfzzj7khg9-go-1.26.1" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/yv6jj27racylbfjw6a1cdr91ndxbgyf6-go-1.26.1", + "default": true + } + ], + "store_path": "/nix/store/yv6jj27racylbfjw6a1cdr91ndxbgyf6-go-1.26.1" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/ckcq2mj8zk0drhaaacy6mp9d924hnr4m-go-1.26.1", + "default": true + } + ], + "store_path": "/nix/store/ckcq2mj8zk0drhaaacy6mp9d924hnr4m-go-1.26.1" + } + } + } + } +} diff --git a/integration/v7/isolated/revision_command_test.go b/integration/v7/isolated/revision_command_test.go index 423eccddb48..8243d2d6e04 100644 --- a/integration/v7/isolated/revision_command_test.go +++ b/integration/v7/isolated/revision_command_test.go @@ -1,13 +1,13 @@ package isolated import ( - "bytes" - "encoding/json" - "fmt" - "os/exec" - "strings" + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" - . "code.cloudfoundry.org/cli/v9/cf/util/testhelpers/matchers" + . "code.cloudfoundry.org/cli/v9/cf/util/testhelpers/matchers" "code.cloudfoundry.org/cli/v9/integration/helpers" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index ed4cf7668b9..5af25b52a42 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -255,4 +255,3 @@ var _ = Describe("update-stack command", func() { }) }) }) - diff --git a/resources/deployment_resource.go b/resources/deployment_resource.go index 03fa7f9d5b6..b4410ad0548 100644 --- a/resources/deployment_resource.go +++ b/resources/deployment_resource.go @@ -5,7 +5,7 @@ import ( "code.cloudfoundry.org/cli/v9/api/cloudcontroller" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/types" ) type Deployment struct { diff --git a/resources/domain_resource.go b/resources/domain_resource.go index 44841048165..a1956561d1d 100644 --- a/resources/domain_resource.go +++ b/resources/domain_resource.go @@ -6,14 +6,14 @@ import ( ) type Domain struct { - GUID string `json:"guid,omitempty"` - Name string `json:"name"` - Internal types.NullBool `json:"internal,omitempty"` - OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` - RouterGroup string `jsonry:"router_group.guid,omitempty"` - Protocols []string `jsonry:"supported_protocols,omitempty"` - EnforceAccessRules types.NullBool `json:"enforce_access_rules,omitempty"` - AccessRulesScope string `json:"access_rules_scope,omitempty"` + GUID string `json:"guid,omitempty"` + Name string `json:"name"` + Internal types.NullBool `json:"internal,omitempty"` + OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` + RouterGroup string `jsonry:"router_group.guid,omitempty"` + Protocols []string `jsonry:"supported_protocols,omitempty"` + EnforceRoutePolicies types.NullBool `json:"enforce_route_policies,omitempty"` + RoutePoliciesScope string `json:"route_policies_scope,omitempty"` // Metadata is used for custom tagging of API resources Metadata *Metadata `json:"metadata,omitempty"` @@ -21,30 +21,30 @@ type Domain struct { func (d Domain) MarshalJSON() ([]byte, error) { type domainWithBoolPointer struct { - GUID string `jsonry:"guid,omitempty"` - Name string `jsonry:"name"` - Internal *bool `jsonry:"internal,omitempty"` - OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` - RouterGroup string `jsonry:"router_group.guid,omitempty"` - Protocols []string `jsonry:"supported_protocols,omitempty"` - EnforceAccessRules *bool `jsonry:"enforce_access_rules,omitempty"` - AccessRulesScope string `jsonry:"access_rules_scope,omitempty"` + GUID string `jsonry:"guid,omitempty"` + Name string `jsonry:"name"` + Internal *bool `jsonry:"internal,omitempty"` + OrganizationGUID string `jsonry:"relationships.organization.data.guid,omitempty"` + RouterGroup string `jsonry:"router_group.guid,omitempty"` + Protocols []string `jsonry:"supported_protocols,omitempty"` + EnforceRoutePolicies *bool `jsonry:"enforce_route_policies,omitempty"` + RoutePoliciesScope string `jsonry:"route_policies_scope,omitempty"` } clone := domainWithBoolPointer{ - GUID: d.GUID, - Name: d.Name, - OrganizationGUID: d.OrganizationGUID, - RouterGroup: d.RouterGroup, - Protocols: d.Protocols, - AccessRulesScope: d.AccessRulesScope, + GUID: d.GUID, + Name: d.Name, + OrganizationGUID: d.OrganizationGUID, + RouterGroup: d.RouterGroup, + Protocols: d.Protocols, + RoutePoliciesScope: d.RoutePoliciesScope, } if d.Internal.IsSet { clone.Internal = &d.Internal.Value } - if d.EnforceAccessRules.IsSet { - clone.EnforceAccessRules = &d.EnforceAccessRules.Value + if d.EnforceRoutePolicies.IsSet { + clone.EnforceRoutePolicies = &d.EnforceRoutePolicies.Value } return jsonry.Marshal(clone) } diff --git a/resources/process_resource.go b/resources/process_resource.go index 1d136266b17..0dd88719e02 100644 --- a/resources/process_resource.go +++ b/resources/process_resource.go @@ -134,9 +134,9 @@ type marshalProcess struct { DiskInMB json.Number `json:"disk_in_mb,omitempty"` LogRateLimitInBPS json.Number `json:"log_rate_limit_in_bytes_per_second,omitempty"` - HealthCheck *healthCheck `json:"health_check,omitempty"` - ReadinessHealthCheck *readinessHealthCheck `json:"readiness_health_check,omitempty"` - EmbeddedProcessInstances []EmbeddedProcessInstance `json:"process_instances,omitempty"` + HealthCheck *healthCheck `json:"health_check,omitempty"` + ReadinessHealthCheck *readinessHealthCheck `json:"readiness_health_check,omitempty"` + EmbeddedProcessInstances []EmbeddedProcessInstance `json:"process_instances,omitempty"` } func marshalCommand(p Process, ccProcess *marshalProcess) { diff --git a/resources/access_rule_resource.go b/resources/route_policy_resource.go similarity index 67% rename from resources/access_rule_resource.go rename to resources/route_policy_resource.go index 7900ca03392..6701876b567 100644 --- a/resources/access_rule_resource.go +++ b/resources/route_policy_resource.go @@ -7,23 +7,23 @@ import ( "code.cloudfoundry.org/cli/v9/api/cloudcontroller" ) -type AccessRule struct { +type RoutePolicy struct { GUID string `json:"guid,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` - Selector string `json:"selector"` + Source string `json:"source"` RouteGUID string `json:"-"` // Metadata is used for custom tagging of API resources Metadata *Metadata `json:"metadata,omitempty"` } -func (a AccessRule) MarshalJSON() ([]byte, error) { +func (r RoutePolicy) MarshalJSON() ([]byte, error) { type alias struct { GUID string `json:"guid,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` - Selector string `json:"selector"` + Source string `json:"source"` Metadata *Metadata `json:"metadata,omitempty"` Relationships struct { @@ -36,22 +36,22 @@ func (a AccessRule) MarshalJSON() ([]byte, error) { } var aliasData alias - aliasData.GUID = a.GUID - aliasData.CreatedAt = a.CreatedAt - aliasData.UpdatedAt = a.UpdatedAt - aliasData.Selector = a.Selector - aliasData.Metadata = a.Metadata - aliasData.Relationships.Route.Data.GUID = a.RouteGUID + aliasData.GUID = r.GUID + aliasData.CreatedAt = r.CreatedAt + aliasData.UpdatedAt = r.UpdatedAt + aliasData.Source = r.Source + aliasData.Metadata = r.Metadata + aliasData.Relationships.Route.Data.GUID = r.RouteGUID return json.Marshal(aliasData) } -func (a *AccessRule) UnmarshalJSON(data []byte) error { +func (r *RoutePolicy) UnmarshalJSON(data []byte) error { var alias struct { GUID string `json:"guid,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` - Selector string `json:"selector"` + Source string `json:"source"` Metadata *Metadata `json:"metadata,omitempty"` Relationships struct { @@ -68,12 +68,12 @@ func (a *AccessRule) UnmarshalJSON(data []byte) error { return err } - a.GUID = alias.GUID - a.CreatedAt = alias.CreatedAt - a.UpdatedAt = alias.UpdatedAt - a.Selector = alias.Selector - a.RouteGUID = alias.Relationships.Route.Data.GUID - a.Metadata = alias.Metadata + r.GUID = alias.GUID + r.CreatedAt = alias.CreatedAt + r.UpdatedAt = alias.UpdatedAt + r.Source = alias.Source + r.RouteGUID = alias.Relationships.Route.Data.GUID + r.Metadata = alias.Metadata return nil } diff --git a/resources/access_rule_resource_test.go b/resources/route_policy_resource_test.go similarity index 53% rename from resources/access_rule_resource_test.go rename to resources/route_policy_resource_test.go index 52e3a9b5110..b742e78a893 100644 --- a/resources/access_rule_resource_test.go +++ b/resources/route_policy_resource_test.go @@ -9,29 +9,27 @@ import ( . "github.com/onsi/gomega" ) -func TestAccessRuleResource(t *testing.T) { +func TestRoutePolicyResource(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "AccessRule Resource Suite") + RunSpecs(t, "RoutePolicy Resource Suite") } -var _ = Describe("AccessRule", func() { +var _ = Describe("RoutePolicy", func() { Describe("MarshalJSON", func() { - It("marshals the access rule with relationships", func() { - rule := resources.AccessRule{ - Name: "allow-backend", - Selector: "cf:app:some-app-guid", + It("marshals the route policy with relationships", func() { + policy := resources.RoutePolicy{ + Source: "cf:app:some-app-guid", RouteGUID: "some-route-guid", } - data, err := json.Marshal(rule) + data, err := json.Marshal(policy) Expect(err).NotTo(HaveOccurred()) var result map[string]interface{} err = json.Unmarshal(data, &result) Expect(err).NotTo(HaveOccurred()) - Expect(result["name"]).To(Equal("allow-backend")) - Expect(result["selector"]).To(Equal("cf:app:some-app-guid")) + Expect(result["source"]).To(Equal("cf:app:some-app-guid")) Expect(result["relationships"]).NotTo(BeNil()) relationships := result["relationships"].(map[string]interface{}) @@ -42,11 +40,10 @@ var _ = Describe("AccessRule", func() { }) Describe("UnmarshalJSON", func() { - It("unmarshals the access rule from relationships", func() { + It("unmarshals the route policy from relationships", func() { jsonData := `{ "guid": "some-guid", - "name": "test-rule", - "selector": "cf:app:app-guid", + "source": "cf:app:app-guid", "relationships": { "route": { "data": { @@ -56,14 +53,13 @@ var _ = Describe("AccessRule", func() { } }` - var rule resources.AccessRule - err := json.Unmarshal([]byte(jsonData), &rule) + var policy resources.RoutePolicy + err := json.Unmarshal([]byte(jsonData), &policy) Expect(err).NotTo(HaveOccurred()) - Expect(rule.GUID).To(Equal("some-guid")) - Expect(rule.Name).To(Equal("test-rule")) - Expect(rule.Selector).To(Equal("cf:app:app-guid")) - Expect(rule.RouteGUID).To(Equal("route-guid-123")) + Expect(policy.GUID).To(Equal("some-guid")) + Expect(policy.Source).To(Equal("cf:app:app-guid")) + Expect(policy.RouteGUID).To(Equal("route-guid-123")) }) }) }) From 1df8f981287009432455bea43c3ee389558f1fb3 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 09:57:32 +0200 Subject: [PATCH 05/17] 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:, etc.), while add-route-policy supported name-based resolution. The two commands now have matching flag sets. --- command/v7/add_route_policy_command.go | 184 +--------------------- command/v7/remove_route_policy_command.go | 32 ++-- command/v7/route_policy_source_flags.go | 167 ++++++++++++++++++++ 3 files changed, 191 insertions(+), 192 deletions(-) create mode 100644 command/v7/route_policy_source_flags.go diff --git a/command/v7/add_route_policy_command.go b/command/v7/add_route_policy_command.go index 7845d4b7e3e..57f515cc696 100644 --- a/command/v7/add_route_policy_command.go +++ b/command/v7/add_route_policy_command.go @@ -3,10 +3,7 @@ package v7 import ( "fmt" - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/command/flag" - "code.cloudfoundry.org/cli/v9/command/translatableerror" ) type AddRoutePolicyCommand struct { @@ -15,23 +12,14 @@ type AddRoutePolicyCommand struct { RequiredArgs flag.AddRoutePolicyArgs `positional-args:"yes"` Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` Path string `long:"path" description:"Path for the route"` - - // Source resolution flags (mutually exclusive as primary source) - SourceApp string `long:"source-app" description:"Allow access from this app (by name)"` - SourceSpace string `long:"source-space" description:"Allow access from all apps in this space (by name) or specify the space for --source-app"` - 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"` - SourceAny bool `long:"source-any" description:"Allow access from any authenticated app"` - - // Advanced: raw source flag - Source string `long:"source" description:"Raw source (cf:app:, cf:space:, cf:org:, or cf:any)"` + RoutePolicySourceFlags 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"` relatedCommands interface{} `related_commands:"route-policies, remove-route-policy, create-shared-domain"` } func (cmd AddRoutePolicyCommand) Execute(args []string) error { - // Validate source flags - if err := cmd.validateSourceFlags(); err != nil { + if err := cmd.RoutePolicySourceFlags.validateSourceFlags(); err != nil { return err } @@ -45,14 +33,12 @@ func (cmd AddRoutePolicyCommand) Execute(args []string) error { return err } - // Resolve source from source flags - source, scopeDisplay, warnings, err := cmd.resolveSource() + source, scopeDisplay, warnings, err := resolveSource(cmd.RoutePolicySourceFlags, cmd.Actor, cmd.Config) cmd.UI.DisplayWarnings(warnings) if err != nil { return err } - // Validate source format if err := validateSource(source); err != nil { return err } @@ -67,7 +53,6 @@ func (cmd AddRoutePolicyCommand) Execute(args []string) error { "User": user.Name, }) - // Display resolved source (for transparency) cmd.UI.DisplayText(" {{.ScopeDisplay}}", map[string]interface{}{ "ScopeDisplay": scopeDisplay, @@ -88,169 +73,7 @@ func (cmd AddRoutePolicyCommand) Execute(args []string) error { return nil } -// validateSourceFlags ensures exactly one source target is specified and validates combinations -func (cmd AddRoutePolicyCommand) validateSourceFlags() error { - sourceFlags := []string{} - - if cmd.Source != "" { - sourceFlags = append(sourceFlags, "--source") - } - if cmd.SourceApp != "" { - sourceFlags = append(sourceFlags, "--source-app") - } - if cmd.SourceSpace != "" && cmd.SourceApp == "" { - // --source-space only counts as a primary source if --source-app is NOT provided - sourceFlags = append(sourceFlags, "--source-space") - } - if cmd.SourceOrg != "" && cmd.SourceSpace == "" && cmd.SourceApp == "" { - // --source-org only counts as a primary source if neither --source-space nor --source-app are provided - sourceFlags = append(sourceFlags, "--source-org") - } - if cmd.SourceAny { - sourceFlags = append(sourceFlags, "--source-any") - } - - if len(sourceFlags) == 0 { - return translatableerror.RequiredArgumentError{ - ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --source", - } - } - - if len(sourceFlags) > 1 { - return translatableerror.ArgumentCombinationError{ - Args: sourceFlags, - } - } - - return nil -} - -// resolveSource resolves source flags to a source string -// Returns (source, scopeDisplay, warnings, error) -// scopeDisplay is a human-readable description for output (e.g., "scope: app, source: frontend-app") -func (cmd AddRoutePolicyCommand) resolveSource() (string, string, v7action.Warnings, error) { - var allWarnings v7action.Warnings - - // Priority: --source flag (raw source, no resolution needed) - if cmd.Source != "" { - return cmd.Source, fmt.Sprintf("source: %s", cmd.Source), allWarnings, nil - } - - // --source-any - if cmd.SourceAny { - return "cf:any", "scope: any, source: any authenticated app", allWarnings, nil - } - - // --source-app (with optional --source-space and --source-org for cross-space/org lookup) - if cmd.SourceApp != "" { - // Determine space GUID for app lookup - spaceGUID := cmd.Config.TargetedSpace().GUID - spaceName := cmd.Config.TargetedSpace().Name - orgName := cmd.Config.TargetedOrganization().Name - - if cmd.SourceSpace != "" { - // Determine org GUID for space lookup - orgGUID := cmd.Config.TargetedOrganization().GUID - if cmd.SourceOrg != "" { - org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg) - allWarnings = append(allWarnings, warnings...) - if err != nil { - return "", "", allWarnings, err - } - orgGUID = org.GUID - orgName = cmd.SourceOrg - } - - // Resolve space by name - space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.SourceSpace, orgGUID) - allWarnings = append(allWarnings, warnings...) - if err != nil { - return "", "", allWarnings, err - } - spaceGUID = space.GUID - spaceName = cmd.SourceSpace - } - - // Resolve app by name in the determined space - app, warnings, err := cmd.Actor.GetApplicationByNameAndSpace(cmd.SourceApp, spaceGUID) - allWarnings = append(allWarnings, warnings...) - if err != nil { - // Enhanced error message for app not found - if _, ok := err.(actionerror.ApplicationNotFoundError); ok { - if cmd.SourceSpace == "" { - // App not found in current space - return "", "", allWarnings, fmt.Errorf( - "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.", - cmd.SourceApp, - cmd.Config.TargetedSpace().Name, - cmd.Config.TargetedOrganization().Name, - ) - } - } - return "", "", allWarnings, err - } - - scopeDisplay := fmt.Sprintf("scope: app, source: %s", cmd.SourceApp) - if cmd.SourceSpace != "" { - scopeDisplay += fmt.Sprintf(" (space: %s", spaceName) - if cmd.SourceOrg != "" { - scopeDisplay += fmt.Sprintf(", org: %s", orgName) - } - scopeDisplay += ")" - } - - return fmt.Sprintf("cf:app:%s", app.GUID), scopeDisplay, allWarnings, nil - } - - // --source-space (without --source-app, so create space-level policy) - if cmd.SourceSpace != "" { - // Determine org GUID for space lookup - orgGUID := cmd.Config.TargetedOrganization().GUID - orgName := cmd.Config.TargetedOrganization().Name - if cmd.SourceOrg != "" { - org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg) - allWarnings = append(allWarnings, warnings...) - if err != nil { - return "", "", allWarnings, err - } - orgGUID = org.GUID - orgName = cmd.SourceOrg - } - - // Resolve space by name - space, warnings, err := cmd.Actor.GetSpaceByNameAndOrganization(cmd.SourceSpace, orgGUID) - allWarnings = append(allWarnings, warnings...) - if err != nil { - return "", "", allWarnings, err - } - - scopeDisplay := fmt.Sprintf("scope: space, source: %s", cmd.SourceSpace) - if cmd.SourceOrg != "" { - scopeDisplay += fmt.Sprintf(" (org: %s)", orgName) - } - - return fmt.Sprintf("cf:space:%s", space.GUID), scopeDisplay, allWarnings, nil - } - - // --source-org (without --source-space or --source-app, so create org-level policy) - if cmd.SourceOrg != "" { - org, warnings, err := cmd.Actor.GetOrganizationByName(cmd.SourceOrg) - allWarnings = append(allWarnings, warnings...) - if err != nil { - return "", "", allWarnings, err - } - - scopeDisplay := fmt.Sprintf("scope: org, source: %s", cmd.SourceOrg) - - return fmt.Sprintf("cf:org:%s", org.GUID), scopeDisplay, allWarnings, nil - } - - // Should never reach here due to validation - return "", "", allWarnings, fmt.Errorf("no source specified") -} - func validateSource(source string) error { - // Basic validation - check for cf:app:, cf:space:, cf:org:, or cf:any prefix validPrefixes := []string{"cf:app:", "cf:space:", "cf:org:", "cf:any"} for _, prefix := range validPrefixes { if len(source) >= len(prefix) && source[:len(prefix)] == prefix { @@ -260,7 +83,6 @@ func validateSource(source string) error { } return nil } - // For other sources, ensure there's a GUID after the prefix if len(source) <= len(prefix) { return fmt.Errorf("source '%s' must include a GUID (e.g., %s)", source, prefix) } diff --git a/command/v7/remove_route_policy_command.go b/command/v7/remove_route_policy_command.go index e026d2fd918..1470ae828fe 100644 --- a/command/v7/remove_route_policy_command.go +++ b/command/v7/remove_route_policy_command.go @@ -7,16 +7,21 @@ import ( type RemoveRoutePolicyCommand struct { BaseCommand - RequiredArgs flag.RemoveRoutePolicyArgs `positional-args:"yes"` - Source string `long:"source" required:"true" description:"Source to identify the route policy (cf:app:, cf:space:, cf:org:, or cf:any)"` - Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` - Path string `long:"path" description:"Path for the route"` - Force bool `short:"f" description:"Force deletion without confirmation"` - 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"` - relatedCommands interface{} `related_commands:"route-policies, add-route-policy"` + RequiredArgs flag.RemoveRoutePolicyArgs `positional-args:"yes"` + RoutePolicySourceFlags + Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` + Path string `long:"path" description:"Path for the route"` + Force bool `short:"f" description:"Force deletion without confirmation"` + + 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"` + relatedCommands interface{} `related_commands:"route-policies, add-route-policy"` } func (cmd RemoveRoutePolicyCommand) Execute(args []string) error { + if err := cmd.RoutePolicySourceFlags.validateSourceFlags(); err != nil { + return err + } + err := cmd.SharedActor.CheckTarget(true, true) if err != nil { return err @@ -27,8 +32,13 @@ func (cmd RemoveRoutePolicyCommand) Execute(args []string) error { return err } - // Validate source format - if err := validateSource(cmd.Source); err != nil { + source, _, warnings, err := resolveSource(cmd.RoutePolicySourceFlags, cmd.Actor, cmd.Config) + cmd.UI.DisplayWarnings(warnings) + if err != nil { + return err + } + + if err := validateSource(source); err != nil { return err } @@ -37,7 +47,7 @@ func (cmd RemoveRoutePolicyCommand) Execute(args []string) error { if !cmd.Force { prompt := "Really remove route policy with source {{.Source}} for route {{.Hostname}}.{{.Domain}}{{.Path}}?" response, promptErr := cmd.UI.DisplayBoolPrompt(false, prompt, map[string]interface{}{ - "Source": cmd.Source, + "Source": source, "Hostname": cmd.Hostname, "Domain": domainName, "Path": formatPath(cmd.Path), @@ -61,7 +71,7 @@ func (cmd RemoveRoutePolicyCommand) Execute(args []string) error { "User": user.Name, }) - warnings, err := cmd.Actor.DeleteRoutePolicyBySource(domainName, cmd.Source, cmd.Hostname, cmd.Path) + warnings, err = cmd.Actor.DeleteRoutePolicyBySource(domainName, source, cmd.Hostname, cmd.Path) cmd.UI.DisplayWarnings(warnings) if err != nil { return err diff --git a/command/v7/route_policy_source_flags.go b/command/v7/route_policy_source_flags.go new file mode 100644 index 00000000000..15c5fb61290 --- /dev/null +++ b/command/v7/route_policy_source_flags.go @@ -0,0 +1,167 @@ +package v7 + +import ( + "fmt" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command" + "code.cloudfoundry.org/cli/v9/command/translatableerror" +) + +// RoutePolicySourceFlags holds the mutually exclusive source-resolution flags +// shared between add-route-policy and remove-route-policy. +type RoutePolicySourceFlags struct { + SourceApp string `long:"source-app" description:"App name to identify the source (resolves to GUID)"` + SourceSpace string `long:"source-space" description:"Space name to identify the source (by name) or specify the space for --source-app"` + SourceOrg string `long:"source-org" description:"Org name to identify the source (by name) or specify the org for --source-space/--source-app"` + SourceAny bool `long:"source-any" description:"Any authenticated app"` + Source string `long:"source" description:"Raw source (cf:app:, cf:space:, cf:org:, or cf:any)"` +} + +// validateSourceFlags ensures exactly one primary source is specified. +func (f RoutePolicySourceFlags) validateSourceFlags() error { + sourceFlags := []string{} + + if f.Source != "" { + sourceFlags = append(sourceFlags, "--source") + } + if f.SourceApp != "" { + sourceFlags = append(sourceFlags, "--source-app") + } + if f.SourceSpace != "" && f.SourceApp == "" { + sourceFlags = append(sourceFlags, "--source-space") + } + if f.SourceOrg != "" && f.SourceSpace == "" && f.SourceApp == "" { + sourceFlags = append(sourceFlags, "--source-org") + } + if f.SourceAny { + sourceFlags = append(sourceFlags, "--source-any") + } + + if len(sourceFlags) == 0 { + return translatableerror.RequiredArgumentError{ + ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --source", + } + } + + if len(sourceFlags) > 1 { + return translatableerror.ArgumentCombinationError{ + Args: sourceFlags, + } + } + + return nil +} + +// resolveSource resolves source flags to a raw source string and a human-readable +// scope description. Returns (source, scopeDisplay, warnings, error). +func resolveSource(f RoutePolicySourceFlags, actor Actor, config command.Config) (string, string, v7action.Warnings, error) { + var allWarnings v7action.Warnings + + // --source: raw value, no resolution needed + if f.Source != "" { + return f.Source, fmt.Sprintf("source: %s", f.Source), allWarnings, nil + } + + // --source-any + if f.SourceAny { + return "cf:any", "scope: any, source: any authenticated app", allWarnings, nil + } + + // --source-app (with optional --source-space / --source-org for cross-space/org lookup) + if f.SourceApp != "" { + spaceGUID := config.TargetedSpace().GUID + spaceName := config.TargetedSpace().Name + orgName := config.TargetedOrganization().Name + + if f.SourceSpace != "" { + orgGUID := config.TargetedOrganization().GUID + if f.SourceOrg != "" { + org, warnings, err := actor.GetOrganizationByName(f.SourceOrg) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + orgGUID = org.GUID + orgName = f.SourceOrg + } + + space, warnings, err := actor.GetSpaceByNameAndOrganization(f.SourceSpace, orgGUID) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + spaceGUID = space.GUID + spaceName = f.SourceSpace + } + + app, warnings, err := actor.GetApplicationByNameAndSpace(f.SourceApp, spaceGUID) + allWarnings = append(allWarnings, warnings...) + if err != nil { + if _, ok := err.(actionerror.ApplicationNotFoundError); ok && f.SourceSpace == "" { + return "", "", allWarnings, fmt.Errorf( + "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.", + f.SourceApp, + config.TargetedSpace().Name, + config.TargetedOrganization().Name, + ) + } + return "", "", allWarnings, err + } + + scopeDisplay := fmt.Sprintf("scope: app, source: %s", f.SourceApp) + if f.SourceSpace != "" { + scopeDisplay += fmt.Sprintf(" (space: %s", spaceName) + if f.SourceOrg != "" { + scopeDisplay += fmt.Sprintf(", org: %s", orgName) + } + scopeDisplay += ")" + } + + return fmt.Sprintf("cf:app:%s", app.GUID), scopeDisplay, allWarnings, nil + } + + // --source-space (primary: space-level policy) + if f.SourceSpace != "" { + orgGUID := config.TargetedOrganization().GUID + orgName := config.TargetedOrganization().Name + if f.SourceOrg != "" { + org, warnings, err := actor.GetOrganizationByName(f.SourceOrg) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + orgGUID = org.GUID + orgName = f.SourceOrg + } + + space, warnings, err := actor.GetSpaceByNameAndOrganization(f.SourceSpace, orgGUID) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + + scopeDisplay := fmt.Sprintf("scope: space, source: %s", f.SourceSpace) + if f.SourceOrg != "" { + scopeDisplay += fmt.Sprintf(" (org: %s)", orgName) + } + + return fmt.Sprintf("cf:space:%s", space.GUID), scopeDisplay, allWarnings, nil + } + + // --source-org (primary: org-level policy) + if f.SourceOrg != "" { + org, warnings, err := actor.GetOrganizationByName(f.SourceOrg) + allWarnings = append(allWarnings, warnings...) + if err != nil { + return "", "", allWarnings, err + } + + return fmt.Sprintf("cf:org:%s", org.GUID), + fmt.Sprintf("scope: org, source: %s", f.SourceOrg), + allWarnings, nil + } + + return "", "", allWarnings, fmt.Errorf("no source specified") +} From 6108fec54d7f128e812b193f252b7eb557f05070 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 10:12:22 +0200 Subject: [PATCH 06/17] Add CAPI version check for route policy commands Guard add-route-policy, remove-route-policy, and route-policies with an unconditional MinimumCCAPIVersionCheck against MinVersionRoutePolicies. Guard create-shared-domain and create-private-domain conditionally when --enforce-route-policies is passed. MinVersionRoutePolicies is currently a placeholder (3.999.0); a failing test in ccversion/minimum_version_test.go keeps the TODO visible until the real CAPI version is confirmed and the constant is updated. --- .../ccversion/minimum_version.go | 6 +++++ .../ccversion/minimum_version_test.go | 25 +++++++++++++++++++ command/v7/add_route_policy_command.go | 6 +++++ command/v7/create_private_domain_command.go | 8 ++++++ command/v7/create_shared_domain_command.go | 8 ++++++ command/v7/remove_route_policy_command.go | 6 +++++ command/v7/route_policies_command.go | 6 +++++ 7 files changed, 65 insertions(+) create mode 100644 api/cloudcontroller/ccversion/minimum_version_test.go diff --git a/api/cloudcontroller/ccversion/minimum_version.go b/api/cloudcontroller/ccversion/minimum_version.go index 1e8194a14a5..4947d0b476c 100644 --- a/api/cloudcontroller/ccversion/minimum_version.go +++ b/api/cloudcontroller/ccversion/minimum_version.go @@ -22,4 +22,10 @@ const ( MinVersionEmbeddedProcessInstances = "3.211.0" MinVersionUpdateStack = "3.211.0" + + // MinVersionRoutePolicies is a placeholder until the CAPI team confirms the + // version that introduces /v3/route_policies and the enforce_route_policies / + // route_policies_scope domain fields. Replace "3.999.0" with the real version + // once known. The test in minimum_version_test.go will keep failing until then. + MinVersionRoutePolicies = "3.999.0" ) diff --git a/api/cloudcontroller/ccversion/minimum_version_test.go b/api/cloudcontroller/ccversion/minimum_version_test.go new file mode 100644 index 00000000000..aa6d8c4a7db --- /dev/null +++ b/api/cloudcontroller/ccversion/minimum_version_test.go @@ -0,0 +1,25 @@ +package ccversion_test + +import ( + "testing" + + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" +) + +// TestMinVersionRoutePoliciesIsNotPlaceholder will keep failing until +// MinVersionRoutePolicies is updated from its placeholder value ("3.999.0") +// to the real CAPI version that introduces /v3/route_policies and the +// enforce_route_policies / route_policies_scope domain fields. +// +// To fix: coordinate with the CAPI team, confirm the released version, and +// replace ccversion.MinVersionRoutePolicies with the real value. +func TestMinVersionRoutePoliciesIsNotPlaceholder(t *testing.T) { + const placeholder = "3.999.0" + if ccversion.MinVersionRoutePolicies == placeholder { + t.Fatalf( + "ccversion.MinVersionRoutePolicies is still the placeholder %q.\n"+ + "Update it with the real CAPI version that supports route policies once known.", + placeholder, + ) + } +} diff --git a/command/v7/add_route_policy_command.go b/command/v7/add_route_policy_command.go index 57f515cc696..9ebb9c435b8 100644 --- a/command/v7/add_route_policy_command.go +++ b/command/v7/add_route_policy_command.go @@ -3,6 +3,8 @@ package v7 import ( "fmt" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" ) @@ -19,6 +21,10 @@ type AddRoutePolicyCommand struct { } func (cmd AddRoutePolicyCommand) Execute(args []string) error { + if err := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionRoutePolicies); err != nil { + return err + } + if err := cmd.RoutePolicySourceFlags.validateSourceFlags(); err != nil { return err } diff --git a/command/v7/create_private_domain_command.go b/command/v7/create_private_domain_command.go index 62ff3143314..c059b82f605 100644 --- a/command/v7/create_private_domain_command.go +++ b/command/v7/create_private_domain_command.go @@ -4,6 +4,8 @@ import ( "fmt" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" ) @@ -41,6 +43,12 @@ func (cmd CreatePrivateDomainCommand) Execute(args []string) error { return fmt.Errorf("--scope must be one of: any, org, space") } + if cmd.EnforceRoutePolicies { + if err := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionRoutePolicies, "--enforce-route-policies"); err != nil { + return err + } + } + cmd.UI.DisplayTextWithFlavor("Creating private domain {{.Domain}} for org {{.Organization}} as {{.User}}...", map[string]interface{}{ "Domain": domain, diff --git a/command/v7/create_shared_domain_command.go b/command/v7/create_shared_domain_command.go index 169133915aa..7db60c90b90 100644 --- a/command/v7/create_shared_domain_command.go +++ b/command/v7/create_shared_domain_command.go @@ -3,6 +3,8 @@ package v7 import ( "fmt" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" ) @@ -41,6 +43,12 @@ func (cmd CreateSharedDomainCommand) Execute(args []string) error { return fmt.Errorf("--scope must be one of: any, org, space") } + if cmd.EnforceRoutePolicies { + if err := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionRoutePolicies, "--enforce-route-policies"); err != nil { + return err + } + } + cmd.UI.DisplayTextWithFlavor("Creating shared domain {{.Domain}} as {{.User}}...", map[string]interface{}{ "Domain": domain, diff --git a/command/v7/remove_route_policy_command.go b/command/v7/remove_route_policy_command.go index 1470ae828fe..4691d05d459 100644 --- a/command/v7/remove_route_policy_command.go +++ b/command/v7/remove_route_policy_command.go @@ -1,6 +1,8 @@ package v7 import ( + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" ) @@ -18,6 +20,10 @@ type RemoveRoutePolicyCommand struct { } func (cmd RemoveRoutePolicyCommand) Execute(args []string) error { + if err := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionRoutePolicies); err != nil { + return err + } + if err := cmd.RoutePolicySourceFlags.validateSourceFlags(); err != nil { return err } diff --git a/command/v7/route_policies_command.go b/command/v7/route_policies_command.go index ef191180233..37bc204da74 100644 --- a/command/v7/route_policies_command.go +++ b/command/v7/route_policies_command.go @@ -1,6 +1,8 @@ package v7 import ( + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/util/ui" ) @@ -17,6 +19,10 @@ type RoutePoliciesCommand struct { } func (cmd RoutePoliciesCommand) Execute(args []string) error { + if err := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionRoutePolicies); err != nil { + return err + } + // Check target (org + space required) err := cmd.SharedActor.CheckTarget(true, true) if err != nil { From 08f4b62a13b4fc823fd32f2e69e00ab2d802f497 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 11:48:15 +0200 Subject: [PATCH 07/17] Add route policies column to cf domains output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show a single 'route policies' column when the CAPI version supports it. The column is blank for plain domains, 'enforced' when enforcement is on with no scope, and 'enforced (org/space/any)' when a scope is set. The column is gated on MinVersionRoutePolicies so it silently disappears on older CAPI targets — no hard error, cf domains still works everywhere. --- command/v7/domains_command.go | 47 ++++++++++++++++++++++------ command/v7/domains_command_test.go | 49 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/command/v7/domains_command.go b/command/v7/domains_command.go index ddf65c2f134..c757104d171 100644 --- a/command/v7/domains_command.go +++ b/command/v7/domains_command.go @@ -1,9 +1,12 @@ package v7 import ( + "fmt" "sort" "strings" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/util/sorting" "code.cloudfoundry.org/cli/v9/util/ui" @@ -51,15 +54,20 @@ func (cmd DomainsCommand) Execute(args []string) error { } func (cmd DomainsCommand) displayDomainsTable(domains []resources.Domain) { - var domainsTable = [][]string{ - { - cmd.UI.TranslateText("name"), - cmd.UI.TranslateText("availability"), - cmd.UI.TranslateText("internal"), - cmd.UI.TranslateText("protocols"), - }, + showRoutePoliciesCol := command.MinimumCCAPIVersionCheck(cmd.Config.APIVersion(), ccversion.MinVersionRoutePolicies) == nil + + headers := []string{ + cmd.UI.TranslateText("name"), + cmd.UI.TranslateText("availability"), + cmd.UI.TranslateText("internal"), + cmd.UI.TranslateText("protocols"), + } + if showRoutePoliciesCol { + headers = append(headers, cmd.UI.TranslateText("route policies")) } + domainsTable := [][]string{headers} + for _, domain := range domains { var availability string var internal string @@ -74,14 +82,35 @@ func (cmd DomainsCommand) displayDomainsTable(domains []resources.Domain) { internal = cmd.UI.TranslateText("true") } - domainsTable = append(domainsTable, []string{ + row := []string{ domain.Name, availability, internal, strings.Join(domain.Protocols, ","), - }) + } + + if showRoutePoliciesCol { + row = append(row, routePoliciesDisplay(domain)) + } + + domainsTable = append(domainsTable, row) } cmd.UI.DisplayTableWithHeader("", domainsTable, ui.DefaultTableSpacePadding) +} +// routePoliciesDisplay returns the combined route policies cell value for a domain: +// - blank — enforcement not enabled +// - "enforced" — enabled, no scope restriction +// - "enforced (org)" — enabled, org-scoped +// - "enforced (space)" — enabled, space-scoped +// - "enforced (any)" — enabled, any-scoped +func routePoliciesDisplay(d resources.Domain) string { + if !d.EnforceRoutePolicies.IsSet || !d.EnforceRoutePolicies.Value { + return "" + } + if d.RoutePoliciesScope == "" { + return "enforced" + } + return fmt.Sprintf("enforced (%s)", d.RoutePoliciesScope) } diff --git a/command/v7/domains_command_test.go b/command/v7/domains_command_test.go index 4585b4d696a..8028be95a26 100644 --- a/command/v7/domains_command_test.go +++ b/command/v7/domains_command_test.go @@ -3,6 +3,7 @@ package v7_test import ( "errors" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/types" @@ -207,5 +208,53 @@ var _ = Describe("domains Command", func() { Expect(labelSelector).To(Equal("fish=moose")) }) }) + + When("the CAPI version supports route policies", func() { + BeforeEach(func() { + fakeConfig.APIVersionReturns(ccversion.MinVersionRoutePolicies) + fakeConfig.TargetedOrganizationReturns(configv3.Organization{GUID: "some-org-guid", Name: "some-org"}) + fakeActor.GetOrganizationDomainsReturns( + []resources.Domain{ + {Name: "aaa-plain.example.com", Protocols: []string{"http"}}, + {Name: "bbb-enforced.example.com", Protocols: []string{"http"}, EnforceRoutePolicies: types.NullBool{IsSet: true, Value: true}}, + {Name: "ccc-any.example.com", Protocols: []string{"http"}, EnforceRoutePolicies: types.NullBool{IsSet: true, Value: true}, RoutePoliciesScope: "any"}, + {Name: "ddd-org.example.com", Protocols: []string{"http"}, EnforceRoutePolicies: types.NullBool{IsSet: true, Value: true}, RoutePoliciesScope: "org"}, + {Name: "eee-space.example.com", Protocols: []string{"http"}, EnforceRoutePolicies: types.NullBool{IsSet: true, Value: true}, RoutePoliciesScope: "space"}, + }, + nil, + nil, + ) + }) + + It("shows the route policies column and renders all cell variants correctly", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say(`name\s+availability\s+internal\s+protocols\s+route policies`)) + Expect(testUI.Out).To(Say(`aaa-plain\.example\.com\s+shared\s+http`)) + Expect(testUI.Out).To(Say(`bbb-enforced\.example\.com\s+shared\s+http\s+enforced`)) + Expect(testUI.Out).To(Say(`ccc-any\.example\.com\s+shared\s+http\s+enforced \(any\)`)) + Expect(testUI.Out).To(Say(`ddd-org\.example\.com\s+shared\s+http\s+enforced \(org\)`)) + Expect(testUI.Out).To(Say(`eee-space\.example\.com\s+shared\s+http\s+enforced \(space\)`)) + }) + }) + + When("the CAPI version does not support route policies", func() { + BeforeEach(func() { + fakeConfig.APIVersionReturns("3.0.0") + fakeConfig.TargetedOrganizationReturns(configv3.Organization{GUID: "some-org-guid", Name: "some-org"}) + fakeActor.GetOrganizationDomainsReturns( + []resources.Domain{ + {Name: "policy.example.com", Protocols: []string{"http"}, EnforceRoutePolicies: types.NullBool{IsSet: true, Value: true}, RoutePoliciesScope: "org"}, + }, + nil, + nil, + ) + }) + + It("omits the route policies column", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).NotTo(Say(`route policies`)) + Expect(testUI.Out).NotTo(Say(`enforced`)) + }) + }) }) }) From fed428821dd4a4835c2a1e23cd3e1152cba70aaf Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 12:16:41 +0200 Subject: [PATCH 08/17] Add cf/cli to .gitignore to prevent binary commits --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 12c343c390c..97b05d91640 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ _testmain.go # Built binaries *.exe /cli +/cf/cli out/ release/* From 3afb795ebf28310e04200d7a2be5acde08e7bd19 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 12:37:27 +0200 Subject: [PATCH 09/17] Add unit tests for route policy commands, actor, and ccv3 client --- actor/v7action/route_policy_test.go | 384 ++++++++++++++++++ api/cloudcontroller/ccv3/route_policy_test.go | 161 ++++++++ command/v7/add_route_policy_command_test.go | 139 +++++++ .../v7/remove_route_policy_command_test.go | 182 +++++++++ command/v7/route_policies_command_test.go | 149 +++++++ 5 files changed, 1015 insertions(+) create mode 100644 actor/v7action/route_policy_test.go create mode 100644 api/cloudcontroller/ccv3/route_policy_test.go create mode 100644 command/v7/add_route_policy_command_test.go create mode 100644 command/v7/remove_route_policy_command_test.go create mode 100644 command/v7/route_policies_command_test.go diff --git a/actor/v7action/route_policy_test.go b/actor/v7action/route_policy_test.go new file mode 100644 index 00000000000..e7f42ad3a39 --- /dev/null +++ b/actor/v7action/route_policy_test.go @@ -0,0 +1,384 @@ +package v7action_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + . "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/actor/v7action/v7actionfakes" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" + "code.cloudfoundry.org/cli/v9/resources" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Route Policy Actions", func() { + var ( + actor *Actor + fakeCloudControllerClient *v7actionfakes.FakeCloudControllerClient + ) + + BeforeEach(func() { + actor, fakeCloudControllerClient, _, _, _, _, _ = NewTestActor() + }) + + Describe("AddRoutePolicy", func() { + var ( + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + warnings, executeErr = actor.AddRoutePolicy("apps.example.com", "cf:any", "myapp", "") + }) + + When("the API calls are successful", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{"domain-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{{GUID: "route-guid", Host: "myapp"}}, + ccv3.Warnings{"routes-warning"}, + nil, + ) + fakeCloudControllerClient.CreateRoutePolicyReturns( + resources.RoutePolicy{GUID: "policy-guid"}, + ccv3.Warnings{"create-warning"}, + nil, + ) + }) + + It("creates the route policy and returns all warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("domain-warning", "routes-warning", "create-warning")) + + Expect(fakeCloudControllerClient.CreateRoutePolicyCallCount()).To(Equal(1)) + passedPolicy := fakeCloudControllerClient.CreateRoutePolicyArgsForCall(0) + Expect(passedPolicy.Source).To(Equal("cf:any")) + Expect(passedPolicy.RouteGUID).To(Equal("route-guid")) + }) + }) + + When("getting the domain fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + nil, + ccv3.Warnings{"domain-warning"}, + errors.New("domain-error"), + ) + }) + + It("returns the error and domain warning", func() { + Expect(executeErr).To(MatchError("domain-error")) + Expect(warnings).To(ConsistOf("domain-warning")) + }) + }) + + When("getting routes fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{"domain-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns(nil, ccv3.Warnings{"routes-warning"}, errors.New("routes-error")) + }) + + It("returns the error and all collected warnings", func() { + Expect(executeErr).To(MatchError("routes-error")) + Expect(warnings).To(ConsistOf("domain-warning", "routes-warning")) + }) + }) + + When("the route is not found", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{"domain-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{}, + ccv3.Warnings{"routes-warning"}, + nil, + ) + }) + + It("returns a RouteNotFoundError", func() { + Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{ + Host: "myapp", + DomainName: "apps.example.com", + Path: "", + })) + Expect(warnings).To(ConsistOf("domain-warning", "routes-warning")) + }) + }) + + When("creating the route policy fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{"domain-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{{GUID: "route-guid", Host: "myapp"}}, + ccv3.Warnings{"routes-warning"}, + nil, + ) + fakeCloudControllerClient.CreateRoutePolicyReturns( + resources.RoutePolicy{}, + ccv3.Warnings{"create-warning"}, + errors.New("create-error"), + ) + }) + + It("returns the error and all collected warnings", func() { + Expect(executeErr).To(MatchError("create-error")) + Expect(warnings).To(ConsistOf("domain-warning", "routes-warning", "create-warning")) + }) + }) + }) + + Describe("DeleteRoutePolicyBySource", func() { + var ( + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + warnings, executeErr = actor.DeleteRoutePolicyBySource("apps.example.com", "cf:any", "myapp", "") + }) + + When("the API calls are successful", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{"domain-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{{GUID: "route-guid", Host: "myapp"}}, + ccv3.Warnings{"routes-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutePoliciesReturns( + []resources.RoutePolicy{ + {GUID: "policy-guid", Source: "cf:any", RouteGUID: "route-guid"}, + }, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-policies-warning"}, + nil, + ) + fakeCloudControllerClient.DeleteRoutePolicyReturns( + "", + ccv3.Warnings{"delete-warning"}, + nil, + ) + }) + + It("deletes the route policy and returns all warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("domain-warning", "routes-warning", "get-policies-warning", "delete-warning")) + + Expect(fakeCloudControllerClient.DeleteRoutePolicyCallCount()).To(Equal(1)) + deletedGUID := fakeCloudControllerClient.DeleteRoutePolicyArgsForCall(0) + Expect(deletedGUID).To(Equal("policy-guid")) + }) + }) + + When("getting the domain fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + nil, + ccv3.Warnings{"domain-warning"}, + errors.New("domain-error"), + ) + }) + + It("returns the error and domain warning", func() { + Expect(executeErr).To(MatchError("domain-error")) + Expect(warnings).To(ConsistOf("domain-warning")) + }) + }) + + When("getting routes fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{"domain-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns(nil, ccv3.Warnings{"routes-warning"}, errors.New("routes-error")) + }) + + It("returns the error and all collected warnings", func() { + Expect(executeErr).To(MatchError("routes-error")) + Expect(warnings).To(ConsistOf("domain-warning", "routes-warning")) + }) + }) + + When("the route is not found", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{"domain-warning"}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{}, + ccv3.Warnings{"routes-warning"}, + nil, + ) + }) + + It("returns a RouteNotFoundError", func() { + Expect(executeErr).To(MatchError(actionerror.RouteNotFoundError{ + Host: "myapp", + DomainName: "apps.example.com", + Path: "", + })) + Expect(warnings).To(ConsistOf("domain-warning", "routes-warning")) + }) + }) + + When("getting route policies fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{{GUID: "route-guid", Host: "myapp"}}, + ccv3.Warnings{}, + nil, + ) + fakeCloudControllerClient.GetRoutePoliciesReturns( + nil, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-policies-warning"}, + errors.New("get-policies-error"), + ) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError("get-policies-error")) + Expect(warnings).To(ConsistOf("get-policies-warning")) + }) + }) + + When("no matching policy is found", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetDomainsReturns( + []resources.Domain{{Name: "apps.example.com", GUID: "domain-guid"}}, + ccv3.Warnings{}, + nil, + ) + fakeCloudControllerClient.GetRoutesReturns( + []resources.Route{{GUID: "route-guid", Host: "myapp"}}, + ccv3.Warnings{}, + nil, + ) + fakeCloudControllerClient.GetRoutePoliciesReturns( + []resources.RoutePolicy{ + {GUID: "other-policy-guid", Source: "cf:app:some-app-guid", RouteGUID: "route-guid"}, + }, + ccv3.IncludedResources{}, + ccv3.Warnings{}, + nil, + ) + }) + + It("returns a RoutePolicyNotFoundError", func() { + Expect(executeErr).To(MatchError(actionerror.RoutePolicyNotFoundError{Source: "cf:any"})) + }) + }) + }) + + Describe("GetRoutePoliciesForSpace", func() { + var ( + policiesWithRoutes []RoutePolicyWithRoute + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + policiesWithRoutes, warnings, executeErr = actor.GetRoutePoliciesForSpace( + "space-guid", "", "", "", "", + ) + }) + + When("the API calls are successful", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetRoutePoliciesReturns( + []resources.RoutePolicy{ + {GUID: "p-guid", Source: "cf:any", RouteGUID: "r-guid"}, + }, + ccv3.IncludedResources{ + Routes: []resources.Route{ + {GUID: "r-guid", Host: "backend", DomainGUID: "domain-guid"}, + }, + }, + ccv3.Warnings{"get-policies-warning"}, + nil, + ) + fakeCloudControllerClient.GetDomainReturns( + resources.Domain{Name: "apps.example.com", GUID: "domain-guid"}, + ccv3.Warnings{}, + nil, + ) + }) + + It("returns policies with route info and all warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("get-policies-warning")) + Expect(policiesWithRoutes).To(HaveLen(1)) + Expect(policiesWithRoutes[0].GUID).To(Equal("p-guid")) + Expect(policiesWithRoutes[0].Source).To(Equal("cf:any")) + Expect(policiesWithRoutes[0].Route.Host).To(Equal("backend")) + Expect(policiesWithRoutes[0].DomainName).To(Equal("apps.example.com")) + Expect(policiesWithRoutes[0].ScopeType).To(Equal("any")) + + Expect(fakeCloudControllerClient.GetDomainCallCount()).To(Equal(1)) + domainGUIDArg := fakeCloudControllerClient.GetDomainArgsForCall(0) + Expect(domainGUIDArg).To(Equal("domain-guid")) + }) + }) + + When("there are no route policies", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetRoutePoliciesReturns( + []resources.RoutePolicy{}, + ccv3.IncludedResources{}, + ccv3.Warnings{}, + nil, + ) + }) + + It("returns an empty slice and no error", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(policiesWithRoutes).To(BeEmpty()) + }) + }) + + When("getting route policies fails", func() { + BeforeEach(func() { + fakeCloudControllerClient.GetRoutePoliciesReturns( + nil, + ccv3.IncludedResources{}, + ccv3.Warnings{"get-policies-warning"}, + errors.New("get-policies-error"), + ) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError("get-policies-error")) + Expect(warnings).To(ConsistOf("get-policies-warning")) + }) + }) + }) +}) diff --git a/api/cloudcontroller/ccv3/route_policy_test.go b/api/cloudcontroller/ccv3/route_policy_test.go new file mode 100644 index 00000000000..c9f9097d68e --- /dev/null +++ b/api/cloudcontroller/ccv3/route_policy_test.go @@ -0,0 +1,161 @@ +package ccv3_test + +import ( + "errors" + + . "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/ccv3fakes" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/internal" + "code.cloudfoundry.org/cli/v9/resources" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RoutePolicy", func() { + var ( + requester *ccv3fakes.FakeRequester + client *Client + ) + + BeforeEach(func() { + requester = new(ccv3fakes.FakeRequester) + client, _ = NewFakeRequesterTestClient(requester) + }) + + Describe("CreateRoutePolicy", func() { + var ( + policy resources.RoutePolicy + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + policy, warnings, executeErr = client.CreateRoutePolicy(resources.RoutePolicy{ + Source: "cf:any", + RouteGUID: "route-guid", + }) + }) + + When("the request succeeds", func() { + BeforeEach(func() { + requester.MakeRequestReturns("", Warnings{"create-warning"}, nil) + }) + + It("makes the correct request and returns warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("create-warning")) + Expect(policy).To(Equal(resources.RoutePolicy{})) + + Expect(requester.MakeRequestCallCount()).To(Equal(1)) + actualParams := requester.MakeRequestArgsForCall(0) + Expect(actualParams.RequestName).To(Equal(internal.PostRoutePolicyRequest)) + Expect(actualParams.RequestBody).To(Equal(resources.RoutePolicy{ + Source: "cf:any", + RouteGUID: "route-guid", + })) + }) + }) + + When("the request fails", func() { + BeforeEach(func() { + requester.MakeRequestReturns("", Warnings{"warning"}, errors.New("bang")) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError("bang")) + Expect(warnings).To(ConsistOf("warning")) + }) + }) + }) + + Describe("GetRoutePolicies", func() { + var ( + policies []resources.RoutePolicy + included IncludedResources + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + policies, included, warnings, executeErr = client.GetRoutePolicies() + }) + + When("the request succeeds", func() { + BeforeEach(func() { + requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) { + Expect(requestParams.AppendToList(resources.RoutePolicy{ + GUID: "policy-guid", + Source: "cf:any", + RouteGUID: "route-guid", + })).NotTo(HaveOccurred()) + return IncludedResources{}, Warnings{"list-warning"}, nil + }) + }) + + It("makes the correct request and returns policies", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("list-warning")) + Expect(policies).To(HaveLen(1)) + Expect(policies[0].GUID).To(Equal("policy-guid")) + Expect(policies[0].Source).To(Equal("cf:any")) + Expect(included).To(Equal(IncludedResources{})) + + Expect(requester.MakeListRequestCallCount()).To(Equal(1)) + actualParams := requester.MakeListRequestArgsForCall(0) + Expect(actualParams.RequestName).To(Equal(internal.GetRoutePoliciesRequest)) + Expect(actualParams.ResponseBody).To(BeAssignableToTypeOf(resources.RoutePolicy{})) + }) + }) + + When("the request fails", func() { + BeforeEach(func() { + requester.MakeListRequestReturns(IncludedResources{}, Warnings{"warning"}, errors.New("bang")) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError("bang")) + Expect(warnings).To(ConsistOf("warning")) + }) + }) + }) + + Describe("DeleteRoutePolicy", func() { + var ( + jobURL JobURL + warnings Warnings + executeErr error + ) + + JustBeforeEach(func() { + jobURL, warnings, executeErr = client.DeleteRoutePolicy("policy-guid") + }) + + When("the request succeeds", func() { + BeforeEach(func() { + requester.MakeRequestReturns("", Warnings{"delete-warning"}, nil) + }) + + It("makes the correct request and returns warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(warnings).To(ConsistOf("delete-warning")) + Expect(jobURL).To(Equal(JobURL(""))) + + Expect(requester.MakeRequestCallCount()).To(Equal(1)) + actualParams := requester.MakeRequestArgsForCall(0) + Expect(actualParams.RequestName).To(Equal(internal.DeleteRoutePolicyRequest)) + Expect(actualParams.URIParams).To(Equal(internal.Params{"route_policy_guid": "policy-guid"})) + }) + }) + + When("the request fails", func() { + BeforeEach(func() { + requester.MakeRequestReturns("", Warnings{"warning"}, errors.New("bang")) + }) + + It("returns the error and warnings", func() { + Expect(executeErr).To(MatchError("bang")) + Expect(warnings).To(ConsistOf("warning")) + }) + }) + }) +}) diff --git a/command/v7/add_route_policy_command_test.go b/command/v7/add_route_policy_command_test.go new file mode 100644 index 00000000000..fbf00869543 --- /dev/null +++ b/command/v7/add_route_policy_command_test.go @@ -0,0 +1,139 @@ +package v7_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/command/translatableerror" + . "code.cloudfoundry.org/cli/v9/command/v7" + "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v9/util/configv3" + "code.cloudfoundry.org/cli/v9/util/ui" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("add-route-policy Command", func() { + var ( + cmd AddRoutePolicyCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + executeErr error + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + cmd = AddRoutePolicyCommand{ + BaseCommand: BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + RequiredArgs: flag.AddRoutePolicyArgs{Domain: "apps.example.com"}, + Hostname: "myapp", + RoutePolicySourceFlags: RoutePolicySourceFlags{ + SourceAny: true, + }, + } + + fakeConfig.BinaryNameReturns("faceman") + fakeConfig.APIVersionReturns("3.999.0") + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + When("the API version check fails", func() { + BeforeEach(func() { + fakeConfig.APIVersionReturns("0.0.0") + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.MinimumCFAPIVersionNotMetError{ + CurrentVersion: "0.0.0", + MinimumVersion: "3.999.0", + })) + }) + }) + + When("source flag validation fails", func() { + BeforeEach(func() { + cmd.RoutePolicySourceFlags = RoutePolicySourceFlags{} + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.RequiredArgumentError{ + ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --source", + })) + }) + }) + + When("checking the target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: "faceman"}) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError(actionerror.NotLoggedInError{BinaryName: "faceman"})) + Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("getting the current user fails", func() { + BeforeEach(func() { + fakeActor.GetCurrentUserReturns(configv3.User{}, errors.New("user-error")) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError("user-error")) + }) + }) + + When("the happy path", func() { + BeforeEach(func() { + fakeActor.GetCurrentUserReturns(configv3.User{Name: "some-user"}, nil) + fakeActor.AddRoutePolicyReturns(v7action.Warnings{"some-warning"}, nil) + }) + + It("displays the progress message and calls actor with right args", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say("Adding route policy")) + Expect(testUI.Err).To(Say("some-warning")) + Expect(testUI.Out).To(Say("OK")) + + Expect(fakeActor.AddRoutePolicyCallCount()).To(Equal(1)) + domainArg, sourceArg, hostnameArg, pathArg := fakeActor.AddRoutePolicyArgsForCall(0) + Expect(domainArg).To(Equal("apps.example.com")) + Expect(sourceArg).To(Equal("cf:any")) + Expect(hostnameArg).To(Equal("myapp")) + Expect(pathArg).To(Equal("")) + }) + }) + + When("adding the route policy fails", func() { + BeforeEach(func() { + fakeActor.GetCurrentUserReturns(configv3.User{Name: "some-user"}, nil) + fakeActor.AddRoutePolicyReturns(v7action.Warnings{"some-warning"}, errors.New("add-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(executeErr).To(MatchError("add-error")) + Expect(testUI.Err).To(Say("some-warning")) + }) + }) +}) diff --git a/command/v7/remove_route_policy_command_test.go b/command/v7/remove_route_policy_command_test.go new file mode 100644 index 00000000000..a328d933857 --- /dev/null +++ b/command/v7/remove_route_policy_command_test.go @@ -0,0 +1,182 @@ +package v7_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/command/translatableerror" + . "code.cloudfoundry.org/cli/v9/command/v7" + "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v9/util/configv3" + "code.cloudfoundry.org/cli/v9/util/ui" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("remove-route-policy Command", func() { + var ( + cmd RemoveRoutePolicyCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + input *Buffer + executeErr error + ) + + BeforeEach(func() { + input = NewBuffer() + testUI = ui.NewTestUI(input, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + cmd = RemoveRoutePolicyCommand{ + BaseCommand: BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + RequiredArgs: flag.RemoveRoutePolicyArgs{Domain: "apps.example.com"}, + Hostname: "myapp", + RoutePolicySourceFlags: RoutePolicySourceFlags{ + SourceAny: true, + }, + } + + fakeConfig.BinaryNameReturns("faceman") + fakeConfig.APIVersionReturns("3.999.0") + fakeActor.GetCurrentUserReturns(configv3.User{Name: "some-user"}, nil) + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + When("the API version check fails", func() { + BeforeEach(func() { + fakeConfig.APIVersionReturns("0.0.0") + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.MinimumCFAPIVersionNotMetError{ + CurrentVersion: "0.0.0", + MinimumVersion: "3.999.0", + })) + }) + }) + + When("source flag validation fails", func() { + BeforeEach(func() { + cmd.RoutePolicySourceFlags = RoutePolicySourceFlags{} + }) + + It("returns an error", func() { + Expect(executeErr).To(HaveOccurred()) + }) + }) + + When("checking the target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: "faceman"}) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError(actionerror.NotLoggedInError{BinaryName: "faceman"})) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("getting the current user fails", func() { + BeforeEach(func() { + fakeActor.GetCurrentUserReturns(configv3.User{}, errors.New("user-error")) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError("user-error")) + }) + }) + + When("the -f flag is set (force)", func() { + BeforeEach(func() { + cmd.Force = true + fakeActor.DeleteRoutePolicyBySourceReturns(v7action.Warnings{"some-warning"}, nil) + }) + + It("removes the policy without prompting and displays OK", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say("Removing route policy")) + Expect(testUI.Err).To(Say("some-warning")) + Expect(testUI.Out).To(Say("OK")) + + Expect(fakeActor.DeleteRoutePolicyBySourceCallCount()).To(Equal(1)) + domainArg, sourceArg, hostnameArg, pathArg := fakeActor.DeleteRoutePolicyBySourceArgsForCall(0) + Expect(domainArg).To(Equal("apps.example.com")) + Expect(sourceArg).To(Equal("cf:any")) + Expect(hostnameArg).To(Equal("myapp")) + Expect(pathArg).To(Equal("")) + }) + }) + + When("the -f flag is NOT set", func() { + BeforeEach(func() { + cmd.Force = false + }) + + When("the user answers no", func() { + BeforeEach(func() { + _, err := input.Write([]byte("n\n")) + Expect(err).NotTo(HaveOccurred()) + }) + + It("does not remove the policy", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say("Really remove route policy")) + Expect(testUI.Out).To(Say("Route policy has not been removed\\.")) + Expect(fakeActor.DeleteRoutePolicyBySourceCallCount()).To(Equal(0)) + }) + }) + + When("the user answers yes", func() { + BeforeEach(func() { + _, err := input.Write([]byte("y\n")) + Expect(err).NotTo(HaveOccurred()) + fakeActor.DeleteRoutePolicyBySourceReturns(v7action.Warnings{"some-warning"}, nil) + }) + + It("removes the policy and displays OK", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say("Really remove route policy")) + Expect(testUI.Out).To(Say("Removing route policy")) + Expect(testUI.Err).To(Say("some-warning")) + Expect(testUI.Out).To(Say("OK")) + + Expect(fakeActor.DeleteRoutePolicyBySourceCallCount()).To(Equal(1)) + domainArg, sourceArg, hostnameArg, pathArg := fakeActor.DeleteRoutePolicyBySourceArgsForCall(0) + Expect(domainArg).To(Equal("apps.example.com")) + Expect(sourceArg).To(Equal("cf:any")) + Expect(hostnameArg).To(Equal("myapp")) + Expect(pathArg).To(Equal("")) + }) + }) + }) + + When("deleting the route policy fails", func() { + BeforeEach(func() { + cmd.Force = true + fakeActor.DeleteRoutePolicyBySourceReturns(v7action.Warnings{"some-warning"}, errors.New("delete-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(executeErr).To(MatchError("delete-error")) + Expect(testUI.Err).To(Say("some-warning")) + }) + }) +}) diff --git a/command/v7/route_policies_command_test.go b/command/v7/route_policies_command_test.go new file mode 100644 index 00000000000..46752b6e3b2 --- /dev/null +++ b/command/v7/route_policies_command_test.go @@ -0,0 +1,149 @@ +package v7_test + +import ( + "errors" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + "code.cloudfoundry.org/cli/v9/command/translatableerror" + . "code.cloudfoundry.org/cli/v9/command/v7" + "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/util/configv3" + "code.cloudfoundry.org/cli/v9/util/ui" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +var _ = Describe("route-policies Command", func() { + var ( + cmd RoutePoliciesCommand + testUI *ui.UI + fakeConfig *commandfakes.FakeConfig + fakeSharedActor *commandfakes.FakeSharedActor + fakeActor *v7fakes.FakeActor + executeErr error + ) + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + fakeConfig = new(commandfakes.FakeConfig) + fakeSharedActor = new(commandfakes.FakeSharedActor) + fakeActor = new(v7fakes.FakeActor) + + cmd = RoutePoliciesCommand{ + BaseCommand: BaseCommand{ + UI: testUI, + Config: fakeConfig, + SharedActor: fakeSharedActor, + Actor: fakeActor, + }, + } + + fakeConfig.BinaryNameReturns("faceman") + fakeConfig.APIVersionReturns("3.999.0") + fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org", GUID: "org-guid"}) + fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space", GUID: "space-guid"}) + fakeActor.GetCurrentUserReturns(configv3.User{Name: "some-user"}, nil) + }) + + JustBeforeEach(func() { + executeErr = cmd.Execute(nil) + }) + + When("the API version check fails", func() { + BeforeEach(func() { + fakeConfig.APIVersionReturns("0.0.0") + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError(translatableerror.MinimumCFAPIVersionNotMetError{ + CurrentVersion: "0.0.0", + MinimumVersion: "3.999.0", + })) + }) + }) + + When("checking the target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: "faceman"}) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError(actionerror.NotLoggedInError{BinaryName: "faceman"})) + checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) + Expect(checkTargetedOrg).To(BeTrue()) + Expect(checkTargetedSpace).To(BeTrue()) + }) + }) + + When("getting the current user fails", func() { + BeforeEach(func() { + fakeActor.GetCurrentUserReturns(configv3.User{}, errors.New("user-error")) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError("user-error")) + }) + }) + + When("GetRoutePoliciesForSpace returns an error", func() { + BeforeEach(func() { + fakeActor.GetRoutePoliciesForSpaceReturns(nil, v7action.Warnings{"some-warning"}, errors.New("list-error")) + }) + + It("returns the error and displays warnings", func() { + Expect(executeErr).To(MatchError("list-error")) + Expect(testUI.Err).To(Say("some-warning")) + }) + }) + + When("there are no route policies", func() { + BeforeEach(func() { + fakeActor.GetRoutePoliciesForSpaceReturns([]v7action.RoutePolicyWithRoute{}, v7action.Warnings{}, nil) + }) + + It("displays 'No route policies found.'", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say("No route policies found\\.")) + Expect(fakeActor.GetRoutePoliciesForSpaceCallCount()).To(Equal(1)) + }) + }) + + When("there are route policies", func() { + BeforeEach(func() { + fakeActor.GetRoutePoliciesForSpaceReturns( + []v7action.RoutePolicyWithRoute{ + { + RoutePolicy: resources.RoutePolicy{GUID: "p-guid", Source: "cf:any"}, + Route: resources.Route{Host: "backend", Path: "/api"}, + DomainName: "apps.example.com", + ScopeType: "any", + SourceName: "", + }, + }, + v7action.Warnings{"some-warning"}, + nil, + ) + }) + + It("displays the table and passes the right args to the actor", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Err).To(Say("some-warning")) + Expect(testUI.Out).To(Say("host")) + Expect(testUI.Out).To(Say("backend")) + Expect(testUI.Out).To(Say("apps.example.com")) + Expect(testUI.Out).To(Say("cf:any")) + + Expect(fakeActor.GetRoutePoliciesForSpaceCallCount()).To(Equal(1)) + spaceGUIDArg, domainArg, hostnameArg, pathArg, labelsArg := fakeActor.GetRoutePoliciesForSpaceArgsForCall(0) + Expect(spaceGUIDArg).To(Equal("space-guid")) + Expect(domainArg).To(Equal("")) + Expect(hostnameArg).To(Equal("")) + Expect(pathArg).To(Equal("")) + Expect(labelsArg).To(Equal("")) + }) + }) +}) From cc265796a1fb728f01afd8dc3dcc71c936940240 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 12:57:25 +0200 Subject: [PATCH 10/17] Fix: reject --source-org with --source-app when --source-space is missing When a user specifies --source-org with --source-app but omits --source-space, validateSourceFlags() previously passed (treating --source-app as the sole primary flag), and resolveSource() silently ignored --source-org, resolving the app in the currently targeted space. Add a pre-check in validateSourceFlags() that returns RequiredFlagsError (--source-org and --source-space must be used together) whenever --source-org is combined with --source-app but --source-space is absent. --- command/v7/add_route_policy_command_test.go | 16 ++++++++++++++++ command/v7/remove_route_policy_command_test.go | 16 ++++++++++++++++ command/v7/route_policy_source_flags.go | 8 ++++++++ 3 files changed, 40 insertions(+) diff --git a/command/v7/add_route_policy_command_test.go b/command/v7/add_route_policy_command_test.go index fbf00869543..5fce35f8d51 100644 --- a/command/v7/add_route_policy_command_test.go +++ b/command/v7/add_route_policy_command_test.go @@ -80,6 +80,22 @@ var _ = Describe("add-route-policy Command", func() { }) }) + When("--source-org is used with --source-app but without --source-space", func() { + BeforeEach(func() { + cmd.RoutePolicySourceFlags = RoutePolicySourceFlags{ + SourceApp: "my-app", + SourceOrg: "my-org", + } + }) + + It("returns a RequiredFlagsError for --source-space", func() { + Expect(executeErr).To(MatchError(translatableerror.RequiredFlagsError{ + Arg1: "--source-org", + Arg2: "--source-space", + })) + }) + }) + When("checking the target fails", func() { BeforeEach(func() { fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: "faceman"}) diff --git a/command/v7/remove_route_policy_command_test.go b/command/v7/remove_route_policy_command_test.go index a328d933857..ff4ce144375 100644 --- a/command/v7/remove_route_policy_command_test.go +++ b/command/v7/remove_route_policy_command_test.go @@ -81,6 +81,22 @@ var _ = Describe("remove-route-policy Command", func() { }) }) + When("--source-org is used with --source-app but without --source-space", func() { + BeforeEach(func() { + cmd.RoutePolicySourceFlags = RoutePolicySourceFlags{ + SourceApp: "my-app", + SourceOrg: "my-org", + } + }) + + It("returns a RequiredFlagsError for --source-space", func() { + Expect(executeErr).To(MatchError(translatableerror.RequiredFlagsError{ + Arg1: "--source-org", + Arg2: "--source-space", + })) + }) + }) + When("checking the target fails", func() { BeforeEach(func() { fakeSharedActor.CheckTargetReturns(actionerror.NotLoggedInError{BinaryName: "faceman"}) diff --git a/command/v7/route_policy_source_flags.go b/command/v7/route_policy_source_flags.go index 15c5fb61290..3acc9e1f67f 100644 --- a/command/v7/route_policy_source_flags.go +++ b/command/v7/route_policy_source_flags.go @@ -39,6 +39,14 @@ func (f RoutePolicySourceFlags) validateSourceFlags() error { sourceFlags = append(sourceFlags, "--source-any") } + // --source-org requires --source-space when used with --source-app + if f.SourceOrg != "" && f.SourceApp != "" && f.SourceSpace == "" { + return translatableerror.RequiredFlagsError{ + Arg1: "--source-org", + Arg2: "--source-space", + } + } + if len(sourceFlags) == 0 { return translatableerror.RequiredArgumentError{ ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --source", From 504b0ea5a455835ed6842619320eaf6a7d2263f9 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 14:27:45 +0200 Subject: [PATCH 11/17] test: add scope/enforce coverage for CreatePrivateDomain in domain_test.go Refactor CreatePrivateDomain describe block to use JustBeforeEach pattern and add Context block for enforceAccessRules=true with non-empty scope, mirroring the existing coverage in CreateSharedDomain. --- actor/v7action/domain_test.go | 59 ++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/actor/v7action/domain_test.go b/actor/v7action/domain_test.go index db30783af06..c773cf19995 100644 --- a/actor/v7action/domain_test.go +++ b/actor/v7action/domain_test.go @@ -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, false, "") + 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")) }) @@ -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"}, @@ -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", false, "") Expect(executeErr).To(MatchError("create-error")) Expect(warnings).To(ConsistOf("get-orgs-warning", "create-warning-1", "create-warning-2")) @@ -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() { From 9125dd389e4353b1c8c106e1e3efc647460e8a9d Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 14:44:19 +0200 Subject: [PATCH 12/17] refactor: consolidate Add/RemoveRoutePolicyArgs into single RoutePolicyArgs Three identical one-field structs replaced with a single shared type. Description aligned with the existing convention in arguments.go. --- command/flag/arguments.go | 12 ++---------- command/v7/add_route_policy_command.go | 2 +- command/v7/add_route_policy_command_test.go | 2 +- command/v7/remove_route_policy_command.go | 2 +- command/v7/remove_route_policy_command_test.go | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/command/flag/arguments.go b/command/flag/arguments.go index 23ee422eace..cb2f313bf45 100644 --- a/command/flag/arguments.go +++ b/command/flag/arguments.go @@ -414,14 +414,6 @@ type TaskArgs struct { TaskID int `positional-arg-name:"TASK_ID" required:"true" description:"The Task ID for the application"` } -type AddRoutePolicyArgs struct { - Domain string `positional-arg-name:"DOMAIN" required:"true" description:"Domain for the route"` -} - -type RemoveRoutePolicyArgs struct { - Domain string `positional-arg-name:"DOMAIN" required:"true" description:"Domain for the route"` -} - -type RemoveAccessRuleArgs struct { - Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain name"` +type RoutePolicyArgs struct { + Domain string `positional-arg-name:"DOMAIN" required:"true" description:"The domain"` } diff --git a/command/v7/add_route_policy_command.go b/command/v7/add_route_policy_command.go index 9ebb9c435b8..62cb3254256 100644 --- a/command/v7/add_route_policy_command.go +++ b/command/v7/add_route_policy_command.go @@ -11,7 +11,7 @@ import ( type AddRoutePolicyCommand struct { BaseCommand - RequiredArgs flag.AddRoutePolicyArgs `positional-args:"yes"` + RequiredArgs flag.RoutePolicyArgs `positional-args:"yes"` Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` Path string `long:"path" description:"Path for the route"` RoutePolicySourceFlags diff --git a/command/v7/add_route_policy_command_test.go b/command/v7/add_route_policy_command_test.go index 5fce35f8d51..5de62bba9b1 100644 --- a/command/v7/add_route_policy_command_test.go +++ b/command/v7/add_route_policy_command_test.go @@ -40,7 +40,7 @@ var _ = Describe("add-route-policy Command", func() { SharedActor: fakeSharedActor, Actor: fakeActor, }, - RequiredArgs: flag.AddRoutePolicyArgs{Domain: "apps.example.com"}, + RequiredArgs: flag.RoutePolicyArgs{Domain: "apps.example.com"}, Hostname: "myapp", RoutePolicySourceFlags: RoutePolicySourceFlags{ SourceAny: true, diff --git a/command/v7/remove_route_policy_command.go b/command/v7/remove_route_policy_command.go index 4691d05d459..f161b78bfe4 100644 --- a/command/v7/remove_route_policy_command.go +++ b/command/v7/remove_route_policy_command.go @@ -9,7 +9,7 @@ import ( type RemoveRoutePolicyCommand struct { BaseCommand - RequiredArgs flag.RemoveRoutePolicyArgs `positional-args:"yes"` + RequiredArgs flag.RoutePolicyArgs `positional-args:"yes"` RoutePolicySourceFlags Hostname string `long:"hostname" required:"true" description:"Hostname for the route"` Path string `long:"path" description:"Path for the route"` diff --git a/command/v7/remove_route_policy_command_test.go b/command/v7/remove_route_policy_command_test.go index ff4ce144375..14ea45c312b 100644 --- a/command/v7/remove_route_policy_command_test.go +++ b/command/v7/remove_route_policy_command_test.go @@ -42,7 +42,7 @@ var _ = Describe("remove-route-policy Command", func() { SharedActor: fakeSharedActor, Actor: fakeActor, }, - RequiredArgs: flag.RemoveRoutePolicyArgs{Domain: "apps.example.com"}, + RequiredArgs: flag.RoutePolicyArgs{Domain: "apps.example.com"}, Hostname: "myapp", RoutePolicySourceFlags: RoutePolicySourceFlags{ SourceAny: true, From e8fdbb3665188b766cde85870084d46eeee32871 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 15:59:42 +0200 Subject: [PATCH 13/17] test: add dedicated tests for route_policy_source_flags and create-private-domain new flags - route_policy_source_flags_test.go: covers all validateSourceFlags branches (no flags, single flags, qualifier combinations, RequiredFlagsError, ArgumentCombinationError) and all resolveSource paths (raw --source, --source-any, --source-app with/without cross-space/org, --source-space, --source-org, error propagation from each actor call) - create_private_domain_command_test.go: adds coverage for --scope without --enforce-route-policies, invalid --scope values, API version check failure, --enforce-route-policies success (identity-aware TIP), and --scope forwarding --- .../v7/create_private_domain_command_test.go | 79 +++- command/v7/route_policy_source_flags_test.go | 358 ++++++++++++++++++ 2 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 command/v7/route_policy_source_flags_test.go diff --git a/command/v7/create_private_domain_command_test.go b/command/v7/create_private_domain_command_test.go index d30941f39d7..1d9fc8d67dc 100644 --- a/command/v7/create_private_domain_command_test.go +++ b/command/v7/create_private_domain_command_test.go @@ -5,8 +5,10 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command/commandfakes" "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/configv3" @@ -113,9 +115,84 @@ var _ = Describe("create-private-domain Command", func() { It("creates the domain", func() { Expect(fakeActor.CreatePrivateDomainCallCount()).To(Equal(1)) - expectedDomainName, expectedOrgName, _, _ := fakeActor.CreatePrivateDomainArgsForCall(0) + expectedDomainName, expectedOrgName, enforceRules, scope := fakeActor.CreatePrivateDomainArgsForCall(0) Expect(expectedDomainName).To(Equal(domainName)) Expect(expectedOrgName).To(Equal(orgName)) + Expect(enforceRules).To(BeFalse()) + Expect(scope).To(BeEmpty()) + }) + }) + + When("--scope is specified without --enforce-route-policies", func() { + BeforeEach(func() { + cmd.Scope = "org" + cmd.EnforceRoutePolicies = false + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError("--scope can only be used with --enforce-route-policies")) + }) + }) + + When("--scope has an invalid value", func() { + BeforeEach(func() { + cmd.EnforceRoutePolicies = true + cmd.Scope = "invalid" + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError("--scope must be one of: any, org, space")) + }) + }) + + When("--enforce-route-policies is specified", func() { + BeforeEach(func() { + cmd.EnforceRoutePolicies = true + fakeConfig.APIVersionReturns(ccversion.MinVersionRoutePolicies) + fakeActor.CreatePrivateDomainReturns(v7action.Warnings{}, nil) + }) + + When("the API version is too old", func() { + BeforeEach(func() { + fakeConfig.APIVersionReturns("0.0.0") + }) + + It("returns a version error", func() { + Expect(executeErr).To(MatchError(translatableerror.MinimumCFAPIVersionNotMetError{ + Command: "--enforce-route-policies", + CurrentVersion: "0.0.0", + MinimumVersion: ccversion.MinVersionRoutePolicies, + })) + }) + }) + + When("the API version is sufficient", func() { + It("passes enforce=true and empty scope to the actor", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(fakeActor.CreatePrivateDomainCallCount()).To(Equal(1)) + _, _, enforceRules, scope := fakeActor.CreatePrivateDomainArgsForCall(0) + Expect(enforceRules).To(BeTrue()) + Expect(scope).To(BeEmpty()) + }) + + It("prints the identity-aware TIP", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say("TIP: Domain '%s' is a private identity-aware domain", domainName)) + }) + + When("--scope is also specified", func() { + BeforeEach(func() { + cmd.Scope = "org" + }) + + It("passes the scope to the actor", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(fakeActor.CreatePrivateDomainCallCount()).To(Equal(1)) + _, _, enforceRules, scope := fakeActor.CreatePrivateDomainArgsForCall(0) + Expect(enforceRules).To(BeTrue()) + Expect(scope).To(Equal("org")) + }) + }) }) }) }) diff --git a/command/v7/route_policy_source_flags_test.go b/command/v7/route_policy_source_flags_test.go new file mode 100644 index 00000000000..73e01bbcc7d --- /dev/null +++ b/command/v7/route_policy_source_flags_test.go @@ -0,0 +1,358 @@ +package v7 + +// Internal test file (package v7, not v7_test) so we can reach the unexported +// validateSourceFlags and resolveSource helpers. We can't import v7fakes here +// because it imports this package (v7) and that would be a circular dependency. +// Instead we build a minimal stub actor inline that embeds the Actor interface +// for compile-time satisfaction and overrides only the three methods that +// resolveSource calls. + +import ( + "errors" + "fmt" + + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + "code.cloudfoundry.org/cli/v9/command/translatableerror" + "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/util/configv3" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// stubSourceActor implements Actor by embedding the interface (nil value). +// Any method not explicitly overridden will panic if called — which is fine +// because our tests only exercise the three methods resolveSource uses. +type stubSourceActor struct { + Actor + getOrgByName func(string) (resources.Organization, v7action.Warnings, error) + getSpaceByNameAndOrg func(string, string) (resources.Space, v7action.Warnings, error) + getAppByNameAndSpace func(string, string) (resources.Application, v7action.Warnings, error) +} + +func (s *stubSourceActor) GetOrganizationByName(name string) (resources.Organization, v7action.Warnings, error) { + return s.getOrgByName(name) +} +func (s *stubSourceActor) GetSpaceByNameAndOrganization(spaceName, orgGUID string) (resources.Space, v7action.Warnings, error) { + return s.getSpaceByNameAndOrg(spaceName, orgGUID) +} +func (s *stubSourceActor) GetApplicationByNameAndSpace(appName, spaceGUID string) (resources.Application, v7action.Warnings, error) { + return s.getAppByNameAndSpace(appName, spaceGUID) +} + +var _ = Describe("RoutePolicySourceFlags", func() { + + Describe("validateSourceFlags", func() { + It("returns RequiredArgumentError when no source flag is given", func() { + f := RoutePolicySourceFlags{} + Expect(f.validateSourceFlags()).To(MatchError(translatableerror.RequiredArgumentError{ + ArgumentName: "one of: --source-app, --source-space, --source-org, --source-any, or --source", + })) + }) + + It("accepts --source alone", func() { + f := RoutePolicySourceFlags{Source: "cf:any"} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("accepts --source-any alone", func() { + f := RoutePolicySourceFlags{SourceAny: true} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("accepts --source-app alone", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app"} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("accepts --source-space alone (standalone space policy)", func() { + f := RoutePolicySourceFlags{SourceSpace: "my-space"} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("accepts --source-org alone (standalone org policy)", func() { + f := RoutePolicySourceFlags{SourceOrg: "my-org"} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("accepts --source-app + --source-space (space qualifies the app lookup)", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceSpace: "other-space"} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("accepts --source-app + --source-space + --source-org", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceSpace: "other-space", SourceOrg: "other-org"} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("accepts --source-space + --source-org (org qualifies the space lookup)", func() { + f := RoutePolicySourceFlags{SourceSpace: "my-space", SourceOrg: "other-org"} + Expect(f.validateSourceFlags()).To(Succeed()) + }) + + It("returns RequiredFlagsError when --source-org is combined with --source-app but --source-space is missing", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceOrg: "my-org"} + Expect(f.validateSourceFlags()).To(MatchError(translatableerror.RequiredFlagsError{ + Arg1: "--source-org", + Arg2: "--source-space", + })) + }) + + It("returns ArgumentCombinationError when two primary flags are given (--source-app + --source-any)", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceAny: true} + err := f.validateSourceFlags() + Expect(err).To(BeAssignableToTypeOf(translatableerror.ArgumentCombinationError{})) + combo := err.(translatableerror.ArgumentCombinationError) + Expect(combo.Args).To(ConsistOf("--source-app", "--source-any")) + }) + + It("returns ArgumentCombinationError when two primary flags are given (--source + --source-any)", func() { + f := RoutePolicySourceFlags{Source: "cf:any", SourceAny: true} + err := f.validateSourceFlags() + Expect(err).To(BeAssignableToTypeOf(translatableerror.ArgumentCombinationError{})) + combo := err.(translatableerror.ArgumentCombinationError) + Expect(combo.Args).To(ConsistOf("--source", "--source-any")) + }) + }) + + Describe("resolveSource", func() { + var ( + fakeConfig *commandfakes.FakeConfig + actor *stubSourceActor + ) + + BeforeEach(func() { + fakeConfig = new(commandfakes.FakeConfig) + fakeConfig.TargetedSpaceReturns(configv3.Space{GUID: "targeted-space-guid", Name: "targeted-space"}) + fakeConfig.TargetedOrganizationReturns(configv3.Organization{GUID: "targeted-org-guid", Name: "targeted-org"}) + + actor = &stubSourceActor{} + }) + + Context("--source (raw passthrough)", func() { + It("returns the value as-is without calling the actor", func() { + f := RoutePolicySourceFlags{Source: "cf:app:some-guid"} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:app:some-guid")) + Expect(scope).To(Equal("source: cf:app:some-guid")) + Expect(warnings).To(BeEmpty()) + }) + }) + + Context("--source-any", func() { + It("returns cf:any with the expected scope display", func() { + f := RoutePolicySourceFlags{SourceAny: true} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:any")) + Expect(scope).To(Equal("scope: any, source: any authenticated app")) + Expect(warnings).To(BeEmpty()) + }) + }) + + Context("--source-app (app in currently targeted space)", func() { + BeforeEach(func() { + actor.getAppByNameAndSpace = func(appName, spaceGUID string) (resources.Application, v7action.Warnings, error) { + Expect(appName).To(Equal("my-app")) + Expect(spaceGUID).To(Equal("targeted-space-guid")) + return resources.Application{GUID: "app-guid"}, v7action.Warnings{"app-warning"}, nil + } + }) + + It("resolves the app GUID and returns the expected source and scope", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app"} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:app:app-guid")) + Expect(scope).To(Equal("scope: app, source: my-app")) + Expect(warnings).To(ConsistOf("app-warning")) + }) + }) + + Context("--source-app when the app is not found (without --source-space)", func() { + BeforeEach(func() { + actor.getAppByNameAndSpace = func(appName, spaceGUID string) (resources.Application, v7action.Warnings, error) { + return resources.Application{}, nil, actionerror.ApplicationNotFoundError{Name: appName} + } + }) + + It("returns a friendly error with a TIP about --source-space / --source-org", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app"} + _, _, _, err := resolveSource(f, actor, fakeConfig) + Expect(err).To(MatchError(fmt.Sprintf( + "App 'my-app' not found in space 'targeted-space' / org 'targeted-org'.\nTIP: If the app is in a different space or org, use --source-space and/or --source-org flags.", + ))) + }) + }) + + Context("--source-app with a non-NotFound actor error (without --source-space)", func() { + BeforeEach(func() { + actor.getAppByNameAndSpace = func(_, _ string) (resources.Application, v7action.Warnings, error) { + return resources.Application{}, v7action.Warnings{"w1"}, errors.New("upstream error") + } + }) + + It("passes the error through", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app"} + _, _, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).To(MatchError("upstream error")) + Expect(warnings).To(ConsistOf("w1")) + }) + }) + + Context("--source-app + --source-space (cross-space app lookup)", func() { + BeforeEach(func() { + actor.getSpaceByNameAndOrg = func(spaceName, orgGUID string) (resources.Space, v7action.Warnings, error) { + Expect(spaceName).To(Equal("other-space")) + Expect(orgGUID).To(Equal("targeted-org-guid")) + return resources.Space{GUID: "other-space-guid", Name: "other-space"}, v7action.Warnings{"space-warning"}, nil + } + actor.getAppByNameAndSpace = func(appName, spaceGUID string) (resources.Application, v7action.Warnings, error) { + Expect(appName).To(Equal("my-app")) + Expect(spaceGUID).To(Equal("other-space-guid")) + return resources.Application{GUID: "app-guid"}, v7action.Warnings{"app-warning"}, nil + } + }) + + It("resolves space then app, includes space in scope display", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceSpace: "other-space"} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:app:app-guid")) + Expect(scope).To(Equal("scope: app, source: my-app (space: other-space)")) + Expect(warnings).To(ConsistOf("space-warning", "app-warning")) + }) + }) + + Context("--source-app + --source-space + --source-org (cross-org/space app lookup)", func() { + BeforeEach(func() { + actor.getOrgByName = func(orgName string) (resources.Organization, v7action.Warnings, error) { + Expect(orgName).To(Equal("other-org")) + return resources.Organization{GUID: "other-org-guid"}, v7action.Warnings{"org-warning"}, nil + } + actor.getSpaceByNameAndOrg = func(spaceName, orgGUID string) (resources.Space, v7action.Warnings, error) { + Expect(spaceName).To(Equal("other-space")) + Expect(orgGUID).To(Equal("other-org-guid")) + return resources.Space{GUID: "other-space-guid", Name: "other-space"}, v7action.Warnings{"space-warning"}, nil + } + actor.getAppByNameAndSpace = func(appName, spaceGUID string) (resources.Application, v7action.Warnings, error) { + Expect(appName).To(Equal("my-app")) + Expect(spaceGUID).To(Equal("other-space-guid")) + return resources.Application{GUID: "app-guid"}, v7action.Warnings{"app-warning"}, nil + } + }) + + It("resolves org, space, then app; includes both in scope display", func() { + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceSpace: "other-space", SourceOrg: "other-org"} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:app:app-guid")) + Expect(scope).To(Equal("scope: app, source: my-app (space: other-space, org: other-org)")) + Expect(warnings).To(ConsistOf("org-warning", "space-warning", "app-warning")) + }) + }) + + Context("--source-space (standalone space policy in targeted org)", func() { + BeforeEach(func() { + actor.getSpaceByNameAndOrg = func(spaceName, orgGUID string) (resources.Space, v7action.Warnings, error) { + Expect(spaceName).To(Equal("my-space")) + Expect(orgGUID).To(Equal("targeted-org-guid")) + return resources.Space{GUID: "space-guid"}, v7action.Warnings{"space-warning"}, nil + } + }) + + It("returns cf:space: with space scope display", func() { + f := RoutePolicySourceFlags{SourceSpace: "my-space"} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:space:space-guid")) + Expect(scope).To(Equal("scope: space, source: my-space")) + Expect(warnings).To(ConsistOf("space-warning")) + }) + }) + + Context("--source-space + --source-org (space in a different org)", func() { + BeforeEach(func() { + actor.getOrgByName = func(orgName string) (resources.Organization, v7action.Warnings, error) { + Expect(orgName).To(Equal("other-org")) + return resources.Organization{GUID: "other-org-guid"}, v7action.Warnings{"org-warning"}, nil + } + actor.getSpaceByNameAndOrg = func(spaceName, orgGUID string) (resources.Space, v7action.Warnings, error) { + Expect(spaceName).To(Equal("my-space")) + Expect(orgGUID).To(Equal("other-org-guid")) + return resources.Space{GUID: "space-guid"}, v7action.Warnings{"space-warning"}, nil + } + }) + + It("resolves org then space; includes org in scope display", func() { + f := RoutePolicySourceFlags{SourceSpace: "my-space", SourceOrg: "other-org"} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:space:space-guid")) + Expect(scope).To(Equal("scope: space, source: my-space (org: other-org)")) + Expect(warnings).To(ConsistOf("org-warning", "space-warning")) + }) + }) + + Context("--source-org (standalone org policy)", func() { + BeforeEach(func() { + actor.getOrgByName = func(orgName string) (resources.Organization, v7action.Warnings, error) { + Expect(orgName).To(Equal("my-org")) + return resources.Organization{GUID: "org-guid"}, v7action.Warnings{"org-warning"}, nil + } + }) + + It("returns cf:org: with org scope display", func() { + f := RoutePolicySourceFlags{SourceOrg: "my-org"} + src, scope, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(src).To(Equal("cf:org:org-guid")) + Expect(scope).To(Equal("scope: org, source: my-org")) + Expect(warnings).To(ConsistOf("org-warning")) + }) + }) + + Context("error propagation", func() { + It("returns the error and warnings when GetOrganizationByName fails (--source-org)", func() { + actor.getOrgByName = func(string) (resources.Organization, v7action.Warnings, error) { + return resources.Organization{}, v7action.Warnings{"warn"}, errors.New("org-lookup-error") + } + f := RoutePolicySourceFlags{SourceOrg: "bad-org"} + _, _, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).To(MatchError("org-lookup-error")) + Expect(warnings).To(ConsistOf("warn")) + }) + + It("returns the error and warnings when GetSpaceByNameAndOrganization fails (--source-space)", func() { + actor.getSpaceByNameAndOrg = func(string, string) (resources.Space, v7action.Warnings, error) { + return resources.Space{}, v7action.Warnings{"warn"}, errors.New("space-lookup-error") + } + f := RoutePolicySourceFlags{SourceSpace: "bad-space"} + _, _, warnings, err := resolveSource(f, actor, fakeConfig) + Expect(err).To(MatchError("space-lookup-error")) + Expect(warnings).To(ConsistOf("warn")) + }) + + It("returns the error when GetOrganizationByName fails (--source-app + --source-space + --source-org)", func() { + actor.getOrgByName = func(string) (resources.Organization, v7action.Warnings, error) { + return resources.Organization{}, v7action.Warnings{"warn"}, errors.New("org-lookup-error") + } + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceSpace: "my-space", SourceOrg: "bad-org"} + _, _, _, err := resolveSource(f, actor, fakeConfig) + Expect(err).To(MatchError("org-lookup-error")) + }) + + It("returns the error when GetSpaceByNameAndOrganization fails (--source-app + --source-space)", func() { + actor.getSpaceByNameAndOrg = func(string, string) (resources.Space, v7action.Warnings, error) { + return resources.Space{}, v7action.Warnings{"warn"}, errors.New("space-lookup-error") + } + f := RoutePolicySourceFlags{SourceApp: "my-app", SourceSpace: "bad-space"} + _, _, _, err := resolveSource(f, actor, fakeConfig) + Expect(err).To(MatchError("space-lookup-error")) + }) + }) + }) +}) From 2375f4d5fd9f1f9d0a1abb35c3f2e1f284551a1a Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 16:05:14 +0200 Subject: [PATCH 14/17] test: add --enforce-route-policies and --scope coverage to create-shared-domain test Mirrors the coverage added to create_private_domain_command_test.go: - --scope without --enforce-route-policies returns an error - invalid --scope value returns an error - --enforce-route-policies with old API version returns MinimumCFAPIVersionNotMetError - --enforce-route-policies success: identity-aware TIP, enforce=true passed to actor - --enforce-route-policies + --scope: scope forwarded to actor - default path now explicitly asserts enforce=false, scope empty --- .../v7/create_shared_domain_command_test.go | 79 ++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/command/v7/create_shared_domain_command_test.go b/command/v7/create_shared_domain_command_test.go index 6e757f92859..05112a9cec9 100644 --- a/command/v7/create_shared_domain_command_test.go +++ b/command/v7/create_shared_domain_command_test.go @@ -5,8 +5,10 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command/commandfakes" "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/configv3" @@ -126,10 +128,85 @@ var _ = Describe("create-shared-domain Command", func() { It("creates the domain", func() { Expect(fakeActor.CreateSharedDomainCallCount()).To(Equal(1)) - expectedDomainName, expectedInternal, expectedRouterGroup, _, _ := fakeActor.CreateSharedDomainArgsForCall(0) + expectedDomainName, expectedInternal, expectedRouterGroup, enforceRules, scope := fakeActor.CreateSharedDomainArgsForCall(0) Expect(expectedDomainName).To(Equal(domainName)) Expect(expectedInternal).To(BeTrue()) Expect(expectedRouterGroup).To(Equal("router-group")) + Expect(enforceRules).To(BeFalse()) + Expect(scope).To(BeEmpty()) + }) + }) + + When("--scope is specified without --enforce-route-policies", func() { + BeforeEach(func() { + cmd.Scope = "org" + cmd.EnforceRoutePolicies = false + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError("--scope can only be used with --enforce-route-policies")) + }) + }) + + When("--scope has an invalid value", func() { + BeforeEach(func() { + cmd.EnforceRoutePolicies = true + cmd.Scope = "invalid" + }) + + It("returns an error", func() { + Expect(executeErr).To(MatchError("--scope must be one of: any, org, space")) + }) + }) + + When("--enforce-route-policies is specified", func() { + BeforeEach(func() { + cmd.EnforceRoutePolicies = true + fakeConfig.APIVersionReturns(ccversion.MinVersionRoutePolicies) + fakeActor.CreateSharedDomainReturns(v7action.Warnings{}, nil) + }) + + When("the API version is too old", func() { + BeforeEach(func() { + fakeConfig.APIVersionReturns("0.0.0") + }) + + It("returns a version error", func() { + Expect(executeErr).To(MatchError(translatableerror.MinimumCFAPIVersionNotMetError{ + Command: "--enforce-route-policies", + CurrentVersion: "0.0.0", + MinimumVersion: ccversion.MinVersionRoutePolicies, + })) + }) + }) + + When("the API version is sufficient", func() { + It("passes enforce=true and empty scope to the actor", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(fakeActor.CreateSharedDomainCallCount()).To(Equal(1)) + _, _, _, enforceRules, scope := fakeActor.CreateSharedDomainArgsForCall(0) + Expect(enforceRules).To(BeTrue()) + Expect(scope).To(BeEmpty()) + }) + + It("prints the identity-aware TIP", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Out).To(Say("TIP: Domain '%s' is a shared identity-aware domain", domainName)) + }) + + When("--scope is also specified", func() { + BeforeEach(func() { + cmd.Scope = "org" + }) + + It("passes the scope to the actor", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(fakeActor.CreateSharedDomainCallCount()).To(Equal(1)) + _, _, _, enforceRules, scope := fakeActor.CreateSharedDomainArgsForCall(0) + Expect(enforceRules).To(BeTrue()) + Expect(scope).To(Equal("org")) + }) + }) }) }) }) From 64d47d4e041a898cf5d8304b232f068c5c2b4674 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 16:11:52 +0200 Subject: [PATCH 15/17] refactor: extract shared --enforce-route-policies / --scope test behaviour Introduce EnforceRoutePoliciesBehavior and ItEnforcesRoutePolicies in enforce_route_policies_shared_test.go. Both create-shared-domain and create-private-domain tests now delegate the duplicate When blocks to the shared helper, parameterised only by TIPAdjective and the actor-specific arg-extraction closures. ccversion and translatableerror imports removed from both individual test files. --- .../v7/create_private_domain_command_test.go | 90 +++----------- .../v7/create_shared_domain_command_test.go | 90 +++----------- .../v7/enforce_route_policies_shared_test.go | 112 ++++++++++++++++++ 3 files changed, 146 insertions(+), 146 deletions(-) create mode 100644 command/v7/enforce_route_policies_shared_test.go diff --git a/command/v7/create_private_domain_command_test.go b/command/v7/create_private_domain_command_test.go index 1d9fc8d67dc..6c9259653ba 100644 --- a/command/v7/create_private_domain_command_test.go +++ b/command/v7/create_private_domain_command_test.go @@ -5,10 +5,8 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command/commandfakes" "code.cloudfoundry.org/cli/v9/command/flag" - "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/configv3" @@ -123,77 +121,23 @@ var _ = Describe("create-private-domain Command", func() { }) }) - When("--scope is specified without --enforce-route-policies", func() { - BeforeEach(func() { - cmd.Scope = "org" - cmd.EnforceRoutePolicies = false - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError("--scope can only be used with --enforce-route-policies")) - }) - }) - - When("--scope has an invalid value", func() { - BeforeEach(func() { - cmd.EnforceRoutePolicies = true - cmd.Scope = "invalid" - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError("--scope must be one of: any, org, space")) - }) - }) - - When("--enforce-route-policies is specified", func() { - BeforeEach(func() { - cmd.EnforceRoutePolicies = true - fakeConfig.APIVersionReturns(ccversion.MinVersionRoutePolicies) - fakeActor.CreatePrivateDomainReturns(v7action.Warnings{}, nil) - }) - - When("the API version is too old", func() { - BeforeEach(func() { - fakeConfig.APIVersionReturns("0.0.0") - }) - - It("returns a version error", func() { - Expect(executeErr).To(MatchError(translatableerror.MinimumCFAPIVersionNotMetError{ - Command: "--enforce-route-policies", - CurrentVersion: "0.0.0", - MinimumVersion: ccversion.MinVersionRoutePolicies, - })) - }) - }) - - When("the API version is sufficient", func() { - It("passes enforce=true and empty scope to the actor", func() { - Expect(executeErr).NotTo(HaveOccurred()) - Expect(fakeActor.CreatePrivateDomainCallCount()).To(Equal(1)) - _, _, enforceRules, scope := fakeActor.CreatePrivateDomainArgsForCall(0) - Expect(enforceRules).To(BeTrue()) - Expect(scope).To(BeEmpty()) - }) - - It("prints the identity-aware TIP", func() { - Expect(executeErr).NotTo(HaveOccurred()) - Expect(testUI.Out).To(Say("TIP: Domain '%s' is a private identity-aware domain", domainName)) - }) - - When("--scope is also specified", func() { - BeforeEach(func() { - cmd.Scope = "org" - }) - - It("passes the scope to the actor", func() { - Expect(executeErr).NotTo(HaveOccurred()) - Expect(fakeActor.CreatePrivateDomainCallCount()).To(Equal(1)) - _, _, enforceRules, scope := fakeActor.CreatePrivateDomainArgsForCall(0) - Expect(enforceRules).To(BeTrue()) - Expect(scope).To(Equal("org")) - }) - }) - }) + ItEnforcesRoutePolicies(&EnforceRoutePoliciesBehavior{ + SetEnforce: func(v bool) { cmd.EnforceRoutePolicies = v }, + SetScope: func(v string) { cmd.Scope = v }, + SetAPIVersion: func(v string) { fakeConfig.APIVersionReturns(v) }, + SetActorSucceeds: func() { fakeActor.CreatePrivateDomainReturns(v7action.Warnings{}, nil) }, + ExecuteErr: func() error { return executeErr }, + UI: func() *ui.UI { return testUI }, + DomainName: func() string { return domainName }, + EnforceArg: func() bool { + _, _, enforce, _ := fakeActor.CreatePrivateDomainArgsForCall(0) + return enforce + }, + ScopeArg: func() string { + _, _, _, scope := fakeActor.CreatePrivateDomainArgsForCall(0) + return scope + }, + TIPAdjective: "private", }) }) }) diff --git a/command/v7/create_shared_domain_command_test.go b/command/v7/create_shared_domain_command_test.go index 05112a9cec9..a19c07d43d5 100644 --- a/command/v7/create_shared_domain_command_test.go +++ b/command/v7/create_shared_domain_command_test.go @@ -5,10 +5,8 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command/commandfakes" "code.cloudfoundry.org/cli/v9/command/flag" - "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/configv3" @@ -137,77 +135,23 @@ var _ = Describe("create-shared-domain Command", func() { }) }) - When("--scope is specified without --enforce-route-policies", func() { - BeforeEach(func() { - cmd.Scope = "org" - cmd.EnforceRoutePolicies = false - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError("--scope can only be used with --enforce-route-policies")) - }) - }) - - When("--scope has an invalid value", func() { - BeforeEach(func() { - cmd.EnforceRoutePolicies = true - cmd.Scope = "invalid" - }) - - It("returns an error", func() { - Expect(executeErr).To(MatchError("--scope must be one of: any, org, space")) - }) - }) - - When("--enforce-route-policies is specified", func() { - BeforeEach(func() { - cmd.EnforceRoutePolicies = true - fakeConfig.APIVersionReturns(ccversion.MinVersionRoutePolicies) - fakeActor.CreateSharedDomainReturns(v7action.Warnings{}, nil) - }) - - When("the API version is too old", func() { - BeforeEach(func() { - fakeConfig.APIVersionReturns("0.0.0") - }) - - It("returns a version error", func() { - Expect(executeErr).To(MatchError(translatableerror.MinimumCFAPIVersionNotMetError{ - Command: "--enforce-route-policies", - CurrentVersion: "0.0.0", - MinimumVersion: ccversion.MinVersionRoutePolicies, - })) - }) - }) - - When("the API version is sufficient", func() { - It("passes enforce=true and empty scope to the actor", func() { - Expect(executeErr).NotTo(HaveOccurred()) - Expect(fakeActor.CreateSharedDomainCallCount()).To(Equal(1)) - _, _, _, enforceRules, scope := fakeActor.CreateSharedDomainArgsForCall(0) - Expect(enforceRules).To(BeTrue()) - Expect(scope).To(BeEmpty()) - }) - - It("prints the identity-aware TIP", func() { - Expect(executeErr).NotTo(HaveOccurred()) - Expect(testUI.Out).To(Say("TIP: Domain '%s' is a shared identity-aware domain", domainName)) - }) - - When("--scope is also specified", func() { - BeforeEach(func() { - cmd.Scope = "org" - }) - - It("passes the scope to the actor", func() { - Expect(executeErr).NotTo(HaveOccurred()) - Expect(fakeActor.CreateSharedDomainCallCount()).To(Equal(1)) - _, _, _, enforceRules, scope := fakeActor.CreateSharedDomainArgsForCall(0) - Expect(enforceRules).To(BeTrue()) - Expect(scope).To(Equal("org")) - }) - }) - }) + ItEnforcesRoutePolicies(&EnforceRoutePoliciesBehavior{ + SetEnforce: func(v bool) { cmd.EnforceRoutePolicies = v }, + SetScope: func(v string) { cmd.Scope = v }, + SetAPIVersion: func(v string) { fakeConfig.APIVersionReturns(v) }, + SetActorSucceeds: func() { fakeActor.CreateSharedDomainReturns(v7action.Warnings{}, nil) }, + ExecuteErr: func() error { return executeErr }, + UI: func() *ui.UI { return testUI }, + DomainName: func() string { return domainName }, + EnforceArg: func() bool { + _, _, _, enforce, _ := fakeActor.CreateSharedDomainArgsForCall(0) + return enforce + }, + ScopeArg: func() string { + _, _, _, _, scope := fakeActor.CreateSharedDomainArgsForCall(0) + return scope + }, + TIPAdjective: "shared", }) }) }) diff --git a/command/v7/enforce_route_policies_shared_test.go b/command/v7/enforce_route_policies_shared_test.go new file mode 100644 index 00000000000..d5ef06b27e5 --- /dev/null +++ b/command/v7/enforce_route_policies_shared_test.go @@ -0,0 +1,112 @@ +package v7_test + +import ( + ccversionPkg "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + translatablerrorPkg "code.cloudfoundry.org/cli/v9/command/translatableerror" + uiPkg "code.cloudfoundry.org/cli/v9/util/ui" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" +) + +// EnforceRoutePoliciesBehavior carries the command-specific hooks needed to +// run the shared --enforce-route-policies / --scope test suite. Callers +// populate it with closures that capture their local test variables so the +// shared specs remain free of command-specific knowledge. +type EnforceRoutePoliciesBehavior struct { + // Setup — called inside BeforeEach blocks within the shared suite. + SetEnforce func(bool) + SetScope func(string) + SetAPIVersion func(string) + SetActorSucceeds func() // configure the actor's create-domain call to return no error + + // Observation — called inside It blocks after JustBeforeEach has run Execute. + ExecuteErr func() error + UI func() *uiPkg.UI + DomainName func() string + EnforceArg func() bool // the enforceAccessRules arg forwarded to the actor + ScopeArg func() string // the scope arg forwarded to the actor + + // The domain-type-specific adjective in the identity-aware TIP message, + // e.g. "shared" or "private". + TIPAdjective string +} + +// ItEnforcesRoutePolicies injects all shared When/It blocks that cover the +// --enforce-route-policies and --scope flag behaviour. It must be called +// inside the "When the environment is setup correctly" context so that +// GetCurrentUser is already stubbed and JustBeforeEach runs Execute. +func ItEnforcesRoutePolicies(b *EnforceRoutePoliciesBehavior) { + When("--scope is specified without --enforce-route-policies", func() { + BeforeEach(func() { + b.SetEnforce(false) + b.SetScope("org") + }) + + It("returns an error", func() { + Expect(b.ExecuteErr()).To(MatchError("--scope can only be used with --enforce-route-policies")) + }) + }) + + When("--scope has an invalid value", func() { + BeforeEach(func() { + b.SetEnforce(true) + b.SetScope("invalid") + }) + + It("returns an error", func() { + Expect(b.ExecuteErr()).To(MatchError("--scope must be one of: any, org, space")) + }) + }) + + When("--enforce-route-policies is specified", func() { + BeforeEach(func() { + b.SetEnforce(true) + b.SetAPIVersion(ccversionPkg.MinVersionRoutePolicies) + b.SetActorSucceeds() + }) + + When("the API version is too old", func() { + BeforeEach(func() { + b.SetAPIVersion("0.0.0") + }) + + It("returns a version error", func() { + Expect(b.ExecuteErr()).To(MatchError(translatablerrorPkg.MinimumCFAPIVersionNotMetError{ + Command: "--enforce-route-policies", + CurrentVersion: "0.0.0", + MinimumVersion: ccversionPkg.MinVersionRoutePolicies, + })) + }) + }) + + When("the API version is sufficient", func() { + It("passes enforce=true and empty scope to the actor", func() { + Expect(b.ExecuteErr()).NotTo(HaveOccurred()) + Expect(b.EnforceArg()).To(BeTrue()) + Expect(b.ScopeArg()).To(BeEmpty()) + }) + + It("prints the identity-aware TIP", func() { + Expect(b.ExecuteErr()).NotTo(HaveOccurred()) + Expect(b.UI().Out).To(Say( + "TIP: Domain '%s' is a %s identity-aware domain", + b.DomainName(), b.TIPAdjective, + )) + }) + + When("--scope is also specified", func() { + BeforeEach(func() { + b.SetScope("org") + }) + + It("passes the scope to the actor", func() { + Expect(b.ExecuteErr()).NotTo(HaveOccurred()) + Expect(b.EnforceArg()).To(BeTrue()) + Expect(b.ScopeArg()).To(Equal("org")) + }) + }) + }) + }) +} From 59f8468fa4c8221501c77b1c8b30d75b2a03c38b Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 16:16:46 +0200 Subject: [PATCH 16/17] chore: remove devbox.json and devbox.lock from tracked files --- devbox.json | 14 ------------- devbox.lock | 57 ----------------------------------------------------- 2 files changed, 71 deletions(-) delete mode 100644 devbox.json delete mode 100644 devbox.lock diff --git a/devbox.json b/devbox.json deleted file mode 100644 index 211ce41b32c..00000000000 --- a/devbox.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.16.0/.schema/devbox.schema.json", - "packages": ["go@1.26.1"], - "shell": { - "init_hook": [ - "echo 'Welcome to devbox!' > /dev/null" - ], - "scripts": { - "test": [ - "echo \"Error: no test specified\" && exit 1" - ] - } - } -} diff --git a/devbox.lock b/devbox.lock deleted file mode 100644 index 19ff081759c..00000000000 --- a/devbox.lock +++ /dev/null @@ -1,57 +0,0 @@ -{ - "lockfile_version": "1", - "packages": { - "github:NixOS/nixpkgs/nixpkgs-unstable": { - "last_modified": "2026-03-16T02:27:38Z", - "resolved": "github:NixOS/nixpkgs/f8573b9c935cfaa162dd62cc9e75ae2db86f85df?lastModified=1773628058&narHash=sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY%3D" - }, - "go@1.26.1": { - "last_modified": "2026-03-21T07:29:51Z", - "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#go", - "source": "devbox-search", - "version": "1.26.1", - "systems": { - "aarch64-darwin": { - "outputs": [ - { - "name": "out", - "path": "/nix/store/kh43nhaz1qcpwws2xq805lrmwpmn9i3k-go-1.26.1", - "default": true - } - ], - "store_path": "/nix/store/kh43nhaz1qcpwws2xq805lrmwpmn9i3k-go-1.26.1" - }, - "aarch64-linux": { - "outputs": [ - { - "name": "out", - "path": "/nix/store/rz1pqbm5z3zfby250i0djfmfzzj7khg9-go-1.26.1", - "default": true - } - ], - "store_path": "/nix/store/rz1pqbm5z3zfby250i0djfmfzzj7khg9-go-1.26.1" - }, - "x86_64-darwin": { - "outputs": [ - { - "name": "out", - "path": "/nix/store/yv6jj27racylbfjw6a1cdr91ndxbgyf6-go-1.26.1", - "default": true - } - ], - "store_path": "/nix/store/yv6jj27racylbfjw6a1cdr91ndxbgyf6-go-1.26.1" - }, - "x86_64-linux": { - "outputs": [ - { - "name": "out", - "path": "/nix/store/ckcq2mj8zk0drhaaacy6mp9d924hnr4m-go-1.26.1", - "default": true - } - ], - "store_path": "/nix/store/ckcq2mj8zk0drhaaacy6mp9d924hnr4m-go-1.26.1" - } - } - } - } -} From cd4b60fe4623e394fab6c3a6e7f919c06d25fa7b Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 17 Jun 2026 16:23:32 +0200 Subject: [PATCH 17/17] Remove whitespace-only changes from unrelated files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bulk find-and-replace tooling from the rebrand commit introduced spaces→tabs indentation fixes and Invocations() mutex-lock removals in files completely unrelated to the route-policies feature. Restore all of them to origin/main to keep the feature diff focused. --- actor/v7action/v7actionfakes/fake_config.go | 42 +++++ .../v7action/v7actionfakes/fake_downloader.go | 2 + .../fake_kubernetes_config_getter.go | 2 + .../v7actionfakes/fake_manifest_parser.go | 4 + .../v7actionfakes/fake_routing_client.go | 4 + .../v7actionfakes/fake_shared_actor.go | 8 + .../v7actionfakes/fake_simple_progress_bar.go | 4 + actor/v7action/v7actionfakes/fake_sshactor.go | 2 + .../v7action/v7actionfakes/fake_uaaclient.go | 22 +++ .../v7action/v7actionfakes/fake_who_am_ier.go | 2 + .../create_deployment_for_push_plan_test.go | 2 +- actor/v7pushaction/handle_disk_override.go | 2 +- .../v7pushaction/handle_disk_override_test.go | 2 +- .../v7pushaction/handle_instances_override.go | 2 +- .../handle_instances_override_test.go | 2 +- .../handle_log_rate_limit_override.go | 2 +- .../handle_log_rate_limit_override_test.go | 2 +- actor/v7pushaction/handle_memory_override.go | 2 +- .../handle_memory_override_test.go | 2 +- ...up_deployment_information_for_push_plan.go | 2 +- ...ployment_information_for_push_plan_test.go | 4 +- api/cfnetworking/cfnetv1/client.go | 144 +++++++++--------- api/cfnetworking/errors.go | 74 ++++----- api/cfnetworking/wrapper/retry_request.go | 64 ++++---- api/cloudcontroller/ccv3/deployment_test.go | 2 +- cf/actors/push.go | 4 +- cf/net/warnings_collector.go | 4 +- command/v7/buildpacks_command.go | 4 +- command/v7/buildpacks_command_test.go | 2 +- command/v7/copy_source_command.go | 10 +- command/v7/create_buildpack_command.go | 2 +- command/v7/create_route_command.go | 4 +- command/v7/create_route_command_test.go | 2 +- .../create_user_provided_service_command.go | 2 +- command/v7/delete_buildpack_command.go | 4 +- command/v7/delete_buildpack_command_test.go | 2 +- command/v7/restage_command.go | 10 +- command/v7/restart_command.go | 10 +- command/v7/revision_command_test.go | 12 +- command/v7/rollback_command.go | 4 +- .../v7/shared/sharedfakes/fake_app_stager.go | 6 + command/v7/stack_command_test.go | 38 ++--- command/v7/update_buildpack_command.go | 2 +- command/v7/update_stack_command_test.go | 95 ++++++------ command/v7/v7fakes/fake_actor_reloader.go | 2 + command/v7/v7fakes/fake_diff_displayer.go | 2 + command/v7/v7fakes/fake_label_setter.go | 2 + command/v7/v7fakes/fake_label_unsetter.go | 2 + command/v7/v7fakes/fake_manifest_locator.go | 2 + command/v7/v7fakes/fake_manifest_parser.go | 6 + .../v7/v7fakes/fake_network_policies_actor.go | 4 + command/v7/v7fakes/fake_networking_actor.go | 2 + command/v7/v7fakes/fake_progress_bar.go | 6 + command/v7/v7fakes/fake_push_actor.go | 8 + .../fake_remove_network_policy_actor.go | 2 + command/v7/v7fakes/fake_revisions_actor.go | 2 + command/v7/v7fakes/fake_set_label_actor.go | 44 ++++-- command/v7/v7fakes/fake_shared_sshactor.go | 2 + command/v7/v7fakes/fake_v7actor_for_push.go | 10 ++ .../v7/isolated/revision_command_test.go | 12 +- .../v7/isolated/update_stack_command_test.go | 1 + resources/deployment_resource.go | 2 +- resources/process_resource.go | 6 +- 63 files changed, 454 insertions(+), 280 deletions(-) diff --git a/actor/v7action/v7actionfakes/fake_config.go b/actor/v7action/v7actionfakes/fake_config.go index 73a32a8baeb..cd46ebbbfc5 100644 --- a/actor/v7action/v7actionfakes/fake_config.go +++ b/actor/v7action/v7actionfakes/fake_config.go @@ -1134,6 +1134,48 @@ func (fake *FakeConfig) UnsetOrganizationAndSpaceInformationCalls(stub func()) { func (fake *FakeConfig) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.aPIVersionMutex.RLock() + defer fake.aPIVersionMutex.RUnlock() + fake.accessTokenMutex.RLock() + defer fake.accessTokenMutex.RUnlock() + fake.currentUserMutex.RLock() + defer fake.currentUserMutex.RUnlock() + fake.dialTimeoutMutex.RLock() + defer fake.dialTimeoutMutex.RUnlock() + fake.isCFOnK8sMutex.RLock() + defer fake.isCFOnK8sMutex.RUnlock() + fake.pollingIntervalMutex.RLock() + defer fake.pollingIntervalMutex.RUnlock() + fake.refreshTokenMutex.RLock() + defer fake.refreshTokenMutex.RUnlock() + fake.sSHOAuthClientMutex.RLock() + defer fake.sSHOAuthClientMutex.RUnlock() + fake.setAccessTokenMutex.RLock() + defer fake.setAccessTokenMutex.RUnlock() + fake.setKubernetesAuthInfoMutex.RLock() + defer fake.setKubernetesAuthInfoMutex.RUnlock() + fake.setRefreshTokenMutex.RLock() + defer fake.setRefreshTokenMutex.RUnlock() + fake.setTargetInformationMutex.RLock() + defer fake.setTargetInformationMutex.RUnlock() + fake.setTokenInformationMutex.RLock() + defer fake.setTokenInformationMutex.RUnlock() + fake.setUAAClientCredentialsMutex.RLock() + defer fake.setUAAClientCredentialsMutex.RUnlock() + fake.setUAAGrantTypeMutex.RLock() + defer fake.setUAAGrantTypeMutex.RUnlock() + fake.skipSSLValidationMutex.RLock() + defer fake.skipSSLValidationMutex.RUnlock() + fake.stagingTimeoutMutex.RLock() + defer fake.stagingTimeoutMutex.RUnlock() + fake.startupTimeoutMutex.RLock() + defer fake.startupTimeoutMutex.RUnlock() + fake.targetMutex.RLock() + defer fake.targetMutex.RUnlock() + fake.uAAGrantTypeMutex.RLock() + defer fake.uAAGrantTypeMutex.RUnlock() + fake.unsetOrganizationAndSpaceInformationMutex.RLock() + defer fake.unsetOrganizationAndSpaceInformationMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_downloader.go b/actor/v7action/v7actionfakes/fake_downloader.go index d77a875d875..4545618272a 100644 --- a/actor/v7action/v7actionfakes/fake_downloader.go +++ b/actor/v7action/v7actionfakes/fake_downloader.go @@ -94,6 +94,8 @@ func (fake *FakeDownloader) DownloadReturnsOnCall(i int, result1 string, result2 func (fake *FakeDownloader) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.downloadMutex.RLock() + defer fake.downloadMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go b/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go index 4336bf2e2e8..7102e140b63 100644 --- a/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go +++ b/actor/v7action/v7actionfakes/fake_kubernetes_config_getter.go @@ -84,6 +84,8 @@ func (fake *FakeKubernetesConfigGetter) GetReturnsOnCall(i int, result1 *api.Con func (fake *FakeKubernetesConfigGetter) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_manifest_parser.go b/actor/v7action/v7actionfakes/fake_manifest_parser.go index 73bf7ac4f52..6d93fd4df35 100644 --- a/actor/v7action/v7actionfakes/fake_manifest_parser.go +++ b/actor/v7action/v7actionfakes/fake_manifest_parser.go @@ -155,6 +155,10 @@ func (fake *FakeManifestParser) RawAppManifestReturnsOnCall(i int, result1 []byt func (fake *FakeManifestParser) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.appNamesMutex.RLock() + defer fake.appNamesMutex.RUnlock() + fake.rawAppManifestMutex.RLock() + defer fake.rawAppManifestMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_routing_client.go b/actor/v7action/v7actionfakes/fake_routing_client.go index 73b5f6fafb3..d7be3997593 100644 --- a/actor/v7action/v7actionfakes/fake_routing_client.go +++ b/actor/v7action/v7actionfakes/fake_routing_client.go @@ -161,6 +161,10 @@ func (fake *FakeRoutingClient) GetRouterGroupsReturnsOnCall(i int, result1 []rou func (fake *FakeRoutingClient) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.getRouterGroupByNameMutex.RLock() + defer fake.getRouterGroupByNameMutex.RUnlock() + fake.getRouterGroupsMutex.RLock() + defer fake.getRouterGroupsMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_shared_actor.go b/actor/v7action/v7actionfakes/fake_shared_actor.go index 1c60d5b4bec..1601a0e3e99 100644 --- a/actor/v7action/v7actionfakes/fake_shared_actor.go +++ b/actor/v7action/v7actionfakes/fake_shared_actor.go @@ -338,6 +338,14 @@ func (fake *FakeSharedActor) ZipDirectoryResourcesReturnsOnCall(i int, result1 s func (fake *FakeSharedActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.gatherArchiveResourcesMutex.RLock() + defer fake.gatherArchiveResourcesMutex.RUnlock() + fake.gatherDirectoryResourcesMutex.RLock() + defer fake.gatherDirectoryResourcesMutex.RUnlock() + fake.zipArchiveResourcesMutex.RLock() + defer fake.zipArchiveResourcesMutex.RUnlock() + fake.zipDirectoryResourcesMutex.RLock() + defer fake.zipDirectoryResourcesMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_simple_progress_bar.go b/actor/v7action/v7actionfakes/fake_simple_progress_bar.go index 19d9442f101..0e2b05b6486 100644 --- a/actor/v7action/v7actionfakes/fake_simple_progress_bar.go +++ b/actor/v7action/v7actionfakes/fake_simple_progress_bar.go @@ -126,6 +126,10 @@ func (fake *FakeSimpleProgressBar) TerminateCalls(stub func()) { func (fake *FakeSimpleProgressBar) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.initializeMutex.RLock() + defer fake.initializeMutex.RUnlock() + fake.terminateMutex.RLock() + defer fake.terminateMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_sshactor.go b/actor/v7action/v7actionfakes/fake_sshactor.go index 24cf1c167be..f849722dd78 100644 --- a/actor/v7action/v7actionfakes/fake_sshactor.go +++ b/actor/v7action/v7actionfakes/fake_sshactor.go @@ -88,6 +88,8 @@ func (fake *FakeSSHActor) ExecuteSecureShellReturnsOnCall(i int, result1 error) func (fake *FakeSSHActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.executeSecureShellMutex.RLock() + defer fake.executeSecureShellMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_uaaclient.go b/actor/v7action/v7actionfakes/fake_uaaclient.go index ae1e07303ac..517b7ec9afa 100644 --- a/actor/v7action/v7actionfakes/fake_uaaclient.go +++ b/actor/v7action/v7actionfakes/fake_uaaclient.go @@ -852,6 +852,28 @@ func (fake *FakeUAAClient) ValidateClientUserReturnsOnCall(i int, result1 error) func (fake *FakeUAAClient) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.authenticateMutex.RLock() + defer fake.authenticateMutex.RUnlock() + fake.createUserMutex.RLock() + defer fake.createUserMutex.RUnlock() + fake.deleteUserMutex.RLock() + defer fake.deleteUserMutex.RUnlock() + fake.getAPIVersionMutex.RLock() + defer fake.getAPIVersionMutex.RUnlock() + fake.getLoginPromptsMutex.RLock() + defer fake.getLoginPromptsMutex.RUnlock() + fake.getSSHPasscodeMutex.RLock() + defer fake.getSSHPasscodeMutex.RUnlock() + fake.listUsersMutex.RLock() + defer fake.listUsersMutex.RUnlock() + fake.refreshAccessTokenMutex.RLock() + defer fake.refreshAccessTokenMutex.RUnlock() + fake.revokeMutex.RLock() + defer fake.revokeMutex.RUnlock() + fake.updatePasswordMutex.RLock() + defer fake.updatePasswordMutex.RUnlock() + fake.validateClientUserMutex.RLock() + defer fake.validateClientUserMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7action/v7actionfakes/fake_who_am_ier.go b/actor/v7action/v7actionfakes/fake_who_am_ier.go index 46640859a22..78dfee83e99 100644 --- a/actor/v7action/v7actionfakes/fake_who_am_ier.go +++ b/actor/v7action/v7actionfakes/fake_who_am_ier.go @@ -90,6 +90,8 @@ func (fake *FakeWhoAmIer) WhoAmIReturnsOnCall(i int, result1 resources.K8sUser, func (fake *FakeWhoAmIer) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.whoAmIMutex.RLock() + defer fake.whoAmIMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/actor/v7pushaction/create_deployment_for_push_plan_test.go b/actor/v7pushaction/create_deployment_for_push_plan_test.go index 04bdbd5ddb6..beded0358b5 100644 --- a/actor/v7pushaction/create_deployment_for_push_plan_test.go +++ b/actor/v7pushaction/create_deployment_for_push_plan_test.go @@ -8,7 +8,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7pushaction/v7pushactionfakes" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) diff --git a/actor/v7pushaction/handle_disk_override.go b/actor/v7pushaction/handle_disk_override.go index e4dd2088450..33b46727511 100644 --- a/actor/v7pushaction/handle_disk_override.go +++ b/actor/v7pushaction/handle_disk_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_disk_override_test.go b/actor/v7pushaction/handle_disk_override_test.go index ab552bcae8e..3781e44e0e4 100644 --- a/actor/v7pushaction/handle_disk_override_test.go +++ b/actor/v7pushaction/handle_disk_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/handle_instances_override.go b/actor/v7pushaction/handle_instances_override.go index 60cc4f104fe..a6322a9882a 100644 --- a/actor/v7pushaction/handle_instances_override.go +++ b/actor/v7pushaction/handle_instances_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_instances_override_test.go b/actor/v7pushaction/handle_instances_override_test.go index 40b7f3c5d56..4808558af41 100644 --- a/actor/v7pushaction/handle_instances_override_test.go +++ b/actor/v7pushaction/handle_instances_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/types" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/handle_log_rate_limit_override.go b/actor/v7pushaction/handle_log_rate_limit_override.go index 218db034cc2..13b1e9dbf8e 100644 --- a/actor/v7pushaction/handle_log_rate_limit_override.go +++ b/actor/v7pushaction/handle_log_rate_limit_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_log_rate_limit_override_test.go b/actor/v7pushaction/handle_log_rate_limit_override_test.go index 0281b7648bc..679d740a90e 100644 --- a/actor/v7pushaction/handle_log_rate_limit_override_test.go +++ b/actor/v7pushaction/handle_log_rate_limit_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/handle_memory_override.go b/actor/v7pushaction/handle_memory_override.go index 4460687c66c..e5ab16ca549 100644 --- a/actor/v7pushaction/handle_memory_override.go +++ b/actor/v7pushaction/handle_memory_override.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" ) diff --git a/actor/v7pushaction/handle_memory_override_test.go b/actor/v7pushaction/handle_memory_override_test.go index 602396fbae9..1ef70722757 100644 --- a/actor/v7pushaction/handle_memory_override_test.go +++ b/actor/v7pushaction/handle_memory_override_test.go @@ -1,7 +1,7 @@ package v7pushaction_test import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/util/manifestparser" diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan.go b/actor/v7pushaction/setup_deployment_information_for_push_plan.go index 328b0dd28c0..82900aa99a1 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan.go @@ -1,7 +1,7 @@ package v7pushaction import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/cf/errors" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/types" diff --git a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go index c170366535c..b07bdade816 100644 --- a/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go +++ b/actor/v7pushaction/setup_deployment_information_for_push_plan_test.go @@ -2,8 +2,8 @@ package v7pushaction_test import ( "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/cf/errors" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/cf/errors" + "code.cloudfoundry.org/cli/v9/types" . "code.cloudfoundry.org/cli/v9/actor/v7pushaction" diff --git a/api/cfnetworking/cfnetv1/client.go b/api/cfnetworking/cfnetv1/client.go index 5068b41e75d..1c301716184 100644 --- a/api/cfnetworking/cfnetv1/client.go +++ b/api/cfnetworking/cfnetv1/client.go @@ -6,7 +6,7 @@ // For more information on the CF Networking API see // https://github.com/cloudfoundry-incubator/cf-networking-release/blob/develop/docs/API.md // -// # Method Naming Conventions +// Method Naming Conventions // // The client takes a '' // approach to method names. If the and @@ -15,40 +15,38 @@ // method name. // // For Example: -// -// Method Name: GetApplication -// Endpoint: /v2/applications/:guid -// Action Name: Get -// Top Level Endpoint: applications -// Return Value: Application -// -// Method Name: GetServiceInstances -// Endpoint: /v2/service_instances -// Action Name: Get -// Top Level Endpoint: service_instances -// Return Value: []ServiceInstance -// -// Method Name: GetSpaceServiceInstances -// Endpoint: /v2/spaces/:guid/service_instances -// Action Name: Get -// Top Level Endpoint: spaces -// Return Value: []ServiceInstance +// Method Name: GetApplication +// Endpoint: /v2/applications/:guid +// Action Name: Get +// Top Level Endpoint: applications +// Return Value: Application +// +// Method Name: GetServiceInstances +// Endpoint: /v2/service_instances +// Action Name: Get +// Top Level Endpoint: service_instances +// Return Value: []ServiceInstance +// +// Method Name: GetSpaceServiceInstances +// Endpoint: /v2/spaces/:guid/service_instances +// Action Name: Get +// Top Level Endpoint: spaces +// Return Value: []ServiceInstance // // Use the following table to determine which HTTP Command equates to which // Action Name: +// HTTP Command -> Action Name +// POST -> Create +// GET -> Get +// PUT -> Update +// DELETE -> Delete // -// HTTP Command -> Action Name -// POST -> Create -// GET -> Get -// PUT -> Update -// DELETE -> Delete -// -// # Method Locations +// Method Locations // // Methods exist in the same file as their return type, regardless of which // endpoint they use. // -// # Error Handling +// Error Handling // // All error handling that requires parsing the error_code/code returned back // from the Cloud Controller should be placed in the errorWrapper. Everything @@ -57,7 +55,7 @@ // exist in the cloudcontroller's errors.go. Errors related to the individaul // operation should exist at the top of that operation's file. // -// # No inline-relations-depth And summary Endpoints +// No inline-relations-depth And summary Endpoints // // This package will not use ever use 'inline-relations-depth' or the // '/summary' endpoints for any operations. These requests can be extremely @@ -67,72 +65,72 @@ package cfnetv1 import ( - "fmt" - "runtime" - "time" + "fmt" + "runtime" + "time" - "code.cloudfoundry.org/cli/v9/api/cfnetworking" - "code.cloudfoundry.org/cli/v9/api/cfnetworking/cfnetv1/internal" + "code.cloudfoundry.org/cli/v9/api/cfnetworking" + "code.cloudfoundry.org/cli/v9/api/cfnetworking/cfnetv1/internal" - "github.com/tedsuo/rata" + "github.com/tedsuo/rata" ) // Client is a client that can be used to talk to a CF Networking API. type Client struct { - connection cfnetworking.Connection - router *rata.RequestGenerator - url string - userAgent string + connection cfnetworking.Connection + router *rata.RequestGenerator + url string + userAgent string } // Config allows the Client to be configured type Config struct { - // AppName is the name of the application/process using the client. - AppName string + // AppName is the name of the application/process using the client. + AppName string - // AppVersion is the version of the application/process using the client. - AppVersion string + // AppVersion is the version of the application/process using the client. + AppVersion string - // DialTimeout is the DNS timeout used to make all requests to the Cloud - // Controller. - DialTimeout time.Duration + // DialTimeout is the DNS timeout used to make all requests to the Cloud + // Controller. + DialTimeout time.Duration - // SkipSSLValidation controls whether a client verifies the server's - // certificate chain and host name. If SkipSSLValidation is true, TLS accepts - // any certificate presented by the server and any host name in that - // certificate for *all* client requests going forward. - // - // In this mode, TLS is susceptible to man-in-the-middle attacks. This should - // be used only for testing. - SkipSSLValidation bool + // SkipSSLValidation controls whether a client verifies the server's + // certificate chain and host name. If SkipSSLValidation is true, TLS accepts + // any certificate presented by the server and any host name in that + // certificate for *all* client requests going forward. + // + // In this mode, TLS is susceptible to man-in-the-middle attacks. This should + // be used only for testing. + SkipSSLValidation bool - // URL is a fully qualified URL to the CF Networking API. - URL string + // URL is a fully qualified URL to the CF Networking API. + URL string - // Wrappers that apply to the client connection. - Wrappers []ConnectionWrapper + // Wrappers that apply to the client connection. + Wrappers []ConnectionWrapper } // NewClient returns a new CF Networking client. func NewClient(config Config) *Client { - userAgent := fmt.Sprintf("%s/%s (%s; %s %s)", config.AppName, config.AppVersion, runtime.Version(), runtime.GOARCH, runtime.GOOS) + userAgent := fmt.Sprintf("%s/%s (%s; %s %s)", config.AppName, config.AppVersion, runtime.Version(), runtime.GOARCH, runtime.GOOS) - connection := cfnetworking.NewConnection(cfnetworking.Config{ - DialTimeout: config.DialTimeout, - SkipSSLValidation: config.SkipSSLValidation, - }) + connection := cfnetworking.NewConnection(cfnetworking.Config{ + DialTimeout: config.DialTimeout, + SkipSSLValidation: config.SkipSSLValidation, + }) - wrappedConnection := cfnetworking.NewErrorWrapper().Wrap(connection) - for _, wrapper := range config.Wrappers { - wrappedConnection = wrapper.Wrap(wrappedConnection) - } + wrappedConnection := cfnetworking.NewErrorWrapper().Wrap(connection) + for _, wrapper := range config.Wrappers { + wrappedConnection = wrapper.Wrap(wrappedConnection) + } - client := &Client{ - connection: wrappedConnection, - router: rata.NewRequestGenerator(config.URL, internal.Routes), - url: config.URL, - userAgent: userAgent, - } + client := &Client{ + connection: wrappedConnection, + router: rata.NewRequestGenerator(config.URL, internal.Routes), + url: config.URL, + userAgent: userAgent, + } - return client + return client } diff --git a/api/cfnetworking/errors.go b/api/cfnetworking/errors.go index fd5fcaacc56..7abd7877c64 100644 --- a/api/cfnetworking/errors.go +++ b/api/cfnetworking/errors.go @@ -1,64 +1,64 @@ package cfnetworking import ( - "encoding/json" - "net/http" + "encoding/json" + "net/http" - "code.cloudfoundry.org/cli/v9/api/cfnetworking/networkerror" + "code.cloudfoundry.org/cli/v9/api/cfnetworking/networkerror" ) // errorWrapper is the wrapper that converts responses with 4xx and 5xx status // codes to an error. type errorWrapper struct { - connection Connection + connection Connection } func NewErrorWrapper() *errorWrapper { - return new(errorWrapper) + return new(errorWrapper) } // Wrap wraps a Cloud Controller connection in this error handling wrapper. func (e *errorWrapper) Wrap(innerconnection Connection) Connection { - e.connection = innerconnection - return e + e.connection = innerconnection + return e } // Make converts RawHTTPStatusError, which represents responses with 4xx and // 5xx status codes, to specific errors. func (e *errorWrapper) Make(request *Request, passedResponse *Response) error { - err := e.connection.Make(request, passedResponse) + err := e.connection.Make(request, passedResponse) - if rawHTTPStatusErr, ok := err.(networkerror.RawHTTPStatusError); ok { - return convert(rawHTTPStatusErr) - } - return err + if rawHTTPStatusErr, ok := err.(networkerror.RawHTTPStatusError); ok { + return convert(rawHTTPStatusErr) + } + return err } func convert(rawHTTPStatusErr networkerror.RawHTTPStatusError) error { - // Try to unmarshal the raw error into a CC error. If unmarshaling fails, - // return the raw error. - var errorResponse networkerror.ErrorResponse - err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse) - if err != nil { - return rawHTTPStatusErr - } + // Try to unmarshal the raw error into a CC error. If unmarshaling fails, + // return the raw error. + var errorResponse networkerror.ErrorResponse + err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse) + if err != nil { + return rawHTTPStatusErr + } - switch rawHTTPStatusErr.StatusCode { - case http.StatusBadRequest: // 400 - return networkerror.BadRequestError(errorResponse) - case http.StatusUnauthorized: // 401 - return networkerror.UnauthorizedError(errorResponse) - case http.StatusForbidden: // 403 - return networkerror.ForbiddenError(errorResponse) - case http.StatusNotAcceptable: // 406 - return networkerror.NotAcceptableError(errorResponse) - case http.StatusConflict: // 409 - return networkerror.ConflictError(errorResponse) - default: - return networkerror.UnexpectedResponseError{ - ErrorResponse: errorResponse, - RequestIDs: rawHTTPStatusErr.RequestIDs, - ResponseCode: rawHTTPStatusErr.StatusCode, - } - } + switch rawHTTPStatusErr.StatusCode { + case http.StatusBadRequest: // 400 + return networkerror.BadRequestError(errorResponse) + case http.StatusUnauthorized: // 401 + return networkerror.UnauthorizedError(errorResponse) + case http.StatusForbidden: // 403 + return networkerror.ForbiddenError(errorResponse) + case http.StatusNotAcceptable: // 406 + return networkerror.NotAcceptableError(errorResponse) + case http.StatusConflict: // 409 + return networkerror.ConflictError(errorResponse) + default: + return networkerror.UnexpectedResponseError{ + ErrorResponse: errorResponse, + RequestIDs: rawHTTPStatusErr.RequestIDs, + ResponseCode: rawHTTPStatusErr.StatusCode, + } + } } diff --git a/api/cfnetworking/wrapper/retry_request.go b/api/cfnetworking/wrapper/retry_request.go index eb4300ecf09..600a79825d5 100644 --- a/api/cfnetworking/wrapper/retry_request.go +++ b/api/cfnetworking/wrapper/retry_request.go @@ -1,54 +1,54 @@ package wrapper import ( - "net/http" + "net/http" - "code.cloudfoundry.org/cli/v9/api/cfnetworking" + "code.cloudfoundry.org/cli/v9/api/cfnetworking" ) // RetryRequest is a wrapper that retries failed requests if they contain a 5XX // status code. type RetryRequest struct { - maxRetries int - connection cfnetworking.Connection + maxRetries int + connection cfnetworking.Connection } // NewRetryRequest returns a pointer to a RetryRequest wrapper. func NewRetryRequest(maxRetries int) *RetryRequest { - return &RetryRequest{ - maxRetries: maxRetries, - } + return &RetryRequest{ + maxRetries: maxRetries, + } } // Wrap sets the connection in the RetryRequest and returns itself. func (retry *RetryRequest) Wrap(innerconnection cfnetworking.Connection) cfnetworking.Connection { - retry.connection = innerconnection - return retry + retry.connection = innerconnection + return retry } // Make retries the request if it comes back with certain status codes. func (retry *RetryRequest) Make(request *cfnetworking.Request, passedResponse *cfnetworking.Response) error { - var err error - - for i := 0; i < retry.maxRetries+1; i += 1 { - err = retry.connection.Make(request, passedResponse) - if err == nil { - return nil - } - - if passedResponse.HTTPResponse != nil && - (passedResponse.HTTPResponse.StatusCode == http.StatusBadGateway || - passedResponse.HTTPResponse.StatusCode == http.StatusServiceUnavailable || - passedResponse.HTTPResponse.StatusCode == http.StatusGatewayTimeout || - (passedResponse.HTTPResponse.StatusCode >= 400 && passedResponse.HTTPResponse.StatusCode < 500)) { - break - } - - // Reset the request body prior to the next retry - resetErr := request.ResetBody() - if resetErr != nil { - return resetErr - } - } - return err + var err error + + for i := 0; i < retry.maxRetries+1; i += 1 { + err = retry.connection.Make(request, passedResponse) + if err == nil { + return nil + } + + if passedResponse.HTTPResponse != nil && + (passedResponse.HTTPResponse.StatusCode == http.StatusBadGateway || + passedResponse.HTTPResponse.StatusCode == http.StatusServiceUnavailable || + passedResponse.HTTPResponse.StatusCode == http.StatusGatewayTimeout || + (passedResponse.HTTPResponse.StatusCode >= 400 && passedResponse.HTTPResponse.StatusCode < 500)) { + break + } + + // Reset the request body prior to the next retry + resetErr := request.ResetBody() + if resetErr != nil { + return resetErr + } + } + return err } diff --git a/api/cloudcontroller/ccv3/deployment_test.go b/api/cloudcontroller/ccv3/deployment_test.go index f725d62763f..3dea39c59a3 100644 --- a/api/cloudcontroller/ccv3/deployment_test.go +++ b/api/cloudcontroller/ccv3/deployment_test.go @@ -7,7 +7,7 @@ import ( "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/types" . "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" . "github.com/onsi/ginkgo/v2" diff --git a/cf/actors/push.go b/cf/actors/push.go index 36a093e2ce5..b51322b084f 100644 --- a/cf/actors/push.go +++ b/cf/actors/push.go @@ -7,9 +7,9 @@ import ( "path/filepath" "runtime" - "errors" + "errors" - "code.cloudfoundry.org/cli/v9/cf/api/applicationbits" + "code.cloudfoundry.org/cli/v9/cf/api/applicationbits" "code.cloudfoundry.org/cli/v9/cf/api/resources" "code.cloudfoundry.org/cli/v9/cf/appfiles" . "code.cloudfoundry.org/cli/v9/cf/i18n" diff --git a/cf/net/warnings_collector.go b/cf/net/warnings_collector.go index 6b2991f7c48..5b164e0b39b 100644 --- a/cf/net/warnings_collector.go +++ b/cf/net/warnings_collector.go @@ -4,9 +4,9 @@ import ( "os" "strings" - "errors" + "errors" - "code.cloudfoundry.org/cli/v9/cf/terminal" + "code.cloudfoundry.org/cli/v9/cf/terminal" ) const DeprecatedEndpointWarning = "Endpoint deprecated" diff --git a/command/v7/buildpacks_command.go b/command/v7/buildpacks_command.go index 4c8d3dc06dd..8ccdc1d5f63 100644 --- a/command/v7/buildpacks_command.go +++ b/command/v7/buildpacks_command.go @@ -3,8 +3,8 @@ package v7 import ( "strconv" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" - "code.cloudfoundry.org/cli/v9/command" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/util/ui" ) diff --git a/command/v7/buildpacks_command_test.go b/command/v7/buildpacks_command_test.go index 34ca62acae4..41240936ba0 100644 --- a/command/v7/buildpacks_command_test.go +++ b/command/v7/buildpacks_command_test.go @@ -10,7 +10,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" "code.cloudfoundry.org/cli/v9/command/commandfakes" - "code.cloudfoundry.org/cli/v9/command/translatableerror" + "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/configv3" diff --git a/command/v7/copy_source_command.go b/command/v7/copy_source_command.go index 658deb4e6c6..9a9b73c26ac 100644 --- a/command/v7/copy_source_command.go +++ b/command/v7/copy_source_command.go @@ -1,18 +1,18 @@ package v7 import ( - "strconv" - "strings" + "strconv" + "strings" - "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/api/logcache" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/util/configv3" ) diff --git a/command/v7/create_buildpack_command.go b/command/v7/create_buildpack_command.go index 7017a115229..da6cbac2c8c 100644 --- a/command/v7/create_buildpack_command.go +++ b/command/v7/create_buildpack_command.go @@ -6,7 +6,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" diff --git a/command/v7/create_route_command.go b/command/v7/create_route_command.go index ff4dd6fc3a3..9c203ebbbb7 100644 --- a/command/v7/create_route_command.go +++ b/command/v7/create_route_command.go @@ -7,8 +7,8 @@ import ( "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/resources" - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/command/flag" ) type CreateRouteCommand struct { diff --git a/command/v7/create_route_command_test.go b/command/v7/create_route_command_test.go index 23dc37ebed2..8803a9bcce5 100644 --- a/command/v7/create_route_command_test.go +++ b/command/v7/create_route_command_test.go @@ -7,7 +7,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command/commandfakes" "code.cloudfoundry.org/cli/v9/command/flag" . "code.cloudfoundry.org/cli/v9/command/v7" diff --git a/command/v7/create_user_provided_service_command.go b/command/v7/create_user_provided_service_command.go index c329297ad51..c5d9ec963c0 100644 --- a/command/v7/create_user_provided_service_command.go +++ b/command/v7/create_user_provided_service_command.go @@ -1,7 +1,7 @@ package v7 import ( - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/resources" diff --git a/command/v7/delete_buildpack_command.go b/command/v7/delete_buildpack_command.go index a564ec7051f..aa139a1b63d 100644 --- a/command/v7/delete_buildpack_command.go +++ b/command/v7/delete_buildpack_command.go @@ -2,8 +2,8 @@ package v7 import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" - "code.cloudfoundry.org/cli/v9/command" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" ) diff --git a/command/v7/delete_buildpack_command_test.go b/command/v7/delete_buildpack_command_test.go index 2bf7ef7f22d..2a61cf2e6ad 100644 --- a/command/v7/delete_buildpack_command_test.go +++ b/command/v7/delete_buildpack_command_test.go @@ -6,7 +6,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/command/commandfakes" - "code.cloudfoundry.org/cli/v9/command/translatableerror" + "code.cloudfoundry.org/cli/v9/command/translatableerror" . "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" "code.cloudfoundry.org/cli/v9/util/ui" diff --git a/command/v7/restage_command.go b/command/v7/restage_command.go index ea39d1d32c0..35042ac3285 100644 --- a/command/v7/restage_command.go +++ b/command/v7/restage_command.go @@ -1,19 +1,19 @@ package v7 import ( - "strconv" - "strings" + "strconv" + "strings" - "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/actionerror" "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/api/logcache" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" ) type RestageCommand struct { diff --git a/command/v7/restart_command.go b/command/v7/restart_command.go index fda8b0ef4b1..6fc987a4e8b 100644 --- a/command/v7/restart_command.go +++ b/command/v7/restart_command.go @@ -1,18 +1,18 @@ package v7 import ( - "strconv" - "strings" + "strconv" + "strings" - "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/api/logcache" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" ) type RestartCommand struct { diff --git a/command/v7/revision_command_test.go b/command/v7/revision_command_test.go index 6003f11b646..4a55130835d 100644 --- a/command/v7/revision_command_test.go +++ b/command/v7/revision_command_test.go @@ -1,18 +1,18 @@ package v7_test import ( - "errors" + "errors" - "code.cloudfoundry.org/cli/v9/actor/actionerror" - "code.cloudfoundry.org/cli/v9/actor/v7action" - "code.cloudfoundry.org/cli/v9/command/commandfakes" - "code.cloudfoundry.org/cli/v9/command/flag" + "code.cloudfoundry.org/cli/v9/actor/actionerror" + "code.cloudfoundry.org/cli/v9/actor/v7action" + "code.cloudfoundry.org/cli/v9/command/commandfakes" + "code.cloudfoundry.org/cli/v9/command/flag" v7 "code.cloudfoundry.org/cli/v9/command/v7" "code.cloudfoundry.org/cli/v9/command/v7/v7fakes" + "code.cloudfoundry.org/cli/v9/util/ui" "code.cloudfoundry.org/cli/v9/resources" "code.cloudfoundry.org/cli/v9/types" "code.cloudfoundry.org/cli/v9/util/configv3" - "code.cloudfoundry.org/cli/v9/util/ui" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gbytes" diff --git a/command/v7/rollback_command.go b/command/v7/rollback_command.go index abf19514f41..ff5ddad6ed9 100644 --- a/command/v7/rollback_command.go +++ b/command/v7/rollback_command.go @@ -7,13 +7,13 @@ import ( "code.cloudfoundry.org/cli/v9/actor/sharedaction" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/cf/errors" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" "code.cloudfoundry.org/cli/v9/command/v7/shared" - "code.cloudfoundry.org/cli/v9/resources" + "code.cloudfoundry.org/cli/v9/resources" ) type RollbackCommand struct { diff --git a/command/v7/shared/sharedfakes/fake_app_stager.go b/command/v7/shared/sharedfakes/fake_app_stager.go index 9e9e9f2e749..09bb3654f5a 100644 --- a/command/v7/shared/sharedfakes/fake_app_stager.go +++ b/command/v7/shared/sharedfakes/fake_app_stager.go @@ -258,6 +258,12 @@ func (fake *FakeAppStager) StartAppReturnsOnCall(i int, result1 error) { func (fake *FakeAppStager) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.stageAndStartMutex.RLock() + defer fake.stageAndStartMutex.RUnlock() + fake.stageAppMutex.RLock() + defer fake.stageAppMutex.RUnlock() + fake.startAppMutex.RLock() + defer fake.startAppMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/stack_command_test.go b/command/v7/stack_command_test.go index e7819b8ba59..537fc822704 100644 --- a/command/v7/stack_command_test.go +++ b/command/v7/stack_command_test.go @@ -196,29 +196,29 @@ var _ = Describe("Stack Command", func() { }) }) - Context("When the state is not ACTIVE but has no reason", func() { - BeforeEach(func() { - stack := resources.Stack{ - Name: "some-stack-name", - GUID: "some-stack-guid", - Description: "some-stack-desc", - State: "RESTRICTED", - } - fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) - }) + Context("When the state is not ACTIVE but has no reason", func() { + BeforeEach(func() { + stack := resources.Stack{ + Name: "some-stack-name", + GUID: "some-stack-guid", + Description: "some-stack-desc", + State: "RESTRICTED", + } + fakeActor.GetStackByNameReturns(stack, v7action.Warnings{}, nil) + }) - It("Displays the stack information with state and empty reason", func() { - Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) - Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) + It("Displays the stack information with state and empty reason", func() { + Expect(executeErr).ToNot(HaveOccurred()) + Expect(fakeActor.GetStackByNameArgsForCall(0)).To(Equal("some-stack-name")) + Expect(fakeActor.GetStackByNameCallCount()).To(Equal(1)) - Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) - Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) - Expect(testUI.Out).To(Say("state:\\s+RESTRICTED")) - Expect(testUI.Out).To(Say("reason:")) - }) + Expect(testUI.Out).To(Say("name:\\s+some-stack-name")) + Expect(testUI.Out).To(Say("description:\\s+some-stack-desc")) + Expect(testUI.Out).To(Say("state:\\s+RESTRICTED")) + Expect(testUI.Out).To(Say("reason:")) }) }) + }) When("The Stack does not Exist", func() { expectedError := actionerror.StackNotFoundError{Name: "some-stack-name"} diff --git a/command/v7/update_buildpack_command.go b/command/v7/update_buildpack_command.go index a73b6524d79..a45d17e3da0 100644 --- a/command/v7/update_buildpack_command.go +++ b/command/v7/update_buildpack_command.go @@ -7,7 +7,7 @@ import ( "code.cloudfoundry.org/cli/v9/actor/v7action" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3" - "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" + "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion" "code.cloudfoundry.org/cli/v9/command" "code.cloudfoundry.org/cli/v9/command/flag" "code.cloudfoundry.org/cli/v9/command/translatableerror" diff --git a/command/v7/update_stack_command_test.go b/command/v7/update_stack_command_test.go index c65d01ff834..5b32106a392 100644 --- a/command/v7/update_stack_command_test.go +++ b/command/v7/update_stack_command_test.go @@ -158,66 +158,67 @@ var _ = Describe("update-stack Command", func() { }) }) - Context("when state values are provided in different cases", func() { - It("accepts 'active' and capitalizes it", func() { - cmd.State = "active" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) + Context("when state values are provided in different cases", func() { + It("accepts 'active' and capitalizes it", func() { + cmd.State = "active" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateActive}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state, _ := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateActive)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateActive)) + }) - It("accepts 'RESTRICTED' and keeps it capitalized", func() { - cmd.State = "RESTRICTED" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) + It("accepts 'RESTRICTED' and keeps it capitalized", func() { + cmd.State = "RESTRICTED" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateRestricted}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state, _ := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateRestricted)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateRestricted)) + }) - It("accepts 'Disabled' and capitalizes it", func() { - cmd.State = "Disabled" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) + It("accepts 'Disabled' and capitalizes it", func() { + cmd.State = "Disabled" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{Name: "some-stack", State: resources.StackStateDisabled}, v7action.Warnings{}, nil) - executeErr = cmd.Execute(args) + executeErr = cmd.Execute(args) - Expect(executeErr).ToNot(HaveOccurred()) - _, state, _ := fakeActor.UpdateStackArgsForCall(0) - Expect(state).To(Equal(resources.StackStateDisabled)) - }) + Expect(executeErr).ToNot(HaveOccurred()) + _, state, _ := fakeActor.UpdateStackArgsForCall(0) + Expect(state).To(Equal(resources.StackStateDisabled)) }) + }) - Context("when the reason flag is provided", func() { - BeforeEach(func() { - cmd.State = "deprecated" - cmd.Reason = "Use cflinuxfs4 instead" - fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) - fakeActor.UpdateStackReturns(resources.Stack{ - Name: "some-stack", - Description: "some description", - State: resources.StackStateDeprecated, - StateReason: "Use cflinuxfs4 instead", - }, v7action.Warnings{}, nil) - }) + Context("when the reason flag is provided", func() { + BeforeEach(func() { + cmd.State = "deprecated" + cmd.Reason = "Use cflinuxfs4 instead" + fakeActor.GetStackByNameReturns(resources.Stack{GUID: "guid"}, v7action.Warnings{}, nil) + fakeActor.UpdateStackReturns(resources.Stack{ + Name: "some-stack", + Description: "some description", + State: resources.StackStateDeprecated, + StateReason: "Use cflinuxfs4 instead", + }, v7action.Warnings{}, nil) + }) - It("passes the reason to the actor and displays it", func() { - Expect(executeErr).ToNot(HaveOccurred()) + It("passes the reason to the actor and displays it", func() { + Expect(executeErr).ToNot(HaveOccurred()) - Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) - _, _, reason := fakeActor.UpdateStackArgsForCall(0) - Expect(reason).To(Equal("Use cflinuxfs4 instead")) + Expect(fakeActor.UpdateStackCallCount()).To(Equal(1)) + _, _, reason := fakeActor.UpdateStackArgsForCall(0) + Expect(reason).To(Equal("Use cflinuxfs4 instead")) - Expect(testUI.Out).To(Say(`reason:\s+Use cflinuxfs4 instead`)) - }) + Expect(testUI.Out).To(Say(`reason:\s+Use cflinuxfs4 instead`)) }) }) + }) }) + diff --git a/command/v7/v7fakes/fake_actor_reloader.go b/command/v7/v7fakes/fake_actor_reloader.go index 2fd49dc658f..87876729587 100644 --- a/command/v7/v7fakes/fake_actor_reloader.go +++ b/command/v7/v7fakes/fake_actor_reloader.go @@ -95,6 +95,8 @@ func (fake *FakeActorReloader) ReloadReturnsOnCall(i int, result1 v7.Actor, resu func (fake *FakeActorReloader) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.reloadMutex.RLock() + defer fake.reloadMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_diff_displayer.go b/command/v7/v7fakes/fake_diff_displayer.go index 9117049608a..8287b97c16d 100644 --- a/command/v7/v7fakes/fake_diff_displayer.go +++ b/command/v7/v7fakes/fake_diff_displayer.go @@ -95,6 +95,8 @@ func (fake *FakeDiffDisplayer) DisplayDiffReturnsOnCall(i int, result1 error) { func (fake *FakeDiffDisplayer) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.displayDiffMutex.RLock() + defer fake.displayDiffMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_label_setter.go b/command/v7/v7fakes/fake_label_setter.go index 0acdb5434c5..36443f1b9bb 100644 --- a/command/v7/v7fakes/fake_label_setter.go +++ b/command/v7/v7fakes/fake_label_setter.go @@ -90,6 +90,8 @@ func (fake *FakeLabelSetter) ExecuteReturnsOnCall(i int, result1 error) { func (fake *FakeLabelSetter) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.executeMutex.RLock() + defer fake.executeMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_label_unsetter.go b/command/v7/v7fakes/fake_label_unsetter.go index 554424cb19a..48ec32fe771 100644 --- a/command/v7/v7fakes/fake_label_unsetter.go +++ b/command/v7/v7fakes/fake_label_unsetter.go @@ -90,6 +90,8 @@ func (fake *FakeLabelUnsetter) ExecuteReturnsOnCall(i int, result1 error) { func (fake *FakeLabelUnsetter) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.executeMutex.RLock() + defer fake.executeMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_manifest_locator.go b/command/v7/v7fakes/fake_manifest_locator.go index 592132dfe0e..0e41343c30c 100644 --- a/command/v7/v7fakes/fake_manifest_locator.go +++ b/command/v7/v7fakes/fake_manifest_locator.go @@ -97,6 +97,8 @@ func (fake *FakeManifestLocator) PathReturnsOnCall(i int, result1 string, result func (fake *FakeManifestLocator) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.pathMutex.RLock() + defer fake.pathMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_manifest_parser.go b/command/v7/v7fakes/fake_manifest_parser.go index d571390ed17..dff85521ea8 100644 --- a/command/v7/v7fakes/fake_manifest_parser.go +++ b/command/v7/v7fakes/fake_manifest_parser.go @@ -269,6 +269,12 @@ func (fake *FakeManifestParser) ParseManifestReturnsOnCall(i int, result1 manife func (fake *FakeManifestParser) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.interpolateManifestMutex.RLock() + defer fake.interpolateManifestMutex.RUnlock() + fake.marshalManifestMutex.RLock() + defer fake.marshalManifestMutex.RUnlock() + fake.parseManifestMutex.RLock() + defer fake.parseManifestMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_network_policies_actor.go b/command/v7/v7fakes/fake_network_policies_actor.go index 91ee1014c2e..8bcdf225bd5 100644 --- a/command/v7/v7fakes/fake_network_policies_actor.go +++ b/command/v7/v7fakes/fake_network_policies_actor.go @@ -182,6 +182,10 @@ func (fake *FakeNetworkPoliciesActor) NetworkPoliciesBySpaceAndAppNameReturnsOnC func (fake *FakeNetworkPoliciesActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.networkPoliciesBySpaceMutex.RLock() + defer fake.networkPoliciesBySpaceMutex.RUnlock() + fake.networkPoliciesBySpaceAndAppNameMutex.RLock() + defer fake.networkPoliciesBySpaceAndAppNameMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_networking_actor.go b/command/v7/v7fakes/fake_networking_actor.go index 7fb0f93c709..1ca88ae4370 100644 --- a/command/v7/v7fakes/fake_networking_actor.go +++ b/command/v7/v7fakes/fake_networking_actor.go @@ -105,6 +105,8 @@ func (fake *FakeNetworkingActor) AddNetworkPolicyReturnsOnCall(i int, result1 cf func (fake *FakeNetworkingActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.addNetworkPolicyMutex.RLock() + defer fake.addNetworkPolicyMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_progress_bar.go b/command/v7/v7fakes/fake_progress_bar.go index 6a6c8a7b163..53118843760 100644 --- a/command/v7/v7fakes/fake_progress_bar.go +++ b/command/v7/v7fakes/fake_progress_bar.go @@ -146,6 +146,12 @@ func (fake *FakeProgressBar) ReadyCalls(stub func()) { func (fake *FakeProgressBar) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.completeMutex.RLock() + defer fake.completeMutex.RUnlock() + fake.newProgressBarWrapperMutex.RLock() + defer fake.newProgressBarWrapperMutex.RUnlock() + fake.readyMutex.RLock() + defer fake.readyMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_push_actor.go b/command/v7/v7fakes/fake_push_actor.go index 8517b124f1b..74c6db82946 100644 --- a/command/v7/v7fakes/fake_push_actor.go +++ b/command/v7/v7fakes/fake_push_actor.go @@ -338,6 +338,14 @@ func (fake *FakePushActor) HandleFlagOverridesReturnsOnCall(i int, result1 manif func (fake *FakePushActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.actualizeMutex.RLock() + defer fake.actualizeMutex.RUnlock() + fake.createPushPlansMutex.RLock() + defer fake.createPushPlansMutex.RUnlock() + fake.handleDeploymentScaleFlagOverridesMutex.RLock() + defer fake.handleDeploymentScaleFlagOverridesMutex.RUnlock() + fake.handleFlagOverridesMutex.RLock() + defer fake.handleFlagOverridesMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_remove_network_policy_actor.go b/command/v7/v7fakes/fake_remove_network_policy_actor.go index 390bf258738..9c3a4d61aa8 100644 --- a/command/v7/v7fakes/fake_remove_network_policy_actor.go +++ b/command/v7/v7fakes/fake_remove_network_policy_actor.go @@ -105,6 +105,8 @@ func (fake *FakeRemoveNetworkPolicyActor) RemoveNetworkPolicyReturnsOnCall(i int func (fake *FakeRemoveNetworkPolicyActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.removeNetworkPolicyMutex.RLock() + defer fake.removeNetworkPolicyMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_revisions_actor.go b/command/v7/v7fakes/fake_revisions_actor.go index 50186a8febd..e126f4ea24b 100644 --- a/command/v7/v7fakes/fake_revisions_actor.go +++ b/command/v7/v7fakes/fake_revisions_actor.go @@ -101,6 +101,8 @@ func (fake *FakeRevisionsActor) GetRevisionsByApplicationNameAndSpaceReturnsOnCa func (fake *FakeRevisionsActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.getRevisionsByApplicationNameAndSpaceMutex.RLock() + defer fake.getRevisionsByApplicationNameAndSpaceMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_set_label_actor.go b/command/v7/v7fakes/fake_set_label_actor.go index 113aa2be611..5cbb7852e91 100644 --- a/command/v7/v7fakes/fake_set_label_actor.go +++ b/command/v7/v7fakes/fake_set_label_actor.go @@ -318,16 +318,16 @@ func (fake *FakeSetLabelActor) UpdateBuildpackLabelsByBuildpackNameAndStackAndLi fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleArgsForCall = append(fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleArgsForCall, struct { arg1 string arg2 string - arg3 string - arg4 map[string]types.NullString - }{arg1, arg2, arg3, arg4}) - stub := fake.UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycleStub - fakeReturns := fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleReturns - fake.recordInvocation("UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycle", []interface{}{arg1, arg2, arg3, arg4}) - fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.Unlock() - if stub != nil { - return stub(arg1, arg2, arg3, arg4) - } + arg3 string + arg4 map[string]types.NullString + }{arg1, arg2, arg3, arg4}) + stub := fake.UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycleStub + fakeReturns := fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleReturns + fake.recordInvocation("UpdateBuildpackLabelsByBuildpackNameAndStackAndLifecycle", []interface{}{arg1, arg2, arg3, arg4}) + fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } if specificReturn { return ret.result1, ret.result2 } @@ -973,6 +973,30 @@ func (fake *FakeSetLabelActor) UpdateStackLabelsByStackNameReturnsOnCall(i int, func (fake *FakeSetLabelActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.getCurrentUserMutex.RLock() + defer fake.getCurrentUserMutex.RUnlock() + fake.updateApplicationLabelsByApplicationNameMutex.RLock() + defer fake.updateApplicationLabelsByApplicationNameMutex.RUnlock() + fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.RLock() + defer fake.updateBuildpackLabelsByBuildpackNameAndStackAndLifecycleMutex.RUnlock() + fake.updateDomainLabelsByDomainNameMutex.RLock() + defer fake.updateDomainLabelsByDomainNameMutex.RUnlock() + fake.updateOrganizationLabelsByOrganizationNameMutex.RLock() + defer fake.updateOrganizationLabelsByOrganizationNameMutex.RUnlock() + fake.updateRouteLabelsMutex.RLock() + defer fake.updateRouteLabelsMutex.RUnlock() + fake.updateServiceBrokerLabelsByServiceBrokerNameMutex.RLock() + defer fake.updateServiceBrokerLabelsByServiceBrokerNameMutex.RUnlock() + fake.updateServiceInstanceLabelsMutex.RLock() + defer fake.updateServiceInstanceLabelsMutex.RUnlock() + fake.updateServiceOfferingLabelsMutex.RLock() + defer fake.updateServiceOfferingLabelsMutex.RUnlock() + fake.updateServicePlanLabelsMutex.RLock() + defer fake.updateServicePlanLabelsMutex.RUnlock() + fake.updateSpaceLabelsBySpaceNameMutex.RLock() + defer fake.updateSpaceLabelsBySpaceNameMutex.RUnlock() + fake.updateStackLabelsByStackNameMutex.RLock() + defer fake.updateStackLabelsByStackNameMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_shared_sshactor.go b/command/v7/v7fakes/fake_shared_sshactor.go index b30b6111543..a7756d048bc 100644 --- a/command/v7/v7fakes/fake_shared_sshactor.go +++ b/command/v7/v7fakes/fake_shared_sshactor.go @@ -90,6 +90,8 @@ func (fake *FakeSharedSSHActor) ExecuteSecureShellReturnsOnCall(i int, result1 e func (fake *FakeSharedSSHActor) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.executeSecureShellMutex.RLock() + defer fake.executeSecureShellMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/command/v7/v7fakes/fake_v7actor_for_push.go b/command/v7/v7fakes/fake_v7actor_for_push.go index 0341fb15432..22407358123 100644 --- a/command/v7/v7fakes/fake_v7actor_for_push.go +++ b/command/v7/v7fakes/fake_v7actor_for_push.go @@ -448,6 +448,16 @@ func (fake *FakeV7ActorForPush) SetSpaceManifestReturnsOnCall(i int, result1 v7a func (fake *FakeV7ActorForPush) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.getApplicationByNameAndSpaceMutex.RLock() + defer fake.getApplicationByNameAndSpaceMutex.RUnlock() + fake.getDetailedAppSummaryMutex.RLock() + defer fake.getDetailedAppSummaryMutex.RUnlock() + fake.getStreamingLogsForApplicationByNameAndSpaceMutex.RLock() + defer fake.getStreamingLogsForApplicationByNameAndSpaceMutex.RUnlock() + fake.restartApplicationMutex.RLock() + defer fake.restartApplicationMutex.RUnlock() + fake.setSpaceManifestMutex.RLock() + defer fake.setSpaceManifestMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/integration/v7/isolated/revision_command_test.go b/integration/v7/isolated/revision_command_test.go index 8243d2d6e04..423eccddb48 100644 --- a/integration/v7/isolated/revision_command_test.go +++ b/integration/v7/isolated/revision_command_test.go @@ -1,13 +1,13 @@ package isolated import ( - "bytes" - "encoding/json" - "fmt" - "os/exec" - "strings" + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" - . "code.cloudfoundry.org/cli/v9/cf/util/testhelpers/matchers" + . "code.cloudfoundry.org/cli/v9/cf/util/testhelpers/matchers" "code.cloudfoundry.org/cli/v9/integration/helpers" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/integration/v7/isolated/update_stack_command_test.go b/integration/v7/isolated/update_stack_command_test.go index 5af25b52a42..ed4cf7668b9 100644 --- a/integration/v7/isolated/update_stack_command_test.go +++ b/integration/v7/isolated/update_stack_command_test.go @@ -255,3 +255,4 @@ var _ = Describe("update-stack command", func() { }) }) }) + diff --git a/resources/deployment_resource.go b/resources/deployment_resource.go index b4410ad0548..03fa7f9d5b6 100644 --- a/resources/deployment_resource.go +++ b/resources/deployment_resource.go @@ -5,7 +5,7 @@ import ( "code.cloudfoundry.org/cli/v9/api/cloudcontroller" "code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant" - "code.cloudfoundry.org/cli/v9/types" + "code.cloudfoundry.org/cli/v9/types" ) type Deployment struct { diff --git a/resources/process_resource.go b/resources/process_resource.go index 0dd88719e02..1d136266b17 100644 --- a/resources/process_resource.go +++ b/resources/process_resource.go @@ -134,9 +134,9 @@ type marshalProcess struct { DiskInMB json.Number `json:"disk_in_mb,omitempty"` LogRateLimitInBPS json.Number `json:"log_rate_limit_in_bytes_per_second,omitempty"` - HealthCheck *healthCheck `json:"health_check,omitempty"` - ReadinessHealthCheck *readinessHealthCheck `json:"readiness_health_check,omitempty"` - EmbeddedProcessInstances []EmbeddedProcessInstance `json:"process_instances,omitempty"` + HealthCheck *healthCheck `json:"health_check,omitempty"` + ReadinessHealthCheck *readinessHealthCheck `json:"readiness_health_check,omitempty"` + EmbeddedProcessInstances []EmbeddedProcessInstance `json:"process_instances,omitempty"` } func marshalCommand(p Process, ccProcess *marshalProcess) {