initial helmfile impl
This commit is contained in:
commit
e5e834f9f0
|
|
@ -0,0 +1,79 @@
|
|||
package helmexec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
command = "helm"
|
||||
)
|
||||
|
||||
type execer struct {
|
||||
writer io.Writer
|
||||
extra []string
|
||||
}
|
||||
|
||||
func NewHelmExec(writer io.Writer) Interface {
|
||||
return &execer{writer: writer}
|
||||
}
|
||||
|
||||
func (helm *execer) SetExtraArgs(args ...string) {
|
||||
helm.extra = args
|
||||
}
|
||||
|
||||
func (helm *execer) AddRepo(name, repository string) error {
|
||||
out, err := helm.exec("repo", "add", name, repository)
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) UpdateRepo() error {
|
||||
out, err := helm.exec("repo", "update")
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) SyncChart(name, chart string, flags ...string) error {
|
||||
out, err := helm.exec(append([]string{"upgrade", "--install", name, chart}, flags...)...)
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) DeleteChart(name string) error {
|
||||
out, err := helm.exec("delete", name)
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) exec(args ...string) ([]byte, error) {
|
||||
dir, err := ioutil.TempDir("", "helmfile-exec")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
cmdargs := args
|
||||
if len(helm.extra) > 0 {
|
||||
cmdargs = append(cmdargs, helm.extra...)
|
||||
}
|
||||
if helm.writer != nil {
|
||||
helm.writer.Write([]byte(fmt.Sprintf("exec: helm %s\n", strings.Join(cmdargs, " "))))
|
||||
}
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Dir = dir
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package helmexec
|
||||
|
||||
type Interface interface {
|
||||
SetExtraArgs(args ...string)
|
||||
|
||||
AddRepo(name, repository string) error
|
||||
UpdateRepo() error
|
||||
|
||||
SyncChart(name, chart string, flags ...string) error
|
||||
DeleteChart(name string) error
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/roboll/helmfile/helmexec"
|
||||
"github.com/roboll/helmfile/state"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
helmfile = "charts.yaml"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "helmfile"
|
||||
app.Usage = ""
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "file, f",
|
||||
Value: helmfile,
|
||||
Usage: "load config from `FILE`",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "silence output",
|
||||
},
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "repos",
|
||||
Usage: "sync repositories from state file",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
state, helm, err := before(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := c.String("args")
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(strings.Split(args, " ")...)
|
||||
}
|
||||
|
||||
if errs := state.SyncRepos(helm); err != nil && len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Printf("err: %s", err.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "charts",
|
||||
Usage: "sync charts from state file",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
state, helm, err := before(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := c.String("args")
|
||||
if len(args) > 0 {
|
||||
helm.SetExtraArgs(strings.Split(args, " ")...)
|
||||
}
|
||||
|
||||
if errs := state.SyncCharts(helm); err != nil && len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Printf("err: %s", err.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "sync",
|
||||
Usage: "sync all resources from state file",
|
||||
Action: func(c *cli.Context) error {
|
||||
state, helm, err := before(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if errs := state.SyncRepos(helm); err != nil && len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Printf("err: %s", err.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if errs := state.SyncCharts(helm); err != nil && len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Printf("err: %s", err.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "delete charts from state file",
|
||||
Action: func(c *cli.Context) error {
|
||||
state, helm, err := before(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if errs := state.DeleteCharts(helm); err != nil && len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
fmt.Printf("err: %s", err.Error())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Printf("err: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func before(c *cli.Context) (*state.HelmState, helmexec.Interface, error) {
|
||||
file := c.GlobalString("file")
|
||||
quiet := c.GlobalBool("quiet")
|
||||
|
||||
state, err := state.ReadFromFile(file)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var writer io.Writer
|
||||
if !quiet {
|
||||
writer = os.Stdout
|
||||
}
|
||||
|
||||
return state, helmexec.NewHelmExec(writer), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/roboll/helmfile/helmexec"
|
||||
|
||||
yaml "gopkg.in/yaml.v1"
|
||||
)
|
||||
|
||||
type HelmState struct {
|
||||
Repositories []RepositorySpec `yaml:"repositories"`
|
||||
Charts []ChartSpec `yaml:"charts"`
|
||||
}
|
||||
|
||||
type RepositorySpec struct {
|
||||
Name string `yaml:"name"`
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
type ChartSpec struct {
|
||||
Chart string `yaml:"chart"`
|
||||
Version string `yaml:"version"`
|
||||
Verify bool `yaml:"verify"`
|
||||
|
||||
Name string `yaml:"name"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
Values []string `yaml:"values"`
|
||||
SetValues []SetValue `yaml:"set"`
|
||||
}
|
||||
|
||||
type SetValue struct {
|
||||
Name string `yaml:"name"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
func ReadFromFile(file string) (*HelmState, error) {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var state HelmState
|
||||
if err := yaml.Unmarshal(content, &state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
func (state *HelmState) SyncRepos(helm helmexec.Interface) []error {
|
||||
var wg sync.WaitGroup
|
||||
errs := []error{}
|
||||
|
||||
for _, repo := range state.Repositories {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup) {
|
||||
if err := helm.AddRepo(repo.Name, repo.URL); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
wg.Done()
|
||||
}(&wg)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
if err := helm.UpdateRepo(); err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *HelmState) SyncCharts(helm helmexec.Interface) []error {
|
||||
var wg sync.WaitGroup
|
||||
errs := []error{}
|
||||
|
||||
for _, chart := range state.Charts {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup, chart ChartSpec) {
|
||||
flags, err := flagsForChart(&chart)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
if err := helm.SyncChart(chart.Name, chart.Chart, flags...); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}(&wg, chart)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *HelmState) DeleteCharts(helm helmexec.Interface) []error {
|
||||
var wg sync.WaitGroup
|
||||
errs := []error{}
|
||||
|
||||
for _, chart := range state.Charts {
|
||||
wg.Add(1)
|
||||
go func(wg *sync.WaitGroup, chart ChartSpec) {
|
||||
if err := helm.DeleteChart(chart.Name); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
wg.Done()
|
||||
}(&wg, chart)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func flagsForChart(chart *ChartSpec) ([]string, error) {
|
||||
flags := []string{}
|
||||
if chart.Version != "" {
|
||||
flags = append(flags, "--version", chart.Version)
|
||||
}
|
||||
if chart.Verify {
|
||||
flags = append(flags, "--verify")
|
||||
}
|
||||
if chart.Namespace != "" {
|
||||
flags = append(flags, "--namespace", chart.Namespace)
|
||||
}
|
||||
for _, value := range chart.Values {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valfile := filepath.Join(wd, value)
|
||||
flags = append(flags, "--values", valfile)
|
||||
}
|
||||
if len(chart.SetValues) > 0 {
|
||||
val := []string{}
|
||||
for _, set := range chart.SetValues {
|
||||
val = append(val, fmt.Sprintf("%s=%s", set.Name, set.Value))
|
||||
}
|
||||
flags = append(flags, "--set", strings.Join(val, ","))
|
||||
}
|
||||
return flags, nil
|
||||
}
|
||||
Loading…
Reference in New Issue