mirror of
https://github.com/dokku/dokku.git
synced 2026-08-29 10:08:53 +02:00
feat: route wildcard domains through traefik on k3s
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.
This commit is contained in:
@@ -343,6 +343,20 @@ In the above example, the `internal-web` process is exposed as a service. The `P
|
||||
> [!NOTE]
|
||||
> It is not possible to modify the port mapping, nor is it possible to assign domains or SSL to a non-web process.
|
||||
|
||||
### Wildcard domains
|
||||
|
||||
Both the `nginx` and `traefik` ingress classes route wildcard domains. Add the wildcard as a domain on the app:
|
||||
|
||||
```shell
|
||||
dokku domains:add node-js-app '*.node-js-app.com'
|
||||
```
|
||||
|
||||
A wildcard matches exactly one label, matching DNS itself, so `*.node-js-app.com` covers `api.node-js-app.com` but not `node-js-app.com` or `api.staging.node-js-app.com`. Add the apex as a separate domain if it should also be served.
|
||||
|
||||
An exact domain always takes precedence over a wildcard, including across apps. If one app serves `*.node-js-app.com` and another serves `api.node-js-app.com`, requests for `api.node-js-app.com` are routed to the second app.
|
||||
|
||||
Only the leading label may be wildcarded. A domain such as `api.*.node-js-app.com` is treated as a literal hostname and will not match anything.
|
||||
|
||||
### SSL Certificates
|
||||
|
||||
#### Enabling letsencrypt integration
|
||||
@@ -485,10 +499,7 @@ A `dns01` issuer can issue wildcard certificates. Add the wildcard as a domain o
|
||||
dokku domains:add node-js-app '*.node-js-app.com'
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Wildcard domains require the `nginx` ingress class. The Traefik integration matches hosts exactly and will not route requests for a wildcard domain.
|
||||
|
||||
Note that a Kubernetes wildcard host matches exactly one label, so `*.node-js-app.com` does not cover `node-js-app.com`. Add both domains if the apex should also be served.
|
||||
See [wildcard domains](#wildcard-domains) for how a wildcard is matched against incoming requests.
|
||||
|
||||
#### Using imported SSL certificates
|
||||
|
||||
|
||||
@@ -281,6 +281,83 @@ func TestIngressRouteTemplateMultipleDomainsRenderOneRoutePerDomain(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressRouteTemplateWildcardDomainUsesHostRegexp(t *testing.T) {
|
||||
values := testIngressRouteValues(true)
|
||||
web := values["processes"].(map[string]interface{})["web"].(map[string]interface{})["web"].(map[string]interface{})
|
||||
web["domains"] = []interface{}{
|
||||
map[string]interface{}{"name": "*.example.com"},
|
||||
}
|
||||
|
||||
docs := renderIngressRouteTemplate(t, values)
|
||||
if len(docs) != 2 {
|
||||
t.Fatalf("expected 2 ingress routes when tls enabled, got %d", len(docs))
|
||||
}
|
||||
|
||||
for _, name := range []string{"myapp-web-http-80-5000", "myapp-web-http-80-5000-websecure"} {
|
||||
routes := findDocByName(t, docs, name)["spec"].(map[string]interface{})["routes"].([]interface{})
|
||||
if len(routes) != 1 {
|
||||
t.Fatalf("expected %s to have 1 route, got %d", name, len(routes))
|
||||
}
|
||||
|
||||
route := routes[0].(map[string]interface{})
|
||||
if got := route["match"]; got != "HostRegexp(`{subdomain:[^.]+}.example.com`)" {
|
||||
t.Fatalf("expected %s match HostRegexp(`{subdomain:[^.]+}.example.com`), got %#v", name, got)
|
||||
}
|
||||
if got := route["priority"]; got != 1 {
|
||||
t.Fatalf("expected %s priority 1, got %#v", name, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressRouteTemplateWildcardRouteYieldsToExactDomain(t *testing.T) {
|
||||
values := testIngressRouteValues(false)
|
||||
web := values["processes"].(map[string]interface{})["web"].(map[string]interface{})["web"].(map[string]interface{})
|
||||
web["domains"] = []interface{}{
|
||||
map[string]interface{}{"name": "*.example.com"},
|
||||
map[string]interface{}{"name": "app.example.com"},
|
||||
}
|
||||
|
||||
docs := renderIngressRouteTemplate(t, values)
|
||||
routes := findDocByName(t, docs, "myapp-web-http-80-5000")["spec"].(map[string]interface{})["routes"].([]interface{})
|
||||
if len(routes) != 2 {
|
||||
t.Fatalf("expected 2 routes (one per domain), got %d", len(routes))
|
||||
}
|
||||
|
||||
wildcard := routes[0].(map[string]interface{})
|
||||
if got := wildcard["match"]; got != "HostRegexp(`{subdomain:[^.]+}.example.com`)" {
|
||||
t.Fatalf("expected wildcard match HostRegexp(`{subdomain:[^.]+}.example.com`), got %#v", got)
|
||||
}
|
||||
if got := wildcard["priority"]; got != 1 {
|
||||
t.Fatalf("expected wildcard priority 1, got %#v", got)
|
||||
}
|
||||
|
||||
exact := routes[1].(map[string]interface{})
|
||||
if got := exact["match"]; got != "Host(`app.example.com`)" {
|
||||
t.Fatalf("expected exact match Host(`app.example.com`), got %#v", got)
|
||||
}
|
||||
if got, ok := exact["priority"]; ok {
|
||||
t.Fatalf("expected exact route to omit priority so traefik defaults it above the wildcard, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressRouteTemplateNonLeadingWildcardStaysExactHost(t *testing.T) {
|
||||
values := testIngressRouteValues(false)
|
||||
web := values["processes"].(map[string]interface{})["web"].(map[string]interface{})["web"].(map[string]interface{})
|
||||
web["domains"] = []interface{}{
|
||||
map[string]interface{}{"name": "app.*.example.com"},
|
||||
}
|
||||
|
||||
docs := renderIngressRouteTemplate(t, values)
|
||||
routes := findDocByName(t, docs, "myapp-web-http-80-5000")["spec"].(map[string]interface{})["routes"].([]interface{})
|
||||
route := routes[0].(map[string]interface{})
|
||||
if got := route["match"]; got != "Host(`app.*.example.com`)" {
|
||||
t.Fatalf("expected non-leading wildcard to stay Host(`app.*.example.com`), got %#v", got)
|
||||
}
|
||||
if got, ok := route["priority"]; ok {
|
||||
t.Fatalf("expected non-leading wildcard to omit priority, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressRouteTemplateNonTraefikIngressClassRendersNothing(t *testing.T) {
|
||||
values := testIngressRouteValues(true)
|
||||
values["global"].(map[string]interface{})["network"].(map[string]interface{})["ingress_class"] = "nginx"
|
||||
|
||||
205
plugins/scheduler-k3s/ingress_template_test.go
Normal file
205
plugins/scheduler-k3s/ingress_template_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,20 @@
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
traefik.host.match renders a domain as a Traefik router rule. Traefik v2 resolves HostRegexp
|
||||
through gorilla/mux, where the pattern is a {name:regexp} template and every literal segment is
|
||||
quoted, so a wildcard label is spelled out as a named group rather than a raw regexp. The group
|
||||
matches a single label, mirroring both DNS and Kubernetes Ingress wildcard hosts.
|
||||
*/}}
|
||||
{{- define "traefik.host.match" -}}
|
||||
{{- if hasPrefix "*." . -}}
|
||||
HostRegexp(`{subdomain:[^.]+}.{{ trimPrefix "*." . }}`)
|
||||
{{- else -}}
|
||||
Host(`{{ . }}`)
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "primary.port" -}}
|
||||
{{- $found := dict -}}
|
||||
{{- range $idx, $port_map := . -}}
|
||||
|
||||
@@ -39,7 +39,10 @@ spec:
|
||||
routes:
|
||||
{{- range $ddx, $domain := $config.web.domains }}
|
||||
- kind: Rule
|
||||
match: Host(`{{ $domain.name }}`)
|
||||
match: {{ include "traefik.host.match" $domain.name }}
|
||||
{{- if hasPrefix "*." $domain.name }}
|
||||
priority: 1
|
||||
{{- end }}
|
||||
middlewares:
|
||||
- name: {{ $.Values.global.app_name}}-{{ $processName }}-compression
|
||||
namespace: {{ $.Values.global.namespace }}
|
||||
|
||||
93
tests/unit/scheduler-k3s-wildcard-1.bats
Normal file
93
tests/unit/scheduler-k3s-wildcard-1.bats
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
TEST_APP="rdmtestapp"
|
||||
EXACT_APP="rdmtestapp2"
|
||||
|
||||
setup() {
|
||||
uninstall_k3s || true
|
||||
global_setup
|
||||
dokku nginx:stop
|
||||
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
global_teardown
|
||||
dokku nginx:start
|
||||
uninstall_k3s || true
|
||||
}
|
||||
|
||||
@test "(scheduler-k3s) [ingress] traefik routes wildcard domains and prefers exact domains" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=traefik install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku domains:set $TEST_APP '*.dokku.me'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku config:set $TEST_APP HELLO=wildcard"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku apps:create $EXACT_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku domains:set $EXACT_APP exact.dokku.me"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku config:set $EXACT_APP HELLO=exact"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$EXACT_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "sleep 30"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000 -n default -o jsonpath='{.spec.routes[0].match}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output 'HostRegexp(`{subdomain:[^.]+}.dokku.me`)'
|
||||
|
||||
run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000 -n default -o jsonpath='{.spec.routes[0].priority}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "1"
|
||||
|
||||
run /bin/bash -c "kubectl get ingressroutes.traefik.io ${EXACT_APP}-web-http-80-5000 -n default -o jsonpath='{.spec.routes[0].priority}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output ""
|
||||
|
||||
assert_http_localhost_response "http" "anything.dokku.me" "80" "/hello" "wildcard"
|
||||
assert_http_localhost_response "http" "exact.dokku.me" "80" "/hello" "exact"
|
||||
}
|
||||
80
tests/unit/scheduler-k3s-wildcard-2.bats
Normal file
80
tests/unit/scheduler-k3s-wildcard-2.bats
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
TEST_APP="rdmtestapp"
|
||||
|
||||
setup_wildcard_tls() {
|
||||
TLS=$BATS_TMPDIR/tls
|
||||
mkdir -p $TLS
|
||||
tar xf $BATS_TEST_DIRNAME/server_ssl_wildcard.tar -C $TLS
|
||||
sudo chown -R dokku:dokku $TLS
|
||||
}
|
||||
|
||||
teardown_wildcard_tls() {
|
||||
TLS=$BATS_TMPDIR/tls
|
||||
rm -R $TLS
|
||||
}
|
||||
|
||||
setup() {
|
||||
uninstall_k3s || true
|
||||
global_setup
|
||||
dokku nginx:stop
|
||||
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
|
||||
setup_wildcard_tls
|
||||
}
|
||||
|
||||
teardown() {
|
||||
global_teardown
|
||||
dokku nginx:start
|
||||
uninstall_k3s || true
|
||||
teardown_wildcard_tls
|
||||
}
|
||||
|
||||
@test "(scheduler-k3s) [ingress] traefik serves a wildcard domain over https" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
INGRESS_CLASS=traefik install_k3s
|
||||
|
||||
run /bin/bash -c "dokku apps:create $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku domains:set $TEST_APP '*.dokku.me'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "sleep 30"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000-websecure -n default -o jsonpath='{.spec.routes[0].match}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output 'HostRegexp(`{subdomain:[^.]+}.dokku.me`)'
|
||||
|
||||
run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000-websecure -n default -o jsonpath='{.spec.tls.secretName}'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output "tls-${TEST_APP}"
|
||||
|
||||
assert_http_redirect "http://wild.dokku.me" "https://wild.dokku.me/"
|
||||
assert_http_localhost_response "https" "wild.dokku.me" "443" "" "python/http.server"
|
||||
}
|
||||
Reference in New Issue
Block a user