mirror of https://github.com/helm/helm
Merge pull request #63 from technosophos/feat/helm-lint
feat(helm): add a very basic lint commandpull/613/head
commit
7e317e82db
@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/deis/tiller/pkg/lint"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var longLintHelp = `
|
||||||
|
This command takes a path to a chart and runs a series of tests to verify that
|
||||||
|
the chart is well-formed.
|
||||||
|
|
||||||
|
If the linter encounters things that will cause the chart to fail installation,
|
||||||
|
it will emit [ERROR] messages. If it encounters issues that break with convention
|
||||||
|
or recommendation, it will emit [WARNING] messages.
|
||||||
|
`
|
||||||
|
|
||||||
|
var lintCommand = &cobra.Command{
|
||||||
|
Use: "lint [flags] PATH",
|
||||||
|
Short: "Examines a chart for possible issues",
|
||||||
|
Long: longLintHelp,
|
||||||
|
Run: lintCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCommand.AddCommand(lintCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lintCmd(cmd *cobra.Command, args []string) {
|
||||||
|
path := "."
|
||||||
|
if len(args) > 0 {
|
||||||
|
path = args[0]
|
||||||
|
}
|
||||||
|
issues := lint.All(path)
|
||||||
|
for _, i := range issues {
|
||||||
|
fmt.Printf("%s\n", i)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package lint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
chartutil "github.com/deis/tiller/pkg/chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Chartfile(basepath string) (m []Message) {
|
||||||
|
m = []Message{}
|
||||||
|
|
||||||
|
path := filepath.Join(basepath, "Chart.yaml")
|
||||||
|
if fi, err := os.Stat(path); err != nil {
|
||||||
|
m = append(m, Message{Severity: ErrorSev, Text: "No Chart.yaml file"})
|
||||||
|
return
|
||||||
|
} else if fi.IsDir() {
|
||||||
|
m = append(m, Message{Severity: ErrorSev, Text: "Chart.yaml is a directory."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cf, err := chartutil.LoadChartfile(path)
|
||||||
|
if err != nil {
|
||||||
|
m = append(m, Message{
|
||||||
|
Severity: ErrorSev,
|
||||||
|
Text: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cf.Name == "" {
|
||||||
|
m = append(m, Message{
|
||||||
|
Severity: ErrorSev,
|
||||||
|
Text: "Chart.yaml: 'name' is required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if cf.Version == "" || cf.Version == "0.0.0" {
|
||||||
|
m = append(m, Message{
|
||||||
|
Severity: ErrorSev,
|
||||||
|
Text: "Chart.yaml: 'version' is required, and must be greater than 0.0.0",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package lint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const badchartfile = "testdata/badchartfile"
|
||||||
|
|
||||||
|
func TestChartfile(t *testing.T) {
|
||||||
|
msgs := Chartfile(badchartfile)
|
||||||
|
if len(msgs) != 2 {
|
||||||
|
t.Errorf("Expected 2 errors, got %d", len(msgs))
|
||||||
|
}
|
||||||
|
|
||||||
|
if msgs[0].Text != "Chart.yaml: 'name' is required" {
|
||||||
|
t.Errorf("Unexpected message 0: %s", msgs[0].Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if msgs[1].Text != "Chart.yaml: 'version' is required, and must be greater than 0.0.0" {
|
||||||
|
t.Errorf("Unexpected message 1: %s", msgs[1].Text)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
/*Package lint contains tools for linting charts.
|
||||||
|
|
||||||
|
Linting is the process of testing charts for errors or warnings regarding
|
||||||
|
formatting, compilation, or standards compliance.
|
||||||
|
*/
|
||||||
|
package lint
|
@ -0,0 +1,7 @@
|
|||||||
|
package lint
|
||||||
|
|
||||||
|
func All(basedir string) []Message {
|
||||||
|
out := Chartfile(basedir)
|
||||||
|
out = append(out, Templates(basedir)...)
|
||||||
|
return out
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package lint
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Severity int
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownSev = iota
|
||||||
|
WarningSev
|
||||||
|
ErrorSev
|
||||||
|
)
|
||||||
|
|
||||||
|
var sev = []string{"INFO", "WARNING", "ERROR"}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
// Severity is one of the *Sev constants
|
||||||
|
Severity int
|
||||||
|
// Text contains the message text
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints a string representation of this Message.
|
||||||
|
//
|
||||||
|
// Implements fmt.Stringer.
|
||||||
|
func (m Message) String() string {
|
||||||
|
return fmt.Sprintf("[%s] %s", sev[m.Severity], m.Text)
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package lint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ fmt.Stringer = Message{}
|
||||||
|
|
||||||
|
func TestMessage(t *testing.T) {
|
||||||
|
m := Message{ErrorSev, "Foo"}
|
||||||
|
if m.String() != "[ERROR] Foo" {
|
||||||
|
t.Errorf("Unexpected output: %s", m.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
m = Message{WarningSev, "Bar"}
|
||||||
|
if m.String() != "[WARNING] Bar" {
|
||||||
|
t.Errorf("Unexpected output: %s", m.String())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package lint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Templates(basepath string) (messages []Message) {
|
||||||
|
messages = []Message{}
|
||||||
|
path := filepath.Join(basepath, "templates")
|
||||||
|
if fi, err := os.Stat(path); err != nil {
|
||||||
|
messages = append(messages, Message{Severity: WarningSev, Text: "No templates"})
|
||||||
|
return
|
||||||
|
} else if !fi.IsDir() {
|
||||||
|
messages = append(messages, Message{Severity: ErrorSev, Text: "'templates' is not a directory"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl := template.New("tpl").Funcs(sprig.TxtFuncMap())
|
||||||
|
|
||||||
|
err := filepath.Walk(basepath, func(name string, fi os.FileInfo, e error) error {
|
||||||
|
// If an error is returned, we fail. Non-fatal errors should just be
|
||||||
|
// added directly to messages.
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, Message{
|
||||||
|
Severity: ErrorSev,
|
||||||
|
Text: fmt.Sprintf("cannot read %s: %s", name, err),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// An error rendering a file should emit a warning.
|
||||||
|
newtpl, err := tpl.Parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, Message{
|
||||||
|
Severity: ErrorSev,
|
||||||
|
Text: fmt.Sprintf("error processing %s: %s", name, err),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tpl = newtpl
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, Message{Severity: ErrorSev, Text: err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package lint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const templateTestBasedir = "./testdata/albatross"
|
||||||
|
|
||||||
|
func TestTemplate(t *testing.T) {
|
||||||
|
res := Templates(templateTestBasedir)
|
||||||
|
|
||||||
|
if len(res) != 1 {
|
||||||
|
t.Fatalf("Expected one error, got %d", len(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(res[0].Text, "deliberateSyntaxError") {
|
||||||
|
t.Errorf("Unexpected error: %s", res[0])
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
name: albatross
|
||||||
|
description: testing chart
|
||||||
|
version: 199.44.12345-Alpha.1+cafe009
|
@ -0,0 +1,2 @@
|
|||||||
|
metadata:
|
||||||
|
name: {{.name | default "foo" | title}}
|
@ -0,0 +1 @@
|
|||||||
|
{{ deliberateSyntaxError }}
|
@ -0,0 +1 @@
|
|||||||
|
name = "mariner"
|
@ -0,0 +1,3 @@
|
|||||||
|
description: A Helm chart for Kubernetes
|
||||||
|
version: 0.0.0
|
||||||
|
home: ""
|
@ -0,0 +1,4 @@
|
|||||||
|
# Default values for badchartfile.
|
||||||
|
# This is a TOML-formatted file. https://github.com/toml-lang/toml
|
||||||
|
# Declare name/value pairs to be passed into your templates.
|
||||||
|
# name = "value"
|
Loading…
Reference in new issue