diff --git a/cmd/dkl-dir2config/main.go b/cmd/dkl-dir2config/main.go index bceb80a..e63dc8d 100644 --- a/cmd/dkl-dir2config/main.go +++ b/cmd/dkl-dir2config/main.go @@ -1,9 +1,7 @@ package main import ( - "bytes" "flag" - "fmt" "log" "os" @@ -44,8 +42,9 @@ func main() { // ---------------------------------------------------------------------- for _, cluster := range src.Clusters { dst.Clusters = append(dst.Clusters, &localconfig.Cluster{ - Name: cluster.Name, - Addons: renderAddons(cluster), + Name: cluster.Name, + Addons: renderAddons(cluster), + BootstrapPods: renderBootstrapPodsDS(cluster), }) } @@ -104,34 +103,3 @@ func main() { } } - -func renderAddons(cluster *clustersconfig.Cluster) string { - if len(cluster.Addons) == 0 { - return "" - } - - addons := src.Addons[cluster.Addons] - if addons == nil { - log.Fatalf("cluster %q: no addons with name %q", cluster.Name, cluster.Addons) - } - - clusterAsMap := asMap(cluster) - clusterAsMap["kubernetes_svc_ip"] = cluster.KubernetesSvcIP().String() - clusterAsMap["dns_svc_ip"] = cluster.DNSSvcIP().String() - - buf := &bytes.Buffer{} - - for _, addon := range addons { - fmt.Fprintf(buf, "---\n# addon: %s\n", addon.Name) - err := addon.Execute(buf, clusterAsMap, nil) - - if err != nil { - log.Fatalf("cluster %q: addons %q: failed to render %q: %v", - cluster.Name, cluster.Addons, addon.Name, err) - } - - fmt.Fprintln(buf) - } - - return buf.String() -} diff --git a/cmd/dkl-dir2config/render-cluster.go b/cmd/dkl-dir2config/render-cluster.go new file mode 100644 index 0000000..deb4ae6 --- /dev/null +++ b/cmd/dkl-dir2config/render-cluster.go @@ -0,0 +1,141 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "log" + + yaml "gopkg.in/yaml.v2" + "novit.nc/direktil/local-server/pkg/clustersconfig" +) + +func renderClusterTemplates(cluster *clustersconfig.Cluster, setName string, + templates []*clustersconfig.Template) []byte { + clusterAsMap := asMap(cluster) + clusterAsMap["kubernetes_svc_ip"] = cluster.KubernetesSvcIP().String() + clusterAsMap["dns_svc_ip"] = cluster.DNSSvcIP().String() + + buf := &bytes.Buffer{} + + for _, t := range templates { + fmt.Fprintf(buf, "---\n# %s: %s\n", setName, t.Name) + err := t.Execute(buf, clusterAsMap, nil) + + if err != nil { + log.Fatalf("cluster %q: %s: failed to render %q: %v", + cluster.Name, setName, t.Name, err) + } + + fmt.Fprintln(buf) + } + + return buf.Bytes() +} + +func renderAddons(cluster *clustersconfig.Cluster) string { + if len(cluster.Addons) == 0 { + return "" + } + + addons := src.Addons[cluster.Addons] + if addons == nil { + log.Fatalf("cluster %q: no addons with name %q", cluster.Name, cluster.Addons) + } + + return string(renderClusterTemplates(cluster, "addons", addons)) +} + +type namePod struct { + Namespace string + Name string + Pod map[string]interface{} +} + +func renderBootstrapPods(cluster *clustersconfig.Cluster) (pods []namePod) { + if cluster.BootstrapPods == "" { + return nil + } + + bootstrapPods := src.BootstrapPods[cluster.BootstrapPods] + if bootstrapPods == nil { + log.Fatalf("no bootstrap pods template named %q", cluster.BootstrapPods) + } + + // render bootstrap pods + buf := bytes.NewBuffer(renderClusterTemplates(cluster, "bootstrap pods", bootstrapPods)) + dec := yaml.NewDecoder(buf) + + for n := 0; ; n++ { + podMap := map[string]interface{}{} + err := dec.Decode(podMap) + + if err == io.EOF { + break + } else if err != nil { + log.Fatalf("bootstrap pod %d: failed to parse: %v\n%s", n, err, buf.String()) + } + + if len(podMap) == 0 { + continue + } + + if podMap["metadata"] == nil { + log.Fatalf("bootstrap pod %d: no metadata\n%s", n, buf.String()) + } + + md := podMap["metadata"].(map[interface{}]interface{}) + + namespace := md["namespace"].(string) + name := md["name"].(string) + + pods = append(pods, namePod{namespace, name, podMap}) + } + + return +} + +func renderBootstrapPodsDS(cluster *clustersconfig.Cluster) string { + buf := &bytes.Buffer{} + enc := yaml.NewEncoder(buf) + for _, namePod := range renderBootstrapPods(cluster) { + pod := namePod.Pod + + md := pod["metadata"].(map[interface{}]interface{}) + labels := md["labels"] + + ann := md["annotations"] + annotations := map[interface{}]interface{}{} + if ann != nil { + annotations = ann.(map[interface{}]interface{}) + } + annotations["node.kubernetes.io/bootstrap-checkpoint"] = "true" + + md["annotations"] = annotations + + delete(md, "name") + delete(md, "namespace") + + err := enc.Encode(map[string]interface{}{ + "apiVersion": "extensions/v1beta1", + "kind": "DaemonSet", + "metadata": map[string]interface{}{ + "namespace": namePod.Namespace, + "name": namePod.Name, + "labels": labels, + }, + "spec": map[string]interface{}{ + "minReadySeconds": 60, + "selector": map[string]interface{}{ + "matchLabels": labels, + }, + "template": pod, + }, + }) + + if err != nil { + panic(err) + } + } + return buf.String() +} diff --git a/cmd/dkl-dir2config/render-context.go b/cmd/dkl-dir2config/render-context.go index 5cc775a..a463563 100644 --- a/cmd/dkl-dir2config/render-context.go +++ b/cmd/dkl-dir2config/render-context.go @@ -4,10 +4,12 @@ import ( "bytes" "fmt" "log" + "path" yaml "gopkg.in/yaml.v2" "novit.nc/direktil/local-server/pkg/clustersconfig" + "novit.nc/direktil/pkg/config" ) type renderContext struct { @@ -108,6 +110,30 @@ func (ctx *renderContext) Config() string { return render("static pods", t) } + extraFuncs["bootstrap_pods_files"] = func(dir string) (string, error) { + namePods := renderBootstrapPods(ctx.Cluster) + + defs := make([]config.FileDef, 0) + + for _, namePod := range namePods { + name := namePod.Namespace + "_" + namePod.Name + + ba, err := yaml.Marshal(namePod.Pod) + if err != nil { + return "", fmt.Errorf("bootstrap pod %s: failed to render: %v", name, err) + } + + defs = append(defs, config.FileDef{ + Path: path.Join(dir, name+".yaml"), + Mode: 0640, + Content: string(ba), + }) + } + + ba, err := yaml.Marshal(defs) + return string(ba), err + } + buf := bytes.NewBuffer(make([]byte, 0, 4096)) if err := ctx.ConfigTemplate.Execute(buf, ctxMap, extraFuncs); err != nil { log.Fatalf("failed to render config %q for host %q: %v", ctx.Group.Config, ctx.Host.Name, err) diff --git a/cmd/dkl-local-server/ws-clusters.go b/cmd/dkl-local-server/ws-clusters.go index 262b0cd..ec6562d 100644 --- a/cmd/dkl-local-server/ws-clusters.go +++ b/cmd/dkl-local-server/ws-clusters.go @@ -100,3 +100,18 @@ func wsClusterSetPassword(req *restful.Request, resp *restful.Response) { wsError(resp, err) } } + +func wsClusterBootstrapPods(req *restful.Request, resp *restful.Response) { + cluster := wsReadCluster(req, resp) + if cluster == nil { + return + } + + if len(cluster.BootstrapPods) == 0 { + log.Printf("cluster %q has no bootstrap pods defined", cluster.Name) + wsNotFound(req, resp) + return + } + + resp.Write([]byte(cluster.BootstrapPods)) +} diff --git a/go.mod b/go.mod index 8a7f55e..e2aafc5 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,9 @@ require ( golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 // indirect gopkg.in/src-d/go-billy.v4 v4.3.0 gopkg.in/src-d/go-git.v4 v4.10.0 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.4 k8s.io/apimachinery v0.0.0-20190201131811-df262fa1a1ba - novit.nc/direktil/pkg v0.0.0-20181210211743-9dc80cd34b09 + novit.nc/direktil/pkg v0.0.0-20191009054056-6e432c2a06e6 ) + +go 1.13 diff --git a/go.sum b/go.sum index d6cc79a..959ef4b 100644 --- a/go.sum +++ b/go.sum @@ -365,6 +365,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= @@ -479,7 +480,11 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= k8s.io/apimachinery v0.0.0-20190201131811-df262fa1a1ba h1:HEhywVhwcfpe9vpG7nc3wxA/YG6pb1W9zkvmFxs+320= k8s.io/apimachinery v0.0.0-20190201131811-df262fa1a1ba/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= novit.nc/direktil/pkg v0.0.0-20181210211743-9dc80cd34b09 h1:Y5GRTymITxgwaV5JVqKaxZ8U9qbLo+9jdhsGHxf/K2E= novit.nc/direktil/pkg v0.0.0-20181210211743-9dc80cd34b09/go.mod h1:z5JgQ2ybqxBC1ZE5xC9FgH4rE9whqa7Gft+iP9J9jzo= +novit.nc/direktil/pkg v0.0.0-20191009054056-6e432c2a06e6 h1:zJFvtQXH8euAzEvbJRME7EhIy7hyyNRMIVYc9tNc/oo= +novit.nc/direktil/pkg v0.0.0-20191009054056-6e432c2a06e6/go.mod h1:zwTVO6U0tXFEaga73megQIBK7yVIKZJVePaIh/UtdfU= diff --git a/pkg/clustersconfig/clustersconfig.go b/pkg/clustersconfig/clustersconfig.go index 9061272..e0c89ec 100644 --- a/pkg/clustersconfig/clustersconfig.go +++ b/pkg/clustersconfig/clustersconfig.go @@ -12,14 +12,15 @@ import ( ) type Config struct { - Hosts []*Host - Groups []*Group - Clusters []*Cluster - Configs []*Template - StaticPods []*Template `yaml:"static_pods"` - Addons map[string][]*Template - SSLConfig string `yaml:"ssl_config"` - CertRequests []*CertRequest `yaml:"cert_requests"` + Hosts []*Host + Groups []*Group + Clusters []*Cluster + Configs []*Template + StaticPods []*Template `yaml:"static_pods"` + BootstrapPods map[string][]*Template `yaml:"bootstrap_pods"` + Addons map[string][]*Template + SSLConfig string `yaml:"ssl_config"` + CertRequests []*CertRequest `yaml:"cert_requests"` } func FromBytes(data []byte) (*Config, error) { @@ -194,10 +195,11 @@ type Vars map[string]interface{} // Cluster represents a cluster of hosts, allowing for cluster-wide variables. type Cluster struct { WithRev - Name string - Domain string - Addons string - Subnets struct { + Name string + Domain string + Addons string + BootstrapPods string `yaml:"bootstrap_pods"` + Subnets struct { Services string Pods string } diff --git a/pkg/clustersconfig/defaults.go b/pkg/clustersconfig/defaults.go index 42ed2c2..d582b47 100644 --- a/pkg/clustersconfig/defaults.go +++ b/pkg/clustersconfig/defaults.go @@ -99,7 +99,11 @@ func (d *Defaults) List(rev, dir string) (names []string, err error) { return } + dirPrefix := dir + "/" err = tree.Files().ForEach(func(f *object.File) (err error) { + if !strings.HasPrefix(f.Name, dirPrefix) { + return + } if !strings.HasSuffix(f.Name, ".yaml") { return } diff --git a/pkg/clustersconfig/dir.go b/pkg/clustersconfig/dir.go index 9845708..e197ebe 100644 --- a/pkg/clustersconfig/dir.go +++ b/pkg/clustersconfig/dir.go @@ -37,7 +37,10 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) { return nil } - config := &Config{Addons: make(map[string][]*Template)} + config := &Config{ + Addons: make(map[string][]*Template), + BootstrapPods: make(map[string][]*Template), + } // load clusters names, err := store.List("clusters") @@ -127,7 +130,8 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) { } if Debug { - log.Printf("group %q: config=%q static_pods=%q", group.Name, group.Config, group.StaticPods) + 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) @@ -189,6 +193,7 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) { return nil } + // cluster addons for _, cluster := range config.Clusters { addonSet := cluster.Addons if len(addonSet) == 0 { @@ -207,6 +212,25 @@ func FromDir(dirPath, defaultsPath string) (*Config, error) { config.Addons[addonSet] = templates } + // 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 + } + // load SSL configuration if ba, err := ioutil.ReadFile(filepath.Join(dirPath, "ssl-config.json")); err == nil { config.SSLConfig = string(ba)