-
Florent Aide authoredFlorent Aide authored
cmd.go 5.85 KiB
package runner
import (
"bytes"
"fmt"
"os"
"path/filepath"
"text/template"
"github.com/go-cmd/cmd"
"github.com/rs/zerolog"
"gopkg.in/yaml.v2"
)
func RunCMD(name string, args ...string) (err error, stdout, stderr []string) {
// helm template -f base.yaml -f base.values.yaml -f ns.yaml -f ns.values.yaml
// ytt -f /chart-folder -f base.yaml -f ns.yaml -v ... -v ...
c := cmd.NewCmd(name, args...)
statusChan := c.Start()
status := <-statusChan
if status.Error != nil {
return err, status.Stdout, status.Stderr
}
stdout = status.Stdout
stderr = status.Stderr
return
}
func NewCmdConfig(logger zerolog.Logger, configDir string, namespace string) (*CmdConfig, error) {
cmdConfig := &CmdConfig{}
cmdConfig.Spec.Charts.Helm = make(map[string]CmdHelmChart)
cmdConfig.Spec.Charts.Ytt = make(map[string]CmdYttChart)
cmdConfig.Namespace = namespace
cmdConfig.Logger = logger
baseCfg, err := NewConfig(configDir)
if err != nil {
return nil, err
}
nsCfgDir := filepath.Join(configDir, namespace)
nsCfg, err := NewConfig(nsCfgDir)
if err != nil && err != os.ErrNotExist {
return nil, err
}
// first "import" all variables from baseCfg
cmdConfig.Spec.Variables = baseCfg.Spec.Variables
// then merge in all variables from the nsCfg
cmdConfig.MergeVariables(nsCfg)
// TODO:
// - merge baseCfg & nsCfg charts
if err := cmdConfig.importCharts(baseCfg); err != nil {
return nil, err
}
if err := cmdConfig.importCharts(nsCfg); err != nil {
return nil, err
}
// - hydrate
if err := cmdConfig.hydrate(); err != nil {
return nil, err
}
return cmdConfig, nil
}
type CmdConfig struct {
Spec CmdSpec
Namespace string
Logger zerolog.Logger
}
type CmdSpec struct {
Variables []Variable
Charts CmdChart
}
type CmdChart struct {
Helm map[string]CmdHelmChart
Ytt map[string]CmdYttChart
}
type CmdHelmChart struct {
Type string
Name string
Values []string
}
type CmdYttChart struct {
Type string
Name string
Files []string
Values []Value
}
// hydrate expands templated variables in our config with concrete values
func (c *CmdConfig) hydrate() error {
if err := c.hydrateHelmCharts(); err != nil {
return err
}
if err := c.hydrateYttCharts(); err != nil {
return err
}
return nil
}
func (c *CmdConfig) prepareVariables(v []Variable) map[string]string {
variables := make(map[string]string)
for _, variable := range v {
variables[variable.Name] = variable.Value
}
variables["namespace"] = c.Namespace
return variables
}
func (c *CmdConfig) hydrateYttCharts() error {
for entryFileName, entry := range c.Spec.Charts.Ytt {
for valIndex, val := range entry.Values {
valueTmpl, err := template.New("ytt entry value").Parse(val.Value)
if err != nil {
return fmt.Errorf("failed to parse ytt entry value as template: %q, %w", val.Value, err)
}
buf := new(bytes.Buffer)
if err := valueTmpl.Execute(buf, c.prepareVariables(c.Spec.Variables)); err != nil {
return fmt.Errorf("failed to hydrate ytt entry: %q, %w", val.Value, err)
}
// replace original content with hydrated version
c.Spec.Charts.Ytt[entryFileName].Values[valIndex].Value = buf.String()
}
}
return nil
}
func (c *CmdConfig) hydrateHelmCharts() error {
for name, chart := range c.Spec.Charts.Helm {
var newVals []string
for _, value := range chart.Values {
rawChartValue, err := yaml.Marshal(value)
if err != nil {
return fmt.Errorf("failed to get chart values as string: %w", err)
}
valueTmpl, err := template.New("chart").Parse(string(rawChartValue))
if err != nil {
return fmt.Errorf("failed to parse chart values as template: %q, %w", chart.Values, err)
}
buf := new(bytes.Buffer)
if err := valueTmpl.Execute(buf, c.prepareVariables(c.Spec.Variables)); err != nil {
return fmt.Errorf("failed to hydrate chart values entry: %q, %w", chart.Values, err)
}
newVals = append(newVals, buf.String())
}
chart.Values = newVals
c.Spec.Charts.Helm[name] = chart
}
return nil
}
// MergeVariables takes a config (from a file, not a cmd one) and import its
// variables into the current cmdconfig by replacing old ones
// and adding the new ones
func (c *CmdConfig) MergeVariables(other *Config) {
for _, variable := range other.Spec.Variables {
c.overlayVariable(variable)
}
}
// overlayVariable takes a variable in and either replaces an existing variable
// of the same name or create a new variable in the config if no matching name
// is found
func (c *CmdConfig) overlayVariable(v Variable) {
// find same variable by name and replace is value
// if not found then create the variable
for index, originalVariable := range c.Spec.Variables {
if originalVariable.Name == v.Name {
c.Spec.Variables[index].Value = v.Value
return
}
}
c.Spec.Variables = append(c.Spec.Variables, v)
}
func (c *CmdConfig) importCharts(other *Config) error {
if err := c.importHelmCharts(other.Spec.Charts.Helm); err != nil {
return nil
}
if err := c.importYttCharts(other.Spec.Charts.Ytt); err != nil {
return nil
}
return nil
}
func (c *CmdConfig) importHelmCharts(helmCharts map[string]HelmChart) error {
for id, chart := range helmCharts {
convertedChart, err := cmdHelmChartFromHelmChart(chart)
if err != nil {
return err
}
_, ok := c.Spec.Charts.Helm[id]
if !ok {
// we have no chart by that name yet...
// create one
c.Spec.Charts.Helm[id] = *convertedChart
continue
}
// else just append values to existing one
convertedChart.Values = append(c.Spec.Charts.Helm[id].Values, convertedChart.Values...)
c.Spec.Charts.Helm[id] = *convertedChart
}
return nil
}
func cmdHelmChartFromHelmChart(c HelmChart) (*CmdHelmChart, error) {
strValues, err := yaml.Marshal(c.Values)
if err != nil {
return nil, err
}
return &CmdHelmChart{
Type: c.Type,
Name: c.Name,
Values: []string{string(strValues)},
}, nil
}
func (c *CmdConfig) importYttCharts(yttCharts map[string]YttChart) error {
return nil
}