mirror of
https://github.com/dokku/dokku.git
synced 2026-08-29 10:08:53 +02:00
The `cert-issuer-name` and `cert-issuer-kind` properties point an app's generated `Certificate` at a cert-manager issuer created outside of Dokku, allowing certificates to be issued through solvers the built-in letsencrypt integration cannot use, such as `dns01` for wildcard certificates. Setting an issuer enables https on its own, as a manually managed issuer has no email for Dokku to configure. An imported certificate still takes precedence, and `letsencrypt-server false` remains the single off switch. Dokku warns before a build starts when the referenced issuer is absent from the cluster, without blocking the deploy. Wildcard domains no longer collide with their apex domain when generating ingress names, and `letsencrypt-server` values are now validated when set rather than at deploy time.
684 lines
18 KiB
Go
684 lines
18 KiB
Go
package scheduler_k3s
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/dokku/dokku/plugins/common"
|
|
corev1 "k8s.io/api/core/v1"
|
|
)
|
|
|
|
func TestNeedsImagePullSecretsPrune(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
live []corev1.LocalObjectReference
|
|
keepSet map[string]struct{}
|
|
want bool
|
|
}{
|
|
{
|
|
name: "empty live, empty keep",
|
|
live: []corev1.LocalObjectReference{},
|
|
keepSet: map[string]struct{}{},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "matches keep set exactly",
|
|
live: []corev1.LocalObjectReference{{Name: "pull-secret-foo"}},
|
|
keepSet: map[string]struct{}{
|
|
"pull-secret-foo": {},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "leaked entries beyond keep set",
|
|
live: []corev1.LocalObjectReference{
|
|
{Name: "ims-foo.111"},
|
|
{Name: "ims-foo.222"},
|
|
{Name: "pull-secret-foo"},
|
|
},
|
|
keepSet: map[string]struct{}{
|
|
"pull-secret-foo": {},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "live missing the keep entry",
|
|
live: []corev1.LocalObjectReference{
|
|
{Name: "ims-foo.111"},
|
|
},
|
|
keepSet: map[string]struct{}{
|
|
"pull-secret-foo": {},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "live populated, keep set empty",
|
|
live: []corev1.LocalObjectReference{{Name: "ims-foo.111"}},
|
|
keepSet: map[string]struct{}{},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "empty live, keep set populated",
|
|
live: []corev1.LocalObjectReference{},
|
|
keepSet: map[string]struct{}{"pull-secret-foo": {}},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "user override only, no leaked entries",
|
|
live: []corev1.LocalObjectReference{{Name: "my-custom-secret"}},
|
|
keepSet: map[string]struct{}{
|
|
"my-custom-secret": {},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "user override with leaked dokku-managed entries",
|
|
live: []corev1.LocalObjectReference{
|
|
{Name: "ims-foo.111"},
|
|
{Name: "my-custom-secret"},
|
|
},
|
|
keepSet: map[string]struct{}{
|
|
"my-custom-secret": {},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := needsImagePullSecretsPrune(tc.live, tc.keepSet)
|
|
if got != tc.want {
|
|
t.Errorf("needsImagePullSecretsPrune() = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInitializeInstallerArgs(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
input InitializeInstallerArgsInput
|
|
wantPairs [][2]string
|
|
wantAbsent []string
|
|
}{
|
|
{
|
|
name: "no kubelet args",
|
|
input: InitializeInstallerArgsInput{
|
|
IngressClass: "traefik",
|
|
NodeName: "ip-10-0-0-1-abc",
|
|
Token: "sometoken",
|
|
},
|
|
wantAbsent: []string{"--kubelet-arg", "--node-taint"},
|
|
},
|
|
{
|
|
name: "single kubelet arg",
|
|
input: InitializeInstallerArgsInput{
|
|
IngressClass: "traefik",
|
|
KubeletArgs: []string{"allowed-unsafe-sysctls=net.ipv4.tcp_rmem"},
|
|
NodeName: "ip-10-0-0-1-abc",
|
|
Token: "sometoken",
|
|
},
|
|
wantPairs: [][2]string{
|
|
{"--kubelet-arg", "allowed-unsafe-sysctls=net.ipv4.tcp_rmem"},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple kubelet args each get their own flag",
|
|
input: InitializeInstallerArgsInput{
|
|
IngressClass: "traefik",
|
|
KubeletArgs: []string{"allowed-unsafe-sysctls=net.ipv4.tcp_rmem", "max-pods=150"},
|
|
NodeName: "ip-10-0-0-1-abc",
|
|
Token: "sometoken",
|
|
},
|
|
wantPairs: [][2]string{
|
|
{"--kubelet-arg", "allowed-unsafe-sysctls=net.ipv4.tcp_rmem"},
|
|
{"--kubelet-arg", "max-pods=150"},
|
|
},
|
|
},
|
|
{
|
|
name: "kubelet args coexist with taint scheduling",
|
|
input: InitializeInstallerArgsInput{
|
|
IngressClass: "nginx",
|
|
KubeletArgs: []string{"max-pods=150"},
|
|
NodeName: "ip-10-0-0-1-abc",
|
|
TaintScheduling: true,
|
|
Token: "sometoken",
|
|
},
|
|
wantPairs: [][2]string{
|
|
{"--node-taint", "CriticalAddonsOnly=true:NoSchedule"},
|
|
{"--kubelet-arg", "max-pods=150"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := initializeInstallerArgs(tc.input)
|
|
|
|
for _, pair := range tc.wantPairs {
|
|
if !containsArgPair(got, pair[0], pair[1]) {
|
|
t.Errorf("initializeInstallerArgs() missing %q %q, got %v", pair[0], pair[1], got)
|
|
}
|
|
}
|
|
|
|
for _, absent := range tc.wantAbsent {
|
|
for _, arg := range got {
|
|
if arg == absent {
|
|
t.Errorf("initializeInstallerArgs() unexpectedly contains %q, got %v", absent, got)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func containsArgPair(args []string, flag string, value string) bool {
|
|
for i := 0; i < len(args)-1; i++ {
|
|
if args[i] == flag && args[i+1] == value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func TestNodeLabels(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
role string
|
|
profileName string
|
|
wantKey string
|
|
wantValue string
|
|
wantProfile bool
|
|
}{
|
|
{
|
|
name: "server without a profile",
|
|
role: "server",
|
|
profileName: "",
|
|
wantKey: "svccontroller.k3s.cattle.io/enablelb",
|
|
wantValue: "true",
|
|
wantProfile: false,
|
|
},
|
|
{
|
|
name: "worker without a profile",
|
|
role: "worker",
|
|
profileName: "",
|
|
wantKey: "node-role.kubernetes.io/worker",
|
|
wantValue: "worker",
|
|
wantProfile: false,
|
|
},
|
|
{
|
|
name: "worker with a profile",
|
|
role: "worker",
|
|
profileName: "edge-workers",
|
|
wantKey: "node-role.kubernetes.io/worker",
|
|
wantValue: "worker",
|
|
wantProfile: true,
|
|
},
|
|
{
|
|
name: "server with a profile",
|
|
role: "server",
|
|
profileName: "control-plane",
|
|
wantKey: "svccontroller.k3s.cattle.io/enablelb",
|
|
wantValue: "true",
|
|
wantProfile: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := nodeLabels(tc.role, tc.profileName)
|
|
|
|
if got[tc.wantKey] != tc.wantValue {
|
|
t.Errorf("nodeLabels() role label = %q, want %q", got[tc.wantKey], tc.wantValue)
|
|
}
|
|
|
|
profile, ok := got[NodeProfileLabel]
|
|
if ok != tc.wantProfile {
|
|
t.Errorf("nodeLabels() has %s = %v, want %v", NodeProfileLabel, ok, tc.wantProfile)
|
|
}
|
|
if tc.wantProfile && profile != tc.profileName {
|
|
t.Errorf("nodeLabels() %s = %q, want %q", NodeProfileLabel, profile, tc.profileName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNodeLabelsDoesNotMutatePackageLabels(t *testing.T) {
|
|
serverBefore := len(ServerLabels)
|
|
workerBefore := len(WorkerLabels)
|
|
|
|
nodeLabels("server", "control-plane")
|
|
nodeLabels("worker", "edge-workers")
|
|
|
|
if len(ServerLabels) != serverBefore {
|
|
t.Errorf("nodeLabels() mutated ServerLabels: len = %d, want %d", len(ServerLabels), serverBefore)
|
|
}
|
|
if len(WorkerLabels) != workerBefore {
|
|
t.Errorf("nodeLabels() mutated WorkerLabels: len = %d, want %d", len(WorkerLabels), workerBefore)
|
|
}
|
|
}
|
|
|
|
func TestIsNamespacedSysctl(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
want bool
|
|
}{
|
|
{name: "net.core.somaxconn", want: true},
|
|
{name: "net.ipv4.tcp_rmem", want: true},
|
|
{name: "kernel.shm_rmid_forced", want: true},
|
|
{name: "kernel.shmmax", want: true},
|
|
{name: "kernel.msgmax", want: true},
|
|
{name: "kernel.sem", want: true},
|
|
{name: "fs.mqueue.msg_max", want: true},
|
|
{name: "vm.max_map_count", want: false},
|
|
{name: "vm.swappiness", want: false},
|
|
{name: "kernel.pid_max", want: false},
|
|
{name: "kernel.semaphore", want: false},
|
|
{name: "fs.file-max", want: false},
|
|
{name: "", want: false},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := isNamespacedSysctl(tc.name); got != tc.want {
|
|
t.Errorf("isNamespacedSysctl(%q) = %v, want %v", tc.name, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseSysctls(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
values []string
|
|
want []Sysctl
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "empty input",
|
|
values: []string{},
|
|
want: []Sysctl{},
|
|
},
|
|
{
|
|
name: "single namespaced sysctl",
|
|
values: []string{"net.core.somaxconn=1024"},
|
|
want: []Sysctl{{Name: "net.core.somaxconn", Value: "1024"}},
|
|
},
|
|
{
|
|
name: "sorted by name regardless of input order",
|
|
values: []string{"net.core.somaxconn=1024", "kernel.sem=250", "fs.mqueue.msg_max=20"},
|
|
want: []Sysctl{
|
|
{Name: "fs.mqueue.msg_max", Value: "20"},
|
|
{Name: "kernel.sem", Value: "250"},
|
|
{Name: "net.core.somaxconn", Value: "1024"},
|
|
},
|
|
},
|
|
{
|
|
name: "value containing an equals sign is preserved",
|
|
values: []string{"net.ipv4.tcp_rmem=4096 87380 6291456"},
|
|
want: []Sysctl{{Name: "net.ipv4.tcp_rmem", Value: "4096 87380 6291456"}},
|
|
},
|
|
{
|
|
name: "non-namespaced sysctl is rejected",
|
|
values: []string{"vm.max_map_count=262144"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "missing equals sign is rejected",
|
|
values: []string{"net.core.somaxconn"},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty name is rejected",
|
|
values: []string{"=1024"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := parseSysctls(tc.values)
|
|
if tc.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("parseSysctls(%v) expected an error, got %v", tc.values, got)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("parseSysctls(%v) unexpected error: %v", tc.values, err)
|
|
}
|
|
|
|
if len(got) != len(tc.want) {
|
|
t.Fatalf("parseSysctls(%v) = %v, want %v", tc.values, got, tc.want)
|
|
}
|
|
for i := range got {
|
|
if got[i] != tc.want[i] {
|
|
t.Errorf("parseSysctls(%v)[%d] = %v, want %v", tc.values, i, got[i], tc.want[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNormalizeCertIssuerKind(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
value string
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
{name: "empty is left unset", value: "", want: ""},
|
|
{name: "canonical issuer", value: "Issuer", want: CertIssuerKindIssuer},
|
|
{name: "canonical cluster issuer", value: "ClusterIssuer", want: CertIssuerKindClusterIssuer},
|
|
{name: "lowercase issuer is canonicalized", value: "issuer", want: CertIssuerKindIssuer},
|
|
{name: "lowercase cluster issuer is canonicalized", value: "clusterissuer", want: CertIssuerKindClusterIssuer},
|
|
{name: "uppercase issuer is canonicalized", value: "ISSUER", want: CertIssuerKindIssuer},
|
|
{name: "unknown kind", value: "Certificate", wantErr: true},
|
|
{name: "typo", value: "ClusterIssue", wantErr: true},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got, err := normalizeCertIssuerKind(tc.value)
|
|
if tc.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("normalizeCertIssuerKind(%q) expected an error, got %q", tc.value, got)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("normalizeCertIssuerKind(%q) unexpected error: %v", tc.value, err)
|
|
}
|
|
if got != tc.want {
|
|
t.Errorf("normalizeCertIssuerKind(%q) = %q, want %q", tc.value, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateCertIssuerName(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
value string
|
|
wantErr bool
|
|
}{
|
|
{name: "empty unsets the property", value: ""},
|
|
{name: "reserved disabled value", value: CertIssuerNameDisabled},
|
|
{name: "simple name", value: "acme-dns"},
|
|
{name: "dotted name", value: "my.issuer.example"},
|
|
{name: "underscores are not legal kubernetes names", value: "my_own_issuer_name", wantErr: true},
|
|
{name: "uppercase is not a legal kubernetes name", value: "AcmeDNS", wantErr: true},
|
|
{name: "leading dash", value: "-acme-dns", wantErr: true},
|
|
{name: "trailing dash", value: "acme-dns-", wantErr: true},
|
|
{name: "too long", value: strings.Repeat("a", 254), wantErr: true},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
err := validateCertIssuerName(tc.value)
|
|
if tc.wantErr && err == nil {
|
|
t.Fatalf("validateCertIssuerName(%q) expected an error", tc.value)
|
|
}
|
|
if !tc.wantErr && err != nil {
|
|
t.Fatalf("validateCertIssuerName(%q) unexpected error: %v", tc.value, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateLetsencryptServer(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
value string
|
|
wantErr bool
|
|
}{
|
|
{name: "empty unsets the property", value: ""},
|
|
{name: "prod", value: "prod"},
|
|
{name: "production", value: "production"},
|
|
{name: "stag", value: "stag"},
|
|
{name: "staging", value: "staging"},
|
|
{name: "disabled", value: LetsencryptServerDisabled},
|
|
{name: "typo", value: "prodd", wantErr: true},
|
|
{name: "issuer name is not a server", value: "my-own-issuer", wantErr: true},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
err := validateLetsencryptServer(tc.value)
|
|
if tc.wantErr && err == nil {
|
|
t.Fatalf("validateLetsencryptServer(%q) expected an error", tc.value)
|
|
}
|
|
if !tc.wantErr && err != nil {
|
|
t.Fatalf("validateLetsencryptServer(%q) unexpected error: %v", tc.value, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomainSlug(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
domain string
|
|
want string
|
|
}{
|
|
{name: "plain domain", domain: "example.com", want: "example-com"},
|
|
{name: "wildcard domain keeps a distinct slug", domain: "*.example.com", want: "wildcard-example-com"},
|
|
{name: "subdomain", domain: "app.example.com", want: "app-example-com"},
|
|
{name: "wildcard subdomain", domain: "*.app.example.com", want: "wildcard-app-example-com"},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := domainSlug(tc.domain); got != tc.want {
|
|
t.Errorf("domainSlug(%q) = %q, want %q", tc.domain, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDomainSlugDistinguishesWildcardFromApex(t *testing.T) {
|
|
// A kubernetes wildcard host matches exactly one label, so *.example.com does not
|
|
// cover example.com and both are commonly added to the same app. The generated
|
|
// ingress name is derived from this slug, so a collision would render two objects
|
|
// with the same name and fail the helm install.
|
|
if domainSlug("*.example.com") == domainSlug("example.com") {
|
|
t.Fatalf("domainSlug collides for *.example.com and example.com: %q", domainSlug("example.com"))
|
|
}
|
|
}
|
|
|
|
func TestResolveAppTLSConfig(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
appProperties map[string]string
|
|
globalProperties map[string]string
|
|
importedCertExists bool
|
|
want AppTLSConfig
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "defaults to the shared production ClusterIssuer once a global email is set",
|
|
globalProperties: map[string]string{
|
|
"letsencrypt-email-prod": "ops@dokku.me",
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
IssuerKind: CertIssuerKindClusterIssuer,
|
|
IssuerName: "letsencrypt-prod",
|
|
},
|
|
},
|
|
{
|
|
name: "no letsencrypt email leaves tls disabled",
|
|
want: AppTLSConfig{
|
|
IssuerKind: CertIssuerKindClusterIssuer,
|
|
IssuerName: "letsencrypt-prod",
|
|
},
|
|
},
|
|
{
|
|
name: "an app level email renders a namespaced Issuer",
|
|
appProperties: map[string]string{
|
|
"letsencrypt-server": "staging",
|
|
"letsencrypt-email-stag": "app@dokku.me",
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
IssuerKind: CertIssuerKindIssuer,
|
|
IssuerName: "myapp-letsencrypt-stag",
|
|
Issuer: AppIssuer{
|
|
Email: "app@dokku.me",
|
|
Enabled: true,
|
|
IngressClass: DefaultIngressClass,
|
|
Name: "myapp-letsencrypt-stag",
|
|
Server: LetsencryptServerStag,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "an imported certificate wins over a custom issuer",
|
|
importedCertExists: true,
|
|
appProperties: map[string]string{
|
|
"cert-issuer-name": "acme-dns",
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
UseImportedCert: true,
|
|
},
|
|
},
|
|
{
|
|
name: "letsencrypt-server false disables a globally configured custom issuer",
|
|
appProperties: map[string]string{
|
|
"letsencrypt-server": LetsencryptServerDisabled,
|
|
},
|
|
globalProperties: map[string]string{
|
|
"cert-issuer-name": "acme-dns",
|
|
},
|
|
want: AppTLSConfig{},
|
|
},
|
|
{
|
|
name: "a custom issuer enables tls with no letsencrypt email anywhere",
|
|
appProperties: map[string]string{
|
|
"cert-issuer-name": "acme-dns",
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
IssuerKind: CertIssuerKindClusterIssuer,
|
|
IssuerName: "acme-dns",
|
|
UsesCustomIssuer: true,
|
|
},
|
|
},
|
|
{
|
|
name: "a namespaced custom issuer renders no Dokku managed issuer",
|
|
appProperties: map[string]string{
|
|
"cert-issuer-name": "acme-dns",
|
|
"cert-issuer-kind": CertIssuerKindIssuer,
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
IssuerKind: CertIssuerKindIssuer,
|
|
IssuerName: "acme-dns",
|
|
UsesCustomIssuer: true,
|
|
},
|
|
},
|
|
{
|
|
name: "a custom issuer beats a fully configured letsencrypt setup",
|
|
appProperties: map[string]string{
|
|
"cert-issuer-name": "acme-dns",
|
|
"letsencrypt-email-prod": "app@dokku.me",
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
IssuerKind: CertIssuerKindClusterIssuer,
|
|
IssuerName: "acme-dns",
|
|
UsesCustomIssuer: true,
|
|
},
|
|
},
|
|
{
|
|
name: "an app level custom issuer overrides the global one",
|
|
appProperties: map[string]string{
|
|
"cert-issuer-name": "app-issuer",
|
|
},
|
|
globalProperties: map[string]string{
|
|
"cert-issuer-name": "global-issuer",
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
IssuerKind: CertIssuerKindClusterIssuer,
|
|
IssuerName: "app-issuer",
|
|
UsesCustomIssuer: true,
|
|
},
|
|
},
|
|
{
|
|
name: "the reserved disabled value falls back to letsencrypt",
|
|
appProperties: map[string]string{
|
|
"cert-issuer-name": CertIssuerNameDisabled,
|
|
},
|
|
globalProperties: map[string]string{
|
|
"cert-issuer-name": "global-issuer",
|
|
"letsencrypt-email-prod": "ops@dokku.me",
|
|
},
|
|
want: AppTLSConfig{
|
|
Enabled: true,
|
|
IssuerKind: CertIssuerKindClusterIssuer,
|
|
IssuerName: "letsencrypt-prod",
|
|
},
|
|
},
|
|
{
|
|
name: "an invalid letsencrypt server still errors",
|
|
appProperties: map[string]string{
|
|
"letsencrypt-server": "prodd",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "an invalid stored cert issuer kind errors",
|
|
appProperties: map[string]string{
|
|
"cert-issuer-name": "acme-dns",
|
|
"cert-issuer-kind": "Certificate",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
setupReportTest(t, "myapp")
|
|
for key, value := range tc.appProperties {
|
|
if err := common.PropertyWrite("scheduler-k3s", "myapp", key, value); err != nil {
|
|
t.Fatalf("PropertyWrite(%q): %v", key, err)
|
|
}
|
|
}
|
|
for key, value := range tc.globalProperties {
|
|
if err := common.PropertyWrite("scheduler-k3s", "--global", key, value); err != nil {
|
|
t.Fatalf("PropertyWrite(--global, %q): %v", key, err)
|
|
}
|
|
}
|
|
|
|
got, err := resolveAppTLSConfig(ResolveAppTLSConfigInput{
|
|
AppName: "myapp",
|
|
ImportedCertExists: tc.importedCertExists,
|
|
})
|
|
if tc.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("resolveAppTLSConfig() expected an error, got %+v", got)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("resolveAppTLSConfig() unexpected error: %v", err)
|
|
}
|
|
|
|
if got != tc.want {
|
|
t.Errorf("resolveAppTLSConfig() = %+v, want %+v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|