Skip to content

Commit

Permalink
using path now and checking the right versions for the right snapshots
Browse files Browse the repository at this point in the history
  • Loading branch information
Skarlso committed Aug 19, 2024
1 parent 3efabf6 commit bd92033
Show file tree
Hide file tree
Showing 17 changed files with 2,240 additions and 123 deletions.
4 changes: 2 additions & 2 deletions cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ func init() {
f.BoolVarP(&testArgs.update, "update", "u", false, "Update any existing snapshots.")
}

func runTest(_ *cobra.Command, args []string) error {
func runTest(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("test needs an argument where the tests are located at")
}

path := args[0]
runner := tests.NewSuiteRunner(path, testArgs.update)
outcome, err := runner.Run()
outcome, err := runner.Run(cmd.Context())
if err != nil {
return err
}
Expand Down
10 changes: 9 additions & 1 deletion pkg/matches/matcher.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package matches

import "context"

type ContextKey string

// UpdateSnapshotKey defines a signal to the snapshot watcher to update the snapshot its checking.
var UpdateSnapshotKey = ContextKey("update-snapshot")

// Matcher that can assert information given a CRD and a payload configuration of the matcher.
type Matcher interface {
Match(sourceTemplateLocation string, payload []byte) error
Match(ctx context.Context, crdLocation string, payload []byte) error
}
87 changes: 73 additions & 14 deletions pkg/matches/matchsnapshot/matcher.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,100 @@
package matchsnapshot

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/Skarlso/crd-to-sample-yaml/pkg/matches"
"k8s.io/apimachinery/pkg/util/yaml"

"github.com/Skarlso/crd-to-sample-yaml/pkg/matches"
"github.com/Skarlso/crd-to-sample-yaml/pkg/tests"
)

const MatcherName = "matchSnapshot"

type Config struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Minimal bool `yaml:"minimal"`
}
type Matcher struct {
Updater Updater
}
type Matcher struct{}

func (m *Matcher) Match(sourceTemplateLocation string, payload []byte) error {
content, err := os.ReadFile(sourceTemplateLocation)
if err != nil {
return fmt.Errorf("failed to read source template: %w", err)
}
func init() {
tests.Register(&Matcher{
Updater: &Update{},
}, MatcherName)
}

func (m *Matcher) Match(ctx context.Context, crdLocation string, payload []byte) error {
c := Config{}
if err := yaml.Unmarshal(payload, &c); err != nil {
return err
}

snapshot, err := os.ReadFile(c.Name)
// we only create the snapshots if update is requested, otherwise,
// we just loop check existing snapshots
if v := ctx.Value(matches.UpdateSnapshotKey); v != nil {
if err := m.Updater.Update(crdLocation, c.Path, c.Minimal); err != nil {
return fmt.Errorf("failed to update snapshot at %s: %w", c.Path, err)
}
}

var snapshots []string
err := filepath.Walk(c.Path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// skip reading folders
if info.IsDir() {
return nil
}

// make sure we only check the snapshots that belong to this crd being checked.
if strings.Contains(filepath.Base(path), filepath.Base(crdLocation)) {
if filepath.Ext(path) == ".yaml" {
if c.Minimal {
// only check files that have the min extension.
if strings.HasSuffix(filepath.Base(path), ".min.yaml") {
snapshots = append(snapshots, path)
}
} else if !strings.HasSuffix(filepath.Base(path), ".min.yaml") {
// only add the file if it specifically does NOT contain the min extension.
snapshots = append(snapshots, path)
}
}
}

return nil
})
if err != nil {
return err
}

content, err := os.ReadFile(crdLocation)
if err != nil {
return fmt.Errorf("failed to read snapshot template: %w", err)
return fmt.Errorf("failed to read source template: %w", err)
}

return matches.Validate(content, snapshot)
}
// gather all the errors for all the files
var validationErrors error
for _, s := range snapshots {
// one snapshot will contain a single version and the validation
// will know which version to check against
snapshotContent, err := os.ReadFile(s)
if err != nil {
return fmt.Errorf("failed to read snapshot template: %w", err)
}

func init() {
tests.Register(&Matcher{}, MatcherName)
if err := matches.Validate(content, snapshotContent); err != nil {
validationErrors = errors.Join(validationErrors, err)
}
}

return validationErrors
}
56 changes: 56 additions & 0 deletions pkg/matches/matchsnapshot/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package matchsnapshot

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/Skarlso/crd-to-sample-yaml/pkg"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/util/yaml"
)

const (
perm = 0o644
)

