mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-17 08:57:02 +00:00
Add example how to use kubeconform as a library
This commit is contained in:
parent
4672ded043
commit
4e96b44a8b
7 changed files with 93 additions and 18 deletions
|
|
@ -191,6 +191,13 @@ sys 0m1,069s
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using kubeconform as a Go Module
|
||||||
|
|
||||||
|
**Warning**: This is a work-in-progress, the interface is not yet considered stable. Feedback is encouraged.
|
||||||
|
|
||||||
|
Kubeconform contains a package that can be used as a library.
|
||||||
|
An example of usage can be found in [examples/main.go](examples/main.go)
|
||||||
|
|
||||||
### Credits
|
### Credits
|
||||||
|
|
||||||
* @garethr for the [Kubeval](https://github.com/instrumenta/kubeval) and
|
* @garethr for the [Kubeval](https://github.com/instrumenta/kubeval) and
|
||||||
|
|
|
||||||
|
|
@ -74,25 +74,25 @@ func realMain() int {
|
||||||
IgnoreMissingSchemas: cfg.IgnoreMissingSchemas,
|
IgnoreMissingSchemas: cfg.IgnoreMissingSchemas,
|
||||||
})
|
})
|
||||||
|
|
||||||
var resourcesChan <-chan resource.Resource
|
|
||||||
var errors <-chan error
|
|
||||||
validationResults := make(chan validator.Result)
|
validationResults := make(chan validator.Result)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
successChan := processResults(ctx, o, validationResults, cfg.ExitOnError)
|
successChan := processResults(ctx, o, validationResults, cfg.ExitOnError)
|
||||||
|
|
||||||
|
var resourcesChan <-chan resource.Resource
|
||||||
|
var errors <-chan error
|
||||||
if isStdin {
|
if isStdin {
|
||||||
resourcesChan, errors = resource.FromStream(ctx, "stdin", os.Stdin)
|
resourcesChan, errors = resource.FromStream(ctx, "stdin", os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
resourcesChan, errors = resource.FromFiles(ctx, cfg.IgnoreFilenamePatterns, cfg.Files...)
|
resourcesChan, errors = resource.FromFiles(ctx, cfg.IgnoreFilenamePatterns, cfg.Files...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process discovered resources across multiple workers
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
for i := 0; i < cfg.NumberOfWorkers; i++ {
|
for i := 0; i < cfg.NumberOfWorkers; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(resources <-chan resource.Resource, validationResults chan<- validator.Result, v validator.Validator) {
|
go func(resources <-chan resource.Resource, validationResults chan<- validator.Result, v validator.Validator) {
|
||||||
for res := range resources {
|
for res := range resources {
|
||||||
validationResults <- v.Validate(res)
|
validationResults <- v.ValidateResource(res)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}(resourcesChan, validationResults, v)
|
}(resourcesChan, validationResults, v)
|
||||||
|
|
@ -100,6 +100,7 @@ func realMain() int {
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
// Process errors while discovering resources
|
||||||
for err := range errors {
|
for err := range errors {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
|
|
@ -111,8 +112,14 @@ func realMain() int {
|
||||||
Err: err.Err,
|
Err: err.Err,
|
||||||
Status: validator.Error,
|
Status: validator.Error,
|
||||||
}
|
}
|
||||||
ctx.Done()
|
} else {
|
||||||
|
validationResults <- validator.Result{
|
||||||
|
Resource: resource.Resource{},
|
||||||
|
Err: err,
|
||||||
|
Status: validator.Error,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ctx.Done()
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
26
examples/main.go
Normal file
26
examples/main.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// WARNING: API of Kubeconform is still under development and not yet
|
||||||
|
// considered stable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yannh/kubeconform/pkg/validator"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
filepath := "../fixtures/valid.yaml"
|
||||||
|
f, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed opening %s: %s", filepath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := validator.New(nil, validator.Opts{Strict: true})
|
||||||
|
for i, res := range v.Validate(filepath, f) { // A file might contain multiple resources
|
||||||
|
// File starts with ---, the parser assumes a first empty resource
|
||||||
|
if res.Status != validator.Valid && res.Status != validator.Empty {
|
||||||
|
log.Fatalf("resource %d in file %s is not valid: %d, %s", i, filepath, res.Status, res.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -88,10 +88,6 @@ func FromFlags(progName string, args []string) (Config, string, error) {
|
||||||
c.RejectKinds = splitCSV(rejectKindsCSV)
|
c.RejectKinds = splitCSV(rejectKindsCSV)
|
||||||
c.IgnoreFilenamePatterns = ignoreFilenamePatterns
|
c.IgnoreFilenamePatterns = ignoreFilenamePatterns
|
||||||
c.SchemaLocations = schemaLocationsParam
|
c.SchemaLocations = schemaLocationsParam
|
||||||
if len(c.SchemaLocations) == 0 {
|
|
||||||
c.SchemaLocations = append(c.SchemaLocations, "https://kubernetesjsonschema.dev") // if not specified, default behaviour is to use kubernetesjson-schema.dev as registry
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Files = flags.Args()
|
c.Files = flags.Args()
|
||||||
|
|
||||||
if c.Help {
|
if c.Help {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func TestFromFlags(t *testing.T) {
|
||||||
KubernetesVersion: "1.18.0",
|
KubernetesVersion: "1.18.0",
|
||||||
NumberOfWorkers: 4,
|
NumberOfWorkers: 4,
|
||||||
OutputFormat: "text",
|
OutputFormat: "text",
|
||||||
SchemaLocations: []string{"https://kubernetesjsonschema.dev"},
|
SchemaLocations: nil,
|
||||||
SkipKinds: map[string]bool{},
|
SkipKinds: map[string]bool{},
|
||||||
RejectKinds: map[string]bool{},
|
RejectKinds: map[string]bool{},
|
||||||
},
|
},
|
||||||
|
|
@ -65,7 +65,7 @@ func TestFromFlags(t *testing.T) {
|
||||||
KubernetesVersion: "1.18.0",
|
KubernetesVersion: "1.18.0",
|
||||||
NumberOfWorkers: 4,
|
NumberOfWorkers: 4,
|
||||||
OutputFormat: "text",
|
OutputFormat: "text",
|
||||||
SchemaLocations: []string{"https://kubernetesjsonschema.dev"},
|
SchemaLocations: nil,
|
||||||
SkipKinds: map[string]bool{},
|
SkipKinds: map[string]bool{},
|
||||||
RejectKinds: map[string]bool{},
|
RejectKinds: map[string]bool{},
|
||||||
},
|
},
|
||||||
|
|
@ -77,7 +77,7 @@ func TestFromFlags(t *testing.T) {
|
||||||
KubernetesVersion: "1.18.0",
|
KubernetesVersion: "1.18.0",
|
||||||
NumberOfWorkers: 4,
|
NumberOfWorkers: 4,
|
||||||
OutputFormat: "text",
|
OutputFormat: "text",
|
||||||
SchemaLocations: []string{"https://kubernetesjsonschema.dev"},
|
SchemaLocations: nil,
|
||||||
SkipKinds: map[string]bool{"a": true, "b": true, "c": true},
|
SkipKinds: map[string]bool{"a": true, "b": true, "c": true},
|
||||||
RejectKinds: map[string]bool{},
|
RejectKinds: map[string]bool{},
|
||||||
},
|
},
|
||||||
|
|
@ -89,7 +89,7 @@ func TestFromFlags(t *testing.T) {
|
||||||
KubernetesVersion: "1.18.0",
|
KubernetesVersion: "1.18.0",
|
||||||
NumberOfWorkers: 4,
|
NumberOfWorkers: 4,
|
||||||
OutputFormat: "text",
|
OutputFormat: "text",
|
||||||
SchemaLocations: []string{"https://kubernetesjsonschema.dev"},
|
SchemaLocations: nil,
|
||||||
SkipKinds: map[string]bool{},
|
SkipKinds: map[string]bool{},
|
||||||
RejectKinds: map[string]bool{},
|
RejectKinds: map[string]bool{},
|
||||||
Summary: true,
|
Summary: true,
|
||||||
|
|
@ -116,10 +116,10 @@ func TestFromFlags(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
cfg, _, _ := FromFlags("kubeconform", testCase.args)
|
cfg, _, _ := FromFlags("kubeconform", testCase.args)
|
||||||
if reflect.DeepEqual(cfg, testCase.conf) != true {
|
if reflect.DeepEqual(cfg, testCase.conf) != true {
|
||||||
t.Errorf("failed parsing config - expected , got: \n%+v\n%+v", testCase.conf, cfg)
|
t.Errorf("test %d: failed parsing config - expected , got: \n%+v\n%+v", i, testCase.conf, cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/yannh/kubeconform/pkg/cache"
|
"github.com/yannh/kubeconform/pkg/cache"
|
||||||
"github.com/yannh/kubeconform/pkg/registry"
|
"github.com/yannh/kubeconform/pkg/registry"
|
||||||
"github.com/yannh/kubeconform/pkg/resource"
|
"github.com/yannh/kubeconform/pkg/resource"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/xeipuuv/gojsonschema"
|
"github.com/xeipuuv/gojsonschema"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
@ -29,7 +31,8 @@ type Result struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Validator interface {
|
type Validator interface {
|
||||||
Validate(res resource.Resource) Result
|
ValidateResource(res resource.Resource) Result
|
||||||
|
Validate(filename string, r io.Reader) []Result
|
||||||
}
|
}
|
||||||
|
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
|
|
@ -42,11 +45,20 @@ type Opts struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(schemaLocations []string, opts Opts) Validator {
|
func New(schemaLocations []string, opts Opts) Validator {
|
||||||
|
// Default to kubernetesjsonschema.dev
|
||||||
|
if schemaLocations == nil || len(schemaLocations) == 0 {
|
||||||
|
schemaLocations = []string{"https://kubernetesjsonschema.dev"}
|
||||||
|
}
|
||||||
|
|
||||||
registries := []registry.Registry{}
|
registries := []registry.Registry{}
|
||||||
for _, schemaLocation := range schemaLocations {
|
for _, schemaLocation := range schemaLocations {
|
||||||
registries = append(registries, registry.New(schemaLocation, opts.Strict, opts.SkipTLS))
|
registries = append(registries, registry.New(schemaLocation, opts.Strict, opts.SkipTLS))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.KubernetesVersion == "" {
|
||||||
|
opts.KubernetesVersion = "1.18.0"
|
||||||
|
}
|
||||||
|
|
||||||
if opts.SkipKinds == nil {
|
if opts.SkipKinds == nil {
|
||||||
opts.SkipKinds = map[string]bool{}
|
opts.SkipKinds = map[string]bool{}
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +81,7 @@ type v struct {
|
||||||
regs []registry.Registry
|
regs []registry.Registry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (val *v) Validate(res resource.Resource) Result {
|
func (val *v) ValidateResource(res resource.Resource) Result {
|
||||||
skip := func(signature resource.Signature) bool {
|
skip := func(signature resource.Signature) bool {
|
||||||
isSkipKind, ok := val.opts.SkipKinds[signature.Kind]
|
isSkipKind, ok := val.opts.SkipKinds[signature.Kind]
|
||||||
return ok && isSkipKind
|
return ok && isSkipKind
|
||||||
|
|
@ -151,6 +163,33 @@ func (val *v) Validate(res resource.Resource) Result {
|
||||||
return Result{Resource: res, Status: Invalid, Err: fmt.Errorf("%s", msg)}
|
return Result{Resource: res, Status: Invalid, Err: fmt.Errorf("%s", msg)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (val *v) ValidateWithContext(ctx context.Context, filename string, r io.Reader) []Result {
|
||||||
|
validationResults := []Result{}
|
||||||
|
resourcesChan, _ := resource.FromStream(ctx, filename, r)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case res, ok := <-resourcesChan:
|
||||||
|
validationResults = append(validationResults, val.ValidateResource(res))
|
||||||
|
if !ok {
|
||||||
|
resourcesChan = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if resourcesChan == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationResults
|
||||||
|
}
|
||||||
|
|
||||||
|
func (val *v) Validate(filename string, r io.Reader) []Result {
|
||||||
|
return val.ValidateWithContext(context.Background(), filename, r)
|
||||||
|
}
|
||||||
|
|
||||||
func downloadSchema(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error) {
|
func downloadSchema(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error) {
|
||||||
var err error
|
var err error
|
||||||
var schemaBytes []byte
|
var schemaBytes []byte
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ lastName: bar
|
||||||
},
|
},
|
||||||
regs: nil,
|
regs: nil,
|
||||||
}
|
}
|
||||||
if got := val.Validate(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect {
|
if got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect {
|
||||||
t.Errorf("%d - expected %d, got %d", i, testCase.expect, got.Status)
|
t.Errorf("%d - expected %d, got %d", i, testCase.expect, got.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue