Skip to content

Commit 58abf48

Browse files
authored
Merge pull request #2708 from gauravkghildiyal/gwctl-separate-model-view
gwctl: Decouple data model from printing for flexibility and clarity
2 parents fac9d97 + 4fab620 commit 58abf48

31 files changed

+1740
-1103
lines changed

gwctl/CODE_ORGANIZATION.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
## Code Organization
2+
3+
* **cmd/main.go:**
4+
* Entry point for the application.
5+
* Uses the widely adopted Cobra: [https://github.com/spf13/cobra](https://github.com/spf13/cobra) library to manage subcommands and flags.
6+
7+
* **pkg/cmd/(describe, get):**
8+
* Contain the starting points for individual subcommands.
9+
* Tasks:
10+
* Parse command-line arguments into internal structures.
11+
* Gather necessary data.
12+
* Output the data to the console.
13+
14+
* **Conceptual Separation (Model-View):**
15+
* **pkg/resourcediscovery:** Handles data model definition and construction.
16+
* **pkg/printer:** Manages formatting and printing the data model to the command line (currently the sole view available).
17+
18+
* **pkg/policymanager:**
19+
* Provides logic to work with Policies, Policy CRDs, and facilitates policy merging based on type and hierarchy.
20+
21+
* **pkg/resourcediscovery:**
22+
* **ResourceModel (pkg/resourcediscovery/resourcemodel.go):**
23+
* Employs a graph-like structure.
24+
```
25+
+------------+
26+
| Policy |
27+
+------+-----+
28+
|
29+
|
30+
|
31+
+------------+ +--------------+ TargetRef is Namespace
32+
| Policy | +----> GatewayClass <-------+ |
33+
+----+-------+ | +--------------+ | |
34+
| | | +---------+ |
35+
| | | +-------->|Namespace|<--------+
36+
| Parent GatewayClass | | +---------+
37+
| | | |
38+
| | | |
39+
| | | |
40+
| +-----+-----+ +-----+--+--+
41+
TargetRef is HTTPRoute +-------> Gateway <---+ | Gateway <--+
42+
| | +-----------+ | +-----------+ |
43+
| | | |
44+
| | | |
45+
| Attached to Attached to Attached to
46+
| | | |
47+
| | | |
48+
| +----+------+ +--+--------+ +----+------+
49+
+---------->| HTTPRoute | | HTTPRoute | | HTTPRoute |
50+
+----+------+ +----+------+ +-----+-----+
51+
| | |
52+
| | |
53+
Routes to Routes to Routes to
54+
| | |
55+
| +---------+ | +----v----+
56+
+-------> Backend <-------+ | Backend |
57+
+---------+ +---------+
58+
```
59+
* Nodes: Resources from the Gateway API (Gateways, HTTPRoutes, etc.).
60+
* Edges: Connections between resources (e.g., HTTPRoutes linked to Backends).
61+
* **Discoverer (pkg/resourcediscovery/discoverer.go):**
62+
* Builds the `ResourceModel`.
63+
* **Example (`gwctl describe httproutes -l key1=value1`):**
64+
1. `DiscoverResourcesForHTTPRoute(filter)`:
65+
* Identifies HTTPRoutes with the `key1=value1` label.
66+
* Inserts them as source nodes into the `ResourceModel`.
67+
2. **Graph Traversal:**
68+
* Uses a BFS-like algorithm that begins from the source HTTPRoute nodes.
69+
* Finds associated Gateways, GatewayClasses, Namespaces, Policies, and Backends
70+
71+
* **pkg/printer:**
72+
* Receives the `ResourceModel` as input.
73+
* Extracts relevant information, arranges it into a printable format, and sends it to the command line.
74+
75+
76+
### Additional Notes:
77+
78+
* The code partially adheres to the Model-View pattern, with `resourcediscovery` managing data representation and `printer` controlling output.
79+
* The `Discoverer` constructs the `ResourceModel` graph, performing BFS-like graph traversals to locate connected resources.
80+
* Future improvements could add alternative output views (e.g., Graphviz visualizations, interactive web interfaces).

gwctl/pkg/cmd/describe/describe.go

Lines changed: 47 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,17 @@ limitations under the License.
1717
package describe
1818

1919
import (
20-
"context"
2120
"fmt"
2221
"os"
2322

2423
"github.com/spf13/cobra"
25-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2624

27-
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
2825
"sigs.k8s.io/gateway-api/gwctl/pkg/cmd/utils"
29-
"sigs.k8s.io/gateway-api/gwctl/pkg/cmd/utils/printer"
30-
"sigs.k8s.io/gateway-api/gwctl/pkg/common/resourcehelpers"
31-
"sigs.k8s.io/gateway-api/gwctl/pkg/effectivepolicy"
3226
"sigs.k8s.io/gateway-api/gwctl/pkg/policymanager"
27+
"sigs.k8s.io/gateway-api/gwctl/pkg/printer"
28+
"sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery"
29+
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3331
)
3432

3533
type describeFlags struct {
@@ -58,15 +56,18 @@ func runDescribe(args []string, params *utils.CmdParams, flags *describeFlags) {
5856
kind := args[0]
5957
ns := flags.namespace
6058
if flags.allNamespaces {
61-
ns = ""
59+
ns = metav1.NamespaceAll
6260
}
6361

64-
epc := effectivepolicy.NewCalculator(params.K8sClients, params.PolicyManager)
62+
discoverer := resourcediscovery.Discoverer{
63+
K8sClients: params.K8sClients,
64+
PolicyManager: params.PolicyManager,
65+
}
6566
policiesPrinter := &printer.PoliciesPrinter{Out: params.Out}
66-
httpRoutesPrinter := &printer.HTTPRoutesPrinter{Out: params.Out, EPC: epc}
67-
gwPrinter := &printer.GatewaysPrinter{Out: params.Out, EPC: epc}
68-
gwcPrinter := &printer.GatewayClassesPrinter{Out: params.Out, EPC: epc}
69-
backendsPrinter := &printer.BackendsPrinter{Out: params.Out, EPC: epc}
67+
httpRoutesPrinter := &printer.HTTPRoutesPrinter{Out: params.Out}
68+
gwPrinter := &printer.GatewaysPrinter{Out: params.Out}
69+
gwcPrinter := &printer.GatewayClassesPrinter{Out: params.Out}
70+
backendsPrinter := &printer.BackendsPrinter{Out: params.Out}
7071

7172
switch kind {
7273
case "policy", "policies":
@@ -86,76 +87,50 @@ func runDescribe(args []string, params *utils.CmdParams, flags *describeFlags) {
8687
policiesPrinter.PrintDescribeView(policyList)
8788

8889
case "httproute", "httproutes":
89-
var httpRoutes []gatewayv1beta1.HTTPRoute
90-
if len(args) == 1 {
91-
var err error
92-
httpRoutes, err = resourcehelpers.ListHTTPRoutes(context.TODO(), params.K8sClients, ns)
93-
if err != nil {
94-
panic(err)
95-
}
96-
} else {
97-
httpRoute, err := resourcehelpers.GetHTTPRoute(context.TODO(), params.K8sClients, ns, args[1])
98-
if err != nil {
99-
panic(err)
100-
}
101-
httpRoutes = []gatewayv1beta1.HTTPRoute{httpRoute}
90+
filter := resourcediscovery.Filter{Namespace: ns}
91+
if len(args) > 1 {
92+
filter.Name = args[1]
93+
}
94+
resourceModel, err := discoverer.DiscoverResourcesForHTTPRoute(filter)
95+
if err != nil {
96+
panic(err)
10297
}
103-
httpRoutesPrinter.PrintDescribeView(context.TODO(), httpRoutes)
98+
httpRoutesPrinter.PrintDescribeView(resourceModel)
10499

105100
case "gateway", "gateways":
106-
var gws []gatewayv1beta1.Gateway
107-
if len(args) == 1 {
108-
var err error
109-
gws, err = resourcehelpers.ListGateways(context.TODO(), params.K8sClients, ns)
110-
if err != nil {
111-
panic(err)
112-
}
113-
} else {
114-
gw, err := resourcehelpers.GetGateways(context.TODO(), params.K8sClients, ns, args[1])
115-
if err != nil {
116-
panic(err)
117-
}
118-
gws = []gatewayv1beta1.Gateway{gw}
101+
filter := resourcediscovery.Filter{Namespace: ns}
102+
if len(args) > 1 {
103+
filter.Name = args[1]
104+
}
105+
resourceModel, err := discoverer.DiscoverResourcesForGateway(filter)
106+
if err != nil {
107+
panic(err)
119108
}
120-
gwPrinter.PrintDescribeView(context.TODO(), gws)
109+
gwPrinter.PrintDescribeView(resourceModel)
121110

122111
case "gatewayclass", "gatewayclasses":
123-
var gwClasses []gatewayv1beta1.GatewayClass
124-
if len(args) == 1 {
125-
var err error
126-
gwClasses, err = resourcehelpers.ListGatewayClasses(context.TODO(), params.K8sClients)
127-
if err != nil {
128-
panic(err)
129-
}
130-
} else {
131-
gwc, err := resourcehelpers.GetGatewayClass(context.TODO(), params.K8sClients, args[1])
132-
if err != nil {
133-
panic(err)
134-
}
135-
gwClasses = []gatewayv1beta1.GatewayClass{gwc}
112+
filter := resourcediscovery.Filter{}
113+
if len(args) > 1 {
114+
filter.Name = args[1]
136115
}
137-
gwcPrinter.PrintDescribeView(context.TODO(), gwClasses)
116+
resourceModel, err := discoverer.DiscoverResourcesForGatewayClass(filter)
117+
if err != nil {
118+
panic(err)
119+
}
120+
gwcPrinter.PrintDescribeView(resourceModel)
138121

139122
case "backend", "backends":
140-
var backendsList []unstructured.Unstructured
141-
142-
// We default the backends to just "Service" types initially.
143-
resourceType := "service"
144-
145-
if len(args) == 1 {
146-
var err error
147-
backendsList, err = resourcehelpers.ListBackends(context.TODO(), params.K8sClients, resourceType, ns)
148-
if err != nil {
149-
panic(err)
150-
}
151-
} else {
152-
backend, err := resourcehelpers.GetBackend(context.TODO(), params.K8sClients, resourceType, ns, args[1])
153-
if err != nil {
154-
panic(err)
155-
}
156-
backendsList = []unstructured.Unstructured{backend}
123+
filter := resourcediscovery.Filter{
124+
Namespace: ns,
125+
}
126+
if len(args) > 1 {
127+
filter.Name = args[1]
128+
}
129+
resourceModel, err := discoverer.DiscoverResourcesForBackend(filter)
130+
if err != nil {
131+
panic(err)
157132
}
158-
backendsPrinter.PrintDescribeView(context.TODO(), backendsList)
133+
backendsPrinter.PrintDescribeView(resourceModel)
159134

160135
default:
161136
fmt.Fprintf(os.Stderr, "Unrecognized RESOURCE_TYPE\n")

gwctl/pkg/cmd/get/get.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ limitations under the License.
1717
package get
1818

1919
import (
20-
"context"
2120
"fmt"
2221
"os"
2322

2423
"github.com/spf13/cobra"
2524

2625
"sigs.k8s.io/gateway-api/gwctl/pkg/cmd/utils"
27-
"sigs.k8s.io/gateway-api/gwctl/pkg/cmd/utils/printer"
28-
"sigs.k8s.io/gateway-api/gwctl/pkg/common/resourcehelpers"
29-
"sigs.k8s.io/gateway-api/gwctl/pkg/effectivepolicy"
26+
"sigs.k8s.io/gateway-api/gwctl/pkg/printer"
27+
"sigs.k8s.io/gateway-api/gwctl/pkg/resourcediscovery"
3028
)
3129

3230
type getFlags struct {
@@ -58,9 +56,8 @@ func runGet(args []string, params *utils.CmdParams, flags *getFlags) {
5856
ns = ""
5957
}
6058

61-
epc := effectivepolicy.NewCalculator(params.K8sClients, params.PolicyManager)
6259
policiesPrinter := &printer.PoliciesPrinter{Out: params.Out}
63-
httpRoutesPrinter := &printer.HTTPRoutesPrinter{Out: params.Out, EPC: epc}
60+
httpRoutesPrinter := &printer.HTTPRoutesPrinter{Out: params.Out}
6461

6562
switch kind {
6663
case "policy", "policies":
@@ -72,11 +69,19 @@ func runGet(args []string, params *utils.CmdParams, flags *getFlags) {
7269
policiesPrinter.PrintCRDs(list)
7370

7471
case "httproute", "httproutes":
75-
list, err := resourcehelpers.ListHTTPRoutes(context.TODO(), params.K8sClients, ns)
72+
discoverer := resourcediscovery.Discoverer{
73+
K8sClients: params.K8sClients,
74+
PolicyManager: params.PolicyManager,
75+
}
76+
filter := resourcediscovery.Filter{Namespace: ns}
77+
if len(args) > 1 {
78+
filter.Name = args[1]
79+
}
80+
resourceModel, err := discoverer.DiscoverResourcesForHTTPRoute(filter)
7681
if err != nil {
7782
panic(err)
7883
}
79-
httpRoutesPrinter.Print(list)
84+
httpRoutesPrinter.Print(resourceModel)
8085

8186
default:
8287
fmt.Fprintf(os.Stderr, "Unrecognized RESOURCE_TYPE\n")

gwctl/pkg/cmd/utils/printer/backends.go

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)