From 093739d99a97626f4adf5ea4c20a0fe0e0448fae Mon Sep 17 00:00:00 2001 From: Kalman Speier Date: Mon, 16 Dec 2024 16:03:29 +0100 Subject: [PATCH] refactoring templater --- Makefile | 2 +- cmd/kube.go | 50 +++++++++++++ cmd/run.go | 2 +- internal/parser/js/js.go | 8 +++ internal/schema/component.go | 66 ++++-------------- internal/schema/kubeconfig.go | 97 ++++++++++++++++++++++++++ internal/schema/stack.go | 1 + internal/schema/templater.go | 128 ++++++++++++++++++++++++++++++++++ internal/schema/tpl.go | 39 ----------- stacks/dev.stack.js | 20 ++++++ 10 files changed, 318 insertions(+), 95 deletions(-) create mode 100644 cmd/kube.go create mode 100644 internal/schema/kubeconfig.go create mode 100644 internal/schema/templater.go delete mode 100644 internal/schema/tpl.go diff --git a/Makefile b/Makefile index 1409e28..6d42496 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=v0.2.6 +VERSION=v0.3.0 tag: @git tag -a ${VERSION} -m "version ${VERSION}" && git push origin ${VERSION} diff --git a/cmd/kube.go b/cmd/kube.go new file mode 100644 index 0000000..150afa4 --- /dev/null +++ b/cmd/kube.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/moonwalker/comet/internal/exec" + "github.com/moonwalker/comet/internal/log" + "github.com/moonwalker/comet/internal/parser" +) + +var ( + kubeCmd = &cobra.Command{ + Use: "kube [stack]", + Short: "Kubeconfig for stack", + Aliases: []string{"kubeconfig"}, + RunE: kube, + Args: cobra.ExactArgs(1), + } +) + +func init() { + rootCmd.AddCommand(kubeCmd) +} + +func kube(cmd *cobra.Command, args []string) error { + executor, err := exec.GetExecutor(config) + if err != nil { + log.Fatal(err) + } + + stacks, err := parser.LoadStacks(config.StacksDir) + if err != nil { + return err + } + + stack, err := stacks.GetStack(args[0]) + if stack == nil { + return err + } + + kubeconfig, err := stack.Kubeconfig.Render(config, stacks, executor, stack.Name) + if err != nil { + return err + } + + fmt.Println(kubeconfig) + return nil +} diff --git a/cmd/run.go b/cmd/run.go index 411a07b..16e39db 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -41,7 +41,7 @@ func run(args []string, reverse bool, cb func(*schema.Component, schema.Executor } for _, component := range components { - err := component.EnsurePath(config) + err := component.EnsurePath(config, true) if err != nil { log.Fatal(err) } diff --git a/internal/parser/js/js.go b/internal/parser/js/js.go index 615287e..bdd0688 100644 --- a/internal/parser/js/js.go +++ b/internal/parser/js/js.go @@ -51,6 +51,7 @@ func (vm *jsinterpreter) Parse(path string) (*schema.Stack, error) { vm.rt.Set("backend", vm.registerBackend(stack)) vm.rt.Set("component", vm.registerComponent(stack)) vm.rt.Set("append", vm.registerAppend(stack)) + vm.rt.Set("kubeconfig", vm.registerKubeconfig(stack)) src := result.OutputFiles[0].Contents _, err := vm.rt.RunString(string(src)) @@ -147,6 +148,13 @@ func (vm *jsinterpreter) registerAppend(stack *schema.Stack) func(string, []stri } } +func (vm *jsinterpreter) registerKubeconfig(stack *schema.Stack) func(*schema.Kubeconfig) { + return func(kubeconfig *schema.Kubeconfig) { + log.Debug("register kubeconfig", "stack", stack.Name) + stack.Kubeconfig = kubeconfig + } +} + func (vm *jsinterpreter) getProxy(get func(property string) any) goja.Proxy { obj := vm.rt.NewObject() return vm.rt.NewProxy(obj, &goja.ProxyTrapConfig{ diff --git a/internal/schema/component.go b/internal/schema/component.go index a27fbdb..ce731e3 100644 --- a/internal/schema/component.go +++ b/internal/schema/component.go @@ -3,9 +3,7 @@ package schema import ( "fmt" "path" - "path/filepath" - "dario.cat/mergo" cp "github.com/otiai10/copy" ) @@ -22,16 +20,17 @@ type ( ) // copy component to workdir if needed -func (c *Component) EnsurePath(config *Config) error { +func (c *Component) EnsurePath(config *Config, copy bool) error { if len(config.WorkDir) > 0 { dest := path.Join(config.WorkDir, c.Stack, c.Name) - err := cp.Copy(c.Path, dest) - if err != nil { - return err + if copy { + err := cp.Copy(c.Path, dest) + if err != nil { + return err + } } c.Path = dest } - return nil } @@ -42,73 +41,32 @@ func (c *Component) PropertyRef(property string) string { // resolve templates in component func (c *Component) ResolveVars(config *Config, stacks *Stacks, executor Executor) error { - stack, err := stacks.GetStack(c.Stack) - if err != nil { - return err - } - - stacksDirAbs, err := filepath.Abs(config.StacksDir) - if err != nil { - return err - } - tdata := map[string]interface{}{ - "stacks_dir": stacksDirAbs, - "stack": stack.Name, - "component": c.Name, + "component": c.Name, } - err = mergo.Merge(&tdata, stack.Options) + + t, err := NewTemplater(config, stacks, executor, c.Stack) if err != nil { return err } - funcMap := map[string]interface{}{ - "state": stateFunc(stacks, executor), - } - // template backend - c.Backend.Config, err = tpl(c.Backend.Config, tdata, funcMap) + c.Backend.Config, err = t.Map(c.Backend.Config, tdata) if err != nil { return err } // template vars - c.Inputs, err = tpl(c.Inputs, tdata, funcMap) + c.Inputs, err = t.Map(c.Inputs, tdata) if err != nil { return err } // template providers - c.Providers, err = tpl(c.Providers, tdata, funcMap) + c.Providers, err = t.Map(c.Providers, tdata) if err != nil { return err } return nil } - -func stateFunc(stacks *Stacks, executor Executor) func(stack, component string) any { - return func(stack, component string) any { - refStack, err := stacks.GetStack(stack) - if err != nil { - return nil - } - - refComponent, err := refStack.GetComponent(component) - if err != nil { - return nil - } - - refState, err := executor.Output(refComponent) - if err != nil { - return nil - } - - res := map[string]string{} - for k, v := range refState { - res[k] = v.String() - } - - return res - } -} diff --git a/internal/schema/kubeconfig.go b/internal/schema/kubeconfig.go new file mode 100644 index 0000000..de9452a --- /dev/null +++ b/internal/schema/kubeconfig.go @@ -0,0 +1,97 @@ +package schema + +import ( + "bytes" + "text/template" +) + +type ( + Kubeconfig struct { + Current int `json:"current"` + Clusters []*KubeconfgCluster `json:"clusters"` + } + + KubeconfgCluster struct { + Context string `json:"context"` + Host string `json:"host"` + Cert string `json:"cert"` + ExecApiVersion string `json:"exec_apiversion"` + ExecCommand string `json:"exec_command"` + ExecArgs []string `json:"exec_args"` + } +) + +const ( + KubeconfigDefaultApiVersion = "client.authentication.k8s.io/v1beta1" +) + +func (k *Kubeconfig) Render(config *Config, stacks *Stacks, executor Executor, stackName string) (string, error) { + if len(k.Clusters) == 0 { + return "", nil + } + + if k.Current < 0 || k.Current >= len(k.Clusters) { + k.Current = 0 + } + + for _, c := range k.Clusters { + if len(c.ExecApiVersion) == 0 { + c.ExecApiVersion = KubeconfigDefaultApiVersion + } + } + + t, err := NewTemplater(config, stacks, executor, stackName) + if err != nil { + return "", err + } + + err = t.Any(k, nil) + if err != nil { + return "", err + } + + tmpl, err := template.New("k").Parse(kubeconfigTemplate) + if err != nil { + return "", err + } + + var b bytes.Buffer + err = tmpl.Execute(&b, k) + if err != nil { + return "", err + } + + return b.String(), nil +} + +const kubeconfigTemplate = `apiVersion: v1 +kind: Config +current-context: {{ (index .Clusters .Current).Context }} +contexts: +{{- range .Clusters }} + - name: {{ .Context }} + context: + cluster: {{ .Context }} + user: {{ .Context }} +{{- end }} +clusters: +{{- range .Clusters }} + - name: {{ .Context }} + cluster: + server: {{ .Host }} + certificate-authority-data: {{ .Cert }} +{{- end }} +users: +{{- range .Clusters }} + - name: {{ .Context }} + user: + exec: + apiVersion: {{ .ExecApiVersion }} + command: {{ .ExecCommand }} + {{- if .ExecArgs }} + args: + {{- range .ExecArgs }} + - {{ . }} + {{- end }} + {{- end }} +{{- end }}` diff --git a/internal/schema/stack.go b/internal/schema/stack.go index 26722ab..e6ac837 100644 --- a/internal/schema/stack.go +++ b/internal/schema/stack.go @@ -22,6 +22,7 @@ type ( Backend Backend `json:"backend"` Appends map[string][]string `json:"appends"` Components []*Component `json:"components"` + Kubeconfig *Kubeconfig `json:"kubeconfig"` } Stacks struct { diff --git a/internal/schema/templater.go b/internal/schema/templater.go new file mode 100644 index 0000000..26c7715 --- /dev/null +++ b/internal/schema/templater.go @@ -0,0 +1,128 @@ +package schema + +import ( + "bytes" + "encoding/json" + "fmt" + "path/filepath" + "strings" + "text/template" + + "dario.cat/mergo" +) + +type Templater struct { + data map[string]interface{} + funcMap template.FuncMap +} + +func NewTemplater(config *Config, stacks *Stacks, executor Executor, stackName string) (*Templater, error) { + stacksDirAbs, err := filepath.Abs(config.StacksDir) + if err != nil { + return nil, err + } + + stack, err := stacks.GetStack(stackName) + if err != nil { + return nil, err + } + + data := map[string]interface{}{ + "stacks_dir": stacksDirAbs, + "stack": stack.Name, + } + + err = mergo.Merge(&data, stack.Options) + if err != nil { + return nil, err + } + + return &Templater{ + data: data, + funcMap: template.FuncMap{ + "state": stateFunc(config, stacks, executor), + }, + }, nil +} + +func (t *Templater) Map(src any, data any) (map[string]interface{}, error) { + dst := make(map[string]interface{}) + + err := t.Execute(src, &dst, data) + if err != nil { + return nil, err + } + + return dst, nil +} + +func (t *Templater) Any(v any, data any) error { + return t.Execute(v, &v, data) +} + +func (t *Templater) Execute(src any, dst any, data any) error { + jb, err := json.Marshal(src) + if err != nil { + return err + } + + // remove escaped quotes + js := strings.ReplaceAll(string(jb), `\"`, `"`) + + tmpl, err := template.New("t").Funcs(t.funcMap).Parse(js) + if err != nil { + return err + } + + if data != nil { + err = mergo.Merge(&t.data, data) + if err != nil { + return err + } + } + + var b bytes.Buffer + err = tmpl.Execute(&b, t.data) + if err != nil { + return err + } + + err = json.Unmarshal(b.Bytes(), &dst) + if err != nil { + return err + } + + return nil +} + +func stateFunc(config *Config, stacks *Stacks, executor Executor) func(stack, component string) any { + return func(stack, component string) any { + refStack, err := stacks.GetStack(stack) + if err != nil { + return nil + } + + refComponent, err := refStack.GetComponent(component) + if err != nil { + return nil + } + + err = refComponent.EnsurePath(config, false) + if err != nil { + return nil + } + + refState, err := executor.Output(refComponent) + if err != nil { + fmt.Println(err) + return nil + } + + res := map[string]string{} + for k, v := range refState { + res[k] = v.String() + } + + return res + } +} diff --git a/internal/schema/tpl.go b/internal/schema/tpl.go deleted file mode 100644 index f49fadf..0000000 --- a/internal/schema/tpl.go +++ /dev/null @@ -1,39 +0,0 @@ -package schema - -import ( - "bytes" - "encoding/json" - "strings" - "text/template" -) - -func tpl(v any, data any, funcMap map[string]any) (map[string]interface{}, error) { - res := make(map[string]interface{}) - - jb, err := json.Marshal(v) - if err != nil { - return nil, err - } - - // remove escaped quotes - js := strings.ReplaceAll(string(jb), `\"`, `"`) - - t := template.New("t").Funcs(funcMap) - tmpl, err := t.Parse(js) - if err != nil { - return nil, err - } - - var b bytes.Buffer - err = tmpl.Execute(&b, data) - if err != nil { - return nil, err - } - - err = json.Unmarshal(b.Bytes(), &res) - if err != nil { - return nil, err - } - - return res, nil -} diff --git a/stacks/dev.stack.js b/stacks/dev.stack.js index 65b2695..ffd4e75 100644 --- a/stacks/dev.stack.js +++ b/stacks/dev.stack.js @@ -49,3 +49,23 @@ const metsrv = component('metsrv', 'test/modules/kubernetes', { helm: helm } }) + +kubeconfig({ + current: 0, + clusters: [ + { + context: 'cluster-1-gke-eu', + host: '1.2.3.4', + cert: 'LS0tL...', + exec_command: 'gke-gcloud-auth-plugin', + exec_args: [ + 'kubernetes', + 'cluster', + 'kubeconfig', + 'exec-credential', + '--version=v1beta1', + '--context=default' + ] + } + ] +})