mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-24 09:51:41 +00:00
cmd/k8s-operator: default ingress paths to '/' if not specified by user (#15706)
in resource Fixes #14908 Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
This commit is contained in:
parent
26f31f73f4
commit
9666c2e700
@ -292,9 +292,15 @@ func validateIngressClass(ctx context.Context, cl client.Client) error {
|
|||||||
|
|
||||||
func handlersForIngress(ctx context.Context, ing *networkingv1.Ingress, cl client.Client, rec record.EventRecorder, tlsHost string, logger *zap.SugaredLogger) (handlers map[string]*ipn.HTTPHandler, err error) {
|
func handlersForIngress(ctx context.Context, ing *networkingv1.Ingress, cl client.Client, rec record.EventRecorder, tlsHost string, logger *zap.SugaredLogger) (handlers map[string]*ipn.HTTPHandler, err error) {
|
||||||
addIngressBackend := func(b *networkingv1.IngressBackend, path string) {
|
addIngressBackend := func(b *networkingv1.IngressBackend, path string) {
|
||||||
|
if path == "" {
|
||||||
|
path = "/"
|
||||||
|
rec.Eventf(ing, corev1.EventTypeNormal, "PathUndefined", "configured backend is missing a path, defaulting to '/'")
|
||||||
|
}
|
||||||
|
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Service == nil {
|
if b.Service == nil {
|
||||||
rec.Eventf(ing, corev1.EventTypeWarning, "InvalidIngressBackend", "backend for path %q is missing service", path)
|
rec.Eventf(ing, corev1.EventTypeWarning, "InvalidIngressBackend", "backend for path %q is missing service", path)
|
||||||
return
|
return
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/client-go/tools/record"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
@ -487,6 +488,138 @@ func TestIngressLetsEncryptStaging(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmptyPath(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
paths []networkingv1.HTTPIngressPath
|
||||||
|
expectedEvents []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty_path_with_prefix_type",
|
||||||
|
paths: []networkingv1.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
PathType: ptrPathType(networkingv1.PathTypePrefix),
|
||||||
|
Path: "",
|
||||||
|
Backend: *backend(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedEvents: []string{
|
||||||
|
"Normal PathUndefined configured backend is missing a path, defaulting to '/'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_path_with_implementation_specific_type",
|
||||||
|
paths: []networkingv1.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
PathType: ptrPathType(networkingv1.PathTypeImplementationSpecific),
|
||||||
|
Path: "",
|
||||||
|
Backend: *backend(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedEvents: []string{
|
||||||
|
"Normal PathUndefined configured backend is missing a path, defaulting to '/'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_path_with_exact_type",
|
||||||
|
paths: []networkingv1.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
PathType: ptrPathType(networkingv1.PathTypeExact),
|
||||||
|
Path: "",
|
||||||
|
Backend: *backend(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedEvents: []string{
|
||||||
|
"Warning UnsupportedPathTypeExact Exact path type strict matching is currently not supported and requests will be routed as for Prefix path type. This behaviour might change in the future.",
|
||||||
|
"Normal PathUndefined configured backend is missing a path, defaulting to '/'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two_competing_but_not_identical_paths_including_one_empty",
|
||||||
|
paths: []networkingv1.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
PathType: ptrPathType(networkingv1.PathTypeImplementationSpecific),
|
||||||
|
Path: "",
|
||||||
|
Backend: *backend(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PathType: ptrPathType(networkingv1.PathTypeImplementationSpecific),
|
||||||
|
Path: "/",
|
||||||
|
Backend: *backend(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedEvents: []string{
|
||||||
|
"Normal PathUndefined configured backend is missing a path, defaulting to '/'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
fc := fake.NewFakeClient(ingressClass())
|
||||||
|
ft := &fakeTSClient{}
|
||||||
|
fr := record.NewFakeRecorder(3) // bump this if you expect a test case to throw more events
|
||||||
|
fakeTsnetServer := &fakeTSNetServer{certDomains: []string{"foo.com"}}
|
||||||
|
zl, err := zap.NewDevelopment()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ingR := &IngressReconciler{
|
||||||
|
recorder: fr,
|
||||||
|
Client: fc,
|
||||||
|
ssr: &tailscaleSTSReconciler{
|
||||||
|
Client: fc,
|
||||||
|
tsClient: ft,
|
||||||
|
tsnetServer: fakeTsnetServer,
|
||||||
|
defaultTags: []string{"tag:k8s"},
|
||||||
|
operatorNamespace: "operator-ns",
|
||||||
|
proxyImage: "tailscale/tailscale",
|
||||||
|
},
|
||||||
|
logger: zl.Sugar(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Resources get created for regular Ingress
|
||||||
|
mustCreate(t, fc, ingressWithPaths(tt.paths))
|
||||||
|
mustCreate(t, fc, service())
|
||||||
|
|
||||||
|
expectReconciled(t, ingR, "default", "test")
|
||||||
|
|
||||||
|
fullName, shortName := findGenName(t, fc, "default", "test", "ingress")
|
||||||
|
mustCreate(t, fc, &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fullName,
|
||||||
|
Namespace: "operator-ns",
|
||||||
|
UID: "test-uid",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
opts := configOpts{
|
||||||
|
stsName: shortName,
|
||||||
|
secretName: fullName,
|
||||||
|
namespace: "default",
|
||||||
|
parentType: "ingress",
|
||||||
|
hostname: "foo",
|
||||||
|
app: kubetypes.AppIngressResource,
|
||||||
|
}
|
||||||
|
serveConfig := &ipn.ServeConfig{
|
||||||
|
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
||||||
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{"${TS_CERT_DOMAIN}:443": {Handlers: map[string]*ipn.HTTPHandler{"/": {Proxy: "http://1.2.3.4:8080/"}}}},
|
||||||
|
}
|
||||||
|
opts.serveConfig = serveConfig
|
||||||
|
|
||||||
|
expectEqual(t, fc, expectedSecret(t, fc, opts))
|
||||||
|
expectEqual(t, fc, expectedHeadlessService(shortName, "ingress"))
|
||||||
|
expectEqual(t, fc, expectedSTSUserspace(t, fc, opts), removeHashAnnotation, removeResourceReqs)
|
||||||
|
|
||||||
|
expectEvents(t, fr, tt.expectedEvents)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptrPathType is a helper function to return a pointer to the pathtype string (required for TestEmptyPath)
|
||||||
|
func ptrPathType(p networkingv1.PathType) *networkingv1.PathType {
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
func ingressClass() *networkingv1.IngressClass {
|
func ingressClass() *networkingv1.IngressClass {
|
||||||
return &networkingv1.IngressClass{
|
return &networkingv1.IngressClass{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "tailscale"},
|
ObjectMeta: metav1.ObjectMeta{Name: "tailscale"},
|
||||||
@ -520,17 +653,48 @@ func ingress() *networkingv1.Ingress {
|
|||||||
},
|
},
|
||||||
Spec: networkingv1.IngressSpec{
|
Spec: networkingv1.IngressSpec{
|
||||||
IngressClassName: ptr.To("tailscale"),
|
IngressClassName: ptr.To("tailscale"),
|
||||||
DefaultBackend: &networkingv1.IngressBackend{
|
DefaultBackend: backend(),
|
||||||
Service: &networkingv1.IngressServiceBackend{
|
|
||||||
Name: "test",
|
|
||||||
Port: networkingv1.ServiceBackendPort{
|
|
||||||
Number: 8080,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TLS: []networkingv1.IngressTLS{
|
TLS: []networkingv1.IngressTLS{
|
||||||
{Hosts: []string{"default-test"}},
|
{Hosts: []string{"default-test"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ingressWithPaths(paths []networkingv1.HTTPIngressPath) *networkingv1.Ingress {
|
||||||
|
return &networkingv1.Ingress{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "Ingress", APIVersion: "networking.k8s.io/v1"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test",
|
||||||
|
Namespace: "default",
|
||||||
|
UID: types.UID("1234-UID"),
|
||||||
|
},
|
||||||
|
Spec: networkingv1.IngressSpec{
|
||||||
|
IngressClassName: ptr.To("tailscale"),
|
||||||
|
Rules: []networkingv1.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.tailnetxyz.ts.net",
|
||||||
|
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||||
|
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||||
|
Paths: paths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: []networkingv1.IngressTLS{
|
||||||
|
{Hosts: []string{"foo.tailnetxyz.ts.net"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func backend() *networkingv1.IngressBackend {
|
||||||
|
return &networkingv1.IngressBackend{
|
||||||
|
Service: &networkingv1.IngressServiceBackend{
|
||||||
|
Name: "test",
|
||||||
|
Port: networkingv1.ServiceBackendPort{
|
||||||
|
Number: 8080,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user