mirror of
https://github.com/dokku/dokku.git
synced 2026-08-29 10:08:53 +02:00
Traefik matches hosts exactly, so an app serving a wildcard domain under the `traefik` ingress class had a valid certificate but silently 404d on every request. Wildcard domains now render as a `HostRegexp` rule that matches a single label, the same semantics as a Kubernetes wildcard host, so both ingress classes behave the same. Those routes carry an explicit low priority so an exact domain on any app still wins over another app's wildcard, mirroring ingress-nginx.
206 lines
6.0 KiB
Go
206 lines
6.0 KiB
Go
package scheduler_k3s
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
"helm.sh/helm/v3/pkg/chart/loader"
|
|
"helm.sh/helm/v3/pkg/chartutil"
|
|
"helm.sh/helm/v3/pkg/engine"
|
|
)
|
|
|
|
func renderIngressTemplate(t *testing.T, values map[string]interface{}) []map[string]interface{} {
|
|
t.Helper()
|
|
|
|
chartDir := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(chartDir, "templates"), 0o755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
|
|
chartYAML := []byte("apiVersion: v2\nname: test\nversion: 0.0.1\n")
|
|
if err := os.WriteFile(filepath.Join(chartDir, "Chart.yaml"), chartYAML, 0o644); err != nil {
|
|
t.Fatalf("write Chart.yaml: %v", err)
|
|
}
|
|
|
|
ingressTpl, err := templates.ReadFile("templates/chart/ingress.yaml")
|
|
if err != nil {
|
|
t.Fatalf("read ingress template: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(chartDir, "templates", "ingress.yaml"), ingressTpl, 0o644); err != nil {
|
|
t.Fatalf("write ingress template: %v", err)
|
|
}
|
|
|
|
helpersTpl, err := templates.ReadFile("templates/chart/_helpers.tpl")
|
|
if err != nil {
|
|
t.Fatalf("read _helpers: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(chartDir, "templates", "_helpers.tpl"), helpersTpl, 0o644); err != nil {
|
|
t.Fatalf("write _helpers: %v", err)
|
|
}
|
|
|
|
loaded, err := loader.Load(chartDir)
|
|
if err != nil {
|
|
t.Fatalf("load chart: %v", err)
|
|
}
|
|
|
|
renderValues, err := chartutil.ToRenderValues(loaded, values, chartutil.ReleaseOptions{Name: "test", Namespace: "default"}, nil)
|
|
if err != nil {
|
|
t.Fatalf("ToRenderValues: %v", err)
|
|
}
|
|
|
|
rendered, err := engine.Render(loaded, renderValues)
|
|
if err != nil {
|
|
t.Fatalf("render: %v", err)
|
|
}
|
|
|
|
manifest := rendered["test/templates/ingress.yaml"]
|
|
|
|
var docs []map[string]interface{}
|
|
decoder := yaml.NewDecoder(strings.NewReader(manifest))
|
|
for {
|
|
var doc map[string]interface{}
|
|
if err := decoder.Decode(&doc); err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
t.Fatalf("yaml decode failed: %v\nrendered:\n%s", err, manifest)
|
|
}
|
|
if doc != nil {
|
|
docs = append(docs, doc)
|
|
}
|
|
}
|
|
|
|
return docs
|
|
}
|
|
|
|
func testIngressValues(tlsEnabled bool, domains ...map[string]interface{}) map[string]interface{} {
|
|
domainValues := []interface{}{}
|
|
for _, domain := range domains {
|
|
domainValues = append(domainValues, domain)
|
|
}
|
|
|
|
web := map[string]interface{}{
|
|
"domains": domainValues,
|
|
"port_maps": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "http-80-5000",
|
|
"scheme": "http",
|
|
"container_port": 5000,
|
|
},
|
|
},
|
|
"tls": map[string]interface{}{
|
|
"enabled": tlsEnabled,
|
|
"use_imported_cert": false,
|
|
},
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"global": map[string]interface{}{
|
|
"app_name": "myapp",
|
|
"namespace": "myns",
|
|
"network": map[string]interface{}{
|
|
"ingress_class": "nginx",
|
|
},
|
|
},
|
|
"processes": map[string]interface{}{
|
|
"web": map[string]interface{}{
|
|
"web": web,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func ingressHost(t *testing.T, doc map[string]interface{}) string {
|
|
t.Helper()
|
|
|
|
rules, ok := doc["spec"].(map[string]interface{})["rules"].([]interface{})
|
|
if !ok || len(rules) != 1 {
|
|
t.Fatalf("expected ingress to contain a single rule, got %#v", doc["spec"])
|
|
}
|
|
|
|
host, ok := rules[0].(map[string]interface{})["host"].(string)
|
|
if !ok {
|
|
t.Fatalf("expected rule to contain a string host, got %#v", rules[0])
|
|
}
|
|
|
|
return host
|
|
}
|
|
|
|
func TestIngressTemplateExactDomainUsesSlugForObjectName(t *testing.T) {
|
|
values := testIngressValues(false, map[string]interface{}{"name": "app.example.com", "slug": "app-example-com"})
|
|
|
|
docs := renderIngressTemplate(t, values)
|
|
if len(docs) != 1 {
|
|
t.Fatalf("expected 1 ingress, got %d", len(docs))
|
|
}
|
|
|
|
doc := findDocByName(t, docs, "myapp-web-app-example-com")
|
|
if got := ingressHost(t, doc); got != "app.example.com" {
|
|
t.Fatalf("expected host app.example.com, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestIngressTemplateWildcardDomainRendersWildcardHost(t *testing.T) {
|
|
values := testIngressValues(true, map[string]interface{}{"name": "*.example.com", "slug": "wildcard-example-com"})
|
|
|
|
docs := renderIngressTemplate(t, values)
|
|
if len(docs) != 1 {
|
|
t.Fatalf("expected 1 ingress, got %d", len(docs))
|
|
}
|
|
|
|
doc := findDocByName(t, docs, "myapp-web-wildcard-example-com")
|
|
if got := ingressHost(t, doc); got != "*.example.com" {
|
|
t.Fatalf("expected host *.example.com, got %q", got)
|
|
}
|
|
|
|
tls, ok := doc["spec"].(map[string]interface{})["tls"].([]interface{})
|
|
if !ok || len(tls) != 1 {
|
|
t.Fatalf("expected ingress to contain a single tls entry, got %#v", doc["spec"])
|
|
}
|
|
|
|
hosts, ok := tls[0].(map[string]interface{})["hosts"].([]interface{})
|
|
if !ok || len(hosts) != 1 || hosts[0] != "*.example.com" {
|
|
t.Fatalf("expected tls hosts [*.example.com], got %#v", tls[0])
|
|
}
|
|
if got := tls[0].(map[string]interface{})["secretName"]; got != "tls-myapp-web" {
|
|
t.Fatalf("expected tls secretName tls-myapp-web, got %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestIngressTemplateWildcardAndApexRenderDistinctObjects(t *testing.T) {
|
|
values := testIngressValues(false,
|
|
map[string]interface{}{"name": "*.example.com", "slug": "wildcard-example-com"},
|
|
map[string]interface{}{"name": "example.com", "slug": "example-com"},
|
|
)
|
|
|
|
docs := renderIngressTemplate(t, values)
|
|
if len(docs) != 2 {
|
|
t.Fatalf("expected 2 ingresses (one per domain), got %d", len(docs))
|
|
}
|
|
|
|
wildcard := findDocByName(t, docs, "myapp-web-wildcard-example-com")
|
|
if got := ingressHost(t, wildcard); got != "*.example.com" {
|
|
t.Fatalf("expected wildcard host *.example.com, got %q", got)
|
|
}
|
|
|
|
apex := findDocByName(t, docs, "myapp-web-example-com")
|
|
if got := ingressHost(t, apex); got != "example.com" {
|
|
t.Fatalf("expected apex host example.com, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestIngressTemplateNonNginxIngressClassRendersNothing(t *testing.T) {
|
|
values := testIngressValues(false, map[string]interface{}{"name": "app.example.com", "slug": "app-example-com"})
|
|
values["global"].(map[string]interface{})["network"].(map[string]interface{})["ingress_class"] = "traefik"
|
|
|
|
docs := renderIngressTemplate(t, values)
|
|
if len(docs) != 0 {
|
|
t.Fatalf("expected 0 ingresses when ingress_class is not nginx, got %d: %#v", len(docs), docs)
|
|
}
|
|
}
|