2018-06-17 07:32:44 +00:00
|
|
|
package clustersconfig
|
|
|
|
|
|
|
|
import (
|
2019-02-28 08:27:09 +00:00
|
|
|
"fmt"
|
2018-06-17 07:32:44 +00:00
|
|
|
"io/ioutil"
|
2019-02-28 08:27:09 +00:00
|
|
|
"log"
|
2018-06-17 07:32:44 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
yaml "gopkg.in/yaml.v2"
|
|
|
|
)
|
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
defaults, err := NewDefaults(defaultsPath)
|
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
return nil, fmt.Errorf("failed to load defaults: %v", err)
|
2019-02-28 08:27:09 +00:00
|
|
|
}
|
2018-06-17 07:32:44 +00:00
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
store := &dirStore{dirPath}
|
|
|
|
load := func(dir, name string, out Rev) error {
|
2018-06-17 07:32:44 +00:00
|
|
|
ba, err := store.Get(path.Join(dir, name))
|
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
return fmt.Errorf("failed to load %s/%s from dir: %v", dir, name, err)
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
2019-03-01 01:37:51 +00:00
|
|
|
if err = defaults.Load(dir, ".yaml", out, ba); err != nil {
|
|
|
|
return fmt.Errorf("failed to enrich %s/%s from defaults: %v", dir, name, err)
|
|
|
|
}
|
|
|
|
return nil
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
2019-10-09 05:58:28 +00:00
|
|
|
config := &Config{
|
|
|
|
Addons: make(map[string][]*Template),
|
|
|
|
BootstrapPods: make(map[string][]*Template),
|
|
|
|
}
|
2019-02-28 08:27:09 +00:00
|
|
|
|
2018-06-17 07:32:44 +00:00
|
|
|
// load clusters
|
|
|
|
names, err := store.List("clusters")
|
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
return nil, fmt.Errorf("failed to list clusters: %v", err)
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, name := range names {
|
|
|
|
cluster := &Cluster{Name: name}
|
|
|
|
if err := load("clusters", name, cluster); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
config.Clusters = append(config.Clusters, cluster)
|
|
|
|
}
|
|
|
|
|
|
|
|
// load groups
|
|
|
|
names, err = store.List("groups")
|
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
return nil, fmt.Errorf("failed to list groups: %v", err)
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
read := func(rev, filePath string) (data []byte, fromDefaults bool, err error) {
|
|
|
|
data, err = store.Get(filePath)
|
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
err = fmt.Errorf("faild to read %s: %v", filePath, err)
|
2019-02-28 08:27:09 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-01 01:37:51 +00:00
|
|
|
if data != nil {
|
|
|
|
return // ok
|
|
|
|
}
|
2019-02-28 08:27:09 +00:00
|
|
|
|
2019-03-01 01:37:51 +00:00
|
|
|
if len(rev) == 0 {
|
|
|
|
err = fmt.Errorf("entry not found: %s", filePath)
|
|
|
|
return
|
|
|
|
}
|
2019-02-28 08:27:09 +00:00
|
|
|
|
2019-03-01 01:37:51 +00:00
|
|
|
data, err = defaults.ReadAll(rev, filePath+".yaml")
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("failed to read %s:%s: %v", rev, filePath, err)
|
|
|
|
return
|
2019-02-28 08:27:09 +00:00
|
|
|
}
|
|
|
|
|
2019-03-01 01:37:51 +00:00
|
|
|
fromDefaults = true
|
2019-02-28 08:27:09 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-06-17 07:32:44 +00:00
|
|
|
for _, name := range names {
|
2019-02-28 08:27:09 +00:00
|
|
|
group := &Group{Name: name}
|
|
|
|
if err := load("groups", name, group); err != nil {
|
2018-06-17 07:32:44 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-04-28 01:33:19 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
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 {
|
2019-10-09 05:58:28 +00:00
|
|
|
log.Printf("group %q: config=%q static_pods=%q",
|
|
|
|
group.Name, group.Config, group.StaticPods)
|
2019-02-28 08:27:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// load hosts
|
|
|
|
names, err = store.List("hosts")
|
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
return nil, fmt.Errorf("failed to list hosts: %v", err)
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, name := range names {
|
|
|
|
o := &Host{Name: name}
|
|
|
|
if err := load("hosts", name, o); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
config.Hosts = append(config.Hosts, o)
|
|
|
|
}
|
|
|
|
|
|
|
|
// load config templates
|
2019-02-28 08:27:09 +00:00
|
|
|
loadTemplates := func(rev, dir string, templates *[]*Template) error {
|
|
|
|
names, err := store.List(dir)
|
2018-06-17 07:32:44 +00:00
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
return fmt.Errorf("failed to list %s: %v", dir, err)
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
if len(rev) != 0 {
|
|
|
|
var defaultsNames []string
|
|
|
|
defaultsNames, err = defaults.List(rev, dir)
|
2018-06-17 07:32:44 +00:00
|
|
|
if err != nil {
|
2019-03-01 01:37:51 +00:00
|
|
|
return fmt.Errorf("failed to list %s:%s: %v", rev, dir, err)
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
names = append(names, defaultsNames...)
|
|
|
|
}
|
2018-06-17 07:32:44 +00:00
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
for _, name := range names {
|
|
|
|
if hasTemplate(name, *templates) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ba, _, err := read(rev, path.Join(dir, name))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*templates = append(*templates, &Template{
|
|
|
|
Name: name,
|
|
|
|
Template: string(ba),
|
|
|
|
})
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-09 05:58:28 +00:00
|
|
|
// cluster addons
|
2019-02-28 08:27:09 +00:00
|
|
|
for _, cluster := range config.Clusters {
|
|
|
|
addonSet := cluster.Addons
|
|
|
|
if len(addonSet) == 0 {
|
|
|
|
continue
|
2018-07-07 01:22:35 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
if _, ok := config.Addons[addonSet]; ok {
|
|
|
|
continue
|
|
|
|
}
|
2018-07-07 01:22:35 +00:00
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
templates := make([]*Template, 0)
|
|
|
|
if err = loadTemplates(cluster.Rev(), path.Join("addons", addonSet), &templates); err != nil {
|
|
|
|
return nil, err
|
2018-07-07 01:22:35 +00:00
|
|
|
}
|
2019-02-28 08:27:09 +00:00
|
|
|
|
|
|
|
config.Addons[addonSet] = templates
|
2018-07-07 01:22:35 +00:00
|
|
|
}
|
|
|
|
|
2019-10-09 05:58:28 +00:00
|
|
|
// cluster bootstrap pods
|
|
|
|
for _, cluster := range config.Clusters {
|
|
|
|
bpSet := cluster.BootstrapPods
|
|
|
|
if bpSet == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := config.BootstrapPods[bpSet]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
templates := make([]*Template, 0)
|
|
|
|
if err = loadTemplates(cluster.Rev(), path.Join("bootstrap-pods", bpSet), &templates); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
config.BootstrapPods[bpSet] = templates
|
|
|
|
}
|
|
|
|
|
2018-07-07 01:22:35 +00:00
|
|
|
// load SSL configuration
|
2018-06-17 07:32:44 +00:00
|
|
|
if ba, err := ioutil.ReadFile(filepath.Join(dirPath, "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 {
|
|
|
|
reqs := make([]*CertRequest, 0)
|
|
|
|
if err = yaml.Unmarshal(ba, &reqs); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
config.CertRequests = reqs
|
|
|
|
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
|
2019-02-28 08:27:09 +00:00
|
|
|
func hasTemplate(name string, templates []*Template) bool {
|
|
|
|
for _, tmpl := range templates {
|
|
|
|
if tmpl.Name == name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-06-17 07:32:44 +00:00
|
|
|
type dirStore struct {
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
2018-07-07 01:22:35 +00:00
|
|
|
// listDir
|
|
|
|
func (b *dirStore) listDir(prefix string) (subDirs []string, err error) {
|
|
|
|
entries, err := ioutil.ReadDir(filepath.Join(b.path, prefix))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
subDirs = make([]string, 0, len(entries))
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
if !entry.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
name := entry.Name()
|
|
|
|
|
|
|
|
if len(name) == 0 || name[0] == '.' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
subDirs = append(subDirs, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-06-17 07:32:44 +00:00
|
|
|
// Names is part of the kvStore interface
|
|
|
|
func (b *dirStore) List(prefix string) ([]string, error) {
|
|
|
|
files, err := filepath.Glob(filepath.Join(b.path, filepath.Join(path.Split(prefix)), "*.yaml"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
names := make([]string, 0, len(files))
|
|
|
|
for _, f := range files {
|
|
|
|
f2 := strings.TrimSuffix(f, ".yaml")
|
|
|
|
f2 = filepath.Base(f2)
|
|
|
|
|
|
|
|
if f2[0] == '.' { // ignore hidden files
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
names = append(names, f2)
|
|
|
|
}
|
|
|
|
|
|
|
|
return names, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load is part of the DataBackend interface
|
2019-02-28 08:27:09 +00:00
|
|
|
func (b *dirStore) Get(key string) (ba []byte, err error) {
|
|
|
|
ba, err = ioutil.ReadFile(filepath.Join(b.path, filepath.Join(path.Split(key))+".yaml"))
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return
|
2018-06-17 07:32:44 +00:00
|
|
|
}
|