forked from grafana/tanka
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor: helmraiser -> helm Renames the helmraiser package to helm, because its scope is going to be extended in the following. It will not only cover helmraiser functionality going on, but also house declarative chart management code * refactor: helm.go -> template.go * feat(cli): Charttool Adds a new `tk tool charts` command, which enables declarative management of vendored Helm charts. As proposed by https://docs.google.com/document/d/171F0cm_VliMStmHe6oy5pAbuXihr-7Cb5yD-vmwqpP8, Helm Charts should be vendored by the individual library that consumes them. To simplify this process, `tk tool charts` provides a declarative config file to automate `helm pull`. The rest of above document will be implemented in subsequent pull requests * chore: don't ignore cmd/tk folder wtf happened here * style: Load -> LoadChartFile
- Loading branch information
Showing
11 changed files
with
520 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1 @@ | ||
dist | ||
tk | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/go-clix/cli" | ||
"github.com/grafana/tanka/pkg/helm" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
func chartsCmd() *cli.Command { | ||
cmd := &cli.Command{ | ||
Use: "charts", | ||
Short: "Declarative vendoring of Helm Charts", | ||
} | ||
|
||
cmd.AddCommand( | ||
chartsInitCmd(), | ||
chartsAddCmd(), | ||
chartsVendorCmd(), | ||
chartsConfigCmd(), | ||
) | ||
|
||
return cmd | ||
} | ||
|
||
func chartsVendorCmd() *cli.Command { | ||
cmd := &cli.Command{ | ||
Use: "vendor", | ||
Short: "Download Charts to a local folder", | ||
} | ||
|
||
cmd.Run = func(cmd *cli.Command, args []string) error { | ||
c, err := loadChartfile() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.Vendor() | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
func chartsAddCmd() *cli.Command { | ||
cmd := &cli.Command{ | ||
Use: "add [chart@version] [...]", | ||
Short: "Adds Charts to the chartfile", | ||
} | ||
|
||
cmd.Run = func(cmd *cli.Command, args []string) error { | ||
c, err := loadChartfile() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return c.Add(args) | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
func chartsConfigCmd() *cli.Command { | ||
cmd := &cli.Command{ | ||
Use: "config", | ||
Short: "Displays the current manifest", | ||
} | ||
|
||
cmd.Run = func(cmd *cli.Command, args []string) error { | ||
c, err := loadChartfile() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
data, err := yaml.Marshal(c.Manifest) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Print(string(data)) | ||
|
||
return nil | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
func chartsInitCmd() *cli.Command { | ||
cmd := &cli.Command{ | ||
Use: "init", | ||
Short: "Create a new Chartfile", | ||
} | ||
|
||
cmd.Run = func(cmd *cli.Command, args []string) error { | ||
wd, err := os.Getwd() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
path := filepath.Join(wd, helm.Filename) | ||
if _, err := os.Stat(path); err == nil { | ||
return fmt.Errorf("Chartfile at '%s' already exists. Aborting", path) | ||
} | ||
|
||
if _, err := helm.InitChartfile(path); err != nil { | ||
return err | ||
} | ||
|
||
log.Printf("Success! New Chartfile created at '%s'", path) | ||
return nil | ||
} | ||
|
||
return cmd | ||
} | ||
|
||
func loadChartfile() (*helm.Charts, error) { | ||
wd, err := os.Getwd() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return helm.LoadChartfile(wd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
package helm | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/Masterminds/semver" | ||
"sigs.k8s.io/yaml" | ||
) | ||
|
||
// LoadChartfile opens a Chartfile tree | ||
func LoadChartfile(projectRoot string) (*Charts, error) { | ||
// make sure project root is valid | ||
abs, err := filepath.Abs(projectRoot) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// open chartfile | ||
chartfile := filepath.Join(abs, Filename) | ||
data, err := ioutil.ReadFile(chartfile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// parse it | ||
c := Chartfile{ | ||
Version: Version, | ||
Directory: DefaultDir, | ||
} | ||
if err := yaml.UnmarshalStrict(data, &c); err != nil { | ||
return nil, err | ||
} | ||
|
||
for i, r := range c.Requires { | ||
if r.Chart == "" { | ||
return nil, fmt.Errorf("requirements[%v]: 'chart' must be set", i) | ||
} | ||
} | ||
|
||
// return Charts handle | ||
charts := &Charts{ | ||
Manifest: c, | ||
projectRoot: abs, | ||
|
||
// default to ExecHelm, but allow injecting from the outside | ||
Helm: ExecHelm{}, | ||
} | ||
return charts, nil | ||
} | ||
|
||
// Charts exposes the central Chartfile management functions | ||
type Charts struct { | ||
// Manifest are the chartfile.yaml contents. It holds data about the developers intentions | ||
Manifest Chartfile | ||
|
||
// projectRoot is the enclosing directory of chartfile.yaml | ||
projectRoot string | ||
|
||
// Helm is the helm implementation underneath. ExecHelm is the default, but | ||
// any implementation of the Helm interface may be used | ||
Helm Helm | ||
} | ||
|
||
// ChartDir returns the directory pulled charts are saved in | ||
func (c Charts) ChartDir() string { | ||
return filepath.Join(c.projectRoot, c.Manifest.Directory) | ||
} | ||
|
||
// ManifestFile returns the full path to the chartfile.yaml | ||
func (c Charts) ManifestFile() string { | ||
return filepath.Join(c.projectRoot, Filename) | ||
} | ||
|
||
// Vendor pulls all Charts specified in the manifest into the local charts | ||
// directory. It fetches the repository index before doing so. | ||
func (c Charts) Vendor() error { | ||
dir := c.ChartDir() | ||
if err := os.MkdirAll(dir, os.ModePerm); err != nil { | ||
return err | ||
} | ||
|
||
log.Println("Syncing Repositories ...") | ||
if err := c.Helm.RepoUpdate(Opts{Repositories: c.Manifest.Repositories}); err != nil { | ||
return err | ||
} | ||
|
||
log.Println("Pulling Charts ...") | ||
for _, r := range c.Manifest.Requires { | ||
err := c.Helm.Pull(r.Chart, r.Version.String(), PullOpts{ | ||
Destination: dir, | ||
Opts: Opts{Repositories: c.Manifest.Repositories}, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
log.Printf(" %s@%s", r.Chart, r.Version.String()) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Add adds every Chart in reqs to the Manifest after validation, and runs | ||
// Vendor afterwards | ||
func (c Charts) Add(reqs []string) error { | ||
log.Printf("Adding %v Charts ...", len(reqs)) | ||
|
||
skip := func(s string, err error) { | ||
log.Printf(" Skipping %s: %s.", s, err) | ||
} | ||
|
||
// parse new charts, append in memory | ||
added := 0 | ||
for _, s := range reqs { | ||
r, err := parseReq(s) | ||
if err != nil { | ||
skip(s, err) | ||
continue | ||
} | ||
|
||
if c.Manifest.Requires.Has(*r) { | ||
skip(s, fmt.Errorf("already exists")) | ||
continue | ||
} | ||
|
||
c.Manifest.Requires = append(c.Manifest.Requires, *r) | ||
added++ | ||
log.Println(" OK:", s) | ||
} | ||
|
||
// write out | ||
if err := write(c.Manifest, c.ManifestFile()); err != nil { | ||
return err | ||
} | ||
|
||
// skipped some? fail then | ||
if added != len(reqs) { | ||
return fmt.Errorf("%v Charts were skipped. Please check above logs for details", len(reqs)-added) | ||
} | ||
|
||
// worked fine? vendor it | ||
log.Printf("Added %v Charts to helmfile.yaml. Vendoring ...", added) | ||
return c.Vendor() | ||
} | ||
|
||
func InitChartfile(path string) (*Charts, error) { | ||
c := Chartfile{ | ||
Version: Version, | ||
Repositories: []Repo{{ | ||
Name: "stable", | ||
URL: "https://kubernetes-charts.storage.googleapis.com", | ||
}}, | ||
Requires: make(Requirements, 0), | ||
} | ||
|
||
if err := write(c, path); err != nil { | ||
return nil, err | ||
} | ||
|
||
return LoadChartfile(filepath.Dir(path)) | ||
} | ||
|
||
// write saves a Chartfile to dest | ||
func write(c Chartfile, dest string) error { | ||
data, err := yaml.Marshal(c) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return ioutil.WriteFile(dest, data, 0644) | ||
} | ||
|
||
var chartExp = regexp.MustCompile(`\w+\/\w+@.+`) | ||
|
||
// parseReq parses a requirement from a string of the format `repo/name@version` | ||
func parseReq(s string) (*Requirement, error) { | ||
if !chartExp.MatchString(s) { | ||
return nil, fmt.Errorf("not of form 'repo/chart@version'") | ||
} | ||
|
||
elems := strings.Split(s, "@") | ||
chart := elems[0] | ||
ver, err := semver.NewVersion(elems[1]) | ||
if errors.Is(err, semver.ErrInvalidSemVer) { | ||
return nil, fmt.Errorf("version is invalid") | ||
} else if err != nil { | ||
return nil, fmt.Errorf("version is invalid: %s", err) | ||
} | ||
|
||
return &Requirement{ | ||
Chart: chart, | ||
Version: *ver, | ||
}, nil | ||
} |
Oops, something went wrong.