Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WiP] Support template expansion of YAML files #387

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/file/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import (
)

// from a list of paths, returns an array of runtime objects
func ToObjects(paths []string) ([]client.Object, error) {
func ToObjects(paths []string, templatingContext testutils.TemplatingContext) ([]client.Object, error) {
apply := []client.Object{}

for _, path := range paths {
objs, err := testutils.LoadYAMLFromFile(path)
objs, err := testutils.LoadYAMLFromFile(path, templatingContext)
if err != nil {
return nil, fmt.Errorf("file %q load yaml error: %w", path, err)
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/file/files_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package file

import (
"github.com/kudobuilder/kuttl/pkg/test/utils"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -51,13 +52,13 @@ func TestFromPath(t *testing.T) {

func TestToRuntimeObjects(t *testing.T) {
files := []string{"testdata/path/test1.yaml"}
objs, err := ToObjects(files)
objs, err := ToObjects(files, utils.TemplatingContext{})
assert.NoError(t, err)
assert.Equal(t, 1, len(objs))
assert.Equal(t, "Pod", objs[0].GetObjectKind().GroupVersionKind().Kind)

files = append(files, "testdata/path/test2.yaml")
_, err = ToObjects(files)
_, err = ToObjects(files, utils.TemplatingContext{})
assert.Error(t, err, "file \"testdata/path/test2.yaml\" load yaml error")
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/kuttlctl/cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ For more detailed documentation, visit: https://kuttl.dev`,

// Load the configuration YAML into options.
if configPath != "" {
objects, err := testutils.LoadYAMLFromFile(configPath)
objects, err := testutils.LoadYAMLFromFile(configPath, testutils.TemplatingContext{})
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/test/assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func Assert(namespace string, timeout int, assertFiles ...string) error {
var objects []client.Object

for _, file := range assertFiles {
o, err := ObjectsFromPath(file, "")
o, err := ObjectsFromPath(file, "", testutils.GetTemplatingContext(namespace))
if err != nil {
return err
}
Expand Down Expand Up @@ -64,7 +64,7 @@ func Errors(namespace string, timeout int, errorFiles ...string) error {
var objects []client.Object

for _, file := range errorFiles {
o, err := ObjectsFromPath(file, "")
o, err := ObjectsFromPath(file, "", testutils.GetTemplatingContext(namespace))
if err != nil {
return err
}
Expand Down
59 changes: 30 additions & 29 deletions pkg/test/case.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
)

// testStepRegex contains one capturing group to determine the index of a step file.
var testStepRegex = regexp.MustCompile(`^(\d+)-(?:[^\.]+)(?:\.yaml)?$`)
var testStepRegex = regexp.MustCompile(`^(\d+)-(?:[^\.]+)(?:\.gotmpl)?(?:\.yaml)?$`)

// Case contains all of the test steps and the Kubernetes client and other global configuration
// for a test.
Expand All @@ -42,6 +42,7 @@ type Case struct {

Client func(forceNew bool) (client.Client, error)
DiscoveryClient func() (discovery.DiscoveryInterface, error)
ns *namespace

Logger testutils.Logger
// Suppress is used to suppress logs
Expand All @@ -54,13 +55,13 @@ type namespace struct {
}

// DeleteNamespace deletes a namespace in Kubernetes after we are done using it.
func (t *Case) DeleteNamespace(cl client.Client, ns *namespace) error {
if !ns.AutoCreated {
t.Logger.Log("Skipping deletion of user-supplied namespace:", ns.Name)
func (t *Case) DeleteNamespace(cl client.Client) error {
if !t.ns.AutoCreated {
t.Logger.Log("Skipping deletion of user-supplied namespace:", t.ns.Name)
return nil
}

t.Logger.Log("Deleting namespace:", ns.Name)
t.Logger.Log("Deleting namespace:", t.ns.Name)

ctx := context.Background()
if t.Timeout > 0 {
Expand All @@ -71,7 +72,7 @@ func (t *Case) DeleteNamespace(cl client.Client, ns *namespace) error {

return cl.Delete(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns.Name,
Name: t.ns.Name,
},
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
Expand All @@ -80,12 +81,12 @@ func (t *Case) DeleteNamespace(cl client.Client, ns *namespace) error {
}

// CreateNamespace creates a namespace in Kubernetes to use for a test.
func (t *Case) CreateNamespace(cl client.Client, ns *namespace) error {
if !ns.AutoCreated {
t.Logger.Log("Skipping creation of user-supplied namespace:", ns.Name)
func (t *Case) CreateNamespace(cl client.Client) error {
if !t.ns.AutoCreated {
t.Logger.Log("Skipping creation of user-supplied namespace:", t.ns.Name)
return nil
}
t.Logger.Log("Creating namespace:", ns.Name)
t.Logger.Log("Creating namespace:", t.ns.Name)

ctx := context.Background()
if t.Timeout > 0 {
Expand All @@ -96,7 +97,7 @@ func (t *Case) CreateNamespace(cl client.Client, ns *namespace) error {

return cl.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns.Name,
Name: t.ns.Name,
},
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
Expand Down Expand Up @@ -292,8 +293,6 @@ func shortString(obj *corev1.ObjectReference) string {

// Run runs a test case including all of its steps.
func (t *Case) Run(test *testing.T, tc *report.Testcase) {
ns := t.determineNamespace()

cl, err := t.Client(false)
if err != nil {
tc.Failure = report.NewFailure(err.Error(), nil)
Expand All @@ -317,7 +316,7 @@ func (t *Case) Run(test *testing.T, tc *report.Testcase) {
}

for _, c := range clients {
if err := t.CreateNamespace(c, ns); err != nil {
if err := t.CreateNamespace(c); err != nil {
tc.Failure = report.NewFailure(err.Error(), nil)
test.Fatal(err)
}
Expand All @@ -326,7 +325,7 @@ func (t *Case) Run(test *testing.T, tc *report.Testcase) {
if !t.SkipDelete {
defer func() {
for _, c := range clients {
if err := t.DeleteNamespace(c, ns); err != nil {
if err := t.DeleteNamespace(c); err != nil {
test.Error(err)
}
}
Expand All @@ -348,13 +347,13 @@ func (t *Case) Run(test *testing.T, tc *report.Testcase) {

if !t.SkipDelete {
defer func() {
if err := testStep.Clean(ns.Name); err != nil {
if err := testStep.Clean(t.ns.Name); err != nil {
test.Error(err)
}
}()
}

if errs := testStep.Run(ns.Name); len(errs) > 0 {
if errs := testStep.Run(t.ns.Name); len(errs) > 0 {
caseErr := fmt.Errorf("failed in step %s", testStep.String())
tc.Failure = report.NewFailure(caseErr.Error(), errs)

Expand All @@ -369,22 +368,22 @@ func (t *Case) Run(test *testing.T, tc *report.Testcase) {
if funk.Contains(t.Suppress, "events") {
t.Logger.Logf("skipping kubernetes event logging")
} else {
t.CollectEvents(ns.Name)
t.CollectEvents(t.ns.Name)
}
}

func (t *Case) determineNamespace() *namespace {
ns := &namespace{
Name: t.PreferredNamespace,
AutoCreated: false,
}
// no preferred ns, means we auto-create with petnames
func (t *Case) determineNamespace() {
if t.PreferredNamespace == "" {
ns.Name = fmt.Sprintf("kuttl-test-%s", petname.Generate(2, "-"))
ns.AutoCreated = true
t.ns = &namespace{
Name: fmt.Sprintf("kuttl-test-%s", petname.Generate(2, "-")),
AutoCreated: true,
}
} else {
t.ns = &namespace{
Name: t.PreferredNamespace,
AutoCreated: false,
}
}
// if we have a preferred namespace, we do NOT auto-create
return ns
}

// CollectTestStepFiles collects a map of test steps and their associated files
Expand Down Expand Up @@ -446,6 +445,8 @@ func getIndexFromFile(fileName string) (int64, error) {

// LoadTestSteps loads all of the test steps for a test case.
func (t *Case) LoadTestSteps() error {
t.determineNamespace()

testStepFiles, err := t.CollectTestStepFiles()
if err != nil {
return err
Expand All @@ -464,7 +465,7 @@ func (t *Case) LoadTestSteps() error {
}

for _, file := range files {
if err := testStep.LoadYAML(file); err != nil {
if err := testStep.LoadYAML(file, testutils.GetTemplatingContext(t.ns.Name)); err != nil {
return err
}
}
Expand Down
1 change: 1 addition & 0 deletions pkg/test/case_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func TestMultiClusterCase(t *testing.T) {
return testenv.DiscoveryClient, nil
},
}
c.determineNamespace()

c.Run(t, &report.Testcase{})
}
18 changes: 9 additions & 9 deletions pkg/test/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (

// fileNameRegex contains two capturing groups to determine whether a file has special
// meaning (ex. assert) or contains an appliable object, and extra name elements.
var fileNameRegex = regexp.MustCompile(`^(?:\d+-)?([^-\.]+)(-[^\.]+)?(?:\.yaml)?$`)
var fileNameRegex = regexp.MustCompile(`^(?:\d+-)?([^-\.]+)(-[^\.]+)?(?:\.gotmpl)?(?:\.yaml)?$`)

// A Step contains the name of the test step, its index in the test,
// and all of the test step's settings (including objects to apply and assert on).
Expand Down Expand Up @@ -476,8 +476,8 @@ func (s *Step) String() string {
// * If the YAML file is called "errors", then it contains objects that,
// if seen, mark a test immediately failed.
// * All other YAML files are considered resources to create.
func (s *Step) LoadYAML(file string) error {
objects, err := testutils.LoadYAMLFromFile(file)
func (s *Step) LoadYAML(file string, templatingContext testutils.TemplatingContext) error {
objects, err := testutils.LoadYAMLFromFile(file, templatingContext)
if err != nil {
return fmt.Errorf("loading %s: %s", file, err)
}
Expand Down Expand Up @@ -530,7 +530,7 @@ func (s *Step) LoadYAML(file string) error {
// process configured step applies
for _, applyPath := range s.Step.Apply {
exApply := env.Expand(applyPath)
apply, err := ObjectsFromPath(exApply, s.Dir)
apply, err := ObjectsFromPath(exApply, s.Dir, templatingContext)
if err != nil {
return fmt.Errorf("step %q apply path %s: %w", s.Name, exApply, err)
}
Expand All @@ -539,7 +539,7 @@ func (s *Step) LoadYAML(file string) error {
// process configured step asserts
for _, assertPath := range s.Step.Assert {
exAssert := env.Expand(assertPath)
assert, err := ObjectsFromPath(exAssert, s.Dir)
assert, err := ObjectsFromPath(exAssert, s.Dir, templatingContext)
if err != nil {
return fmt.Errorf("step %q assert path %s: %w", s.Name, exAssert, err)
}
Expand All @@ -548,7 +548,7 @@ func (s *Step) LoadYAML(file string) error {
// process configured errors
for _, errorPath := range s.Step.Error {
exError := env.Expand(errorPath)
errObjs, err := ObjectsFromPath(exError, s.Dir)
errObjs, err := ObjectsFromPath(exError, s.Dir, templatingContext)
if err != nil {
return fmt.Errorf("step %q error path %s: %w", s.Name, exError, err)
}
Expand All @@ -566,7 +566,7 @@ func (s *Step) LoadYAML(file string) error {
func (s *Step) populateObjectsByFileName(fileName string, objects []client.Object) error {
matches := fileNameRegex.FindStringSubmatch(fileName)
if len(matches) < 2 {
return fmt.Errorf("%s does not match file name regexp: %s", fileName, testStepRegex.String())
return fmt.Errorf("%s does not match file name regexp: %s", fileName, fileNameRegex.String())
}

switch fname := strings.ToLower(matches[1]); fname {
Expand All @@ -590,7 +590,7 @@ func (s *Step) populateObjectsByFileName(fileName string, objects []client.Objec
}

// ObjectsFromPath returns an array of runtime.Objects for files / urls provided
func ObjectsFromPath(path, dir string) ([]client.Object, error) {
func ObjectsFromPath(path, dir string, templatingContext testutils.TemplatingContext) ([]client.Object, error) {
if http.IsURL(path) {
apply, err := http.ToObjects(path)
if err != nil {
Expand All @@ -605,7 +605,7 @@ func ObjectsFromPath(path, dir string) ([]client.Object, error) {
if err != nil {
return nil, fmt.Errorf("failed to find YAML files in %s: %w", cPath, err)
}
apply, err := kfile.ToObjects(paths)
apply, err := kfile.ToObjects(paths, templatingContext)
if err != nil {
return nil, err
}
Expand Down
Loading