Skip to content

Commit 53e3415

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

File tree

4 files changed

+142
-179
lines changed

4 files changed

+142
-179
lines changed

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

+50-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,55 @@ 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+
// HelmOptions represent Helm-specific values that can be provided to modify how objects are parsed to create lint contexts
131+
type HelmOptions struct {
132+
Options
133+
134+
// HelmValuesOptions provide options for additional values.yamls that can be provided to Helm on loading a chart
135+
// These will be ignored for contexts that are not Helm-based
136+
HelmValuesOptions []values.Options
123137

124-
return []LintContext{ctx}, nil
138+
// Whether to treat this as a Helm chart directory
139+
FromDir bool
140+
// Whether to treat this as a Helm chart archive (tgz).
141+
FromArchive bool
142+
// FromReader is used if isDir and isArchive are both false
143+
FromReader io.Reader
144+
}
145+
146+
// CreateContextsFromHelmArchive creates a context based on provided options
147+
func CreateHelmContextsWithOptions(options HelmOptions, fileOrDir string) ([]LintContext, error) {
148+
contextsByHelmValues := []LintContext{}
149+
for _, helmValueOptions := range options.HelmValuesOptions {
150+
ctx := newHelmCtx(options.Options, helmValueOptions)
151+
if err := ctx.loadObjectsFromHelmChart(fileOrDir, options); err != nil {
152+
return nil, err
153+
}
154+
contextsByHelmValues = append(contextsByHelmValues, ctx)
155+
}
156+
return contextsByHelmValues, nil
125157
}

pkg/lintcontext/parse_helm.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
return nil, errors.New("cannot specify that helm chart is both a directory and an archive")
44+
}
45+
46+
switch {
47+
case options.FromArchive:
48+
chrt, err = loader.LoadFile(path)
49+
case options.FromDir:
50+
chrt, err = loader.Load(path)
51+
default:
52+
chrt, err = loader.LoadArchive(options.FromReader)
53+
}
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
if err := chrt.Validate(); err != nil {
59+
return nil, err
60+
}
61+
values, err := l.helmValuesOptions.MergeValues(nil)
62+
if err != nil {
63+
return nil, errors.Wrap(err, "loading provided Helm value options")
64+
}
65+
66+
return l.renderValues(chrt, values)
67+
}
68+
69+
func (l *lintContextImpl) renderValues(chrt *chart.Chart, values map[string]interface{}) (map[string]string, error) {
70+
valuesToRender, err := chartutil.ToRenderValues(chrt, values, chartutil.ReleaseOptions{Name: "test-release", Namespace: "default"}, nil)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
e := engine.Engine{LintMode: true}
76+
rendered, err := e.Render(chrt, valuesToRender)
77+
if err != nil {
78+
return nil, errors.Wrap(err, "failed to render")
79+
}
80+
81+
return rendered, nil
82+
}

0 commit comments

Comments
 (0)