Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checks for OpenShift Route objects #578

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions domain/kube-score.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package domain
import (
"io"

routev1 "github.com/openshift/api/route/v1"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -60,6 +61,15 @@ type Ingress interface {
FileLocationer
}

type Route interface {
Route() routev1.Route
FileLocationer
}

type Routes interface {
Routes() []Route
}

type Metas interface {
Metas() []BothMeta
}
Expand Down Expand Up @@ -158,4 +168,5 @@ type AllTypes interface {
CronJobs
PodDisruptionBudgets
HorizontalPodAutoscalers
Routes
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require (
github.com/fatih/color v1.16.0
github.com/google/go-cmp v0.6.0
github.com/mattn/go-isatty v0.0.20
github.com/openshift/api v0.0.0-20240104110125-c7a2d3b41e1f
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4
golang.org/x/term v0.15.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/openshift/api v0.0.0-20240104110125-c7a2d3b41e1f h1:3BMVfQpz1xe8MmJprp1+NL8hrpl9I04JVP9EczdCOqE=
github.com/openshift/api v0.0.0-20240104110125-c7a2d3b41e1f/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
Expand Down
21 changes: 21 additions & 0 deletions parser/internal/route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package internal

import (
routev1 "github.com/openshift/api/route/v1"
ks "github.com/zegl/kube-score/domain"
)

var _ ks.Route = (*RouteV1)(nil)

type RouteV1 struct {
Obj routev1.Route
Location ks.FileLocation
}

func (r RouteV1) FileLocation() ks.FileLocation {
return r.Location
}

func (r RouteV1) Route() routev1.Route {
return r.Obj
}
14 changes: 14 additions & 0 deletions parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"

routev1 "github.com/openshift/api/route/v1"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/parser/internal"
Expand Down Expand Up @@ -68,6 +69,7 @@ func (p *Parser) addToScheme() error {
batchv1beta1.AddToScheme,
policyv1beta1.AddToScheme,
policyv1.AddToScheme,
routev1.AddToScheme,
}

for _, adder := range adders {
Expand Down Expand Up @@ -96,6 +98,7 @@ type parsedObjects struct {
ingresses []ks.Ingress // supports multiple versions of ingress
cronjobs []ks.CronJob
hpaTargeters []ks.HpaTargeter // all versions of HPAs
routes []ks.Route
}

func (p *parsedObjects) Services() []ks.Service {
Expand Down Expand Up @@ -142,6 +145,10 @@ func (p *parsedObjects) HorizontalPodAutoscalers() []ks.HpaTargeter {
return p.hpaTargeters
}

func (p *parsedObjects) Routes() []ks.Route {
return p.routes
}

func Empty() ks.AllTypes {
return &parsedObjects{}
}
Expand Down Expand Up @@ -385,6 +392,13 @@ func (p *Parser) decodeItem(cnf config.Configuration, s *parsedObjects, detected
s.ingresses = append(s.ingresses, ing)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: ingress.TypeMeta, ObjectMeta: ingress.ObjectMeta, FileLocationer: ing})

case routev1.SchemeGroupVersion.WithKind("Route"):
var route routev1.Route
errs.AddIfErr(p.decode(fileContents, &route))
rt := internal.RouteV1{Obj: route, Location: fileLocation}
s.routes = append(s.routes, rt)
s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: route.TypeMeta, ObjectMeta: route.ObjectMeta, FileLocationer: rt})

case autoscalingv1.SchemeGroupVersion.WithKind("HorizontalPodAutoscaler"):
var hpa autoscalingv1.HorizontalPodAutoscaler
errs.AddIfErr(p.decode(fileContents, &hpa))
Expand Down
15 changes: 15 additions & 0 deletions score/checks/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package checks
import (
"strings"

routev1 "github.com/openshift/api/route/v1"
"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/scorecard"
Expand All @@ -26,6 +27,7 @@ func New(cnf config.Configuration) *Checks {
cronjobs: make(map[string]GenCheck[ks.CronJob]),
horizontalPodAutoscalers: make(map[string]GenCheck[ks.HpaTargeter]),
poddisruptionbudgets: make(map[string]GenCheck[ks.PodDisruptionBudget]),
routes: make(map[string]GenCheck[routev1.Route]),
}
}

Expand Down Expand Up @@ -70,6 +72,7 @@ type Checks struct {
cronjobs map[string]GenCheck[ks.CronJob]
horizontalPodAutoscalers map[string]GenCheck[ks.HpaTargeter]
poddisruptionbudgets map[string]GenCheck[ks.PodDisruptionBudget]
routes map[string]GenCheck[routev1.Route]

cnf config.Configuration
}
Expand Down Expand Up @@ -205,6 +208,18 @@ func (c *Checks) Services() map[string]GenCheck[corev1.Service] {
return c.services
}

func (c *Checks) RegisterRouteCheck(name, comment string, fn CheckFunc[routev1.Route]) {
reg(c, "Route", name, comment, false, fn, c.routes)
}

func (c *Checks) RegisterOptionalRouteCheck(name, comment string, fn CheckFunc[routev1.Route]) {
reg(c, "Route", name, comment, true, fn, c.routes)
}

func (c *Checks) Routes() map[string]GenCheck[routev1.Route] {
return c.routes
}

func (c *Checks) All() []ks.Check {
return c.all
}
60 changes: 60 additions & 0 deletions score/route/route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package route

import (
routev1 "github.com/openshift/api/route/v1"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/score/checks"
"github.com/zegl/kube-score/scorecard"
)

func Register(allChecks *checks.Checks, services ks.Services) {
allChecks.RegisterRouteCheck("Route targets Service", `Makes sure that the Route targets a Service`, routeTargetsService(services.Services()))
}

// routeTargetsService checks if a Service targets a pod and issues a critical warning if no matching pod
func routeTargetsService(svcs []ks.Service) func(routev1.Route) (scorecard.TestScore, error) {
return func(route routev1.Route) (score scorecard.TestScore, err error) {
hasMatchService := false
hasMatchPort := false

if route.Spec.Port != nil && route.Spec.Port.TargetPort.IntValue() != 0 {
// We consider this as a match as this now matches the pod port which is very difficult to determine
hasMatchPort = true
hasMatchService = true
} else {
for _, s := range svcs {
svc := s.Service()
if svc.Namespace != route.Namespace {
break
}
if route.Spec.To.Name == svc.Name {
hasMatchService = true
if route.Spec.Port == nil && len(svc.Spec.Ports) == 1 {
hasMatchPort = true
break
}
if route.Spec.Port != nil && route.Spec.Port.TargetPort.String() != "" {
for _, p := range svc.Spec.Ports {
if route.Spec.Port.TargetPort.String() == p.Name {
hasMatchPort = true
break
}
}
}
}
}
}

if hasMatchService && hasMatchPort {
score.Grade = scorecard.GradeAllOK
} else if hasMatchService && !hasMatchPort {
score.Grade = scorecard.GradeAlmostOK
score.AddComment("", "The route does not match any port on the service", "")
} else {
score.Grade = scorecard.GradeCritical
score.AddComment("", "The route does not reference a service", "")
}

return
}
}
32 changes: 32 additions & 0 deletions score/route_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package score

import (
"testing"

"github.com/zegl/kube-score/scorecard"
)

func TestRouteTargetsService(t *testing.T) {
t.Parallel()
testExpectedScore(t, "route-targets-service.yaml", "Route targets Service", scorecard.GradeAllOK)
}

func TestRouteTargetsServiceNoMatch(t *testing.T) {
t.Parallel()
testExpectedScore(t, "route-targets-service-no-match.yaml", "Route targets Service", scorecard.GradeCritical)
}

func TestRouteTargetsServiceNumberedPort(t *testing.T) {
t.Parallel()
testExpectedScore(t, "route-targets-service-numbered-port.yaml", "Route targets Service", scorecard.GradeAllOK)
}

func TestRouteTargetsServiceNoPortOk(t *testing.T) {
t.Parallel()
testExpectedScore(t, "route-targets-service-no-port-ok.yaml", "Route targets Service", scorecard.GradeAllOK)
}

func TestRouteTargetsServiceNoPortNok(t *testing.T) {
t.Parallel()
testExpectedScore(t, "route-targets-service-no-port-nok.yaml", "Route targets Service", scorecard.GradeAlmostOK)
}
13 changes: 13 additions & 0 deletions score/score.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/zegl/kube-score/score/networkpolicy"
"github.com/zegl/kube-score/score/podtopologyspreadconstraints"
"github.com/zegl/kube-score/score/probes"
"github.com/zegl/kube-score/score/route"
"github.com/zegl/kube-score/score/security"
"github.com/zegl/kube-score/score/service"
"github.com/zegl/kube-score/score/stable"
Expand All @@ -28,6 +29,7 @@ func RegisterAllChecks(allObjects ks.AllTypes, cnf config.Configuration) *checks

deployment.Register(allChecks, allObjects)
ingress.Register(allChecks, allObjects)
route.Register(allChecks, allObjects)
cronjob.Register(allChecks)
container.Register(allChecks, cnf)
disruptionbudget.Register(allChecks, allObjects)
Expand Down Expand Up @@ -204,5 +206,16 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
}
}

