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

feat(controllers) handle default IngressClass #2313

Merged
merged 3 commits into from
Mar 21, 2022
Merged
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
5 changes: 4 additions & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ linters:
- wastedassign
linters-settings:
gci:
local-prefixes: github.com/kong/kubernetes-ingress-controller/v2
sections:
- standard
- default
- prefix(github.com/kong/kubernetes-ingress-controller/v2)
issues:
fix: true
exclude-rules:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
- Deployment manifests now include an IngressClass resource and permissions to
read IngressClass resources.
[#2292](https:/Kong/kubernetes-ingress-controller/pull/2292)
- The controller now reads IngressClass resources to determine if its
IngressClass is the default IngressClass. If so, the controller will ingest
resources that require a class (Ingress, KongConsumer, KongClusterPlugin,
etc.) but have none set.
[#2313](https:/Kong/kubernetes-ingress-controller/pull/2313)
- HTTPRoute header matches now support regular expressions.
[#2302](https:/Kong/kubernetes-ingress-controller/pull/2302)
- HTTPRoutes that define multiple matches for the same header are rejected to
Expand Down
46 changes: 44 additions & 2 deletions hack/generators/controllers/networking/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,15 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
knativev1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
knativeApis "knative.dev/pkg/apis"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"

ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils"
Expand Down Expand Up @@ -477,7 +480,15 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) SetupWithManager(mgr ctrl.Manager
}
{{- end}}
{{- if .AcceptsIngressClassNameAnnotation}}
preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName, {{.AcceptsIngressClassNameSpec}}, true)
err = c.Watch(
&source.Kind{Type: &netv1.IngressClass{}},
handler.EnqueueRequestsFromMapFunc(r.listClassless),
predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
)
if err != nil {
return err
}
preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
{{- end}}
return c.Watch(
&source.Kind{Type: &{{.PackageImportAlias}}.{{.Kind}}{}},
Expand All @@ -488,6 +499,29 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) SetupWithManager(mgr ctrl.Manager
)
}

{{- if .AcceptsIngressClassNameAnnotation}}
// listClassless finds and reconciles all objects without ingress class information
func (r *{{.PackageAlias}}{{.Kind}}Reconciler) listClassless(obj client.Object) []reconcile.Request {
resourceList := &{{.PackageImportAlias}}.{{.Kind}}List{}
if err := r.Client.List(context.Background(), resourceList); err != nil {
r.Log.Error(err, "failed to list classless {{.Plural}}")
return nil
}
var recs []reconcile.Request
for _, resource := range resourceList.Items {
if ctrlutils.IsIngressClassEmpty(&resource) {
recs = append(recs, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: resource.Namespace,
Name: resource.Name,
},
})
}
}
return recs
}
{{- end}}

//+kubebuilder:rbac:groups={{.Group}},resources={{.Plural}},verbs={{ .RBACVerbs | join ";" }}
{{- if .NeedsStatusPermissions}}
//+kubebuilder:rbac:groups={{.Group}},resources={{.Plural}}/status,verbs=get;update;patch
Expand Down Expand Up @@ -525,8 +559,16 @@ func (r *{{.PackageAlias}}{{.Kind}}Reconciler) Reconcile(ctx context.Context, re
return ctrl.Result{}, nil
}
{{if .AcceptsIngressClassNameAnnotation}}
class := new(netv1.IngressClass)
if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
// we log this without taking action to support legacy configurations that only set ingressClassName or
// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
// if none exists.
log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
}
// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
if !ctrlutils.MatchesIngressClassName(obj, r.IngressClassName) {
if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
}
Expand Down
7 changes: 4 additions & 3 deletions internal/admission/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type KongHTTPValidator struct {
SecretGetter kongstate.SecretGetter
ManagerClient client.Client

ingressClassMatcher func(*metav1.ObjectMeta, annotations.ClassMatching) bool
ingressClassMatcher func(*metav1.ObjectMeta, string, annotations.ClassMatching) bool
}

