dir2config: switch to includes instead of ad-hoc defaults

This commit is contained in:
Mikaël Cluseau
2023-05-01 16:09:24 +02:00
parent c6320049ff
commit 0d0494b825
13 changed files with 502 additions and 265 deletions

View File

@ -25,7 +25,6 @@ var (
type Config struct {
Hosts []*Host
Groups []*Group
Clusters []*Cluster
Configs []*Template
StaticPods []*Template `yaml:"static_pods"`
@ -89,15 +88,6 @@ func (c *Config) HostByMAC(mac string) *Host {
return nil
}
func (c *Config) Group(name string) *Group {
for _, group := range c.Groups {
if group.Name == name {
return group
}
}
return nil
}
func (c *Config) Cluster(name string) *Cluster {
for _, cluster := range c.Clusters {
if cluster.Name == name {
@ -116,15 +106,6 @@ func (c *Config) ConfigTemplate(name string) *Template {
return nil
}
func (c *Config) StaticPodsTemplate(name string) *Template {
for _, s := range c.StaticPods {
if s.Name == name {
return s
}
}
return nil
}
func (c *Config) CSR(name string) *CertRequest {
for _, s := range c.CertRequests {
if s.Name == name {
@ -146,30 +127,25 @@ func (c *Config) SaveTo(path string) error {
type Template struct {
Name string
Template string
parsedTemplate *template.Template
}
func (t *Template) Execute(contextName, elementName string, wr io.Writer, data interface{}, extraFuncs map[string]interface{}) error {
if t.parsedTemplate == nil {
var templateFuncs = map[string]interface{}{
"indent": func(indent, s string) (indented string) {
indented = indent + strings.Replace(s, "\n", "\n"+indent, -1)
return
},
}
var templateFuncs = map[string]interface{}{
"indent": func(indent, s string) (indented string) {
indented = indent + strings.Replace(s, "\n", "\n"+indent, -1)
return
},
}
for name, f := range extraFuncs {
templateFuncs[name] = f
}
for name, f := range extraFuncs {
templateFuncs[name] = f
}
tmpl, err := template.New(t.Name).
Funcs(templateFuncs).
Parse(t.Template)
if err != nil {
return err
}
t.parsedTemplate = tmpl
tmpl, err := template.New(t.Name).
Funcs(templateFuncs).
Parse(t.Template)
if err != nil {
return err
}
if *templateDetailsDir != "" {
@ -204,7 +180,7 @@ func (t *Template) Execute(contextName, elementName string, wr io.Writer, data i
wr = io.MultiWriter(wr, out)
}
return t.parsedTemplate.Execute(wr, data)
return tmpl.Execute(wr, data)
}
// Host represents a host served by this server.
@ -220,26 +196,17 @@ type Host struct {
IPs []string
Cluster string
Group string
Vars Vars
}
// Group represents a group of hosts and provides their configuration.
type Group struct {
WithRev
Name string
Labels map[string]string
Annotations map[string]string
Master bool
IPXE string
Kernel string
Initrd string
BootstrapConfig string `yaml:"bootstrap_config"`
Config string
StaticPods string `yaml:"static_pods"`
Versions map[string]string
Vars Vars
BootstrapPods string `yaml:"bootstrap_pods"`
Vars Vars
}
// Vars store user-defined key-values
@ -253,13 +220,13 @@ type Cluster struct {
Labels map[string]string
Annotations map[string]string
Domain string
Addons string
BootstrapPods string `yaml:"bootstrap_pods"`
Subnets struct {
Domain string
Addons string
Subnets struct {
Services string
Pods string
}
Vars Vars
}
@ -274,7 +241,7 @@ func (c *Cluster) DNSSvcIP() net.IP {
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))
panic(fmt.Errorf("invalid services CIDR: %v", err))
}
ip := cidr.IP

View File

@ -3,7 +3,6 @@ package clustersconfig
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
@ -15,24 +14,21 @@ import (
// Debug enables debug logs from this package.
var Debug = false
func FromDir(dirPath, defaultsPath string) (*Config, error) {
if Debug {
log.Printf("loading config from dir %s (defaults from %s)", dirPath, defaultsPath)
}
func FromDir(
read func(path string) ([]byte, error),
assemble func(path string) ([]byte, error),
listBase func(path string) ([]string, error),
listMerged func(path string) ([]string, error),
) (*Config, error) {
defaults, err := NewDefaults(defaultsPath)
if err != nil {
return nil, fmt.Errorf("failed to load defaults: %v", err)
}
store := &dirStore{dirPath}
load := func(dir, name string, out Rev) error {
ba, err := store.Get(path.Join(dir, name))
load := func(dir, name string, out any) (err error) {
ba, err := assemble(filepath.Join(dir, name))
if err != nil {
return fmt.Errorf("failed to load %s/%s from dir: %v", dir, name, err)
return
}
if err = defaults.Load(dir, ".yaml", out, ba); err != nil {
return fmt.Errorf("failed to enrich %s/%s from defaults: %v", dir, name, err)
err = yaml.UnmarshalStrict(ba, out)
if err != nil {
return
}
return nil
}
@ -43,12 +39,13 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
}
// load clusters
names, err := store.List("clusters")
names, err := listBase("clusters")
if err != nil {
return nil, fmt.Errorf("failed to list clusters: %v", err)
}
for _, name := range names {
name, _ = strings.CutSuffix(name, ".yaml")
cluster := &Cluster{Name: name}
if err := load("clusters", name, cluster); err != nil {
return nil, err
@ -57,103 +54,14 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
config.Clusters = append(config.Clusters, cluster)
}
// load groups
names, err = store.List("groups")
if err != nil {
return nil, fmt.Errorf("failed to list groups: %v", err)
}
read := func(rev, filePath string) (data []byte, fromDefaults bool, err error) {
data, err = store.Get(filePath)
if err != nil {
err = fmt.Errorf("faild to read %s: %v", filePath, err)
return
}
if data != nil {
return // ok
}
if len(rev) == 0 {
err = fmt.Errorf("entry not found: %s", filePath)
return
}
data, err = defaults.ReadAll(rev, filePath+".yaml")
if err != nil {
err = fmt.Errorf("failed to read %s:%s: %v", rev, filePath, err)
return
}
fromDefaults = true
return
}
template := func(rev, dir, name string, templates *[]*Template) (ref string, err error) {
ref = name
if len(name) == 0 {
return
}
ba, fromDefaults, err := read(rev, path.Join(dir, name))
if err != nil {
return
}
if fromDefaults {
ref = rev + ":" + name
}
if !hasTemplate(ref, *templates) {
if Debug {
log.Printf("new template in %s: %s", dir, ref)
}
*templates = append(*templates, &Template{
Name: ref,
Template: string(ba),
})
}
return
}
for _, name := range names {
group := &Group{Name: name}
if err := load("groups", name, group); err != nil {
return nil, err
}
group.BootstrapConfig, err = template(group.Rev(), "configs", group.BootstrapConfig, &config.Configs)
if err != nil {
return nil, fmt.Errorf("failed to load config for group %q: %v", name, err)
}
group.Config, err = template(group.Rev(), "configs", group.Config, &config.Configs)
if err != nil {
return nil, fmt.Errorf("failed to load config for group %q: %v", name, err)
}
if Debug {
log.Printf("group %q: config=%q static_pods=%q",
group.Name, group.Config, group.StaticPods)
}
group.StaticPods, err = template(group.Rev(), "static-pods", group.StaticPods, &config.StaticPods)
if err != nil {
return nil, fmt.Errorf("failed to load static pods for group %q: %v", name, err)
}
config.Groups = append(config.Groups, group)
}
// load hosts
names, err = store.List("hosts")
names, err = listBase("hosts")
if err != nil {
return nil, fmt.Errorf("failed to list hosts: %v", err)
}
for _, name := range names {
name, _ = strings.CutSuffix(name, ".yaml")
o := &Host{Name: name}
if err := load("hosts", name, o); err != nil {
return nil, err
@ -163,28 +71,20 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
}
// load config templates
loadTemplates := func(rev, dir string, templates *[]*Template) error {
names, err := store.List(dir)
loadTemplates := func(dir string, templates *[]*Template) error {
names, err := listMerged(dir)
if err != nil {
return fmt.Errorf("failed to list %s: %v", dir, err)
}
if len(rev) != 0 {
var defaultsNames []string
defaultsNames, err = defaults.List(rev, dir)
if err != nil {
return fmt.Errorf("failed to list %s:%s: %v", rev, dir, err)
}
for _, fullName := range names {
name, _ := strings.CutSuffix(fullName, ".yaml")
names = append(names, defaultsNames...)
}
for _, name := range names {
if hasTemplate(name, *templates) {
continue
}
ba, _, err := read(rev, path.Join(dir, name))
ba, err := read(path.Join(dir, fullName))
if err != nil {
return err
}
@ -198,6 +98,8 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
return nil
}
loadTemplates("configs", &config.Configs)
// cluster addons
for _, cluster := range config.Clusters {
addonSet := cluster.Addons
@ -210,7 +112,7 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
}
templates := make([]*Template, 0)
if err = loadTemplates(cluster.Rev(), path.Join("addons", addonSet), &templates); err != nil {
if err = loadTemplates(path.Join("addons", addonSet), &templates); err != nil {
return nil, err
}
@ -218,8 +120,8 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
}
// cluster bootstrap pods
for _, cluster := range config.Clusters {
bpSet := cluster.BootstrapPods
for _, host := range config.Hosts {
bpSet := host.BootstrapPods
if bpSet == "" {
continue
}
@ -229,7 +131,7 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
}
templates := make([]*Template, 0)
if err = loadTemplates(cluster.Rev(), path.Join("bootstrap-pods", bpSet), &templates); err != nil {
if err = loadTemplates(path.Join("bootstrap-pods", bpSet), &templates); err != nil {
return nil, err
}
@ -237,14 +139,14 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) {
}
// load SSL configuration
if ba, err := ioutil.ReadFile(filepath.Join(dirPath, "ssl-config.json")); err == nil {
if ba, err := read("ssl-config.json"); err == nil {
config.SSLConfig = string(ba)
} else if !os.IsNotExist(err) {
return nil, err
}
if ba, err := ioutil.ReadFile(filepath.Join(dirPath, "cert-requests.yaml")); err == nil {
if ba, err := read("cert-requests.yaml"); err == nil {
reqs := make([]*CertRequest, 0)
if err = yaml.Unmarshal(ba, &reqs); err != nil {
return nil, err