for _, route := range allObjects.Routes() {
o := newObject(route.Route().TypeMeta, route.Route().ObjectMeta)
for _, test := range allChecks.Routes() {
fn, err := test.Fn(route.Route())
if err != nil {
return nil, err
}
o.Add(fn, test.Check, route, route.Route().Annotations)
}
}

return &scoreCard, nil
}
24 changes: 24 additions & 0 deletions score/testdata/route-targets-service-no-match.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
kind: Route
apiVersion: route.openshift.io/v1
metadata:
name: console
spec:
host: console-openshift-console.apps.xyz01.example.com
to:
kind: Service
name: console
weight: 100
port:
targetPort: https
wildcardPolicy: None
---
kind: Service
apiVersion: v1
metadata:
name: no-console
spec:
ports:
- name: https
protocol: TCP
port: 443
targetPort: 8443
26 changes: 26 additions & 0 deletions score/testdata/route-targets-service-no-port-nok.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
kind: Route
apiVersion: route.openshift.io/v1
metadata:
name: console
spec:
host: console-openshift-console.apps.xyz01.example.com
to:
kind: Service
name: console
weight: 100
wildcardPolicy: None
---
kind: Service
apiVersion: v1
metadata:
name: console
spec:
ports:
- name: https
protocol: TCP
port: 443
targetPort: 8443
- name: http
protocol: TCP
port: 80
targetPort: 8080
22 changes: 22 additions & 0 deletions score/testdata/route-targets-service-no-port-ok.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
kind: Route
apiVersion: route.openshift.io/v1
metadata:
name: console
spec:
host: console-openshift-console.apps.xyz01.example.com
to:
kind: Service
name: console
weight: 100
wildcardPolicy: None
---
kind: Service
apiVersion: v1
metadata:
name: console
spec:
ports:
- name: https
protocol: TCP
port: 443
targetPort: 8443
13 changes: 13 additions & 0 deletions score/testdata/route-targets-service-numbered-port.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
kind: Route
apiVersion: route.openshift.io/v1
metadata:
name: console
spec:
host: console-openshift-console.apps.xyz01.example.com
to:
kind: Service
name: console
weight: 100
port:
targetPort: 8443
wildcardPolicy: None
Loading