// NewKongHTTPValidator provides a new KongHTTPValidator object provided a
Expand Down Expand Up @@ -76,7 +76,7 @@ func (validator KongHTTPValidator) ValidateConsumer(
consumer kongv1.KongConsumer,
) (bool, string, error) {
// ignore consumers that are being managed by another controller
if !validator.ingressClassMatcher(&consumer.ObjectMeta, annotations.ExactClassMatch) {
if !validator.ingressClassMatcher(&consumer.ObjectMeta, annotations.IngressClassKey, annotations.ExactClassMatch) {
return true, "", nil
}

Expand Down Expand Up @@ -381,7 +381,8 @@ func (validator KongHTTPValidator) listManagedConsumers(ctx context.Context) ([]
// reduce the consumer set to consumers managed by this controller
managedConsumers := make([]*kongv1.KongConsumer, 0)
for _, consumer := range consumers.Items {
if !validator.ingressClassMatcher(&consumer.ObjectMeta, annotations.ExactClassMatch) {
if !validator.ingressClassMatcher(&consumer.ObjectMeta, annotations.IngressClassKey,
annotations.ExactClassMatch) {
// ignore consumers (and subsequently secrets) that are managed by other controllers
continue
}
Expand Down
2 changes: 1 addition & 1 deletion internal/admission/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,4 @@ func TestKongHTTPValidator_ValidateClusterPlugin(t *testing.T) {
}
}

func fakeClassMatcher(*metav1.ObjectMeta, annotations.ClassMatching) bool { return true }
func fakeClassMatcher(*metav1.ObjectMeta, string, annotations.ClassMatching) bool { return true }
19 changes: 4 additions & 15 deletions internal/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,14 @@ func validIngress(ingressAnnotationValue, ingressClass string, handling ClassMat
}
}

// IngressClassValidatorFunc returns a function which can validate if an Object
// belongs to an the ingressClass or not.
func IngressClassValidatorFunc(
ingressClass string) func(obj metav1.Object, handling ClassMatching) bool {

return func(obj metav1.Object, handling ClassMatching) bool {
ingress := obj.GetAnnotations()[IngressClassKey]
return validIngress(ingress, ingressClass, handling)
}
}

// IngressClassValidatorFuncFromObjectMeta returns a function which
// can validate if an ObjectMeta belongs to an the ingressClass or not.
func IngressClassValidatorFuncFromObjectMeta(
ingressClass string) func(obj *metav1.ObjectMeta, handling ClassMatching) bool {
ingressClass string) func(obj *metav1.ObjectMeta, annotation string, handling ClassMatching) bool {

return func(obj *metav1.ObjectMeta, handling ClassMatching) bool {
ingress := obj.GetAnnotations()[IngressClassKey]
return validIngress(ingress, ingressClass, handling)
return func(obj *metav1.ObjectMeta, annotation string, handling ClassMatching) bool {
class := obj.GetAnnotations()[annotation]
return validIngress(class, ingressClass, handling)
}
}

Expand Down
7 changes: 1 addition & 6 deletions internal/annotations/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,10 @@ func TestIngressClassValidatorFunc(t *testing.T) {
// TODO: unclear if we truly use IngressClassValidatorFunc anymore
// IngressClassValidatorFuncFromObjectMeta appears to effectively supersede it, and is what we use in store
// IngressClassValidatorFunc appears to be a test-only relic at this point
f := IngressClassValidatorFunc(test.controller)
fmeta := IngressClassValidatorFuncFromObjectMeta(test.controller)
fv1 := IngressClassValidatorFuncFromV1Ingress(test.controller)

result := f(&ing.ObjectMeta, test.classMatching)
if result != test.isValid {
t.Errorf("test %v - expected %v but %v was returned", test, test.isValid, result)
}
resultMeta := fmeta(&ing.ObjectMeta, test.classMatching)
resultMeta := fmeta(&ing.ObjectMeta, IngressClassKey, test.classMatching)
if resultMeta != test.isValid {
t.Errorf("meta test %v - expected %v but %v was returned", test, test.isValid, resultMeta)
}
Expand Down
Loading