type Updater interface {
Update(sourceTemplateLocation string, targetSnapshot string, minimal bool) error
}

type Update struct{}

// Update any given files in the snapshots.
func (u *Update) Update(sourceTemplateLocation string, targetSnapshotLocation string, minimal bool) error {
sourceTemplate, err := os.ReadFile(sourceTemplateLocation)
if err != nil {
return err
}
baseName := strings.Trim(filepath.Base(sourceTemplateLocation), filepath.Ext(sourceTemplateLocation))

crd := &v1beta1.CustomResourceDefinition{}
if err := yaml.Unmarshal(sourceTemplate, crd); err != nil {
return fmt.Errorf("failed to unmarshal into custom resource definition: %w", err)
}

for _, version := range crd.Spec.Versions {
name := baseName + "-" + version.Name + ".yaml"
if minimal {
name = baseName + "-" + version.Name + ".min.yaml"
}
file, err := os.OpenFile(filepath.Join(targetSnapshotLocation, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", filepath.Join(targetSnapshotLocation, name), err)
}

defer file.Close()

parser := pkg.NewParser(crd.Spec.Group, crd.Spec.Names.Kind, false, minimal)
if err := parser.ParseProperties(version.Name, file, version.Schema.OpenAPIV3Schema.Properties, pkg.RootRequiredFields); err != nil {
return fmt.Errorf("failed to parse properties: %w", err)
}
}

return nil
}
9 changes: 5 additions & 4 deletions pkg/matches/matchstring/matcher.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
package matchstring

import (
"context"
"fmt"
"os"

"github.com/Skarlso/crd-to-sample-yaml/pkg/matches"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/util/yaml"

"github.com/Skarlso/crd-to-sample-yaml/pkg/matches"
"github.com/Skarlso/crd-to-sample-yaml/pkg/tests"
)

type Matcher struct{}

func (m *Matcher) Match(sourceTemplateLocation string, payload []byte) error {
func (m *Matcher) Match(_ context.Context, crdLocation string, payload []byte) error {
c := &apiextensionsv1.JSON{}
if err := yaml.Unmarshal(payload, &c); err != nil {
return err
}

crdContent, err := os.ReadFile(sourceTemplateLocation)
crdContent, err := os.ReadFile(crdLocation)
if err != nil {
return fmt.Errorf("error reading file %s: %w", sourceTemplateLocation, err)
return fmt.Errorf("error reading file %s: %w", crdLocation, err)
}

return matches.Validate(crdContent, payload)
Expand Down
46 changes: 30 additions & 16 deletions pkg/matches/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"strings"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
Expand All @@ -27,28 +28,41 @@ func Validate(sourceCRD []byte, sampleFile []byte) error {
return errors.New("failed to unmarshal into custom resource definition")
}

availableVersions := make([]string, 0, len(crd.Spec.Versions))

// Add checking out the api version from the provided template and only eval against that.
// TODO: this should be a specific version instead.
for _, v := range crd.Spec.Versions {
eval, _, err := validation.NewSchemaValidator(v.Schema.OpenAPIV3Schema)
if err != nil {
return fmt.Errorf("invalid schema: %w", err)
}
availableVersions = append(availableVersions, v.Name)

var resultErrors error
result := eval.Validate(obj)
for _, e := range result.Errors {
resultErrors = errors.Join(resultErrors, e)
}
// Make sure we are only testing versions that equal to the CRD's version.
// This is important in case there are multiple versions in the CRD.
if obj.GroupVersionKind().Version == v.Name {
eval, _, err := validation.NewSchemaValidator(v.Schema.OpenAPIV3Schema)
if err != nil {
return fmt.Errorf("invalid schema: %w", err)
}

for _, e := range result.Warnings {
resultErrors = errors.Join(resultErrors, e)
}
var resultErrors error
result := eval.Validate(obj)
for _, e := range result.Errors {
resultErrors = errors.Join(resultErrors, e)
}

for _, e := range result.Warnings {
resultErrors = errors.Join(resultErrors, e)
}

if resultErrors != nil {
return fmt.Errorf("failed to validate kind %s: %w", crd.Spec.Names.Kind, resultErrors)
}

if resultErrors != nil {
return fmt.Errorf("failed to validate kind %s: %w", crd.Spec.Names.Kind, resultErrors)
return nil
}
}

return nil
return fmt.Errorf(
"version of the snapshot %s not found amongst the available testing versions of the CRD %s",
obj.GroupVersionKind().Version,
strings.Join(availableVersions, ","),
)
}
Loading

0 comments on commit bd92033

Please sign in to comment.