package clustersconfig import ( "flag" "fmt" "io" "log" "net" "os" "path/filepath" "strings" "text/template" yaml "gopkg.in/yaml.v2" ) var ( templateDetailsDir = flag.String("template-details-dir", filepath.Join(os.TempDir(), "dkl-dir2config"), "write details of template execute in this dir") templateID = 0 ) type Config struct { Hosts []*Host Clusters []*Cluster Configs []*Template StaticPods map[string][]*Template `yaml:"static_pods"` Addons map[string][]*Template SSLConfig string `yaml:"ssl_config"` CertRequests []*CertRequest `yaml:"cert_requests"` } func FromBytes(data []byte) (*Config, error) { config := &Config{Addons: make(map[string][]*Template)} if err := yaml.Unmarshal(data, config); err != nil { return nil, err } return config, nil } func FromFile(path string) (*Config, error) { ba, err := os.ReadFile(path) if err != nil { return nil, err } return FromBytes(ba) } func (c *Config) Host(name string) *Host { for _, host := range c.Hosts { if host.Name == name { return host } } return nil } func (c *Config) HostByIP(ip string) *Host { for _, host := range c.Hosts { if host.IP == ip { return host } for _, otherIP := range host.IPs { if otherIP == ip { return host } } } return nil } func (c *Config) HostByMAC(mac string) *Host { // a bit of normalization mac = strings.Replace(strings.ToLower(mac), "-", ":", -1) for _, host := range c.Hosts { if strings.ToLower(host.MAC) == mac { return host } } return nil } func (c *Config) Cluster(name string) *Cluster { for _, cluster := range c.Clusters { if cluster.Name == name { return cluster } } return nil } func (c *Config) ConfigTemplate(name string) *Template { for _, cfg := range c.Configs { if cfg.Name == name { return cfg } } return nil } func (c *Config) CSR(name string) *CertRequest { for _, s := range c.CertRequests { if s.Name == name { return s } } return nil } func (c *Config) SaveTo(path string) error { ba, err := yaml.Marshal(c) if err != nil { return err } return os.WriteFile(path, ba, 0600) } type Template struct { Name string Template string } func (t *Template) Execute(contextName, elementName string, wr io.Writer, data interface{}, extraFuncs map[string]interface{}) error { var templateFuncs = map[string]interface{}{ "indent": func(indent, s string) (indented string) { indented = indent + strings.Replace(s, "\n", "\n"+indent, -1) return }, "yaml": func(v any) (s string, err error) { ba, err := yaml.Marshal(v) s = string(ba) return }, } for name, f := range extraFuncs { templateFuncs[name] = f } tmpl, err := template.New(t.Name). Funcs(templateFuncs). Parse(t.Template) if err != nil { return err } if *templateDetailsDir != "" { templateID++ base := filepath.Join(*templateDetailsDir, contextName, fmt.Sprintf("%s-%03d", elementName, templateID)) os.MkdirAll(base, 0700) base += string(filepath.Separator) log.Print("writing template details: ", base, "{in,data,out}") if err := os.WriteFile(base+"in", []byte(t.Template), 0600); err != nil { return err } yamlBytes, err := yaml.Marshal(data) if err != nil { return err } if err := os.WriteFile(base+"data", yamlBytes, 0600); err != nil { return err } out, err := os.Create(base + "out") if err != nil { return err } defer out.Close() wr = io.MultiWriter(wr, out) } return tmpl.Execute(wr, data) } // Host represents a host served by this server. type Host struct { WithRev Template bool `json:",omitempty"` Name string Labels map[string]string `json:",omitempty"` Annotations map[string]string `json:",omitempty"` MAC string `json:",omitempty"` IP string IPs []string `json:",omitempty"` Cluster string Group string Net string IPFrom map[string]string `json:",omitempty" yaml:"ip_from"` IPXE string `json:",omitempty"` Kernel string Initrd string BootstrapConfig string `yaml:"bootstrap_config"` Config string Versions map[string]string StaticPods string `yaml:"static_pods"` Vars Vars } // Vars store user-defined key-values type Vars map[string]interface{} // Cluster represents a cluster of hosts, allowing for cluster-wide variables. type Cluster struct { WithRev Name string Labels map[string]string Annotations map[string]string Domain string Addons []string Subnets struct { Services string Pods string } Vars Vars } func (c *Cluster) KubernetesSvcIP() net.IP { return c.NthSvcIP(1) } func (c *Cluster) DNSSvcIP() net.IP { return c.NthSvcIP(2) } func (c *Cluster) NthSvcIP(n byte) net.IP { _, cidr, err := net.ParseCIDR(c.Subnets.Services) if err != nil { panic(fmt.Errorf("invalid services CIDR: %v", err)) } ip := cidr.IP ip[len(ip)-1] += n return ip }