Skip to content

Commit

Permalink
feat(deploy): modularize Traefik dashboard and root CA args
Browse files Browse the repository at this point in the history
Separated the generation of arguments for Traefik dashboard and root CA into individual functions for better readability and maintainability.
  • Loading branch information
ojsef39 committed Jan 9, 2025
1 parent e0cd8df commit e8e227e
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 41 deletions.
17 changes: 16 additions & 1 deletion .github/workflows/k8s-deploy-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ on:
external_domain:
required: true
type: string
traefik_dashboard:
required: false
type: boolean
default: false
root_ca:
required: false
type: string
default: ""
custom_deployment:
required: false
type: boolean
default: false
secrets:
KUBE_CONFIG_DEV:
required: true
Expand Down Expand Up @@ -78,7 +90,10 @@ jobs:
--chart ${{ inputs.helm_chart }} \
--version ${{ inputs.helm_version }} \
--repo ${{ inputs.helm_repository }} \
--domain ${{ steps.vars.outputs.domain }}
--domain ${{ steps.vars.outputs.domain }} \
--traefik-dashboard ${{ inputs.traefik_dashboard }} \
--root_ca ${{ inputs.root_ca }} \
--custom ${{ inputs.custom_deployment }}
- name: Create deployment status
uses: chrnorm/deployment-action@v2
if: success()
Expand Down
213 changes: 173 additions & 40 deletions deploy/main.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
package main

import (
"bytes"
"crypto/tls"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
)

type Config struct {
Stage string
AppName string
Environment string
PRNumber string
ValuesPath string
Custom bool
Chart string
Version string
Repository string
Namespace string
ReleaseName string
IngressHost string
GitHubToken string
GitHubRepo string
GitHubOwner string
Domain string
Stage string
AppName string
Environment string
PRNumber string
ValuesPath string
Custom bool
Chart string
Version string
Repository string
Namespace string
ReleaseName string
IngressHost string
GitHubToken string
GitHubRepo string
GitHubOwner string
Domain string
TraefikDashboard bool
RootCA string
}

