mirror of https://github.com/helm/helm
Merge pull request #31015 from zachburg/crds_lint
Add linter support for the `crds/` directorypull/31074/head
commit
cd5ae5b19e
@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"helm.sh/helm/v4/pkg/chart/v2/loader"
|
||||
"helm.sh/helm/v4/pkg/lint/support"
|
||||
)
|
||||
|
||||
// Crds lints the CRDs in the Linter.
|
||||
func Crds(linter *support.Linter) {
|
||||
fpath := "crds/"
|
||||
crdsPath := filepath.Join(linter.ChartDir, fpath)
|
||||
|
||||
// crds directory is optional
|
||||
if _, err := os.Stat(crdsPath); errors.Is(err, fs.ErrNotExist) {
|
||||
return
|
||||
}
|
||||
|
||||
crdsDirValid := linter.RunLinterRule(support.ErrorSev, fpath, validateCrdsDir(crdsPath))
|
||||
if !crdsDirValid {
|
||||
return
|
||||
}
|
||||
|
||||
// Load chart and parse CRDs
|
||||
chart, err := loader.Load(linter.ChartDir)
|
||||
|
||||
chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err)
|
||||
|
||||
if !chartLoaded {
|
||||
return
|
||||
}
|
||||
|
||||
/* Iterate over all the CRDs to check:
|
||||
1. It is a YAML file and not a template
|
||||
2. The API version is apiextensions.k8s.io
|
||||
3. The kind is CustomResourceDefinition
|
||||
*/
|
||||
for _, crd := range chart.CRDObjects() {
|
||||
fileName := crd.Name
|
||||
fpath = fileName
|
||||
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(crd.File.Data), 4096)
|
||||
for {
|
||||
var yamlStruct *k8sYamlStruct
|
||||
|
||||
err := decoder.Decode(&yamlStruct)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
// If YAML parsing fails here, it will always fail in the next block as well, so we should return here.
|
||||
// This also confirms the YAML is not a template, since templates can't be decoded into a K8sYamlStruct.
|
||||
if !linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) {
|
||||
return
|
||||
}
|
||||
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateCrdAPIVersion(yamlStruct))
|
||||
linter.RunLinterRule(support.ErrorSev, fpath, validateCrdKind(yamlStruct))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validation functions
|
||||
func validateCrdsDir(crdsPath string) error {
|
||||
fi, err := os.Stat(crdsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return errors.New("not a directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCrdAPIVersion(obj *k8sYamlStruct) error {
|
||||
if !strings.HasPrefix(obj.APIVersion, "apiextensions.k8s.io") {
|
||||
return fmt.Errorf("apiVersion is not in 'apiextensions.k8s.io'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCrdKind(obj *k8sYamlStruct) error {
|
||||
if obj.Kind != "CustomResourceDefinition" {
|
||||
return fmt.Errorf("object kind is not 'CustomResourceDefinition'")
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"helm.sh/helm/v4/pkg/lint/support"
|
||||
)
|
||||
|
||||
const invalidCrdsDir = "./testdata/invalidcrdsdir"
|
||||
|
||||
func TestInvalidCrdsDir(t *testing.T) {
|
||||
linter := support.Linter{ChartDir: invalidCrdsDir}
|
||||
Crds(&linter)
|
||||
res := linter.Messages
|
||||
|
||||
assert.Len(t, res, 1)
|
||||
assert.ErrorContains(t, res[0].Err, "not a directory")
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 0.1.0
|
||||
name: badcrdfile
|
||||
type: application
|
||||
icon: http://riverrun.io
|
@ -0,0 +1,2 @@
|
||||
apiVersion: bad.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
@ -0,0 +1,2 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: NotACustomResourceDefinition
|
@ -0,0 +1 @@
|
||||
# Default values for badcrdfile.
|
@ -0,0 +1,19 @@
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tests.test.io
|
||||
spec:
|
||||
group: test.io
|
||||
names:
|
||||
kind: Test
|
||||
listKind: TestList
|
||||
plural: tests
|
||||
singular: test
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name : v1alpha2
|
||||
served: true
|
||||
storage: true
|
||||
- name : v1alpha1
|
||||
served: true
|
||||
storage: false
|
@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
description: A Helm chart for Kubernetes
|
||||
version: 0.1.0
|
||||
name: invalidcrdsdir
|
||||
type: application
|
||||
icon: http://riverrun.io
|
@ -0,0 +1 @@
|
||||
# Default values for invalidcrdsdir.
|
Loading…
Reference in new issue