local-server/render-context.go
2018-06-16 23:04:58 +11:00

253 lines
5.0 KiB
Go

package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"log"
"path/filepath"
cfsslconfig "github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
yaml "gopkg.in/yaml.v2"
"novit.nc/direktil/pkg/clustersconfig"
"novit.nc/direktil/pkg/config"
)
type renderContext struct {
Host *clustersconfig.Host
Group *clustersconfig.Group
Cluster *clustersconfig.Cluster
Vars map[string]interface{}
ConfigTemplate *clustersconfig.Template
StaticPodsTemplate *clustersconfig.Template
clusterConfig *clustersconfig.Config
}
func newRenderContext(host *clustersconfig.Host, cfg *clustersconfig.Config) *renderContext {
group := cfg.Group(host.Group)
cluster := cfg.Cluster(host.Cluster)
vars := make(map[string]interface{})
for _, oVars := range []map[string]interface{}{
cluster.Vars,
group.Vars,
host.Vars,
} {
for k, v := range oVars {
vars[k] = v
}
}
return &renderContext{
Host: host,
Group: group,
Cluster: cluster,
Vars: vars,
ConfigTemplate: cfg.ConfigTemplate(group.Config),
StaticPodsTemplate: cfg.StaticPodsTemplate(group.StaticPods),
clusterConfig: cfg,
}
}
func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
if ctx.ConfigTemplate == nil {
err = notFoundError{fmt.Sprintf("config %q", ctx.Group.Config)}
return
}
ctxMap := ctx.asMap()
sslCfg, err := cfsslconfig.LoadConfig([]byte(ctx.clusterConfig.SSLConfig))
if err != nil {
return
}
secretData, err := loadSecretData(sslCfg)
if err != nil {
return
}
cluster := ctx.Cluster.Name
getKeyCert := func(name string) (kc *KeyCert, err error) {
req := ctx.clusterConfig.CSR(name)
if req == nil {
err = errors.New("no such certificate request")
return
}
if req.CA == "" {
err = errors.New("CA not defined")
return
}
buf := &bytes.Buffer{}
err = req.Execute(buf, ctxMap, nil)
if err != nil {
return
}
certReq := &csr.CertificateRequest{
KeyRequest: csr.NewBasicKeyRequest(),
}
err = json.Unmarshal(buf.Bytes(), certReq)
if err != nil {
log.Print("unmarshal failed on: ", buf)
return
}
if req.PerHost {
name = name + "/" + ctx.Host.Name
}
return secretData.KeyCert(cluster, req.CA, name, req.Profile, req.Label, certReq)
}
extraFuncs := map[string]interface{}{
"static_pods": func(name string) (string, error) {
t := ctx.clusterConfig.StaticPodsTemplate(name)
if t == nil {
return "", nil
}
buf := &bytes.Buffer{}
err := t.Execute(buf, ctxMap, nil)
if err != nil {
log.Printf("host %s: failed to render static pods: %v", ctx.Host.Name, err)
return "", err
}
return buf.String(), nil
},
"ca_key": func(name string) (s string, err error) {
ca, err := secretData.CA(cluster, name)
if err != nil {
return
}
s = string(ca.Key)
return
},
"ca_crt": func(name string) (s string, err error) {
ca, err := secretData.CA(cluster, name)
if err != nil {
return
}
s = string(ca.Cert)
return
},
"tls_key": func(name string) (s string, err error) {
kc, err := getKeyCert(name)
if err != nil {
return
}
s = string(kc.Key)
return
},
"tls_crt": func(name string) (s string, err error) {
kc, err := getKeyCert(name)
if err != nil {
return
}
s = string(kc.Cert)
return
},
}
buf := bytes.NewBuffer(make([]byte, 0, 4096))
if err = ctx.ConfigTemplate.Execute(buf, ctxMap, extraFuncs); err != nil {
return
}
if secretData.Changed() {
err = secretData.Save()
if err != nil {
return
}
}
ba = buf.Bytes()
cfg = &config.Config{}
if err = yaml.Unmarshal(buf.Bytes(), cfg); err != nil {
return
}
return
}
func (ctx *renderContext) StaticPods() (ba []byte, err error) {
if ctx.StaticPodsTemplate == nil {
err = notFoundError{fmt.Sprintf("static-pods %q", ctx.Group.StaticPods)}
return
}
buf := bytes.NewBuffer(make([]byte, 0, 4096))
if err = ctx.StaticPodsTemplate.Execute(buf, ctx.asMap(), nil); err != nil {
return
}
ba = buf.Bytes()
return
}
func (ctx *renderContext) distFilePath(path ...string) string {
return filepath.Join(append([]string{*dataDir, "dist"}, path...)...)
}
func (ctx *renderContext) Tag() (string, error) {
h := sha256.New()
_, cfg, err := ctx.Config()
if err != nil {
return "", err
}
enc := yaml.NewEncoder(h)
for _, o := range []interface{}{cfg, ctx} {
if err := enc.Encode(o); err != nil {
return "", err
}
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func (ctx *renderContext) asMap() map[string]interface{} {
ba, err := yaml.Marshal(ctx)
if err != nil {
panic(err) // shouldn't happen
}
result := make(map[string]interface{})
if err := yaml.Unmarshal(ba, result); err != nil {
panic(err)
}
// also expand cluster:
cluster := result["cluster"].(map[interface{}]interface{})
cluster["kubernetes_svc_ip"] = ctx.Cluster.KubernetesSvcIP().String()
cluster["dns_svc_ip"] = ctx.Cluster.DNSSvcIP().String()
return result
}