func parseFlags() *Config {
Expand All @@ -44,6 +51,8 @@ func parseFlags() *Config {
flag.StringVar(&cfg.GitHubOwner, "github-owner", "", "GitHub repository owner")
flag.StringVar(&cfg.Domain, "domain", "", "Ingress domain")
flag.BoolVar(&cfg.Custom, "custom", false, "Custom Kubernetes deployment")
flag.BoolVar(&cfg.TraefikDashboard, "traefik-dashboard", false, "Deploy Traefik dashboard")
flag.StringVar(&cfg.RootCA, "root-ca", "", "Path to root CA certificate")
flag.Parse()

if cfg.AppName == "" {
Expand Down Expand Up @@ -77,14 +86,12 @@ func (c *Config) PrintConfig() {
}

func (c *Config) setupNames() {
// Set namespace based on stage
if c.Stage == "live" {
c.Namespace = c.AppName
} else {
c.Namespace = c.AppName + "-dev"
}

// For PRs, only modify the release name to include PR number
if c.Stage == "dev" && c.PRNumber != "" {
c.ReleaseName = fmt.Sprintf("%s-pr-%s", c.AppName, c.PRNumber)
c.IngressHost = fmt.Sprintf("%s-pr-%s.%s", c.AppName, c.PRNumber, c.Domain)
Expand All @@ -94,50 +101,177 @@ func (c *Config) setupNames() {
}
}

func (c *Config) setupRootCA() error {
if c.RootCA == "" {
return nil
}

fmt.Printf("Setting up Root CA from: %s\n", c.RootCA)

var certData []byte
var err error

// Check if RootCA is a URL
if strings.HasPrefix(c.RootCA, "http://") || strings.HasPrefix(c.RootCA, "https://") {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get(c.RootCA)
if err != nil {
return fmt.Errorf("failed to download root CA: %v", err)
}
defer resp.Body.Close()

certData, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read root CA from URL: %v", err)
}
} else {
certData, err = os.ReadFile(c.RootCA)
if err != nil {
return fmt.Errorf("failed to read root CA file: %v", err)
}
}

// Create namespace
fmt.Printf("Creating namespace: %s\n", c.Namespace)
var nsBuffer bytes.Buffer
createNsCmd := exec.Command("kubectl", "create", "namespace", c.Namespace, "--dry-run=client", "-o", "yaml")
createNsCmd.Stdout = &nsBuffer

if err := createNsCmd.Run(); err != nil {
return fmt.Errorf("failed to create namespace yaml: %v", err)
}

applyNsCmd := exec.Command("kubectl", "apply", "-f", "-")
applyNsCmd.Stdin = bytes.NewReader(nsBuffer.Bytes())

if err := applyNsCmd.Run(); err != nil {
return fmt.Errorf("failed to apply namespace: %v", err)
}

// Create secret
fmt.Printf("Creating CA secret in namespace: %s\n", c.Namespace)
var secretBuffer bytes.Buffer
secretCmd := exec.Command("kubectl", "create", "secret", "generic",
"custom-root-ca",
"--from-literal=ca.crt="+string(certData),
"-n", c.Namespace,
"--dry-run=client",
"-o", "yaml")
secretCmd.Stdout = &secretBuffer

if err := secretCmd.Run(); err != nil {
return fmt.Errorf("failed to create secret yaml: %v", err)
}

applySecretCmd := exec.Command("kubectl", "apply", "-f", "-")
applySecretCmd.Stdin = bytes.NewReader(secretBuffer.Bytes())

if err := applySecretCmd.Run(); err != nil {
return fmt.Errorf("failed to apply secret: %v", err)
}

fmt.Println("Root CA setup completed successfully")
return nil
}

func (c *Config) Deploy() error {
if c.Custom {
return c.deployCustom()
}
return c.deployHelm()
}

func (c *Config) getTraefikDashboardArgs() []string {
var args []string

if c.TraefikDashboard {
args = append(args,
"--set", fmt.Sprintf("ingressRoute.dashboard.matchRule=Host(`%s`)", c.IngressHost),
"--set", "ingressRoute.dashboard.entryPoints[0]=websecure",
"--set", "ingressRoute.dashboard.enabled=true",
"--set", fmt.Sprintf("ingressRoute.healthcheck.matchRule=Host(`%s`) && PathPrefix(`/ping`)", c.IngressHost),
"--set", fmt.Sprintf("dashboard.domain=%s", c.IngressHost),
)
}

return args
}

func (c *Config) getRootCAArgs() []string {
var args []string

if c.RootCA != "" {
if c.AppName == "traefik" {
args = append(args,
"--set", "additionalArguments[0]=--serverstransport.rootcas=/usr/local/share/ca-certificates/ca.crt",
"--set", "additionalVolumes[0].name=custom-root-ca",
"--set", "additionalVolumes[0].secret.secretName=custom-root-ca",
"--set", "additionalVolumeMounts[0].name=custom-root-ca",
"--set", "additionalVolumeMounts[0].mountPath=/usr/local/share/ca-certificates/ca.crt",
"--set", "additionalVolumeMounts[0].subPath=ca.crt",
)
} else {
args = append(args,
"--set", "extraVolumes[0].name=custom-root-ca",
"--set", "extraVolumes[0].secret.secretName=custom-root-ca",
"--set", "extraVolumeMounts[0].name=custom-root-ca",
"--set", "extraVolumeMounts[0].mountPath=/etc/ssl/certs/ca.crt",
"--set", "extraVolumeMounts[0].subPath=ca.crt",
)
}
}

return args
}

func (c *Config) deployHelm() error {
// Add the Helm repository
addRepoCmd := exec.Command("helm", "repo", "add", c.AppName, c.Repository)
addRepoCmd.Stdout = os.Stdout
addRepoCmd.Stderr = os.Stderr
if err := addRepoCmd.Run(); err != nil {
if err := c.setupRootCA(); err != nil {
return err
}

var args []string
// Start with the basic command
args = append(args, "upgrade", "--install", c.ReleaseName, c.Chart)
args = append(args, "--namespace", c.Namespace, "--create-namespace")

// Add helm repo for all apps
if err := exec.Command("helm", "repo", "add", c.AppName, c.Repository).Run(); err != nil {
return fmt.Errorf("failed to add Helm repository: %v", err)
}

// Update the Helm repository
updateRepoCmd := exec.Command("helm", "repo", "update")
updateRepoCmd.Stdout = os.Stdout
updateRepoCmd.Stderr = os.Stderr
if err := updateRepoCmd.Run(); err != nil {
if err := exec.Command("helm", "repo", "update").Run(); err != nil {
return fmt.Errorf("failed to update Helm repository: %v", err)
}

// Determine the values files to use
valuesFiles := []string{filepath.Join(c.ValuesPath, "common.yml")}
stageValuesFile := filepath.Join(c.ValuesPath, fmt.Sprintf("%s.yml", c.Stage))
valuesFiles = append(valuesFiles, stageValuesFile)
args = append(args, "--set", fmt.Sprintf("ingress.host=%s", c.IngressHost))

// Deploy the Helm chart
args := []string{
"upgrade", "--install", c.ReleaseName, c.Chart,
"--namespace", c.Namespace,
"--set", fmt.Sprintf("ingress.host=%s", c.IngressHost),
// Add values files
commonValuesFile := filepath.Join(c.ValuesPath, "common.yml")
if _, err := os.Stat(commonValuesFile); err == nil {
args = append(args, "--values", commonValuesFile)
}

for _, valuesFile := range valuesFiles {
args = append(args, "--values", valuesFile)
stageValuesFile := filepath.Join(c.ValuesPath, fmt.Sprintf("%s.yml", c.Stage))
if _, err := os.Stat(stageValuesFile); err == nil {
args = append(args, "--values", stageValuesFile)
}

// Add version if specified
if c.Version != "" {
args = append(args, "--version", c.Version)
}

// Add Traefik dashboard args if applicable
if c.AppName == "traefik" {
args = append(args, c.getTraefikDashboardArgs()...)
}

// Add root CA args
args = append(args, c.getRootCAArgs()...)

cmd := exec.Command("helm", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Expand All @@ -146,7 +280,6 @@ func (c *Config) deployHelm() error {
}

func (c *Config) deployCustom() error {
// Assuming custom Kubernetes manifests are in the values path
manifests, err := filepath.Glob(filepath.Join(c.ValuesPath, "*.yml"))
if err != nil {
return fmt.Errorf("failed to find manifests: %v", err)
Expand Down

0 comments on commit e8e227e

Please sign in to comment.