feat: apply the cluster issuers on every app deploy
This ensures we can always get certificates regardless of what app gets deployed with whatever configuration.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -114,6 +115,123 @@ type WaitForPodToExistInput struct {
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
func applyClusterIssuers(ctx context.Context, appName string) (string, bool, error) {
|
||||
chartDir, err := os.MkdirTemp("", "cluster-issuer-chart-")
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error creating cluster-issuer chart directory: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(chartDir)
|
||||
|
||||
// create the chart.yaml
|
||||
chart := &Chart{
|
||||
ApiVersion: "v2",
|
||||
AppVersion: "1.0.0",
|
||||
Name: appName,
|
||||
Version: "0.0.1",
|
||||
}
|
||||
|
||||
err = writeYaml(WriteYamlInput{
|
||||
Object: chart,
|
||||
Path: filepath.Join(chartDir, "Chart.yaml"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error writing cluster-issuer chart: %w", err)
|
||||
}
|
||||
|
||||
// create the values.yaml
|
||||
issuerName := "letsencrypt-stag"
|
||||
server := getComputedLetsencryptServer(appName)
|
||||
if server == "prod" || server == "production" {
|
||||
issuerName = "letsencrypt-prod"
|
||||
} else if server != "stag" && server != "staging" {
|
||||
return "", false, fmt.Errorf("Invalid letsencrypt server config: %s", server)
|
||||
}
|
||||
|
||||
tls := false
|
||||
letsencryptEmailStag := getGlobalLetsencryptEmailStag()
|
||||
letsencryptEmailProd := getGlobalLetsencryptEmailProd()
|
||||
if issuerName == "letsencrypt-stag" {
|
||||
tls = letsencryptEmailStag != ""
|
||||
}
|
||||
if issuerName == "letsencrypt-prod" {
|
||||
tls = letsencryptEmailProd != ""
|
||||
}
|
||||
|
||||
clusterIssuerValues := ClusterIssuerValues{
|
||||
ClusterIssuers: map[string]ClusterIssuer{
|
||||
"letsencrypt-stag": {
|
||||
Email: letsencryptEmailStag,
|
||||
Enabled: letsencryptEmailStag != "",
|
||||
Name: "letsencrypt-stag",
|
||||
Server: "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||
},
|
||||
"letsencrypt-prod": {
|
||||
Email: letsencryptEmailProd,
|
||||
Enabled: letsencryptEmailProd != "",
|
||||
Name: "letsencrypt-prod",
|
||||
Server: "https://acme-v02.api.letsencrypt.org/directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(chartDir, "templates"), os.FileMode(0755)); err != nil {
|
||||
return "", false, fmt.Errorf("Error creating cluster-issuer chart templates directory: %w", err)
|
||||
}
|
||||
|
||||
err = writeYaml(WriteYamlInput{
|
||||
Object: clusterIssuerValues,
|
||||
Path: filepath.Join(chartDir, "values.yaml"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error writing chart: %w", err)
|
||||
}
|
||||
|
||||
// create the templates/cluster-issuer.yaml
|
||||
b, err := templates.ReadFile("templates/chart/cluster-issuer.yaml")
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error reading cluster-issuer template: %w", err)
|
||||
}
|
||||
|
||||
filename := filepath.Join(chartDir, "templates", "cluster-issuer.yaml")
|
||||
err = os.WriteFile(filename, b, os.FileMode(0644))
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error writing cluster-issuer template: %w", err)
|
||||
}
|
||||
|
||||
if os.Getenv("DOKKU_TRACE") == "1" {
|
||||
common.CatFile(filename)
|
||||
}
|
||||
|
||||
// install the chart
|
||||
helmAgent, err := NewHelmAgent("cert-manager", DevNullPrinter)
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error creating helm agent: %w", err)
|
||||
}
|
||||
|
||||
chartPath, err := filepath.Abs(chartDir)
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error getting chart path: %w", err)
|
||||
}
|
||||
|
||||
timeoutDuration, err := time.ParseDuration("300s")
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error parsing deploy timeout duration: %w", err)
|
||||
}
|
||||
|
||||
err = helmAgent.InstallOrUpgradeChart(ctx, ChartInput{
|
||||
ChartPath: chartPath,
|
||||
Namespace: "cert-manager",
|
||||
ReleaseName: "cluster-issuers",
|
||||
RollbackOnFailure: true,
|
||||
Timeout: timeoutDuration,
|
||||
})
|
||||
if err != nil {
|
||||
return issuerName, tls, fmt.Errorf("Error installing cluster-issuer chart: %w", err)
|
||||
}
|
||||
|
||||
return issuerName, tls, nil
|
||||
}
|
||||
|
||||
func createKubernetesNamespace(ctx context.Context, namespaceName string) error {
|
||||
clientset, err := NewKubernetesClient()
|
||||
if err != nil {
|
||||
|
||||
@@ -6,9 +6,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1"
|
||||
certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
|
||||
certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"gopkg.in/yaml.v3"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
@@ -25,7 +22,11 @@ type Chart struct {
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
type Values struct {
|
||||
type ClusterIssuerValues struct {
|
||||
ClusterIssuers map[string]ClusterIssuer `yaml:"cluster_issuers"`
|
||||
}
|
||||
|
||||
type AppValues struct {
|
||||
Global GlobalValues `yaml:"global"`
|
||||
Processes map[string]ProcessValues `yaml:"processes"`
|
||||
}
|
||||
@@ -164,65 +165,11 @@ type ProcessTls struct {
|
||||
}
|
||||
|
||||
type ClusterIssuer struct {
|
||||
Email string
|
||||
IngressClass string
|
||||
Name string
|
||||
Namespace string
|
||||
Server string
|
||||
}
|
||||
|
||||
func templateKubernetesClusterIssuer(input ClusterIssuer) (certmanagerv1.ClusterIssuer, error) {
|
||||
if input.Email == "" {
|
||||
return certmanagerv1.ClusterIssuer{}, fmt.Errorf("Email cannot be empty")
|
||||
}
|
||||
if input.Server == "" {
|
||||
return certmanagerv1.ClusterIssuer{}, fmt.Errorf("Server cannot be empty")
|
||||
}
|
||||
if input.Name == "" {
|
||||
return certmanagerv1.ClusterIssuer{}, fmt.Errorf("Name cannot be empty")
|
||||
}
|
||||
if input.Namespace == "" {
|
||||
return certmanagerv1.ClusterIssuer{}, fmt.Errorf("Namespace cannot be empty")
|
||||
}
|
||||
|
||||
if input.Server == "staging" {
|
||||
input.Server = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
} else if input.Server == "production" {
|
||||
input.Server = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
} else {
|
||||
return certmanagerv1.ClusterIssuer{}, fmt.Errorf("Server must be either staging or production")
|
||||
}
|
||||
|
||||
clusterIssuer := certmanagerv1.ClusterIssuer{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: input.Name,
|
||||
Namespace: input.Namespace,
|
||||
},
|
||||
Spec: certmanagerv1.IssuerSpec{
|
||||
IssuerConfig: certmanagerv1.IssuerConfig{
|
||||
ACME: &acmev1.ACMEIssuer{
|
||||
Email: input.Email,
|
||||
Server: input.Server,
|
||||
PrivateKey: certmanagermetav1.SecretKeySelector{
|
||||
LocalObjectReference: certmanagermetav1.LocalObjectReference{
|
||||
Name: input.Name,
|
||||
},
|
||||
},
|
||||
Solvers: []acmev1.ACMEChallengeSolver{
|
||||
{
|
||||
HTTP01: &acmev1.ACMEChallengeSolverHTTP01{
|
||||
Ingress: &acmev1.ACMEChallengeSolverHTTP01Ingress{
|
||||
Class: ptr.To(input.IngressClass),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return clusterIssuer, nil
|
||||
Email string `yaml:"email"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
IngressClass string `yaml:"ingress_class"`
|
||||
Name string `yaml:"name"`
|
||||
Server string `yaml:"server"`
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
|
||||
19
plugins/scheduler-k3s/templates/chart/cluster-issuer.yaml
Normal file
19
plugins/scheduler-k3s/templates/chart/cluster-issuer.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
{{- range $name, $config := .Values.cluster_issuers }}
|
||||
{{- if $config.enabled }}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: {{ $config.name }}
|
||||
spec:
|
||||
acme:
|
||||
email: {{ $config.email }}
|
||||
server: {{ $config.server }}
|
||||
privateKeySecretRef:
|
||||
name: {{ $config.name }}
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: {{ $config.ingress_class }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -222,6 +222,11 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e
|
||||
return fmt.Errorf("Error loading environment for deployment: %w", err)
|
||||
}
|
||||
|
||||
issuerName, tlsEnabled, err := applyClusterIssuers(ctx, appName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error applying cluster issuers: %w", err)
|
||||
}
|
||||
|
||||
chartDir, err := os.MkdirTemp("", "dokku-chart-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating chart directory: %w", err)
|
||||
@@ -273,22 +278,6 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e
|
||||
return fmt.Errorf("Error fetching cron entries: %w", err)
|
||||
}
|
||||
|
||||
issuerName := "letsencrypt-stag"
|
||||
server := getComputedLetsencryptServer(appName)
|
||||
if server == "prod" || server == "production" {
|
||||
issuerName = "letsencrypt-prod"
|
||||
} else if server != "stag" && server != "staging" {
|
||||
return fmt.Errorf("Invalid letsencrypt server config: %s", server)
|
||||
}
|
||||
|
||||
tls := false
|
||||
if issuerName == "letsencrypt-stag" {
|
||||
tls = getGlobalLetsencryptEmailStag() != ""
|
||||
}
|
||||
if issuerName == "letsencrypt-prod" {
|
||||
tls = getGlobalLetsencryptEmailProd() != ""
|
||||
}
|
||||
|
||||
domains := []string{}
|
||||
if _, ok := processes["web"]; ok {
|
||||
err = common.PlugnTrigger("domains-vhost-enabled", []string{appName}...)
|
||||
@@ -322,7 +311,7 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e
|
||||
return fmt.Errorf("Error writing chart: %w", err)
|
||||
}
|
||||
|
||||
values := &Values{
|
||||
values := &AppValues{
|
||||
Global: GlobalValues{
|
||||
AppName: appName,
|
||||
DeploymentID: fmt.Sprint(deploymentId),
|
||||
@@ -379,7 +368,7 @@ func TriggerSchedulerDeploy(scheduler string, appName string, imageTag string) e
|
||||
Domains: domains,
|
||||
PortMaps: []ProcessPortMap{},
|
||||
TLS: ProcessTls{
|
||||
Enabled: tls,
|
||||
Enabled: tlsEnabled,
|
||||
IssuerName: issuerName,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user