198 lines
5.0 KiB
Go
198 lines
5.0 KiB
Go
package tests
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/ghodss/yaml"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
|
|
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer"
|
|
"sigs.k8s.io/kustomize/v3/pkg/fs"
|
|
"sigs.k8s.io/kustomize/v3/pkg/loader"
|
|
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
|
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
|
"sigs.k8s.io/kustomize/v3/pkg/resource"
|
|
"sigs.k8s.io/kustomize/v3/pkg/target"
|
|
"sigs.k8s.io/kustomize/v3/pkg/validators"
|
|
)
|
|
|
|
type KustomizeTestCase struct {
|
|
// Package is the path to the kustomize directory to run kustomize in
|
|
Package string
|
|
// Expected is a path to a directory containing the expected resources
|
|
Expected string
|
|
}
|
|
|
|
// RunTestCase runs the specified test case
|
|
func RunTestCase(t *testing.T, testCase *KustomizeTestCase) {
|
|
expected := map[string]*expectedResource{}
|
|
|
|
// Read all the YAML files containing expected resources and parse them.
|
|
files, err := ioutil.ReadDir(testCase.Expected)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, f := range files {
|
|
contents, err := ioutil.ReadFile(filepath.Join(testCase.Expected, f.Name()))
|
|
if err != nil {
|
|
t.Fatalf("Err: %v", err)
|
|
}
|
|
|
|
u := &unstructured.Unstructured{}
|
|
if err := yaml.Unmarshal([]byte(contents), u); err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
|
|
r := &expectedResource{
|
|
fileName: f.Name(),
|
|
yaml: string(contents),
|
|
u: u,
|
|
}
|
|
|
|
expected[r.Key()] = r
|
|
}
|
|
|
|
fsys := fs.MakeRealFS()
|
|
// We don't want to enforce the security check.
|
|
// This is equivalent to running:
|
|
// kustomize build --load_restrictor none
|
|
lrc := loader.RestrictionNone
|
|
_loader, loaderErr := loader.NewLoader(lrc, validators.MakeFakeValidator(), testCase.Package, fsys)
|
|
if loaderErr != nil {
|
|
t.Fatalf("could not load kustomize loader: %v", loaderErr)
|
|
}
|
|
rf := resmap.NewFactory(resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()), transformer.NewFactoryImpl())
|
|
pc := plugins.DefaultPluginConfig()
|
|
kt, err := target.NewKustTarget(_loader, rf, transformer.NewFactoryImpl(), plugins.NewLoader(pc, rf))
|
|
if err != nil {
|
|
t.Fatalf("Unexpected construction error %v", err)
|
|
}
|
|
actual, err := kt.MakeCustomizedResMap()
|
|
if err != nil {
|
|
t.Fatalf("Err: %v", err)
|
|
}
|
|
|
|
actualNames := map[string]bool{}
|
|
|
|
// Check that all the actual resources match the expected resources
|
|
for _, r := range actual.Resources() {
|
|
rKey := key(r.GetKind(), r.GetName())
|
|
actualNames[rKey] = true
|
|
|
|
e, ok := expected[rKey]
|
|
|
|
if !ok {
|
|
t.Errorf("Actual output included an unexpected resource; resource: %v", rKey)
|
|
continue
|
|
}
|
|
|
|
actualYaml, err := r.AsYAML()
|
|
|
|
if err != nil {
|
|
t.Errorf("Could not generate YAML for resource: %v; error: %v", rKey, err)
|
|
continue
|
|
}
|
|
// Ensure the actual YAML matches.
|
|
if string(actualYaml) != e.yaml {
|
|
ReportDiffAndFail(t, actualYaml, e.yaml)
|
|
}
|
|
}
|
|
|
|
// Make sure we aren't missing any expected resources
|
|
for name, _ := range expected {
|
|
if _, ok := actualNames[name]; !ok {
|
|
t.Errorf("Actual resources is missing expected resource: %v", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func convertToArray(x string) ([]string, int) {
|
|
a := strings.Split(strings.TrimSuffix(x, "\n"), "\n")
|
|
maxLen := 0
|
|
for i, v := range a {
|
|
z := tabToSpace(v)
|
|
if len(z) > maxLen {
|
|
maxLen = len(z)
|
|
}
|
|
a[i] = z
|
|
}
|
|
return a, maxLen
|
|
}
|
|
|
|
// expectedResource represents an expected Kubernetes resource to be generated
|
|
// from the kustomize package.
|
|
type expectedResource struct {
|
|
fileName string
|
|
yaml string
|
|
u *unstructured.Unstructured
|
|
}
|
|
|
|
// key generates a name to index resources by. It is used to match expected and actual resources.
|
|
func key(kind string, name string) string {
|
|
return kind + "." + name
|
|
}
|
|
|
|
// Key returns a unique identifier fo this resource that can be used to index it
|
|
func (r *expectedResource) Key() string {
|
|
if r.u == nil {
|
|
return ""
|
|
}
|
|
|
|
return key(r.u.GetKind(), r.u.GetName())
|
|
}
|
|
|
|
// Pretty printing of file differences.
|
|
func ReportDiffAndFail(t *testing.T, actual []byte, expected string) {
|
|
sE, maxLen := convertToArray(expected)
|
|
sA, _ := convertToArray(string(actual))
|
|
fmt.Println("===== ACTUAL BEGIN ========================================")
|
|
fmt.Print(string(actual))
|
|
fmt.Println("===== ACTUAL END ==========================================")
|
|
format := fmt.Sprintf("%%s %%-%ds %%s\n", maxLen+4)
|
|
limit := 0
|
|
if len(sE) < len(sA) {
|
|
limit = len(sE)
|
|
} else {
|
|
limit = len(sA)
|
|
}
|
|
fmt.Printf(format, " ", "EXPECTED", "ACTUAL")
|
|
fmt.Printf(format, " ", "--------", "------")
|
|
for i := 0; i < limit; i++ {
|
|
fmt.Printf(format, hint(sE[i], sA[i]), sE[i], sA[i])
|
|
}
|
|
if len(sE) < len(sA) {
|
|
for i := len(sE); i < len(sA); i++ {
|
|
fmt.Printf(format, "X", "", sA[i])
|
|
}
|
|
} else {
|
|
for i := len(sA); i < len(sE); i++ {
|
|
fmt.Printf(format, "X", sE[i], "")
|
|
}
|
|
}
|
|
t.Fatalf("Expected not equal to actual")
|
|
}
|
|
|
|
func tabToSpace(input string) string {
|
|
var result []string
|
|
for _, i := range input {
|
|
if i == 9 {
|
|
result = append(result, " ")
|
|
} else {
|
|
result = append(result, string(i))
|
|
}
|
|
}
|
|
return strings.Join(result, "")
|
|
}
|
|
|
|
func hint(a, b string) string {
|
|
if a == b {
|
|
return " "
|
|
}
|
|
return "X"
|
|
}
|