Skip to content

Commit c9c732c

Browse files
author
Arvind Iyengar
committed
Add CreateContextWithOptions for lintcontext
Allows users to provide options for creating lint context. Related Issue: #141 Signed-off-by: Arvind Iyengar <[email protected]>
1 parent 9a8ea02 commit c9c732c

File tree

5 files changed

+170
-179
lines changed

5 files changed

+170
-179
lines changed

pkg/builtinchecks/a_builtinchecks-packr.go

+30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/lintcontext/context.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"golang.stackrox.io/kube-linter/internal/stringutils"
77
"golang.stackrox.io/kube-linter/pkg/k8sutil"
8+
"helm.sh/helm/v3/pkg/cli/values"
89
"k8s.io/apimachinery/pkg/runtime"
910
"k8s.io/apimachinery/pkg/runtime/schema"
1011
)
@@ -77,7 +78,8 @@ type lintContextImpl struct {
7778
objects []Object
7879
invalidObjects []InvalidObject
7980

80-
customDecoder runtime.Decoder
81+
customDecoder runtime.Decoder
82+
helmValuesOptions values.Options
8183
}
8284

8385
// Objects returns the (valid) objects loaded from this LintContext.
@@ -106,3 +108,10 @@ func newCtx(options Options) *lintContextImpl {
106108
customDecoder: options.CustomDecoder,
107109
}
108110
}
111+
112+
func newHelmCtx(options Options, helmValueOptions values.Options) *lintContextImpl {
113+
return &lintContextImpl{
114+
customDecoder: options.CustomDecoder,
115+
helmValuesOptions: helmValueOptions,
116+
}
117+
}

pkg/lintcontext/create_contexts.go

+51-18
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/pkg/errors"
1111
"golang.stackrox.io/kube-linter/internal/set"
1212
"helm.sh/helm/v3/pkg/chartutil"
13+
"helm.sh/helm/v3/pkg/cli/values"
1314
"k8s.io/apimachinery/pkg/runtime"
1415
)
1516

