From a5e01b1a96267f62ea4c7f1647012cdf2fca4d41 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Thu, 7 May 2026 12:12:16 +0200 Subject: [PATCH 01/13] add route_policy resource and client --- client/route_policy.go | 96 ++++++++++++++++++++++++++++++++++++++++ resource/route_policy.go | 25 +++++++++++ 2 files changed, 121 insertions(+) create mode 100644 client/route_policy.go create mode 100644 resource/route_policy.go diff --git a/client/route_policy.go b/client/route_policy.go new file mode 100644 index 0000000..9e2420d --- /dev/null +++ b/client/route_policy.go @@ -0,0 +1,96 @@ +package client + +import ( + "context" + "net/url" + + "github.com/cloudfoundry/go-cfclient/v3/internal/path" + "github.com/cloudfoundry/go-cfclient/v3/resource" +) + +type RoutePolicyClient commonClient + +// RoutePolicyListOptions list filters +type RoutePolicyListOptions struct { + *ListOptions + + RouteGUIDs Filter `qs:"route_guids"` + SpaceGUIDs Filter `qs:"space_guids"` + SourceGUIDs Filter `qs:"source_guids"` + OrganizationGUIDs Filter `qs:"organization_guids"` +} + +// NewRoutePolicyListOptions creates new options to pass to list +func NewRoutePolicyListOptions() *RoutePolicyListOptions { + return &RoutePolicyListOptions{ + ListOptions: NewListOptions(), + } +} + +func (o RoutePolicyListOptions) ToQueryString() (url.Values, error) { + return o.ListOptions.ToQueryString(o) +} + +// Create a new route policy +func (c *RoutePolicyClient) Create(ctx context.Context, r *resource.RoutePolicyCreate) (*resource.RoutePolicy, error) { + var routePolicy resource.RoutePolicy + _, err := c.client.post(ctx, "/v3/route_policies", r, &routePolicy) + if err != nil { + return nil, err + } + return &routePolicy, nil +} + +// Delete the specified route policy asynchronously and return a jobGUID. +func (c *RoutePolicyClient) Delete(ctx context.Context, guid string) (string, error) { + return c.client.delete(ctx, path.Format("/v3/route_policies/%s", guid)) +} + +// First returns the first route policy matching the options or an error when less than 1 match +func (c *RoutePolicyClient) First(ctx context.Context, opts *RoutePolicyListOptions) (*resource.RoutePolicy, error) { + return First[*RoutePolicyListOptions, *resource.RoutePolicy](opts, func(opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, *Pager, error) { + return c.List(ctx, opts) + }) +} + +// Get the specified route policy +func (c *RoutePolicyClient) Get(ctx context.Context, guid string) (*resource.RoutePolicy, error) { + var routePolicy resource.RoutePolicy + err := c.client.get(ctx, path.Format("/v3/route_policies/%s", guid), &routePolicy) + if err != nil { + return nil, err + } + return &routePolicy, nil +} + +// List pages all the route policies the user has access to +func (c *RoutePolicyClient) List(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, *Pager, error) { + if opts == nil { + opts = NewRoutePolicyListOptions() + } + + var res resource.RoutePolicyList + err := c.client.list(ctx, "/v3/route_policies", opts.ToQueryString, &res) + if err != nil { + return nil, nil, err + } + pager := NewPager(res.Pagination) + return res.Resources, pager, nil +} + +// ListAll retrieves all route policies the user has access to +func (c *RoutePolicyClient) ListAll(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, error) { + if opts == nil { + opts = NewRoutePolicyListOptions() + } + return AutoPage[*RoutePolicyListOptions, *resource.RoutePolicy](opts, func(opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, *Pager, error) { + return c.List(ctx, opts) + }) +} + +// Single returns a single route policy matching the options or an error if not exactly 1 match +func (c *RoutePolicyClient) Single(ctx context.Context, opts *RoutePolicyListOptions) (*resource.RoutePolicy, error) { + return Single[*RoutePolicyListOptions, *resource.RoutePolicy](opts, func(opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, *Pager, error) { + return c.List(ctx, opts) + }) +} diff --git a/resource/route_policy.go b/resource/route_policy.go new file mode 100644 index 0000000..a097d71 --- /dev/null +++ b/resource/route_policy.go @@ -0,0 +1,25 @@ +package resource + +type RoutePolicy struct { + Source string `json:"source"` + Metadata *Metadata `json:"metadata"` + Relationships RoutePolicyRelationships `json:"relationships"` + Resource `json:",inline"` +} + +type RoutePolicyCreate struct { + Relationships RoutePolicyRelationships `json:"relationships"` + Source string `json:"source"` +} + +type RoutePolicyList struct { + Pagination Pagination `json:"pagination"` + Resources []*RoutePolicy `json:"resources"` +} + +type RoutePolicyRelationships struct { + Route ToOneRelationship `json:"route"` + App ToOneRelationship `json:"app"` + Space ToOneRelationship `json:"space"` + Organization ToOneRelationship `json:"organization"` +} From 16685d955e582b87a3c5042376687866e637e2b3 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Thu, 7 May 2026 12:37:49 +0200 Subject: [PATCH 02/13] add RoutePolicyClient --- client/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/client.go b/client/client.go index a3d8d3e..1d33b8f 100644 --- a/client/client.go +++ b/client/client.go @@ -46,6 +46,7 @@ type Client struct { Roles *RoleClient Root *RootClient Routes *RouteClient + RoutePolicies *RoutePolicyClient SecurityGroups *SecurityGroupClient ServiceBrokers *ServiceBrokerClient ServiceCredentialBindings *ServiceCredentialBindingClient From 836755297e200491ddc6b2d17e330f5c7b50f744 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Thu, 7 May 2026 13:35:46 +0200 Subject: [PATCH 03/13] add RoutePolicyClient in New() --- client/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/client.go b/client/client.go index 1d33b8f..ac0a888 100644 --- a/client/client.go +++ b/client/client.go @@ -111,6 +111,7 @@ func New(config *config.Config) (*Client, error) { client.Roles = (*RoleClient)(&client.common) client.Root = (*RootClient)(&client.common) client.Routes = (*RouteClient)(&client.common) + client.RoutePolicies = (*RoutePolicyClient)(&client.common) client.SecurityGroups = (*SecurityGroupClient)(&client.common) client.ServiceBrokers = (*ServiceBrokerClient)(&client.common) client.ServiceCredentialBindings = (*ServiceCredentialBindingClient)(&client.common) From dbdb1c9f469fa3cab7389c9d969b41c6bb23140d Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Thu, 7 May 2026 15:27:05 +0200 Subject: [PATCH 04/13] omitempty for RelationShips fields --- resource/route_policy.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resource/route_policy.go b/resource/route_policy.go index a097d71..cdbdd5f 100644 --- a/resource/route_policy.go +++ b/resource/route_policy.go @@ -18,8 +18,8 @@ type RoutePolicyList struct { } type RoutePolicyRelationships struct { - Route ToOneRelationship `json:"route"` - App ToOneRelationship `json:"app"` - Space ToOneRelationship `json:"space"` - Organization ToOneRelationship `json:"organization"` + Route ToOneRelationship `json:"route,omitempty"` + App ToOneRelationship `json:"app,omitempty"` + Space ToOneRelationship `json:"space,omitempty"` + Organization ToOneRelationship `json:"organization,omitempty"` } From 11174074ef0c38705f4509e6028237829436204a Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Thu, 7 May 2026 15:31:56 +0200 Subject: [PATCH 05/13] omitempty for RelationShips fields, make them pointers --- resource/route_policy.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resource/route_policy.go b/resource/route_policy.go index cdbdd5f..93707c8 100644 --- a/resource/route_policy.go +++ b/resource/route_policy.go @@ -18,8 +18,8 @@ type RoutePolicyList struct { } type RoutePolicyRelationships struct { - Route ToOneRelationship `json:"route,omitempty"` - App ToOneRelationship `json:"app,omitempty"` - Space ToOneRelationship `json:"space,omitempty"` - Organization ToOneRelationship `json:"organization,omitempty"` + Route *ToOneRelationship `json:"route,omitempty"` + App *ToOneRelationship `json:"app,omitempty"` + Space *ToOneRelationship `json:"space,omitempty"` + Organization *ToOneRelationship `json:"organization,omitempty"` } From 19c7c55c4ed1c326ada5d217d15cc04143ed9ee8 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Fri, 8 May 2026 08:54:15 +0200 Subject: [PATCH 06/13] also provide the filter on Sources --- client/route_policy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/route_policy.go b/client/route_policy.go index 9e2420d..3f533b8 100644 --- a/client/route_policy.go +++ b/client/route_policy.go @@ -17,6 +17,7 @@ type RoutePolicyListOptions struct { RouteGUIDs Filter `qs:"route_guids"` SpaceGUIDs Filter `qs:"space_guids"` SourceGUIDs Filter `qs:"source_guids"` + Sources Filter `qs:"sources"` OrganizationGUIDs Filter `qs:"organization_guids"` } From b84314cbcfd82f25547102b026b4f4c484b65f9d Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Fri, 8 May 2026 10:28:47 +0200 Subject: [PATCH 07/13] generated the test cases --- client/route_policy_test.go | 267 ++++++++++++++++++++++++++++ testutil/object_generator.go | 7 + testutil/template/route_policy.json | 38 ++++ 3 files changed, 312 insertions(+) create mode 100644 client/route_policy_test.go create mode 100644 testutil/template/route_policy.json diff --git a/client/route_policy_test.go b/client/route_policy_test.go new file mode 100644 index 0000000..bec9884 --- /dev/null +++ b/client/route_policy_test.go @@ -0,0 +1,267 @@ +package client + +import ( + "context" + "net/http" + "testing" + + "github.com/cloudfoundry/go-cfclient/v3/resource" + "github.com/cloudfoundry/go-cfclient/v3/testutil" +) + +func TestRoutePolicies(t *testing.T) { + g := testutil.NewObjectJSONGenerator() + routePolicy := g.RoutePolicy().JSON + routePolicy2 := g.RoutePolicy().JSON + + tests := []RouteTest{ + { + Description: "Create route policy", + Route: testutil.MockRoute{ + Method: "POST", + Endpoint: "/v3/route_policies", + Output: g.Single(routePolicy), + Status: http.StatusCreated, + PostForm: `{ + "source": "1cb006ee-fb05-47e1-b541-c34179ddc446", + "relationships": { + "route": { + "data": { "guid": "5a85c020-3e3d-42a5-a475-5084c5357e82" } + }, + "app": { + "data": { "guid": "1cb006ee-fb05-47e1-b541-c34179ddc446" } + } + } + }`, + }, + Expected: routePolicy, + Action: func(c *Client, t *testing.T) (any, error) { + r := &resource.RoutePolicyCreate{ + Source: "1cb006ee-fb05-47e1-b541-c34179ddc446", + Relationships: resource.RoutePolicyRelationships{ + Route: &resource.ToOneRelationship{ + Data: &resource.Relationship{ + GUID: "5a85c020-3e3d-42a5-a475-5084c5357e82", + }, + }, + App: &resource.ToOneRelationship{ + Data: &resource.Relationship{ + GUID: "1cb006ee-fb05-47e1-b541-c34179ddc446", + }, + }, + }, + } + return c.RoutePolicies.Create(context.Background(), r) + }, + }, + { + Description: "Delete route policy", + Route: testutil.MockRoute{ + Method: "DELETE", + Endpoint: "/v3/route_policies/c8dcf27f-39a3-466a-9cbf-1d3c31a43b93", + Status: http.StatusAccepted, + RedirectLocation: "https://api.example.org/api/v3/jobs/c33a5caf-77e0-4d6e-b587-5555d339bc9a", + }, + Expected: "c33a5caf-77e0-4d6e-b587-5555d339bc9a", + Action: func(c *Client, t *testing.T) (any, error) { + return c.RoutePolicies.Delete(context.Background(), "c8dcf27f-39a3-466a-9cbf-1d3c31a43b93") + }, + }, + { + Description: "Get route policy", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies/c8dcf27f-39a3-466a-9cbf-1d3c31a43b93", + Output: g.Single(routePolicy), + Status: http.StatusOK, + }, + Expected: routePolicy, + Action: func(c *Client, t *testing.T) (any, error) { + return c.RoutePolicies.Get(context.Background(), "c8dcf27f-39a3-466a-9cbf-1d3c31a43b93") + }, + }, + { + Description: "List first page of route policies", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + policies, _, err := c.RoutePolicies.List(context.Background(), NewRoutePolicyListOptions()) + return policies, err + }, + }, + { + Description: "List all route policies", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.Paged([]string{routePolicy}, []string{routePolicy2}), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy, routePolicy2), + Action: func(c *Client, t *testing.T) (any, error) { + return c.RoutePolicies.ListAll(context.Background(), nil) + }, + }, + { + Description: "First route policy with no matches", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.Paged([]string{}), + Status: http.StatusOK, + }, + Expected: "error", + Action: func(c *Client, t *testing.T) (any, error) { + _, err := c.RoutePolicies.First(context.Background(), NewRoutePolicyListOptions()) + if err != nil { + return "error", nil + } + return "no error", nil + }, + }, + { + Description: "First route policy", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: routePolicy, + Action: func(c *Client, t *testing.T) (any, error) { + return c.RoutePolicies.First(context.Background(), NewRoutePolicyListOptions()) + }, + }, + { + Description: "Single route policy with no matches", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.Paged([]string{}), + Status: http.StatusOK, + }, + Expected: "error", + Action: func(c *Client, t *testing.T) (any, error) { + _, err := c.RoutePolicies.Single(context.Background(), NewRoutePolicyListOptions()) + if err != nil { + return "error", nil + } + return "no error", nil + }, + }, + { + Description: "Single route policy", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: routePolicy, + Action: func(c *Client, t *testing.T) (any, error) { + return c.RoutePolicies.Single(context.Background(), NewRoutePolicyListOptions()) + }, + }, + { + Description: "List route policies with filter by route GUID", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + QueryString: "page=1&per_page=50&route_guids=5a85c020-3e3d-42a5-a475-5084c5357e82", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + opts := NewRoutePolicyListOptions() + opts.RouteGUIDs = Filter{ + Values: []string{"5a85c020-3e3d-42a5-a475-5084c5357e82"}, + } + policies, _, err := c.RoutePolicies.List(context.Background(), opts) + return policies, err + }, + }, + { + Description: "List route policies with filter by space GUID", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + QueryString: "page=1&per_page=50&space_guids=33d27af8-788d-4de5-8f37-fb80d517f2ed", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + opts := NewRoutePolicyListOptions() + opts.SpaceGUIDs = Filter{ + Values: []string{"33d27af8-788d-4de5-8f37-fb80d517f2ed"}, + } + policies, _, err := c.RoutePolicies.List(context.Background(), opts) + return policies, err + }, + }, + { + Description: "List route policies with filter by source GUID", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + QueryString: "page=1&per_page=50&source_guids=1cb006ee-fb05-47e1-b541-c34179ddc446", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + opts := NewRoutePolicyListOptions() + opts.SourceGUIDs = Filter{ + Values: []string{"1cb006ee-fb05-47e1-b541-c34179ddc446"}, + } + policies, _, err := c.RoutePolicies.List(context.Background(), opts) + return policies, err + }, + }, + { + Description: "List route policies with filter by source", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + QueryString: "page=1&per_page=50&sources=network_policy", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + opts := NewRoutePolicyListOptions() + opts.Sources = Filter{ + Values: []string{"network_policy"}, + } + policies, _, err := c.RoutePolicies.List(context.Background(), opts) + return policies, err + }, + }, + { + Description: "List route policies with filter by organization GUID", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + QueryString: "organization_guids=3a5f687b-2ce8-4ade-be75-8eca99b0db8b&page=1&per_page=50", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + opts := NewRoutePolicyListOptions() + opts.OrganizationGUIDs = Filter{ + Values: []string{"3a5f687b-2ce8-4ade-be75-8eca99b0db8b"}, + } + policies, _, err := c.RoutePolicies.List(context.Background(), opts) + return policies, err + }, + }, + } + ExecuteTests(tests, t) +} diff --git a/testutil/object_generator.go b/testutil/object_generator.go index 76baf9d..5836abe 100644 --- a/testutil/object_generator.go +++ b/testutil/object_generator.go @@ -343,6 +343,13 @@ func (o ObjectJSONGenerator) RouteDestinationWithLinks() *JSONResource { return o.renderTemplate(r, "route_destination_with_links.json") } +func (o ObjectJSONGenerator) RoutePolicy() *JSONResource { + r := &JSONResource{ + GUID: RandomGUID(), + } + return o.renderTemplate(r, "route_policy.json") +} + func (o ObjectJSONGenerator) ServiceBroker() *JSONResource { r := &JSONResource{ GUID: RandomGUID(), diff --git a/testutil/template/route_policy.json b/testutil/template/route_policy.json new file mode 100644 index 0000000..ae7ffb8 --- /dev/null +++ b/testutil/template/route_policy.json @@ -0,0 +1,38 @@ +{ + "guid": "{{.GUID}}", + "source": "1cb006ee-fb05-47e1-b541-c34179ddc446", + "created_at": "2023-01-01T00:00:00Z", + "updated_at": "2023-01-01T00:00:00Z", + "metadata": { + "labels": {}, + "annotations": {} + }, + "relationships": { + "route": { + "data": { + "guid": "5a85c020-3e3d-42a5-a475-5084c5357e82" + } + }, + "app": { + "data": { + "guid": "1cb006ee-fb05-47e1-b541-c34179ddc446" + } + }, + "space": { + "data": { + "guid": "33d27af8-788d-4de5-8f37-fb80d517f2ed" + } + }, + "organization": { + "data": { + "guid": "3a5f687b-2ce8-4ade-be75-8eca99b0db8b" + } + } + }, + "links": { + "self": { + "href": "https://api.example.org/v3/route_policies/{{.GUID}}" + } + } +} + From 23430b723459f18b7dec9f0f3d9a47495cb5620f Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Wed, 13 May 2026 08:43:16 +0200 Subject: [PATCH 08/13] add missing guids (route policy guid) query parameter --- client/route_policy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/route_policy.go b/client/route_policy.go index 3f533b8..4823075 100644 --- a/client/route_policy.go +++ b/client/route_policy.go @@ -14,6 +14,7 @@ type RoutePolicyClient commonClient type RoutePolicyListOptions struct { *ListOptions + GUIDs Filter `qs:"guids"` RouteGUIDs Filter `qs:"route_guids"` SpaceGUIDs Filter `qs:"space_guids"` SourceGUIDs Filter `qs:"source_guids"` From 0ecd8c5877fbb3b0a0bde2f793ac64180ea52116 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Wed, 13 May 2026 11:22:38 +0200 Subject: [PATCH 09/13] add support for include Route and Source --- client/route_policy.go | 125 ++++++++++++++++++++++++++++++++++-- client/route_policy_test.go | 104 ++++++++++++++++++++++++++++++ resource/route_policy.go | 39 ++++++++++- 3 files changed, 260 insertions(+), 8 deletions(-) diff --git a/client/route_policy.go b/client/route_policy.go index 4823075..a81a6cd 100644 --- a/client/route_policy.go +++ b/client/route_policy.go @@ -14,12 +14,13 @@ type RoutePolicyClient commonClient type RoutePolicyListOptions struct { *ListOptions - GUIDs Filter `qs:"guids"` - RouteGUIDs Filter `qs:"route_guids"` - SpaceGUIDs Filter `qs:"space_guids"` - SourceGUIDs Filter `qs:"source_guids"` - Sources Filter `qs:"sources"` - OrganizationGUIDs Filter `qs:"organization_guids"` + GUIDs Filter `qs:"guids"` + RouteGUIDs Filter `qs:"route_guids"` + SpaceGUIDs Filter `qs:"space_guids"` + SourceGUIDs Filter `qs:"source_guids"` + Sources Filter `qs:"sources"` + OrganizationGUIDs Filter `qs:"organization_guids"` + Include resource.RoutePolicyIncludeType `qs:"include"` } // NewRoutePolicyListOptions creates new options to pass to list @@ -65,6 +66,40 @@ func (c *RoutePolicyClient) Get(ctx context.Context, guid string) (*resource.Rou return &routePolicy, nil } +// GetIncludeRoute retrieves the specified route policy and include the associated route +func (c *RoutePolicyClient) GetIncludeRoute(ctx context.Context, guid string) (*resource.RoutePolicy, *resource.Route, error) { + var res resource.RoutePolicyList + err := c.client.get(ctx, path.Format("/v3/route_policies/%s?include=%s", guid, resource.RoutePolicyIncludeRoute), &res) + if err != nil { + return nil, nil, err + } + if len(res.Resources) == 0 { + return nil, nil, err + } + var route *resource.Route + if res.Included != nil && len(res.Included.Routes) > 0 { + route = res.Included.Routes[0] + } + return res.Resources[0], route, nil +} + +// GetIncludeSource retrieves the specified route policy and include the associated source app +func (c *RoutePolicyClient) GetIncludeSource(ctx context.Context, guid string) (*resource.RoutePolicy, *resource.App, error) { + var res resource.RoutePolicyList + err := c.client.get(ctx, path.Format("/v3/route_policies/%s?include=%s", guid, resource.RoutePolicyIncludeSource), &res) + if err != nil { + return nil, nil, err + } + if len(res.Resources) == 0 { + return nil, nil, err + } + var app *resource.App + if res.Included != nil && len(res.Included.Apps) > 0 { + app = res.Included.Apps[0] + } + return res.Resources[0], app, nil +} + // List pages all the route policies the user has access to func (c *RoutePolicyClient) List(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, *Pager, error) { if opts == nil { @@ -90,6 +125,84 @@ func (c *RoutePolicyClient) ListAll(ctx context.Context, opts *RoutePolicyListOp }) } +// ListIncludeRoute page all route policies the user has access to and include the associated routes +func (c *RoutePolicyClient) ListIncludeRoute(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.Route, *Pager, error) { + if opts == nil { + opts = NewRoutePolicyListOptions() + } + opts.Include = resource.RoutePolicyIncludeRoute + + var res resource.RoutePolicyList + err := c.client.list(ctx, "/v3/route_policies", opts.ToQueryString, &res) + if err != nil { + return nil, nil, nil, err + } + pager := NewPager(res.Pagination) + return res.Resources, res.Included.Routes, pager, nil +} + +// ListIncludeRouteAll retrieves all route policies the user has access to and include the associated routes +func (c *RoutePolicyClient) ListIncludeRouteAll(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.Route, error) { + if opts == nil { + opts = NewRoutePolicyListOptions() + } + + var all []*resource.RoutePolicy + var allRoutes []*resource.Route + for { + page, routes, pager, err := c.ListIncludeRoute(ctx, opts) + if err != nil { + return nil, nil, err + } + all = append(all, page...) + allRoutes = append(allRoutes, routes...) + if !pager.HasNextPage() { + break + } + pager.NextPage(opts) + } + return all, allRoutes, nil +} + +// ListIncludeSource page all route policies the user has access to and include the associated source apps +func (c *RoutePolicyClient) ListIncludeSource(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.App, *Pager, error) { + if opts == nil { + opts = NewRoutePolicyListOptions() + } + opts.Include = resource.RoutePolicyIncludeSource + + var res resource.RoutePolicyList + err := c.client.list(ctx, "/v3/route_policies", opts.ToQueryString, &res) + if err != nil { + return nil, nil, nil, err + } + pager := NewPager(res.Pagination) + return res.Resources, res.Included.Apps, pager, nil +} + +// ListIncludeSourceAll retrieves all route policies the user has access to and include the associated source apps +func (c *RoutePolicyClient) ListIncludeSourceAll(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.App, error) { + if opts == nil { + opts = NewRoutePolicyListOptions() + } + + var all []*resource.RoutePolicy + var allApps []*resource.App + for { + page, apps, pager, err := c.ListIncludeSource(ctx, opts) + if err != nil { + return nil, nil, err + } + all = append(all, page...) + allApps = append(allApps, apps...) + if !pager.HasNextPage() { + break + } + pager.NextPage(opts) + } + return all, allApps, nil +} + // Single returns a single route policy matching the options or an error if not exactly 1 match func (c *RoutePolicyClient) Single(ctx context.Context, opts *RoutePolicyListOptions) (*resource.RoutePolicy, error) { return Single[*RoutePolicyListOptions, *resource.RoutePolicy](opts, func(opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, *Pager, error) { diff --git a/client/route_policy_test.go b/client/route_policy_test.go index bec9884..d14d281 100644 --- a/client/route_policy_test.go +++ b/client/route_policy_test.go @@ -13,6 +13,10 @@ func TestRoutePolicies(t *testing.T) { g := testutil.NewObjectJSONGenerator() routePolicy := g.RoutePolicy().JSON routePolicy2 := g.RoutePolicy().JSON + route1 := g.Route().JSON + route2 := g.Route().JSON + app1 := g.Application().JSON + app2 := g.Application().JSON tests := []RouteTest{ { @@ -80,6 +84,42 @@ func TestRoutePolicies(t *testing.T) { return c.RoutePolicies.Get(context.Background(), "c8dcf27f-39a3-466a-9cbf-1d3c31a43b93") }, }, + { + Description: "Get route policy with include route", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies/c8dcf27f-39a3-466a-9cbf-1d3c31a43b93", + Output: g.PagedWithInclude( + testutil.PagedResult{ + Resources: []string{routePolicy}, + Routes: []string{route1}, + }), + Status: http.StatusOK, + }, + Expected: routePolicy, + Expected2: route1, + Action2: func(c *Client, t *testing.T) (any, any, error) { + return c.RoutePolicies.GetIncludeRoute(context.Background(), "c8dcf27f-39a3-466a-9cbf-1d3c31a43b93") + }, + }, + { + Description: "Get route policy with include source", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies/c8dcf27f-39a3-466a-9cbf-1d3c31a43b93", + Output: g.PagedWithInclude( + testutil.PagedResult{ + Resources: []string{routePolicy}, + Apps: []string{app1}, + }), + Status: http.StatusOK, + }, + Expected: routePolicy, + Expected2: app1, + Action2: func(c *Client, t *testing.T) (any, any, error) { + return c.RoutePolicies.GetIncludeSource(context.Background(), "c8dcf27f-39a3-466a-9cbf-1d3c31a43b93") + }, + }, { Description: "List first page of route policies", Route: testutil.MockRoute{ @@ -262,6 +302,70 @@ func TestRoutePolicies(t *testing.T) { return policies, err }, }, + { + Description: "List route policies with include route", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + policies, _, _, err := c.RoutePolicies.ListIncludeRoute(context.Background(), NewRoutePolicyListOptions()) + return policies, err + }, + }, + { + Description: "List all route policies with include route", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.PagedWithInclude( + testutil.PagedResult{ + Resources: []string{routePolicy, routePolicy2}, + Routes: []string{route1, route2}, + }), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy, routePolicy2), + Expected2: g.Array(route1, route2), + Action2: func(c *Client, t *testing.T) (any, any, error) { + return c.RoutePolicies.ListIncludeRouteAll(context.Background(), nil) + }, + }, + { + Description: "List route policies with include source", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.SinglePaged(routePolicy), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy), + Action: func(c *Client, t *testing.T) (any, error) { + policies, _, _, err := c.RoutePolicies.ListIncludeSource(context.Background(), NewRoutePolicyListOptions()) + return policies, err + }, + }, + { + Description: "List all route policies with include source", + Route: testutil.MockRoute{ + Method: "GET", + Endpoint: "/v3/route_policies", + Output: g.PagedWithInclude( + testutil.PagedResult{ + Resources: []string{routePolicy, routePolicy2}, + Apps: []string{app1, app2}, + }), + Status: http.StatusOK, + }, + Expected: g.Array(routePolicy, routePolicy2), + Expected2: g.Array(app1, app2), + Action2: func(c *Client, t *testing.T) (any, any, error) { + return c.RoutePolicies.ListIncludeSourceAll(context.Background(), nil) + }, + }, } ExecuteTests(tests, t) } diff --git a/resource/route_policy.go b/resource/route_policy.go index 93707c8..f51c7c7 100644 --- a/resource/route_policy.go +++ b/resource/route_policy.go @@ -13,8 +13,9 @@ type RoutePolicyCreate struct { } type RoutePolicyList struct { - Pagination Pagination `json:"pagination"` - Resources []*RoutePolicy `json:"resources"` + Pagination Pagination `json:"pagination"` + Resources []*RoutePolicy `json:"resources"` + Included *RoutePolicyIncluded `json:"included"` } type RoutePolicyRelationships struct { @@ -23,3 +24,37 @@ type RoutePolicyRelationships struct { Space *ToOneRelationship `json:"space,omitempty"` Organization *ToOneRelationship `json:"organization,omitempty"` } + +type RoutePolicyWithIncluded struct { + RoutePolicy RoutePolicy `json:"route_policy"` + Included *RoutePolicyIncluded `json:"included"` +} + +type RoutePolicyIncluded struct { + Routes []*Route `json:"routes"` + Apps []*App `json:"apps"` + Spaces []*Space `json:"spaces"` + Organizations []*Organization `json:"organizations"` +} + +// RoutePolicyIncludeType https://v3-apidocs.cloudfoundry.org/version/3.126.0/index.html#include +type RoutePolicyIncludeType int + +const ( + RoutePolicyIncludeRoute RoutePolicyIncludeType = iota + RoutePolicyIncludeSource + RoutePolicyIncludeRouteSource +) + +func (r RoutePolicyIncludeType) String() string { + switch r { + case RoutePolicyIncludeRoute: + return "route" + case RoutePolicyIncludeSource: + return "source" + case RoutePolicyIncludeRouteSource: + return "route,source" + default: + return "" + } +} From 5c3b869a61a28ca02eef9e36d101c59cd5570b2f Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Wed, 13 May 2026 13:19:19 +0200 Subject: [PATCH 10/13] include fixes --- client/route_policy.go | 18 +++++++++++------- client/route_policy_test.go | 4 ++-- client/test_runner.go | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/client/route_policy.go b/client/route_policy.go index a81a6cd..ec3bfdf 100644 --- a/client/route_policy.go +++ b/client/route_policy.go @@ -165,7 +165,7 @@ func (c *RoutePolicyClient) ListIncludeRouteAll(ctx context.Context, opts *Route } // ListIncludeSource page all route policies the user has access to and include the associated source apps -func (c *RoutePolicyClient) ListIncludeSource(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.App, *Pager, error) { +func (c *RoutePolicyClient) ListIncludeSource(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.App, []*resource.Space, []*resource.Organization, *Pager, error) { if opts == nil { opts = NewRoutePolicyListOptions() } @@ -174,33 +174,37 @@ func (c *RoutePolicyClient) ListIncludeSource(ctx context.Context, opts *RoutePo var res resource.RoutePolicyList err := c.client.list(ctx, "/v3/route_policies", opts.ToQueryString, &res) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, nil, err } pager := NewPager(res.Pagination) - return res.Resources, res.Included.Apps, pager, nil + return res.Resources, res.Included.Apps, res.Included.Spaces, res.Included.Organizations, pager, nil } // ListIncludeSourceAll retrieves all route policies the user has access to and include the associated source apps -func (c *RoutePolicyClient) ListIncludeSourceAll(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.App, error) { +func (c *RoutePolicyClient) ListIncludeSourceAll(ctx context.Context, opts *RoutePolicyListOptions) ([]*resource.RoutePolicy, []*resource.App, []*resource.Space, []*resource.Organization, error) { if opts == nil { opts = NewRoutePolicyListOptions() } var all []*resource.RoutePolicy var allApps []*resource.App + var allSpaces []*resource.Space + var allOrganizations []*resource.Organization for { - page, apps, pager, err := c.ListIncludeSource(ctx, opts) + page, apps, spaces, orgs, pager, err := c.ListIncludeSource(ctx, opts) if err != nil { - return nil, nil, err + return nil, nil, nil, nil, err } all = append(all, page...) allApps = append(allApps, apps...) + allSpaces = append(allSpaces, spaces...) + allOrganizations = append(allOrganizations, orgs...) if !pager.HasNextPage() { break } pager.NextPage(opts) } - return all, allApps, nil + return all, allApps, allSpaces, allOrganizations, nil } // Single returns a single route policy matching the options or an error if not exactly 1 match diff --git a/client/route_policy_test.go b/client/route_policy_test.go index d14d281..88bcd2b 100644 --- a/client/route_policy_test.go +++ b/client/route_policy_test.go @@ -344,7 +344,7 @@ func TestRoutePolicies(t *testing.T) { }, Expected: g.Array(routePolicy), Action: func(c *Client, t *testing.T) (any, error) { - policies, _, _, err := c.RoutePolicies.ListIncludeSource(context.Background(), NewRoutePolicyListOptions()) + policies, _, _, _, _, err := c.RoutePolicies.ListIncludeSource(context.Background(), NewRoutePolicyListOptions()) return policies, err }, }, @@ -362,7 +362,7 @@ func TestRoutePolicies(t *testing.T) { }, Expected: g.Array(routePolicy, routePolicy2), Expected2: g.Array(app1, app2), - Action2: func(c *Client, t *testing.T) (any, any, error) { + Action4: func(c *Client, t *testing.T) (any, any, any, any, error) { return c.RoutePolicies.ListIncludeSourceAll(context.Background(), nil) }, }, diff --git a/client/test_runner.go b/client/test_runner.go index 067f67d..9de3478 100644 --- a/client/test_runner.go +++ b/client/test_runner.go @@ -21,6 +21,7 @@ type RouteTest struct { Action func(c *Client, t *testing.T) (any, error) Action2 func(c *Client, t *testing.T) (any, any, error) Action3 func(c *Client, t *testing.T) (any, any, any, error) + Action4 func(c *Client, t *testing.T) (any, any, any, any, error) } func ExecuteTests(tests []RouteTest, t *testing.T) { From a45a9d760b62554d29e58a0e9f16cc3f0790ef80 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Wed, 13 May 2026 13:33:16 +0200 Subject: [PATCH 11/13] more include fixes --- resource/route_policy.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resource/route_policy.go b/resource/route_policy.go index f51c7c7..32c0338 100644 --- a/resource/route_policy.go +++ b/resource/route_policy.go @@ -41,7 +41,8 @@ type RoutePolicyIncluded struct { type RoutePolicyIncludeType int const ( - RoutePolicyIncludeRoute RoutePolicyIncludeType = iota + RoutePolicyIncludeNone RoutePolicyIncludeType = iota + RoutePolicyIncludeRoute RoutePolicyIncludeSource RoutePolicyIncludeRouteSource ) From aa1c8779518ff5fdb37e838253984a58034954be Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Wed, 13 May 2026 13:36:08 +0200 Subject: [PATCH 12/13] even more include fixes --- client/route_policy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/route_policy.go b/client/route_policy.go index ec3bfdf..b302162 100644 --- a/client/route_policy.go +++ b/client/route_policy.go @@ -105,6 +105,7 @@ func (c *RoutePolicyClient) List(ctx context.Context, opts *RoutePolicyListOptio if opts == nil { opts = NewRoutePolicyListOptions() } + opts.Include = resource.RoutePolicyIncludeNone var res resource.RoutePolicyList err := c.client.list(ctx, "/v3/route_policies", opts.ToQueryString, &res) From 42bfaded43ce04ec64d372932e37e68f608f4a47 Mon Sep 17 00:00:00 2001 From: Harry Metske Date: Wed, 13 May 2026 14:07:17 +0200 Subject: [PATCH 13/13] add update function --- client/route_policy.go | 10 ++++++++++ client/route_policy_test.go | 19 +++++++++++++++++++ resource/route_policy.go | 4 ++++ 3 files changed, 33 insertions(+) diff --git a/client/route_policy.go b/client/route_policy.go index b302162..255cb45 100644 --- a/client/route_policy.go +++ b/client/route_policy.go @@ -214,3 +214,13 @@ func (c *RoutePolicyClient) Single(ctx context.Context, opts *RoutePolicyListOpt return c.List(ctx, opts) }) } + +// Update the specified attributes of the route policy +func (c *RoutePolicyClient) Update(ctx context.Context, guid string, r *resource.RoutePolicyUpdate) (*resource.RoutePolicy, error) { + var routePolicy resource.RoutePolicy + _, err := c.client.patch(ctx, path.Format("/v3/route_policies/%s", guid), r, &routePolicy) + if err != nil { + return nil, err + } + return &routePolicy, nil +} diff --git a/client/route_policy_test.go b/client/route_policy_test.go index 88bcd2b..5f83d19 100644 --- a/client/route_policy_test.go +++ b/client/route_policy_test.go @@ -366,6 +366,25 @@ func TestRoutePolicies(t *testing.T) { return c.RoutePolicies.ListIncludeSourceAll(context.Background(), nil) }, }, + { + Description: "Update route policy", + Route: testutil.MockRoute{ + Method: "PATCH", + Endpoint: "/v3/route_policies/c8dcf27f-39a3-466a-9cbf-1d3c31a43b93", + Output: g.Single(routePolicy), + Status: http.StatusOK, + PostForm: `{ "metadata": { "labels": {"key": "value"}, "annotations": {"note": "detailed information"}}}`, + }, + Expected: routePolicy, + Action: func(c *Client, t *testing.T) (any, error) { + r := &resource.RoutePolicyUpdate{ + Metadata: resource.NewMetadata(). + WithLabel("", "key", "value"). + WithAnnotation("", "note", "detailed information"), + } + return c.RoutePolicies.Update(context.Background(), "c8dcf27f-39a3-466a-9cbf-1d3c31a43b93", r) + }, + }, } ExecuteTests(tests, t) } diff --git a/resource/route_policy.go b/resource/route_policy.go index 32c0338..782b0ac 100644 --- a/resource/route_policy.go +++ b/resource/route_policy.go @@ -12,6 +12,10 @@ type RoutePolicyCreate struct { Source string `json:"source"` } +type RoutePolicyUpdate struct { + Metadata *Metadata `json:"metadata,omitempty"` +} + type RoutePolicyList struct { Pagination Pagination `json:"pagination"` Resources []*RoutePolicy `json:"resources"`