Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
7.8 kB · 318 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319package main
import ( "embed" "encoding/json" "errors" "flag" "fmt" "io" "log" "os"
atcinstaller "github.com/yokecd/yoke/cmd/atc-installer/installer" "github.com/yokecd/yoke/pkg/flight" externaldns "go.techaro.lol/hypercloud/helm/external-dns" "k8s.io/apimachinery/pkg/util/yaml"
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" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured")
type Config struct { ACME *ACME `json:"acme"` ATC atcinstaller.Config `json:"atc"` ExternalDNS map[string]any `json:"externalDNS"` ExternalIP IP `json:"externalIP"`}
type IP struct { IPv4 *string `json:"ipv4,omitempty"` IPv6 *string `json:"ipv6,omitempty"`}
func (ip IP) Valid() error { var errs []error if ip.IPv4 == nil && ip.IPv6 == nil { errs = append(errs, fmt.Errorf("ipv4 or ipv6 is required")) } if len(errs) > 0 { return errors.Join(errs...) }
return nil}
func (c Config) Valid() error { var errs []error if c.ACME == nil { errs = append(errs, fmt.Errorf("acme is required")) } else { if err := c.ACME.Valid(); err != nil { errs = append(errs, fmt.Errorf("acme is invalid: %w", err)) } } if c.ExternalDNS == nil { errs = append(errs, fmt.Errorf("externalDNS is required")) } if c.ExternalDNS["extraArgs"] == nil { errs = append(errs, fmt.Errorf("externalDNS.extraArgs is required")) } if _, ok := c.ExternalDNS["extraArgs"].([]any); !ok { errs = append(errs, fmt.Errorf("externalDNS.extraArgs must be a list of strings, it is %T", c.ExternalDNS["extraArgs"])) } if err := c.ExternalIP.Valid(); err != nil { errs = append(errs, fmt.Errorf("externalIP is invalid: %w", err)) } if len(errs) > 0 { return errors.Join(errs...) }
return nil}
type ACME struct { Email string `json:"email"` Directories []ACMEDirectory `json:"directories"` Solvers []acmev1.ACMEChallengeSolver `json:"solvers"`}
func (acme ACME) Valid() error { var errs []error if acme.Email == "" { errs = append(errs, fmt.Errorf("email is required")) } if len(acme.Directories) == 0 { errs = append(errs, fmt.Errorf("directories are required")) } for _, directory := range acme.Directories { if err := directory.Valid(); err != nil { errs = append(errs, fmt.Errorf("directory %s is invalid: %w", directory.Name, err)) } }
if len(errs) > 0 { return errors.Join(errs...) }
return nil}
type ACMEDirectory struct { URL string `json:"url"` Name string `json:"name"`}
func (ad ACMEDirectory) Valid() error { var errs []error if ad.URL == "" { errs = append(errs, fmt.Errorf("url is required")) } if ad.Name == "" { errs = append(errs, fmt.Errorf("name is required")) } if len(errs) > 0 { return errors.Join(errs...) }
return nil}
//go:embed data/*.yamlvar data embed.FS
func main() { flag.Parse() if err := run(); err != nil { log.Fatal(err) }}
func run() error { var cfg Config fin, err := data.Open("data/default-config.yaml") if err != nil { return fmt.Errorf("failed to open default-config.yaml: %w", err) } defer fin.Close()
if err := yaml.NewYAMLToJSONDecoder(fin).Decode(&cfg); err != nil { return fmt.Errorf("failed to decode default-config.yaml: %w", err) }
if err := yaml.NewYAMLToJSONDecoder(os.Stdin).Decode(&cfg); err != nil && err != io.EOF { return fmt.Errorf("failed to decode stdin: %w", err) }
if err := cfg.Valid(); err != nil { return fmt.Errorf("config is invalid: %w", err) }
var result []any
result = append(result, []any{corev1.Namespace{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Namespace", }, ObjectMeta: metav1.ObjectMeta{ Name: "tor-controller-system", }, }})
fin, err = data.Open("data/tor-controller.yaml") if err != nil { return fmt.Errorf("failed to open tor-controller.yaml: %w", err) } defer fin.Close()
torController, err := readEveryDocument(fin) if err != nil { return fmt.Errorf("failed to read tor-controller.yaml: %w", err) }
result = append(result, torController)
result = append(result, []any{corev1.Namespace{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Namespace", }, ObjectMeta: metav1.ObjectMeta{ Name: "cert-manager", }, }})
fin, err = data.Open("data/cert-manager.yaml") if err != nil { return fmt.Errorf("failed to open cert-manager.yaml: %w", err) } defer fin.Close()
certManager, err := readEveryDocument(fin) if err != nil { return fmt.Errorf("failed to read cert-manager.yaml: %w", err) }
result = append(result, certManager)
var directories []any
for _, directory := range cfg.ACME.Directories { directories = append(directories, makeClusterIssuer(cfg.ACME, directory)) }
result = append(result, directories)
fin, err = data.Open("data/external-dns-crd.yaml") if err != nil { return fmt.Errorf("failed to open external-dns-crd.yaml: %w", err) } defer fin.Close()
extDNSCRD, err := readEveryDocument(fin) if err != nil { return fmt.Errorf("failed to read external-dns-crd.yaml: %w", err) }
result = append(result, extDNSCRD)
extraArgs, ok := cfg.ExternalDNS["extraArgs"].([]any) if !ok { return fmt.Errorf("externalDNS.extraArgs must be a list of something") }
for _, recordType := range []string{"A", "AAAA", "CNAME", "TXT"} { extraArgs = append(extraArgs, "--managed-record-types="+recordType) }
if cfg.ExternalIP.IPv4 != nil { extraArgs = append(extraArgs, "--default-targets="+*cfg.ExternalIP.IPv4) } if cfg.ExternalIP.IPv6 != nil { extraArgs = append(extraArgs, "--default-targets="+*cfg.ExternalIP.IPv6) }
cfg.ExternalDNS["extraArgs"] = extraArgs
externalDNS, err := externaldns.RenderChart(flight.Release(), flight.Namespace(), cfg.ExternalDNS) if err != nil { return fmt.Errorf("failed to render external-dns chart: %w", err) }
// Filter out PodDisruptionBudgets from externalDNS var filteredExternalDNS []*unstructured.Unstructured for _, obj := range externalDNS { if obj.GetKind() == "PodDisruptionBudget" { // Skip PodDisruptionBudgets continue } filteredExternalDNS = append(filteredExternalDNS, obj) }
result = append(result, filteredExternalDNS)
stages, err := atcinstaller.Run(cfg.ATC) if err != nil { return fmt.Errorf("failed to run atc installer: %w", err) }
for _, stage := range stages { result = append(result, stage) }
return json.NewEncoder(os.Stdout).Encode(result)}
func makeClusterIssuer(acme *ACME, directory ACMEDirectory) any { return certmanagerv1.ClusterIssuer{ TypeMeta: metav1.TypeMeta{ APIVersion: certmanagerv1.SchemeGroupVersion.Identifier(), Kind: "ClusterIssuer", }, ObjectMeta: metav1.ObjectMeta{ Name: directory.Name, }, Spec: certmanagerv1.IssuerSpec{ IssuerConfig: certmanagerv1.IssuerConfig{ ACME: &acmev1.ACMEIssuer{ Server: directory.URL, Email: acme.Email, PrivateKey: certmanagermetav1.SecretKeySelector{ LocalObjectReference: certmanagermetav1.LocalObjectReference{ Name: directory.Name + "-private-key", }, }, Solvers: acme.Solvers, }, }, }, }}
func readEveryDocument(r io.Reader) ([]unstructured.Unstructured, error) { var result []unstructured.Unstructured
dec := yaml.NewYAMLToJSONDecoder(r) for { var doc unstructured.Unstructured if err := dec.Decode(&doc); err != nil { if err == io.EOF { break } return nil, err }
if doc.GetAPIVersion() == "" { continue }
result = append(result, doc) }
return result, nil}