@@ -36,6 +37,7 @@ func CreateContexts(filesOrDirs ...string) ([]LintContext, error) {
3637
// CreateContextsWithOptions creates a context with additional Options
3738
func CreateContextsWithOptions(options Options, filesOrDirs ...string) ([]LintContext, error) {
3839
contextsByDir := make(map[string]*lintContextImpl)
40+
contextsByChartDir := make(map[string][]LintContext)
3941
for _, fileOrDir := range filesOrDirs {
4042
// Stdin
4143
if fileOrDir == "-" {
@@ -59,14 +61,17 @@ func CreateContextsWithOptions(options Options, filesOrDirs ...string) ([]LintCo
5961
return nil
6062
}
6163

64+
if _, exists := contextsByChartDir[currentPath]; exists {
65+
return nil
66+
}
67+
6268
if !info.IsDir() {
6369
if strings.HasSuffix(strings.ToLower(currentPath), ".tgz") {
64-
ctx := newCtx(options)
65-
if err := ctx.loadObjectsFromTgzHelmChart(currentPath); err != nil {
70+
lintCtxs, err := CreateHelmContextsWithOptions(HelmOptions{Options: options, FromArchive: true}, currentPath)
71+
if err != nil {
6672
return err
6773
}
68-
69-
contextsByDir[currentPath] = ctx
74+
contextsByChartDir[currentPath] = lintCtxs
7075
return nil
7176
}
7277

@@ -85,15 +90,11 @@ func CreateContextsWithOptions(options Options, filesOrDirs ...string) ([]LintCo
8590
return nil
8691
}
8792
if isHelm, _ := chartutil.IsChartDir(currentPath); isHelm {
88-
// Path has already been loaded, possibly through another argument. Skip.
89-
if _, alreadyExists := contextsByDir[currentPath]; alreadyExists {
90-
return nil
91-
}
92-
ctx := newCtx(options)
93-
contextsByDir[currentPath] = ctx
94-
if err := ctx.loadObjectsFromHelmChart(currentPath); err != nil {
93+
lintCtxs, err := CreateHelmContextsWithOptions(HelmOptions{Options: options, FromDir: true}, currentPath)
94+
if err != nil {
9595
return err
9696
}
97+
contextsByChartDir[currentPath] = lintCtxs
9798
return filepath.SkipDir
9899
}
99100
return nil
@@ -102,24 +103,56 @@ func CreateContextsWithOptions(options Options, filesOrDirs ...string) ([]LintCo
102103
return nil, errors.Wrapf(err, "loading from path %q", fileOrDir)
103104
}
104105
}
105-
dirs := make([]string, 0, len(contextsByDir))
106+
dirs := make([]string, 0, len(contextsByDir)+len(contextsByChartDir))
106107
for dir := range contextsByDir {
107108
dirs = append(dirs, dir)
108109
}
110+
for dir := range contextsByChartDir {
111+
dirs = append(dirs, dir)
112+
}
109113
sort.Strings(dirs)
110114
var contexts []LintContext
111115
for _, dir := range dirs {
116+
if helmCtxs, ok := contextsByChartDir[dir]; ok {
117+
contexts = append(contexts, helmCtxs...)
118+
continue
119+
}
112120
contexts = append(contexts, contextsByDir[dir])
113121
}
114122
return contexts, nil
115123
}
116124

117-
// CreateContextsFromHelmArchive creates a context from TGZ reader of Helm Chart.
125+
// CreateContextsFromHelmArchive creates a context from a tgz file based on a provided tgzReader
118126
func CreateContextsFromHelmArchive(fileName string, tgzReader io.Reader) ([]LintContext, error) {
119-
ctx := newCtx(Options{})
120-
if err := ctx.readObjectsFromTgzHelmChart(fileName, tgzReader); err != nil {
121-
return nil, err
122-
}
127+
return CreateHelmContextsWithOptions(HelmOptions{FromReader: tgzReader}, fileName)
128+
}
129+
130+
type HelmOptions struct {
131+
Options
123132

124-
return []LintContext{ctx}, nil
133+
// HelmValuesOptions provide options for additional values.yamls that can be provided to Helm on loading a chart
134+
// These will be ignored for contexts that are not Helm-based
135+
HelmValuesOptions []values.Options
136+
137+
// Whether to treat this as a Helm chart directory
138+
FromDir bool
139+
// Whether to treat this as a Helm chart archive (tgz).
140+
FromArchive bool
141+
// FromReader is used if isDir and isArchive are both false
142+
FromReader io.Reader
143+
}
144+
145+
func CreateHelmContextsWithOptions(options HelmOptions, chartDir string) ([]LintContext, error) {
146+
if isHelm, _ := chartutil.IsChartDir(chartDir); !isHelm {
147+
return nil, errors.New("cannot generate helm context from non-helm dir " + chartDir)
148+
}
149+
contextsByHelmValues := []LintContext{}
150+
for _, helmValueOptions := range options.HelmValuesOptions {
151+
ctx := newHelmCtx(options.Options, helmValueOptions)
152+
if err := ctx.loadObjectsFromHelmChart(chartDir, options); err != nil {
153+
return nil, err
154+
}
155+
contextsByHelmValues = append(contextsByHelmValues, ctx)
156+
}
157+
return contextsByHelmValues, nil
125158
}

pkg/lintcontext/parse_helm.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package lintcontext
2+
3+
import (
4+
"log"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/pkg/errors"
10+
"helm.sh/helm/v3/pkg/chart"
11+
"helm.sh/helm/v3/pkg/chart/loader"
12+
"helm.sh/helm/v3/pkg/chartutil"
13+
"helm.sh/helm/v3/pkg/engine"
14+
)
15+
16+
func (l *lintContextImpl) loadObjectsFromHelmChart(path string, options HelmOptions) error {
17+
metadata := ObjectMetadata{FilePath: path}
18+
renderedFiles, err := l.renderHelmChart(path, options)
19+
if err != nil {
20+
l.addInvalidObjects(InvalidObject{Metadata: metadata, LoadErr: err})
21+
return nil
22+
}
23+
for path, contents := range renderedFiles {
24+
// The first element of path will be the same as the last element of dir, because
25+
// Helm duplicates it.
26+
pathToTemplate := filepath.Join(filepath.Dir(path), path)
27+
if err := l.loadObjectsFromReader(pathToTemplate, strings.NewReader(contents)); err != nil {
28+
return errors.Wrapf(err, "loading objects from rendered helm chart %s/%s", path, pathToTemplate)
29+
}
30+
}
31+
return nil
32+
}
33+
34+
func (l *lintContextImpl) renderHelmChart(path string, options HelmOptions) (map[string]string, error) {
35+
// Helm doesn't have great logging behaviour, and can spam stderr, so silence their logging.
36+
// TODO: capture these logs.
37+
log.SetOutput(nopWriter{})
38+
defer log.SetOutput(os.Stderr)
39+
40+
var chrt *chart.Chart
41+
var err error
42+
if options.FromDir && options.FromArchive {
43+
err = errors.New("cannot specify that helm chart is both a directory and an archive")
44+
} else if options.FromArchive {
45+
chrt, err = loader.LoadFile(path)
46+
} else if options.FromDir {
47+
chrt, err = loader.Load(path)
48+
} else {
49+
chrt, err = loader.LoadArchive(options.FromReader)
50+
}
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
if err := chrt.Validate(); err != nil {
56+
return nil, err
57+
}
58+
values, err := l.helmValuesOptions.MergeValues(nil)
59+
if err != nil {
60+
return nil, errors.Wrap(err, "loading provided Helm value options")
61+
}
62+
63+
return l.renderValues(chrt, values)
64+
}
65+
66+
func (l *lintContextImpl) renderValues(chrt *chart.Chart, values map[string]interface{}) (map[string]string, error) {
67+
valuesToRender, err := chartutil.ToRenderValues(chrt, values, chartutil.ReleaseOptions{Name: "test-release", Namespace: "default"}, nil)
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
e := engine.Engine{LintMode: true}
73+
rendered, err := e.Render(chrt, valuesToRender)
74+
if err != nil {
75+
return nil, errors.Wrap(err, "failed to render")
76+
}
77+
78+
return rendered, nil
79+
}

0 commit comments

Comments
 (0)