Skip to content

Commit 3192b49

Browse files
committed
Support for Gateway-API routes
Also, upgrading to Go 1.22 and K8s 1.30 Signed-off-by: Ziv Nevo <[email protected]>
1 parent ede52bd commit 3192b49

File tree

9 files changed

+523
-138
lines changed

9 files changed

+523
-138
lines changed

cmd/nettop/main_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ var (
137137
false,
138138
[]string{"qotd", "expected_netpol_output.json"},
139139
},
140+
{
141+
"ScoreDemo-with-gateway-api",
142+
[][]string{{"score-demo"}},
143+
yamlFormat,
144+
true,
145+
[]string{"-v"},
146+
false,
147+
[]string{"score-demo", "expected_netpol_output.yaml"},
148+
},
140149
{
141150
"CronJobWithNontrivialNetworkAddresses",
142151
[][]string{{"openshift", "openshift-operator-lifecycle-manager-resources.yaml"}},

go.mod

+25-23
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
11
module github.com/np-guard/cluster-topology-analyzer/v2
22

3-
go 1.21
3+
go 1.22.0
4+
5+
toolchain go1.22.2
46

57
require (
68
github.com/np-guard/netpol-analyzer v1.2.0
79
github.com/openshift/api v0.0.0-20230502160752-c71432710382
810
github.com/stretchr/testify v1.9.0
911
gopkg.in/yaml.v3 v3.0.1
10-
k8s.io/api v0.29.2
11-
k8s.io/apimachinery v0.29.2
12+
k8s.io/api v0.30.0
13+
k8s.io/apimachinery v0.30.0
1214
k8s.io/cli-runtime v0.29.2
15+
sigs.k8s.io/gateway-api v1.1.0
1316
)
1417

1518
require (
1619
github.com/davecgh/go-spew v1.1.1 // indirect
17-
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
18-
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
20+
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
21+
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
1922
github.com/go-errors/errors v1.4.2 // indirect
20-
github.com/go-logr/logr v1.3.0 // indirect
21-
github.com/go-openapi/jsonpointer v0.19.6 // indirect
22-
github.com/go-openapi/jsonreference v0.20.2 // indirect
23-
github.com/go-openapi/swag v0.22.3 // indirect
23+
github.com/go-logr/logr v1.4.1 // indirect
24+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
25+
github.com/go-openapi/jsonreference v0.21.0 // indirect
26+
github.com/go-openapi/swag v0.23.0 // indirect
2427
github.com/gogo/protobuf v1.3.2 // indirect
25-
github.com/golang/protobuf v1.5.3 // indirect
28+
github.com/golang/protobuf v1.5.4 // indirect
2629
github.com/google/gnostic-models v0.6.8 // indirect
2730
github.com/google/gofuzz v1.2.0 // indirect
2831
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
29-
github.com/google/uuid v1.3.0 // indirect
30-
github.com/imdario/mergo v0.3.6 // indirect
32+
github.com/google/uuid v1.6.0 // indirect
33+
github.com/imdario/mergo v0.3.16 // indirect
3134
github.com/josharian/intern v1.0.0 // indirect
3235
github.com/json-iterator/go v1.1.12 // indirect
3336
github.com/mailru/easyjson v0.7.7 // indirect
@@ -39,21 +42,20 @@ require (
3942
github.com/pmezard/go-difflib v1.0.0 // indirect
4043
github.com/xlab/treeprint v1.2.0 // indirect
4144
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
42-
golang.org/x/net v0.23.0 // indirect
43-
golang.org/x/oauth2 v0.10.0 // indirect
44-
golang.org/x/sync v0.5.0 // indirect
45-
golang.org/x/sys v0.18.0 // indirect
46-
golang.org/x/term v0.18.0 // indirect
45+
golang.org/x/net v0.24.0 // indirect
46+
golang.org/x/oauth2 v0.19.0 // indirect
47+
golang.org/x/sync v0.7.0 // indirect
48+
golang.org/x/sys v0.19.0 // indirect
49+
golang.org/x/term v0.19.0 // indirect
4750
golang.org/x/text v0.14.0 // indirect
48-
golang.org/x/time v0.3.0 // indirect
49-
google.golang.org/appengine v1.6.7 // indirect
51+
golang.org/x/time v0.5.0 // indirect
5052
google.golang.org/protobuf v1.33.0 // indirect
5153
gopkg.in/inf.v0 v0.9.1 // indirect
5254
gopkg.in/yaml.v2 v2.4.0 // indirect
53-
k8s.io/client-go v0.29.2 // indirect
54-
k8s.io/klog/v2 v2.110.1 // indirect
55-
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
56-
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
55+
k8s.io/client-go v0.30.0 // indirect
56+
k8s.io/klog/v2 v2.120.1 // indirect
57+
k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect
58+
k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 // indirect
5759
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
5860
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
5961
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect

go.sum

+52-68
Large diffs are not rendered by default.

pkg/analyzer/info_to_resource.go

+52-15
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import (
2424
"k8s.io/apimachinery/pkg/util/intstr"
2525
"k8s.io/apimachinery/pkg/util/validation"
2626
"k8s.io/cli-runtime/pkg/resource"
27+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
2728
)
2829

2930
// k8sWorkloadObjectFromInfo creates a Resource object from an Info object
3031
func k8sWorkloadObjectFromInfo(info *resource.Info) (*Resource, error) {
3132
var podSpecV1 *v1.PodTemplateSpec
3233
var resourceCtx Resource
3334
var metaObj metaV1.Object
35+
resourceCtx.Resource.FilePath = info.Source
3436
resourceCtx.Resource.Kind = info.Object.GetObjectKind().GroupVersionKind().Kind
3537
switch resourceCtx.Resource.Kind {
3638
case pod:
@@ -98,7 +100,9 @@ func k8sServiceFromInfo(info *resource.Info) (*Service, error) {
98100
if svcObj == nil {
99101
return nil, fmt.Errorf("failed to parse Service resource")
100102
}
103+
101104
var serviceCtx Service
105+
serviceCtx.Resource.FilePath = info.Source
102106
serviceCtx.Resource.Name = svcObj.GetName()
103107
serviceCtx.Resource.Namespace = svcObj.Namespace
104108
serviceCtx.Resource.Kind = svcObj.Kind
@@ -146,14 +150,9 @@ func ocRouteFromInfo(info *resource.Info, toExpose servicesToExpose) error {
146150
return fmt.Errorf("failed to parse Route resource")
147151
}
148152

149-
exposedServicesInNamespace, ok := toExpose[routeObj.Namespace]
150-
if !ok {
151-
toExpose[routeObj.Namespace] = map[string][]*intstr.IntOrString{}
152-
exposedServicesInNamespace = toExpose[routeObj.Namespace]
153-
}
154-
appendToSliceInMap(exposedServicesInNamespace, routeObj.Spec.To.Name, &routeObj.Spec.Port.TargetPort)
153+
toExpose.appendPort(routeObj.Namespace, routeObj.Spec.To.Name, &routeObj.Spec.Port.TargetPort)
155154
for _, backend := range routeObj.Spec.AlternateBackends {
156-
appendToSliceInMap(exposedServicesInNamespace, backend.Name, &routeObj.Spec.Port.TargetPort)
155+
toExpose.appendPort(routeObj.Namespace, backend.Name, &routeObj.Spec.Port.TargetPort)
157156
}
158157

159158
return nil
@@ -166,16 +165,10 @@ func k8sIngressFromInfo(info *resource.Info, toExpose servicesToExpose) error {
166165
return fmt.Errorf("failed to parse Ingress resource")
167166
}
168167

169-
exposedServicesInNamespace, ok := toExpose[ingressObj.Namespace]
170-
if !ok {
171-
toExpose[ingressObj.Namespace] = map[string][]*intstr.IntOrString{}
172-
exposedServicesInNamespace = toExpose[ingressObj.Namespace]
173-
}
174-
175168
defaultBackend := ingressObj.Spec.DefaultBackend
176169
if defaultBackend != nil && defaultBackend.Service != nil {
177170
portToAppend := portFromServiceBackendPort(&defaultBackend.Service.Port)
178-
appendToSliceInMap(exposedServicesInNamespace, defaultBackend.Service.Name, portToAppend)
171+
toExpose.appendPort(ingressObj.Namespace, defaultBackend.Service.Name, portToAppend)
179172
}
180173

181174
for ruleIdx := range ingressObj.Spec.Rules {
@@ -185,7 +178,7 @@ func k8sIngressFromInfo(info *resource.Info, toExpose servicesToExpose) error {
185178
svc := rule.HTTP.Paths[pathIdx].Backend.Service
186179
if svc != nil {
187180
portToAppend := portFromServiceBackendPort(&svc.Port)
188-
appendToSliceInMap(exposedServicesInNamespace, svc.Name, portToAppend)
181+
toExpose.appendPort(ingressObj.Namespace, svc.Name, portToAppend)
189182
}
190183
}
191184
}
@@ -202,6 +195,50 @@ func portFromServiceBackendPort(sbp *networkv1.ServiceBackendPort) *intstr.IntOr
202195
return &res
203196
}
204197

198+
func gatewayHTTPRouteFromInfo(info *resource.Info, toExpose servicesToExpose) error {
199+
routeObj := parseResourceFromInfo[gatewayv1.HTTPRoute](info)
200+
if routeObj == nil {
201+
return fmt.Errorf("failed to parse HTTPRoute resource")
202+
}
203+
204+
for i := range routeObj.Spec.Rules {
205+
rule := &routeObj.Spec.Rules[i]
206+
for j := range rule.BackendRefs {
207+
backend := &rule.BackendRefs[j]
208+
namespace := routeObj.Namespace
209+
if backend.Namespace != nil {
210+
namespace = string(*backend.Namespace)
211+
}
212+
port := intstr.FromInt32(int32(*backend.Port))
213+
toExpose.appendPort(namespace, string(backend.Name), &port)
214+
}
215+
}
216+
217+
return nil
218+
}
219+
220+
func gatewayGRPCRouteFromInfo(info *resource.Info, toExpose servicesToExpose) error {
221+
routeObj := parseResourceFromInfo[gatewayv1.GRPCRoute](info)
222+
if routeObj == nil {
223+
return fmt.Errorf("failed to parse GRPCRoute resource")
224+
}
225+
226+
for i := range routeObj.Spec.Rules {
227+
rule := &routeObj.Spec.Rules[i]
228+
for j := range rule.BackendRefs {
229+
backend := &rule.BackendRefs[j]
230+
port := intstr.FromInt32(int32(*backend.Port))
231+
namespace := routeObj.Namespace
232+
if backend.Namespace != nil {
233+
namespace = string(*backend.Namespace)
234+
}
235+
toExpose.appendPort(namespace, string(backend.Name), &port)
236+
}
237+
}
238+
239+
return nil
240+
}
241+
205242
func parseDeployResource(podSpec *v1.PodTemplateSpec, obj metaV1.Object, resourceCtx *Resource) {
206243
resourceCtx.Resource.Name = obj.GetName()
207244
resourceCtx.Resource.Namespace = obj.GetNamespace()

pkg/analyzer/resource_accumulator.go

+26-28
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package analyzer
88

99
import (
1010
"fmt"
11-
"regexp"
11+
"slices"
1212

1313
"k8s.io/cli-runtime/pkg/resource"
1414

@@ -29,12 +29,13 @@ const (
2929
configmap string = "ConfigMap"
3030
route string = "Route"
3131
ingress string = "Ingress"
32+
httpRoute string = "HTTPRoute"
33+
grpcRoute string = "GRPCRoute"
3234
)
3335

3436
var (
35-
acceptedK8sTypesRegex = fmt.Sprintf("(^%s$|^%s$|^%s$|^%s$|^%s$|^%s$|^%s$|^%s$|^%s$|^%s$|^%s$|^%s$)",
36-
pod, replicaSet, replicationController, deployment, daemonSet, statefulSet, job, cronJob, service, configmap, route, ingress)
37-
acceptedK8sTypes = regexp.MustCompile(acceptedK8sTypesRegex)
37+
acceptedK8sKinds = []string{pod, replicaSet, replicationController, deployment, daemonSet, statefulSet, job, cronJob,
38+
service, configmap, route, ingress, httpRoute, grpcRoute}
3839
)
3940

4041
// resourceAccumulator is used to locate all relevant K8s resources in given file-system directories
@@ -116,7 +117,7 @@ func (ra *resourceAccumulator) parseInfo(info *resource.Info) error {
116117
}
117118

118119
kind := info.Object.GetObjectKind().GroupVersionKind().Kind
119-
if !acceptedK8sTypes.MatchString(kind) {
120+
if !slices.Contains(acceptedK8sKinds, kind) {
120121
msg := fmt.Sprintf("skipping object with type: %s", kind)
121122
resourcePath := info.Source
122123
if resourcePath != "" {
@@ -126,40 +127,37 @@ func (ra *resourceAccumulator) parseInfo(info *resource.Info) error {
126127
return nil
127128
}
128129

130+
var err error
129131
switch kind {
130132
case service:
131-
res, err := k8sServiceFromInfo(info)
132-
if err != nil {
133-
return err
133+
var svc *Service
134+
svc, err = k8sServiceFromInfo(info)
135+
if err == nil {
136+
ra.services = append(ra.services, svc)
134137
}
135-
res.Resource.FilePath = info.Source
136-
ra.services = append(ra.services, res)
137138
case route:
138-
err := ocRouteFromInfo(info, ra.servicesToExpose)
139-
if err != nil {
140-
return err
141-
}
139+
err = ocRouteFromInfo(info, ra.servicesToExpose)
142140
case ingress:
143-
err := k8sIngressFromInfo(info, ra.servicesToExpose)
144-
if err != nil {
145-
return err
146-
}
141+
err = k8sIngressFromInfo(info, ra.servicesToExpose)
142+
case httpRoute:
143+
err = gatewayHTTPRouteFromInfo(info, ra.servicesToExpose)
144+
case grpcRoute:
145+
err = gatewayGRPCRouteFromInfo(info, ra.servicesToExpose)
147146
case configmap:
148-
res, err := k8sConfigmapFromInfo(info)
149-
if err != nil {
150-
return err
147+
var cfgmap *cfgMap
148+
cfgmap, err = k8sConfigmapFromInfo(info)
149+
if err == nil {
150+
ra.configmaps = append(ra.configmaps, cfgmap)
151151
}
152-
ra.configmaps = append(ra.configmaps, res)
153152
default:
154-
res, err := k8sWorkloadObjectFromInfo(info)
155-
if err != nil {
156-
return err
153+
var wl *Resource
154+
wl, err = k8sWorkloadObjectFromInfo(info)
155+
if err == nil {
156+
ra.workloads = append(ra.workloads, wl)
157157
}
158-
res.Resource.FilePath = info.Source
159-
ra.workloads = append(ra.workloads, res)
160158
}
161159

162-
return nil
160+
return err
163161
}
164162

165163
// inlineConfigMapRefsAsEnvs appends to the Envs of each given resource the ConfigMap values it is referring to

pkg/analyzer/types.go

+9
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,12 @@ type Connections struct {
8080
// A map from namespaces to a map of service names in each namespaces, which we want to expose within the cluster.
8181
// For each service we hold the ports that should be exposed
8282
type servicesToExpose map[string]map[string][]*intstr.IntOrString
83+
84+
func (ste servicesToExpose) appendPort(namespace, svcName string, port *intstr.IntOrString) {
85+
svcPortsMap, ok := ste[namespace]
86+
if !ok {
87+
ste[namespace] = map[string][]*intstr.IntOrString{}
88+
svcPortsMap = ste[namespace]
89+
}
90+
svcPortsMap[svcName] = append(svcPortsMap[svcName], port)
91+
}

pkg/analyzer/utils.go

-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,3 @@ func appendAndLogNewError(errs []FileProcessingError, newErr *FileProcessingErro
2121
errs = append(errs, *newErr)
2222
return errs
2323
}
24-
25-
func appendToSliceInMap[K comparable, V any](m map[K][]V, key K, newVal V) {
26-
m[key] = append(m[key], newVal)
27-
}

0 commit comments

Comments
 (0)