210
vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go
vendored
Normal file
210
vendor/helm.sh/helm/v3/pkg/lint/rules/chartfile.go
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rules // import "helm.sh/helm/v3/pkg/lint/rules"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/lint/support"
|
||||
)
|
||||
|
||||
// Chartfile runs a set of linter rules related to Chart.yaml file
|
||||
func Chartfile(linter *support.Linter) {
|
||||
chartFileName := "Chart.yaml"
|
||||
chartPath := filepath.Join(linter.ChartDir, chartFileName)
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlNotDirectory(chartPath))
|
||||
|
||||
chartFile, err := chartutil.LoadChartfile(chartPath)
|
||||
validChartFile := linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlFormat(err))
|
||||
|
||||
// Guard clause. Following linter rules require a parsable ChartFile
|
||||
if !validChartFile {
|
||||
return
|
||||
}
|
||||
|
||||
// type check for Chart.yaml . ignoring error as any parse
|
||||
// errors would already be caught in the above load function
|
||||
chartFileForTypeCheck, _ := loadChartFileForTypeCheck(chartPath)
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile))
|
||||
|
||||
// Chart metadata
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile))
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersionType(chartFileForTypeCheck))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAppVersionType(chartFileForTypeCheck))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartMaintainer(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile))
|
||||
linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartType(chartFile))
|
||||
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile))
|
||||
}
|
||||
|
||||
func validateChartVersionType(data map[string]interface{}) error {
|
||||
return isStringValue(data, "version")
|
||||
}
|
||||
|
||||
func validateChartAppVersionType(data map[string]interface{}) error {
|
||||
return isStringValue(data, "appVersion")
|
||||
}
|
||||
|
||||
func isStringValue(data map[string]interface{}, key string) error {
|
||||
value, ok := data[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
valueType := fmt.Sprintf("%T", value)
|
||||
if valueType != "string" {
|
||||
return errors.Errorf("%s should be of type string but it's of type %s", key, valueType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartYamlNotDirectory(chartPath string) error {
|
||||
fi, err := os.Stat(chartPath)
|
||||
|
||||
if err == nil && fi.IsDir() {
|
||||
return errors.New("should be a file, not a directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartYamlFormat(chartFileError error) error {
|
||||
if chartFileError != nil {
|
||||
return errors.Errorf("unable to parse YAML\n\t%s", chartFileError.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartName(cf *chart.Metadata) error {
|
||||
if cf.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartAPIVersion(cf *chart.Metadata) error {
|
||||
if cf.APIVersion == "" {
|
||||
return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"")
|
||||
}
|
||||
|
||||
if cf.APIVersion != chart.APIVersionV1 && cf.APIVersion != chart.APIVersionV2 {
|
||||
return fmt.Errorf("apiVersion '%s' is not valid. The value must be either \"v1\" or \"v2\"", cf.APIVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartVersion(cf *chart.Metadata) error {
|
||||
if cf.Version == "" {
|
||||
return errors.New("version is required")
|
||||
}
|
||||
|
||||
version, err := semver.NewVersion(cf.Version)
|
||||
|
||||
if err != nil {
|
||||
return errors.Errorf("version '%s' is not a valid SemVer", cf.Version)
|
||||
}
|
||||
|
||||
c, err := semver.NewConstraint(">0.0.0-0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valid, msg := c.Validate(version)
|
||||
|
||||
if !valid && len(msg) > 0 {
|
||||
return errors.Errorf("version %v", msg[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartMaintainer(cf *chart.Metadata) error {
|
||||
for _, maintainer := range cf.Maintainers {
|
||||
if maintainer.Name == "" {
|
||||
return errors.New("each maintainer requires a name")
|
||||
} else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) {
|
||||
return errors.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name)
|
||||
} else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) {
|
||||
return errors.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartSources(cf *chart.Metadata) error {
|
||||
for _, source := range cf.Sources {
|
||||
if source == "" || !govalidator.IsRequestURL(source) {
|
||||
return errors.Errorf("invalid source URL '%s'", source)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartIconPresence(cf *chart.Metadata) error {
|
||||
if cf.Icon == "" {
|
||||
return errors.New("icon is recommended")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartIconURL(cf *chart.Metadata) error {
|
||||
if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) {
|
||||
return errors.Errorf("invalid icon URL '%s'", cf.Icon)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartDependencies(cf *chart.Metadata) error {
|
||||
if len(cf.Dependencies) > 0 && cf.APIVersion != chart.APIVersionV2 {
|
||||
return fmt.Errorf("dependencies are not valid in the Chart file with apiVersion '%s'. They are valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateChartType(cf *chart.Metadata) error {
|
||||
if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV2 {
|
||||
return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadChartFileForTypeCheck loads the Chart.yaml
|
||||
// in a generic form of a map[string]interface{}, so that the type
|
||||
// of the values can be checked
|
||||
func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
y := make(map[string]interface{})
|
||||
err = yaml.Unmarshal(b, &y)
|
||||
return y, err
|
||||
}
|
||||
82
vendor/helm.sh/helm/v3/pkg/lint/rules/dependencies.go
vendored
Normal file
82
vendor/helm.sh/helm/v3/pkg/lint/rules/dependencies.go
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rules // import "helm.sh/helm/v3/pkg/lint/rules"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/lint/support"
|
||||
)
|
||||
|
||||
// Dependencies runs lints against a chart's dependencies
|
||||
//
|
||||
// See https://github.com/helm/helm/issues/7910
|
||||
func Dependencies(linter *support.Linter) {
|
||||
c, err := loader.LoadDir(linter.ChartDir)
|
||||
if !linter.RunLinterRule(support.ErrorSev, "", validateChartFormat(err)) {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c))
|
||||
linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c))
|
||||
}
|
||||
|
||||
func validateChartFormat(chartError error) error {
|
||||
if chartError != nil {
|
||||
return errors.Errorf("unable to load chart\n\t%s", chartError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDependencyInChartsDir(c *chart.Chart) (err error) {
|
||||
dependencies := map[string]struct{}{}
|
||||
missing := []string{}
|
||||
for _, dep := range c.Dependencies() {
|
||||
dependencies[dep.Metadata.Name] = struct{}{}
|
||||
}
|
||||
for _, dep := range c.Metadata.Dependencies {
|
||||
if _, ok := dependencies[dep.Name]; !ok {
|
||||
missing = append(missing, dep.Name)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
err = fmt.Errorf("chart directory is missing these dependencies: %s", strings.Join(missing, ","))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func validateDependencyInMetadata(c *chart.Chart) (err error) {
|
||||
dependencies := map[string]struct{}{}
|
||||
missing := []string{}
|
||||
for _, dep := range c.Metadata.Dependencies {
|
||||
dependencies[dep.Name] = struct{}{}
|
||||
}
|
||||
for _, dep := range c.Dependencies() {
|
||||
if _, ok := dependencies[dep.Metadata.Name]; !ok {
|
||||
missing = append(missing, dep.Metadata.Name)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
err = fmt.Errorf("chart metadata is missing these dependencies: %s", strings.Join(missing, ","))
|
||||
}
|
||||
return err
|
||||
}
|
||||
84
vendor/helm.sh/helm/v3/pkg/lint/rules/deprecations.go
vendored
Normal file
84
vendor/helm.sh/helm/v3/pkg/lint/rules/deprecations.go
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rules // import "helm.sh/helm/v3/pkg/lint/rules"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/endpoints/deprecation"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
const (
|
||||
// This should be set in the Makefile based on the version of client-go being imported.
|
||||
// These constants will be overwritten with LDFLAGS
|
||||
k8sVersionMajor = 1
|
||||
k8sVersionMinor = 20
|
||||
)
|
||||
|
||||
// deprecatedAPIError indicates than an API is deprecated in Kubernetes
|
||||
type deprecatedAPIError struct {
|
||||
Deprecated string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e deprecatedAPIError) Error() string {
|
||||
msg := e.Message
|
||||
return msg
|
||||
}
|
||||
|
||||
func validateNoDeprecations(resource *K8sYamlStruct) error {
|
||||
// if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation
|
||||
if resource.APIVersion == "" {
|
||||
return nil
|
||||
}
|
||||
if resource.Kind == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
runtimeObject, err := resourceToRuntimeObject(resource)
|
||||
if err != nil {
|
||||
// do not error for non-kubernetes resources
|
||||
if runtime.IsNotRegisteredError(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !deprecation.IsDeprecated(runtimeObject, k8sVersionMajor, k8sVersionMinor) {
|
||||
return nil
|
||||
}
|
||||
gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind)
|
||||
return deprecatedAPIError{
|
||||
Deprecated: gvk,
|
||||
Message: deprecation.WarningMessage(runtimeObject),
|
||||
}
|
||||
}
|
||||
|
||||
func resourceToRuntimeObject(resource *K8sYamlStruct) (runtime.Object, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
kscheme.AddToScheme(scheme)
|
||||
|
||||
gvk := schema.FromAPIVersionAndKind(resource.APIVersion, resource.Kind)
|
||||
out, err := scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return out, nil
|
||||
}
|
||||
310
vendor/helm.sh/helm/v3/pkg/lint/rules/template.go
vendored
Normal file
310
vendor/helm.sh/helm/v3/pkg/lint/rules/template.go
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
apipath "k8s.io/apimachinery/pkg/api/validation/path"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/engine"
|
||||
"helm.sh/helm/v3/pkg/lint/support"
|
||||
)
|
||||
|
||||
var (
|
||||
crdHookSearch = regexp.MustCompile(`"?helm\.sh/hook"?:\s+crd-install`)
|
||||
releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`)
|
||||
)
|
||||
|
||||
// Templates lints the templates in the Linter.
|
||||
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) {
|
||||
fpath := "templates/"
|
||||
templatesPath := filepath.Join(linter.ChartDir, fpath)
|
||||
|
||||
templatesDirExist := linter.RunLinterRule(support.WarningSev, fpath, validateTemplatesDir(templatesPath))
|
||||
|
||||
// Templates directory is optional for now
|
||||
if !templatesDirExist {
|
||||
return
|
||||
}
|
||||
|
||||
// Load chart and parse templates
|
||||
chart, err := loader.Load(linter.ChartDir)
|
||||
|
||||
chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
|
||||
if !chartLoaded {
|
||||
return
|
||||
}
|
||||
|
||||
options := chartutil.ReleaseOptions{
|
||||
Name: "test-release",
|
||||
Namespace: namespace,
|
||||
}
|
||||
|
||||
cvals, err := chartutil.CoalesceValues(chart, values)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil)
|
||||
if err != nil {
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
return
|
||||
}
|
||||
var e engine.Engine
|
||||
e.LintMode = true
|
||||
renderedContentMap, err := e.Render(chart, valuesToRender)
|
||||
|
||||
renderOk := linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
|
||||
if !renderOk {
|
||||
return
|
||||
}
|
||||
|
||||
/* Iterate over all the templates to check:
|
||||
- It is a .yaml file
|
||||
- All the values in the template file is defined
|
||||
- {{}} include | quote
|
||||
- Generated content is a valid Yaml file
|
||||
- Metadata.Namespace is not set
|
||||
*/
|
||||
for _, template := range chart.Templates {
|
||||
fileName, data := template.Name, template.Data
|
||||
fpath = fileName
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName))
|
||||
// These are v3 specific checks to make sure and warn people if their
|
||||
// chart is not compatible with v3
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateNoCRDHooks(data))
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateNoReleaseTime(data))
|
||||
|
||||
// We only apply the following lint rules to yaml files
|
||||
if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" {
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463
|
||||
// Check that all the templates have a matching value
|
||||
//linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate))
|
||||
|
||||
// NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037
|
||||
// linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate)))
|
||||
|
||||
renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)]
|
||||
if strings.TrimSpace(renderedContent) != "" {
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateTopIndentLevel(renderedContent))
|
||||
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(renderedContent), 4096)
|
||||
|
||||
// Lint all resources if the file contains multiple documents separated by ---
|
||||
for {
|
||||
// Even though K8sYamlStruct only defines a few fields, an error in any other
|
||||
// key will be raised as well
|
||||
var yamlStruct *K8sYamlStruct
|
||||
|
||||
err := decoder.Decode(&yamlStruct)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
// If YAML linting fails, we sill progress. So we don't capture the returned state
|
||||
// on this linter run.
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err))
|
||||
|
||||
if yamlStruct != nil {
|
||||
// NOTE: set to warnings to allow users to support out-of-date kubernetes
|
||||
// Refs https://github.com/helm/helm/issues/8596
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct))
|
||||
linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct))
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validateTopIndentLevel checks that the content does not start with an indent level > 0.
|
||||
//
|
||||
// This error can occur when a template accidentally inserts space. It can cause
|
||||
// unpredictable errors depending on whether the text is normalized before being passed
|
||||
// into the YAML parser. So we trap it here.
|
||||
//
|
||||
// See https://github.com/helm/helm/issues/8467
|
||||
func validateTopIndentLevel(content string) error {
|
||||
// Read lines until we get to a non-empty one
|
||||
scanner := bufio.NewScanner(bytes.NewBufferString(content))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// If line is empty, skip
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
// If it starts with one or more spaces, this is an error
|
||||
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
|
||||
return fmt.Errorf("document starts with an illegal indent: %q, which may cause parsing problems", line)
|
||||
}
|
||||
// Any other condition passes.
|
||||
return nil
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
// Validation functions
|
||||
func validateTemplatesDir(templatesPath string) error {
|
||||
if fi, err := os.Stat(templatesPath); err != nil {
|
||||
return errors.New("directory not found")
|
||||
} else if !fi.IsDir() {
|
||||
return errors.New("not a directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAllowedExtension(fileName string) error {
|
||||
ext := filepath.Ext(fileName)
|
||||
validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"}
|
||||
|
||||
for _, b := range validExtensions {
|
||||
if b == ext {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext)
|
||||
}
|
||||
|
||||
func validateYamlContent(err error) error {
|
||||
return errors.Wrap(err, "unable to parse YAML")
|
||||
}
|
||||
|
||||
// validateMetadataName uses the correct validation function for the object
|
||||
// Kind, or if not set, defaults to the standard definition of a subdomain in
|
||||
// DNS (RFC 1123), used by most resources.
|
||||
func validateMetadataName(obj *K8sYamlStruct) error {
|
||||
fn := validateMetadataNameFunc(obj)
|
||||
allErrs := field.ErrorList{}
|
||||
for _, msg := range fn(obj.Metadata.Name, false) {
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), obj.Metadata.Name, msg))
|
||||
}
|
||||
if len(allErrs) > 0 {
|
||||
return errors.Wrapf(allErrs.ToAggregate(), "object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMetadataNameFunc will return a name validation function for the
|
||||
// object kind, if defined below.
|
||||
//
|
||||
// Rules should match those set in the various api validations:
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/core/validation/validation.go#L205-L274
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/apps/validation/validation.go#L39
|
||||
// ...
|
||||
//
|
||||
// Implementing here to avoid importing k/k.
|
||||
//
|
||||
// If no mapping is defined, returns NameIsDNSSubdomain. This is used by object
|
||||
// kinds that don't have special requirements, so is the most likely to work if
|
||||
// new kinds are added.
|
||||
func validateMetadataNameFunc(obj *K8sYamlStruct) validation.ValidateNameFunc {
|
||||
switch strings.ToLower(obj.Kind) {
|
||||
case "pod", "node", "secret", "endpoints", "resourcequota", // core
|
||||
"controllerrevision", "daemonset", "deployment", "replicaset", "statefulset", // apps
|
||||
"autoscaler", // autoscaler
|
||||
"cronjob", "job", // batch
|
||||
"lease", // coordination
|
||||
"endpointslice", // discovery
|
||||
"networkpolicy", "ingress", // networking
|
||||
"podsecuritypolicy", // policy
|
||||
"priorityclass", // scheduling
|
||||
"podpreset", // settings
|
||||
"storageclass", "volumeattachment", "csinode": // storage
|
||||
return validation.NameIsDNSSubdomain
|
||||
case "service":
|
||||
return validation.NameIsDNS1035Label
|
||||
case "namespace":
|
||||
return validation.ValidateNamespaceName
|
||||
case "serviceaccount":
|
||||
return validation.ValidateServiceAccountName
|
||||
case "certificatesigningrequest":
|
||||
// No validation.
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/certificates/validation/validation.go#L137-L140
|
||||
return func(name string, prefix bool) []string { return nil }
|
||||
case "role", "clusterrole", "rolebinding", "clusterrolebinding":
|
||||
// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34
|
||||
return func(name string, prefix bool) []string {
|
||||
return apipath.IsValidPathSegmentName(name)
|
||||
}
|
||||
default:
|
||||
return validation.NameIsDNSSubdomain
|
||||
}
|
||||
}
|
||||
|
||||
func validateNoCRDHooks(manifest []byte) error {
|
||||
if crdHookSearch.Match(manifest) {
|
||||
return errors.New("manifest is a crd-install hook. This hook is no longer supported in v3 and all CRDs should also exist the crds/ directory at the top level of the chart")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNoReleaseTime(manifest []byte) error {
|
||||
if releaseTimeSearch.Match(manifest) {
|
||||
return errors.New(".Release.Time has been removed in v3, please replace with the `now` function in your templates")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMatchSelector ensures that template specs have a selector declared.
|
||||
// See https://github.com/helm/helm/issues/1990
|
||||
func validateMatchSelector(yamlStruct *K8sYamlStruct, manifest string) error {
|
||||
switch yamlStruct.Kind {
|
||||
case "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet":
|
||||
// verify that matchLabels or matchExpressions is present
|
||||
if !(strings.Contains(manifest, "matchLabels") || strings.Contains(manifest, "matchExpressions")) {
|
||||
return fmt.Errorf("a %s must contain matchLabels or matchExpressions, and %q does not", yamlStruct.Kind, yamlStruct.Metadata.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// K8sYamlStruct stubs a Kubernetes YAML file.
|
||||
//
|
||||
// DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within
|
||||
// the rules package.
|
||||
type K8sYamlStruct struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string
|
||||
Metadata k8sYamlMetadata
|
||||
}
|
||||
|
||||
type k8sYamlMetadata struct {
|
||||
Namespace string
|
||||
Name string
|
||||
}
|
||||
87
vendor/helm.sh/helm/v3/pkg/lint/rules/values.go
vendored
Normal file
87
vendor/helm.sh/helm/v3/pkg/lint/rules/values.go
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/lint/support"
|
||||
)
|
||||
|
||||
// Values lints a chart's values.yaml file.
|
||||
//
|
||||
// This function is deprecated and will be removed in Helm 4.
|
||||
func Values(linter *support.Linter) {
|
||||
ValuesWithOverrides(linter, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// ValuesWithOverrides tests the values.yaml file.
|
||||
//
|
||||
// If a schema is present in the chart, values are tested against that. Otherwise,
|
||||
// they are only tested for well-formedness.
|
||||
//
|
||||
// If additional values are supplied, they are coalesced into the values in values.yaml.
|
||||
func ValuesWithOverrides(linter *support.Linter, values map[string]interface{}) {
|
||||
file := "values.yaml"
|
||||
vf := filepath.Join(linter.ChartDir, file)
|
||||
fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf))
|
||||
|
||||
if !fileExists {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, values))
|
||||
}
|
||||
|
||||
func validateValuesFileExistence(valuesPath string) error {
|
||||
_, err := os.Stat(valuesPath)
|
||||
if err != nil {
|
||||
return errors.Errorf("file does not exist")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateValuesFile(valuesPath string, overrides map[string]interface{}) error {
|
||||
values, err := chartutil.ReadValuesFile(valuesPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse YAML")
|
||||
}
|
||||
|
||||
// Helm 3.0.0 carried over the values linting from Helm 2.x, which only tests the top
|
||||
// level values against the top-level expectations. Subchart values are not linted.
|
||||
// We could change that. For now, though, we retain that strategy, and thus can
|
||||
// coalesce tables (like reuse-values does) instead of doing the full chart
|
||||
// CoalesceValues
|
||||
coalescedValues := chartutil.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides)
|
||||
coalescedValues = chartutil.CoalesceTables(coalescedValues, values)
|
||||
|
||||
ext := filepath.Ext(valuesPath)
|
||||
schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json"
|
||||
schema, err := ioutil.ReadFile(schemaPath)
|
||||
if len(schema) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return chartutil.ValidateAgainstSingleSchema(coalescedValues, schema)
|
||||
}
|
||||
Reference in New Issue
Block a user