diff --git a/action.go b/action.go index af227a0..33a328c 100644 --- a/action.go +++ b/action.go @@ -125,7 +125,7 @@ func (a *Action) get( arg := a.Get.Arg argRep := gdtcontext.ReplaceVariables(ctx, arg) if arg != argRep { - debug.Println( + debug.Printf( ctx, "kube.get: replaced arg: %s -> %s", arg, argRep, @@ -145,7 +145,7 @@ func (a *Action) get( } else { nameRep := gdtcontext.ReplaceVariables(ctx, name) if name != nameRep { - debug.Println( + debug.Printf( ctx, "kube.get: replaced name: %s -> %s", name, nameRep, @@ -177,7 +177,7 @@ func (a *Action) doList( opts.LabelSelector = labelsStr } if c.resourceNamespaced(res) { - debug.Println( + debug.Printf( ctx, "kube.get: %s%s (ns: %s)", resName, labelSelString, ns, ) @@ -185,7 +185,7 @@ func (a *Action) doList( ctx, opts, ) } - debug.Println( + debug.Printf( ctx, "kube.get: %s%s (non-namespaced resource)", resName, labelSelString, ) @@ -204,7 +204,7 @@ func (a *Action) doGet( ) (*unstructured.Unstructured, error) { resName := res.Resource if c.resourceNamespaced(res) { - debug.Println( + debug.Printf( ctx, "kube.get: %s/%s (ns: %s)", resName, name, ns, ) @@ -214,7 +214,7 @@ func (a *Action) doGet( metav1.GetOptions{}, ) } - debug.Println( + debug.Printf( ctx, "kube.get: %s/%s (non-namespaced resource)", resName, name, ) @@ -275,14 +275,14 @@ func (a *Action) create( if ons == "" { ons = ns } - debug.Println(ctx, "kube.create: %s (ns: %s)", resName, ons) + debug.Printf(ctx, "kube.create: %s (ns: %s)", resName, ons) obj, err = c.client.Resource(res).Namespace(ons).Create( ctx, obj, metav1.CreateOptions{}, ) } else { - debug.Println(ctx, "kube.create: %s (non-namespaced resource)", resName) + debug.Printf(ctx, "kube.create: %s (non-namespaced resource)", resName) obj, err = c.client.Resource(res).Create( ctx, obj, @@ -348,7 +348,7 @@ func (a *Action) apply( if ons == "" { ons = ns } - debug.Println(ctx, "kube.apply: %s (ns: %s)", resName, ons) + debug.Printf(ctx, "kube.apply: %s (ns: %s)", resName, ons) obj, err = c.client.Resource(res).Namespace(ns).Apply( ctx, // NOTE(jaypipes): Not sure why a separate name argument is @@ -362,7 +362,7 @@ func (a *Action) apply( metav1.ApplyOptions{FieldManager: fieldManagerName, Force: true}, ) } else { - debug.Println(ctx, "kube.apply: %s (non-namespaced resource)", resName) + debug.Printf(ctx, "kube.apply: %s (non-namespaced resource)", resName) obj, err = c.client.Resource(res).Apply( ctx, // NOTE(jaypipes): Not sure why a separate name argument is @@ -446,7 +446,7 @@ func (a *Action) doDelete( ) error { nameRep := gdtcontext.ReplaceVariables(ctx, name) if name != nameRep { - debug.Println( + debug.Printf( ctx, "kube.delete: replaced name: %s -> %s", name, nameRep, @@ -454,7 +454,7 @@ func (a *Action) doDelete( } resName := res.Resource if c.resourceNamespaced(res) { - debug.Println( + debug.Printf( ctx, "kube.delete: %s/%s (ns: %s)", resName, nameRep, ns, ) @@ -464,7 +464,7 @@ func (a *Action) doDelete( metav1.DeleteOptions{}, ) } - debug.Println( + debug.Printf( ctx, "kube.delete: %s/%s (non-namespaced resource)", resName, nameRep, ) @@ -494,7 +494,7 @@ func (a *Action) doDeleteCollection( } resName := res.Resource if c.resourceNamespaced(res) { - debug.Println( + debug.Printf( ctx, "kube.delete: %s%s (ns: %s)", resName, labelSelString, ns, ) @@ -504,7 +504,7 @@ func (a *Action) doDeleteCollection( opts, ) } - debug.Println( + debug.Printf( ctx, "kube.delete: %s%s (non-namespaced resource)", resName, labelSelString, ) diff --git a/compare.go b/compare.go index 73db629..5d768dc 100644 --- a/compare.go +++ b/compare.go @@ -178,7 +178,7 @@ func replaceVariablesInMapEntry( ) (string, any) { kRep := gdtcontext.ReplaceVariables(ctx, k) if k != kRep { - debug.Println( + debug.Printf( ctx, "kube.assert: replaced match key: %s -> %s", k, kRep, @@ -188,7 +188,7 @@ func replaceVariablesInMapEntry( case string: entryRep := gdtcontext.ReplaceVariables(ctx, entry) if entry != entryRep { - debug.Println( + debug.Printf( ctx, "kube.assert: replaced match key %s value: %s -> %s", k, entry, entryRep, diff --git a/eval.go b/eval.go index 9d73921..5dd1ed1 100644 --- a/eval.go +++ b/eval.go @@ -8,6 +8,7 @@ import ( "context" "github.com/gdt-dev/core/api" + gdtcontext "github.com/gdt-dev/core/context" "github.com/gdt-dev/core/debug" kubeerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,7 +30,7 @@ func (s *Spec) Eval(ctx context.Context) (*api.Result, error) { return nil, err } if nsCreated { - debug.Println(ctx, "auto-created namespace: %s", ns) + debug.Printf(ctx, "auto-created namespace: %s", ns) } var out any @@ -45,6 +46,9 @@ func (s *Spec) Eval(ctx context.Context) (*api.Result, error) { a := newAssertions(c, s.Assert, err, out) if a.OK(ctx) { res := api.NewResult() + if nsCreated { + res.AddCleanup(cleanupAutoNamespace(ctx, c, ns)) + } if err := saveVars(ctx, s.Var, out, res); err != nil { return nil, err } @@ -53,6 +57,54 @@ func (s *Spec) Eval(ctx context.Context) (*api.Result, error) { return api.NewResult(api.WithFailures(a.Failures()...)), nil } +// cleanupAutoNamespace returns a cleanup function that deletes the +// auto-created namespace. +func cleanupAutoNamespace( + ctx context.Context, + c *connection, + ns string, +) func() { + debug.Printf( + ctx, "registered cleanup for auto-created namespace: %s", ns, + ) + // NOTE(jaypipes): We need to create a new context that will be used to + // execute the cleanup because the context supplied is for the spec and + // that context has its own lifecycle (and gets a cancel/timeout that will + // be called before the cleanup function runs... + cleanupCtx := context.Background() + tu := gdtcontext.TestUnit(ctx) + if tu != nil { + cleanupCtx = gdtcontext.SetTestUnit(cleanupCtx, tu) + } + debuggers := gdtcontext.Debug(ctx) + if len(debuggers) > 0 { + cleanupCtx = gdtcontext.SetDebug(cleanupCtx, debuggers...) + } + trace := gdtcontext.Trace(ctx) + cleanupCtx = gdtcontext.SetTrace(cleanupCtx, trace) + return func() { + res, err := c.gvrFromArg("namespaces") + if err != nil { + debug.Printf(cleanupCtx, "failed to get GVR for namespace") + return + } + + err = c.client.Resource(res).Delete( + cleanupCtx, + ns, + metav1.DeleteOptions{}, + ) + if err != nil { + debug.Printf( + cleanupCtx, "failed to delete auto-created namespace %s: %s", + ns, err, + ) + return + } + debug.Printf(cleanupCtx, "deleted auto-created namespace: %s", ns) + } +} + // ensureNamespace automatically creates a supplied Kubernetes Namespace if it // does not already exist, returning whether the namespace was created. func ensureNamespace( diff --git a/eval_test.go b/eval_test.go index 7130069..fc0d271 100644 --- a/eval_test.go +++ b/eval_test.go @@ -22,11 +22,11 @@ var stdKindFix = kindfix.New( kindfix.WithRetainOnStop(), ) -func TestListPodsEmpty(t *testing.T) { +func TestKindListPodsEmpty(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "list-pods-empty.yaml") + fp := filepath.Join("testdata", "kind", "list-pods-empty.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -39,11 +39,11 @@ func TestListPodsEmpty(t *testing.T) { require.Nil(err, "%s", err) } -func TestGetPodNotFound(t *testing.T) { +func TestKindGetPodNotFound(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "get-pod-not-found.yaml") + fp := filepath.Join("testdata", "kind", "get-pod-not-found.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -56,11 +56,11 @@ func TestGetPodNotFound(t *testing.T) { require.Nil(err) } -func TestCreateUnknownResource(t *testing.T) { +func TestKindCreateUnknownResource(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "create-unknown-resource.yaml") + fp := filepath.Join("testdata", "kind", "create-unknown-resource.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -73,11 +73,11 @@ func TestCreateUnknownResource(t *testing.T) { require.Nil(err) } -func TestSameNamedKind(t *testing.T) { +func TestKindSameNamedKind(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "same-named-kind.yaml") + fp := filepath.Join("testdata", "kind", "same-named-kind.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -90,11 +90,11 @@ func TestSameNamedKind(t *testing.T) { require.Nil(err) } -func TestDeleteResourceNotFound(t *testing.T) { +func TestKindDeleteResourceNotFound(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "delete-resource-not-found.yaml") + fp := filepath.Join("testdata", "kind", "delete-resource-not-found.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -107,11 +107,11 @@ func TestDeleteResourceNotFound(t *testing.T) { require.Nil(err) } -func TestDeleteUnknownResource(t *testing.T) { +func TestKindDeleteUnknownResource(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "delete-unknown-resource.yaml") + fp := filepath.Join("testdata", "kind", "delete-unknown-resource.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -124,11 +124,11 @@ func TestDeleteUnknownResource(t *testing.T) { require.Nil(err) } -func TestPodCreateGetDelete(t *testing.T) { +func TestKindPodCreateGetDelete(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "create-get-delete-pod.yaml") + fp := filepath.Join("testdata", "kind", "create-get-delete-pod.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -141,11 +141,11 @@ func TestPodCreateGetDelete(t *testing.T) { require.Nil(err) } -func TestMatches(t *testing.T) { +func TestKindMatches(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "matches.yaml") + fp := filepath.Join("testdata", "kind", "matches.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -158,11 +158,11 @@ func TestMatches(t *testing.T) { require.Nil(err) } -func TestConditions(t *testing.T) { +func TestKindConditions(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "conditions.yaml") + fp := filepath.Join("testdata", "kind", "conditions.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -175,11 +175,11 @@ func TestConditions(t *testing.T) { require.Nil(err) } -func TestJSON(t *testing.T) { +func TestKindJSON(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "json.yaml") + fp := filepath.Join("testdata", "kind", "json.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -192,11 +192,11 @@ func TestJSON(t *testing.T) { require.Nil(err) } -func TestApply(t *testing.T) { +func TestKindApply(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "apply-deployment.yaml") + fp := filepath.Join("testdata", "kind", "apply-deployment.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -209,13 +209,13 @@ func TestApply(t *testing.T) { require.Nil(err) } -func TestEnvvarSubstitution(t *testing.T) { +func TestKindEnvvarSubstitution(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) t.Setenv("pod_name", "foo") - fp := filepath.Join("testdata", "envvar-substitution.yaml") + fp := filepath.Join("testdata", "kind", "envvar-substitution.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -228,11 +228,11 @@ func TestEnvvarSubstitution(t *testing.T) { require.Nil(err) } -func TestWithLabels(t *testing.T) { +func TestKindWithLabels(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "list-pods-with-labels.yaml") + fp := filepath.Join("testdata", "kind", "list-pods-with-labels.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -245,11 +245,11 @@ func TestWithLabels(t *testing.T) { require.Nil(err) } -func TestVarSaveRestore(t *testing.T) { +func TestKindVarSaveRestore(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "var-save-restore.yaml") + fp := filepath.Join("testdata", "kind", "var-save-restore.yaml") s, err := gdt.From(fp) require.Nil(err) @@ -264,11 +264,11 @@ func TestVarSaveRestore(t *testing.T) { require.Nil(err) } -func TestCurlPodIP(t *testing.T) { +func TestKindCurlPodIP(t *testing.T) { testutil.SkipIfNoKind(t) require := require.New(t) - fp := filepath.Join("testdata", "curl-pod-ip.yaml") + fp := filepath.Join("testdata", "kind", "curl-pod-ip.yaml") s, err := gdt.From(fp) require.Nil(err) diff --git a/fixtures/kind/kind.go b/fixtures/kind/kind.go index cab965a..98a1f0b 100644 --- a/fixtures/kind/kind.go +++ b/fixtures/kind/kind.go @@ -76,13 +76,13 @@ func (f *KindFixture) Start(ctx context.Context) error { f.ClusterName = kindconst.DefaultClusterName } if f.isRunning() { - debug.Println(ctx, "cluster %s already running", f.ClusterName) + debug.Printf(ctx, "cluster %s already running", f.ClusterName) f.runningBeforeStart = true return f.waitForDefaultServiceAccount(ctx) } opts := []cluster.CreateOption{} if f.ConfigPath != "" { - debug.Println( + debug.Printf( ctx, "using custom kind config %s for cluster %s", f.ConfigPath, f.ClusterName, ) @@ -91,10 +91,10 @@ func (f *KindFixture) Start(ctx context.Context) error { if err := f.provider.Create(f.ClusterName, opts...); err != nil { return err } - debug.Println(ctx, "cluster %s successfully created", f.ClusterName) + debug.Printf(ctx, "cluster %s successfully created", f.ClusterName) if !f.retainOnStop { f.deleteOnStop = true - debug.Println(ctx, "cluster %s will be deleted on stop", f.ClusterName) + debug.Printf(ctx, "cluster %s will be deleted on stop", f.ClusterName) } return f.waitForDefaultServiceAccount(ctx) } @@ -153,7 +153,7 @@ func (f *KindFixture) waitForDefaultServiceAccount(ctx context.Context) error { } found = false } - debug.Println( + debug.Printf( ctx, "check for default service account: attempt %d, found: %v", attempts, found, ) @@ -172,18 +172,18 @@ func (f *KindFixture) Stop(ctx context.Context) { ctx = gdtcontext.PopTrace(ctx) }() if !f.isRunning() { - debug.Println(ctx, "cluster %s not running", f.ClusterName) + debug.Printf(ctx, "cluster %s not running", f.ClusterName) return } if f.runningBeforeStart && !f.deleteOnStop { - debug.Println(ctx, "cluster %s was running before start and deleteOnStop=false so not deleting", f.ClusterName) + debug.Printf(ctx, "cluster %s was running before start and deleteOnStop=false so not deleting", f.ClusterName) return } if f.deleteOnStop { if err := f.provider.Delete(f.ClusterName, ""); err != nil { panic(err) } - debug.Println(ctx, "cluster %s successfully deleted", f.ClusterName) + debug.Printf(ctx, "cluster %s successfully deleted", f.ClusterName) } } diff --git a/fixtures/kind/kind_test.go b/fixtures/kind/kind_test.go index 19e7663..9a60cc3 100644 --- a/fixtures/kind/kind_test.go +++ b/fixtures/kind/kind_test.go @@ -54,7 +54,7 @@ func TestOneControlPlaneOneWorker(t *testing.T) { require.Nil(err) require.NotNil(s) - kindCfgPath := filepath.Join("testdata", "kind-config-one-cp-one-worker.yaml") + kindCfgPath := "kind-config-one-cp-one-worker.yaml" var b bytes.Buffer w := bufio.NewWriter(&b) diff --git a/go.mod b/go.mod index 1d2ee4c..91d08c5 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/gdt-dev/kube go 1.24.3 require ( - github.com/PaesslerAG/jsonpath v0.1.1 github.com/cenkalti/backoff v2.2.1+incompatible - github.com/gdt-dev/core v1.9.11 + github.com/gdt-dev/core v1.10.0 github.com/gdt-dev/gdt v1.9.9 github.com/samber/lo v1.51.0 github.com/stretchr/testify v1.11.1 + github.com/theory/jsonpath v0.10.1 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.34.1 k8s.io/client-go v0.34.1 @@ -19,7 +19,6 @@ require ( require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect github.com/BurntSushi/toml v1.4.0 // indirect - github.com/PaesslerAG/gval v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect diff --git a/go.sum b/go.sum index aeae82c..48e8d98 100644 --- a/go.sum +++ b/go.sum @@ -2,11 +2,6 @@ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXy al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8= -github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= -github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= -github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= -github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -24,8 +19,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gdt-dev/core v1.9.11 h1:oj5+Abj/R1rSO7xygyAt5ecdvmKL0KUg7YflVP0ZKNw= -github.com/gdt-dev/core v1.9.11/go.mod h1:zbjvL8BzgrwTnsP13/vGO0GUbi+DangZWxshlwu4OaQ= +github.com/gdt-dev/core v1.10.0 h1:yX0MG2Tt+O34JGDFWcS63LV47lWpizto4HAyR/ugAC0= +github.com/gdt-dev/core v1.10.0/go.mod h1:Bw8J6kUW0b7MUL8qW5e7qSbxb4SI9EAWQ0a4cAoPVpo= github.com/gdt-dev/gdt v1.9.9 h1:GATWWI28mF6Vt7HAIOxMVYprgaIfH9tp2GyAkq/vZOg= github.com/gdt-dev/gdt v1.9.9/go.mod h1:Zb8DKqBvjyEJXIySOlC6YLgdDlp2uNNtPjud8tDHmG4= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -120,6 +115,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/theory/jsonpath v0.10.1 h1:Qa3alEtTTLIy2s60U2XzamS0XgQmF9zWIg42mEkSRVg= +github.com/theory/jsonpath v0.10.1/go.mod h1:ZOz+y6MxTEDcN/FOxf9AOgeHSoKHx2B+E0nD3HOtzGE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= diff --git a/parse.go b/parse.go index 69f8e34..723b963 100644 --- a/parse.go +++ b/parse.go @@ -13,6 +13,7 @@ import ( gdtjson "github.com/gdt-dev/core/assertion/json" "github.com/gdt-dev/core/parse" "github.com/samber/lo" + "github.com/theory/jsonpath" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/labels" ) @@ -618,7 +619,7 @@ func (e *VarEntry) UnmarshalYAML(node *yaml.Node) error { if len(path) == 0 || path[0] != '$' { return gdtjson.JSONPathInvalidNoRoot(path, valNode) } - if _, err := lang.NewEvaluable(path); err != nil { + if _, err := jsonpath.Parse(path); err != nil { return gdtjson.JSONPathInvalid(path, err, valNode) } e.From = path diff --git a/parse_test.go b/parse_test.go index 968cc2c..5c8e9a4 100644 --- a/parse_test.go +++ b/parse_test.go @@ -295,7 +295,7 @@ spec: }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ - Apply: "testdata/manifests/nginx-pod.yaml", + Apply: "manifests/nginx-pod.yaml", }, }, }, @@ -320,7 +320,7 @@ spec: Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ Delete: gdtkube.NewResourceIdentifierOrFile( - "testdata/manifests/nginx-pod.yaml", + "manifests/nginx-pod.yaml", "", "", nil, ), }, diff --git a/placement.go b/placement.go index 1fdb3b6..486e22f 100644 --- a/placement.go +++ b/placement.go @@ -164,14 +164,14 @@ func (a *assertions) placementSpreadOK( // Pods are evenly spread across domains defined by the topology key when // the min and max number of pods on each domain is not greater than 1. for domain, nodes := range domainNodes { - debug.Println( + debug.Printf( ctx, "placement-spread: domain: %s, unique nodes: %d", domain, len(nodes), ) if len(nodes) > 0 { nodeCounts := lo.Values(podDomains[domain]) - debug.Println( + debug.Printf( ctx, "placement-spread: domain: %s, pods per node: %d", domain, nodeCounts, ) diff --git a/placement_test.go b/placement_test.go index cd8296c..8891082 100644 --- a/placement_test.go +++ b/placement_test.go @@ -29,7 +29,7 @@ func TestPlacementSpread(t *testing.T) { require.Nil(err) require.NotNil(s) - kindCfgPath := filepath.Join("testdata", "kind-config-three-workers-three-zones.yaml") + kindCfgPath := "kind-config-three-workers-three-zones.yaml" var b bytes.Buffer w := bufio.NewWriter(&b) diff --git a/testdata/apply-deployment.yaml b/testdata/apply-deployment.yaml index 73dbd2a..e7b7d3c 100644 --- a/testdata/apply-deployment.yaml +++ b/testdata/apply-deployment.yaml @@ -1,14 +1,15 @@ name: apply-deployment description: create, get, apply a change, get, delete a Deployment -fixtures: - - kind + defaults: kube: namespace: apply-deployment + tests: - name: create-deployment kube: create: testdata/manifests/nginx-deployment.yaml + - name: deployment-has-2-replicas timeout: after: 20s @@ -18,6 +19,7 @@ tests: matches: status: readyReplicas: 2 + - name: apply-deployment-change kube: apply: | @@ -27,6 +29,7 @@ tests: name: nginx spec: replicas: 1 + - name: deployment-has-1-replica timeout: after: 20s @@ -36,6 +39,7 @@ tests: matches: status: readyReplicas: 1 + - name: delete-deployment kube: delete: deployments/nginx diff --git a/testdata/conditions.yaml b/testdata/conditions.yaml index 18bb1e4..161a51a 100644 --- a/testdata/conditions.yaml +++ b/testdata/conditions.yaml @@ -1,20 +1,22 @@ name: conditions description: create a deployment and check the Ready condition eventually equals True -fixtures: - - kind + defaults: kube: namespace: conditions + tests: - name: create-deployment kube: create: testdata/manifests/nginx-deployment.yaml + - name: deployment-immediately-has-false-or-unknown kube: get: deployments/nginx assert: conditions: available: [false, unknown] + - name: deployment-has-true-progressing-condition timeout: after: 2s @@ -23,6 +25,7 @@ tests: assert: conditions: progressing: true + - name: deployment-last-progressing-reason timeout: after: 20s @@ -33,6 +36,7 @@ tests: progressing: status: true reason: NewReplicaSetAvailable + - name: delete-deployment kube: delete: deployments/nginx diff --git a/testdata/create-get-delete-pod.yaml b/testdata/create-get-delete-pod.yaml index 14a4c4a..6ece629 100644 --- a/testdata/create-get-delete-pod.yaml +++ b/testdata/create-get-delete-pod.yaml @@ -1,20 +1,23 @@ name: create-get-delete-pod description: create, get and delete a Pod -fixtures: - - kind + defaults: kube: namespace: create-get-delete-pod + tests: - name: create-pod kube: create: testdata/manifests/nginx-pod.yaml + - name: pod-exists kube: get: pods/nginx + - name: delete-pod kube: delete: pods/nginx + - name: pod-no-longer-exists kube: get: pods/nginx diff --git a/testdata/create-unknown-resource.yaml b/testdata/create-unknown-resource.yaml index e9eb234..a4e9036 100644 --- a/testdata/create-unknown-resource.yaml +++ b/testdata/create-unknown-resource.yaml @@ -1,7 +1,5 @@ name: create-unknown-resource description: create with YAML for unknown resource -fixtures: - - kind tests: - kube: create: | diff --git a/testdata/curl-pod-ip.yaml b/testdata/curl-pod-ip.yaml index da4d7b0..e28077f 100644 --- a/testdata/curl-pod-ip.yaml +++ b/testdata/curl-pod-ip.yaml @@ -1,10 +1,10 @@ name: curl-pod-ip description: scenario showing how to create two NGinx Pods and test connectivity to internal Pod IP addresses -fixtures: - - kind + defaults: kube: namespace: curl-pod-ip + tests: - name: create-server kube: diff --git a/testdata/delete-resource-not-found.yaml b/testdata/delete-resource-not-found.yaml index 8f0500f..f124c0d 100644 --- a/testdata/delete-resource-not-found.yaml +++ b/testdata/delete-resource-not-found.yaml @@ -1,7 +1,6 @@ name: delete-resource-not-found description: delete resource not found -fixtures: - - kind + tests: - kube: delete: pods/doesnotexist diff --git a/testdata/delete-unknown-resource.yaml b/testdata/delete-unknown-resource.yaml index 7d6fa16..3aa2ffd 100644 --- a/testdata/delete-unknown-resource.yaml +++ b/testdata/delete-unknown-resource.yaml @@ -1,7 +1,6 @@ name: delete-unknown-resource description: delete with YAML for unknown resource -fixtures: - - kind + tests: - kube: delete: unknown/unknown diff --git a/testdata/envvar-substitution.yaml b/testdata/envvar-substitution.yaml index d119c0b..d956ea6 100644 --- a/testdata/envvar-substitution.yaml +++ b/testdata/envvar-substitution.yaml @@ -1,17 +1,19 @@ name: envvar-substitution description: create, get and delete a Pod with envvar substitutions -fixtures: - - kind + tests: - name: create-${pod_name} kube: create: testdata/manifests/nginx-pod-envsubst.yaml + - name: foo-exists kube: get: pods/foo + - name: delete-$pod_name kube: delete: pods/$pod_name + - name: ${pod_name}-no-longer-exists kube: get: pods/${pod_name} diff --git a/testdata/get-pod-not-found.yaml b/testdata/get-pod-not-found.yaml index e9883b0..6245529 100644 --- a/testdata/get-pod-not-found.yaml +++ b/testdata/get-pod-not-found.yaml @@ -1,13 +1,13 @@ name: get-pod-not-found description: test getting a single non-existent Pod resource -fixtures: - - kind + tests: - name: assert-len-zero kube: get: pods/doesnotexist assert: len: 0 + - name: assert-not-found kube: get: pods/doesnotexist diff --git a/testdata/json.yaml b/testdata/json.yaml index 44e93f3..c8c958e 100644 --- a/testdata/json.yaml +++ b/testdata/json.yaml @@ -1,14 +1,15 @@ name: json description: create a deployment and check the json condition succeeds -fixtures: - - kind + defaults: kube: namespace: json + tests: - name: create-deployment kube: create: testdata/manifests/nginx-deployment.yaml + - name: deployment-json-assertions timeout: after: 20s @@ -23,6 +24,7 @@ tests: path_formats: $.metadata.uid: uuid4 $.metadata.creationTimestamp: date-time + - name: delete-deployment kube: delete: deployments/nginx diff --git a/testdata/kind/apply-deployment.yaml b/testdata/kind/apply-deployment.yaml new file mode 100644 index 0000000..39e1196 --- /dev/null +++ b/testdata/kind/apply-deployment.yaml @@ -0,0 +1,41 @@ +name: apply-deployment +description: create, get, apply a change, get, delete a Deployment +fixtures: + - kind +defaults: + kube: + namespace: apply-deployment +tests: + - name: create-deployment + kube: + create: ../manifests/nginx-deployment.yaml + - name: deployment-has-2-replicas + timeout: + after: 20s + kube: + get: deployments/nginx + assert: + matches: + status: + readyReplicas: 2 + - name: apply-deployment-change + kube: + apply: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx + spec: + replicas: 1 + - name: deployment-has-1-replica + timeout: + after: 20s + kube: + get: deployments/nginx + assert: + matches: + status: + readyReplicas: 1 + - name: delete-deployment + kube: + delete: deployments/nginx diff --git a/testdata/kind/conditions.yaml b/testdata/kind/conditions.yaml new file mode 100644 index 0000000..1416cce --- /dev/null +++ b/testdata/kind/conditions.yaml @@ -0,0 +1,38 @@ +name: conditions +description: create a deployment and check the Ready condition eventually equals True +fixtures: + - kind +defaults: + kube: + namespace: conditions +tests: + - name: create-deployment + kube: + create: ../manifests/nginx-deployment.yaml + - name: deployment-immediately-has-false-or-unknown + kube: + get: deployments/nginx + assert: + conditions: + available: [false, unknown] + - name: deployment-has-true-progressing-condition + timeout: + after: 2s + kube: + get: deployments/nginx + assert: + conditions: + progressing: true + - name: deployment-last-progressing-reason + timeout: + after: 20s + kube: + get: deployments/nginx + assert: + conditions: + progressing: + status: true + reason: NewReplicaSetAvailable + - name: delete-deployment + kube: + delete: deployments/nginx diff --git a/testdata/kind/create-get-delete-pod.yaml b/testdata/kind/create-get-delete-pod.yaml new file mode 100644 index 0000000..1195833 --- /dev/null +++ b/testdata/kind/create-get-delete-pod.yaml @@ -0,0 +1,22 @@ +name: create-get-delete-pod +description: create, get and delete a Pod +fixtures: + - kind +defaults: + kube: + namespace: create-get-delete-pod +tests: + - name: create-pod + kube: + create: ../manifests/nginx-pod.yaml + - name: pod-exists + kube: + get: pods/nginx + - name: delete-pod + kube: + delete: pods/nginx + - name: pod-no-longer-exists + kube: + get: pods/nginx + assert: + notfound: true diff --git a/testdata/kind/create-unknown-resource.yaml b/testdata/kind/create-unknown-resource.yaml new file mode 100644 index 0000000..e9eb234 --- /dev/null +++ b/testdata/kind/create-unknown-resource.yaml @@ -0,0 +1,13 @@ +name: create-unknown-resource +description: create with YAML for unknown resource +fixtures: + - kind +tests: + - kube: + create: | + apiVersion: does.not.exist/v1 + kind: unknown + metadata: + name: unknown + assert: + unknown: true diff --git a/testdata/kind/curl-pod-ip.yaml b/testdata/kind/curl-pod-ip.yaml new file mode 100644 index 0000000..1b0e81b --- /dev/null +++ b/testdata/kind/curl-pod-ip.yaml @@ -0,0 +1,59 @@ +name: curl-pod-ip +description: scenario showing how to create two NGinx Pods and test connectivity to internal Pod IP addresses +fixtures: + - kind +defaults: + kube: + namespace: curl-pod-ip +tests: + - name: create-server + kube: + create: ../manifests/nginx-server.yaml + + - name: get-server-pod-ip + kube: + get: pods/server + assert: + conditions: + ready: + status: true + var: + SERVER_IP: + from: $.status.podIP + + - name: create-connect-tester + kube: + create: ../manifests/nginx-connect-test.yaml + + - name: wait-connect-test-ready + kube: + get: pods/connect-test + assert: + conditions: + ready: + status: true + + - name: curl-server-from-connect-tester + exec: kubectl exec -n curl-pod-ip pods/connect-test -- curl -s -I -v $$SERVER_IP + timeout: 2s + assert: + out: + contains: "200 OK" + # curl -v causes output to be sent to stderr that looks like this: + # * Connected to 10.244.0.17 (10.244.0.17) port 80 (#0) + # > GET / HTTP/1.1 + # > Host: 10.244.0.17 + # > User-Agent: curl/7.88.1 + # > Accept: */* + # > + # < HTTP/1.1 200 OK + err: + contains: "Host: $$SERVER_IP" + + - name: delete-connect-tester + kube: + delete: pods/connect-test + + - name: delete-server + kube: + delete: pods/server diff --git a/testdata/kind/delete-resource-not-found.yaml b/testdata/kind/delete-resource-not-found.yaml new file mode 100644 index 0000000..8f0500f --- /dev/null +++ b/testdata/kind/delete-resource-not-found.yaml @@ -0,0 +1,9 @@ +name: delete-resource-not-found +description: delete resource not found +fixtures: + - kind +tests: + - kube: + delete: pods/doesnotexist + assert: + notfound: true diff --git a/testdata/kind/delete-unknown-resource.yaml b/testdata/kind/delete-unknown-resource.yaml new file mode 100644 index 0000000..7d6fa16 --- /dev/null +++ b/testdata/kind/delete-unknown-resource.yaml @@ -0,0 +1,9 @@ +name: delete-unknown-resource +description: delete with YAML for unknown resource +fixtures: + - kind +tests: + - kube: + delete: unknown/unknown + assert: + unknown: true diff --git a/testdata/kind/envvar-substitution.yaml b/testdata/kind/envvar-substitution.yaml new file mode 100644 index 0000000..902a8c9 --- /dev/null +++ b/testdata/kind/envvar-substitution.yaml @@ -0,0 +1,19 @@ +name: envvar-substitution +description: create, get and delete a Pod with envvar substitutions +fixtures: + - kind +tests: + - name: create-${pod_name} + kube: + create: ../manifests/nginx-pod-envsubst.yaml + - name: foo-exists + kube: + get: pods/foo + - name: delete-$pod_name + kube: + delete: pods/$pod_name + - name: ${pod_name}-no-longer-exists + kube: + get: pods/${pod_name} + assert: + notfound: true diff --git a/testdata/kind/get-pod-not-found.yaml b/testdata/kind/get-pod-not-found.yaml new file mode 100644 index 0000000..6245529 --- /dev/null +++ b/testdata/kind/get-pod-not-found.yaml @@ -0,0 +1,15 @@ +name: get-pod-not-found +description: test getting a single non-existent Pod resource + +tests: + - name: assert-len-zero + kube: + get: pods/doesnotexist + assert: + len: 0 + + - name: assert-not-found + kube: + get: pods/doesnotexist + assert: + notfound: true diff --git a/testdata/kind/json.yaml b/testdata/kind/json.yaml new file mode 100644 index 0000000..bb82f58 --- /dev/null +++ b/testdata/kind/json.yaml @@ -0,0 +1,28 @@ +name: json +description: create a deployment and check the json condition succeeds +fixtures: + - kind +defaults: + kube: + namespace: json +tests: + - name: create-deployment + kube: + create: ../manifests/nginx-deployment.yaml + - name: deployment-json-assertions + timeout: + after: 20s + kube: + get: deployments/nginx + assert: + json: + paths: + $.spec.replicas: 2 + $.spec.template.metadata.labels["app"]: nginx + $.status.readyReplicas: 2 + path_formats: + $.metadata.uid: uuid4 + $.metadata.creationTimestamp: date-time + - name: delete-deployment + kube: + delete: deployments/nginx diff --git a/testdata/kind/list-pods-empty.yaml b/testdata/kind/list-pods-empty.yaml new file mode 100644 index 0000000..dc837ad --- /dev/null +++ b/testdata/kind/list-pods-empty.yaml @@ -0,0 +1,13 @@ +name: list-pods-empty +description: test empty list of Pod resources +fixtures: + - kind +defaults: + kube: + namespace: list-pods-empty +tests: + - name: verify-no-pods + kube: + get: pods + assert: + len: 0 diff --git a/testdata/kind/list-pods-with-labels.yaml b/testdata/kind/list-pods-with-labels.yaml new file mode 100644 index 0000000..7858090 --- /dev/null +++ b/testdata/kind/list-pods-with-labels.yaml @@ -0,0 +1,30 @@ +name: list-pods-with-labels +description: test list of Pod resources using label selector +fixtures: + - kind +defaults: + kube: + namespace: list-pods-with-labels +tests: + - name: create-deployment + kube: + create: ../manifests/nginx-deployment.yaml + - name: verify-pods-with-app-nginx-label + kube: + get: + type: pods + labels: + app: nginx + assert: + len: 2 + - name: verify-no-pods-with-app-noexist-label + kube: + get: + type: pods + labels: + app: noexist + assert: + len: 0 + - name: delete-deployment + kube: + delete: deployments/nginx diff --git a/testdata/kind/matches.yaml b/testdata/kind/matches.yaml new file mode 100644 index 0000000..f0c9309 --- /dev/null +++ b/testdata/kind/matches.yaml @@ -0,0 +1,29 @@ +name: matches +description: create a deployment and check the matches condition succeeds +fixtures: + - kind +defaults: + kube: + namespace: matches +tests: + - name: create-deployment + kube: + create: ../manifests/nginx-deployment.yaml + - name: deployment-matches-expected-fields + timeout: + after: 20s + kube: + get: deployments/nginx + assert: + matches: + spec: + replicas: 2 + template: + metadata: + labels: + app: nginx + status: + readyReplicas: 2 + - name: delete-deployment + kube: + delete: deployments/nginx diff --git a/testdata/kind/placement-spread.yaml b/testdata/kind/placement-spread.yaml new file mode 100644 index 0000000..607b052 --- /dev/null +++ b/testdata/kind/placement-spread.yaml @@ -0,0 +1,28 @@ +name: placement-spread +description: check placement spread assertions +fixtures: + - kind-three-workers-three-zones +defaults: + kube: + namespace: placement-spread +tests: + - name: create-deployment + kube: + create: ../manifests/nginx-deployment-spread-zones.yaml + - name: deployment-ready + timeout: 40s + kube: + get: deployments/nginx-spread-zones + assert: + matches: + status: + readyReplicas: 6 + - name: deployment-spread-evenly-across-hosts + kube: + get: deployments/nginx-spread-zones + assert: + placement: + spread: topology.kubernetes.io/zone + - name: delete-deployment + kube: + delete: deployments/nginx-spread-zones diff --git a/testdata/kind/same-named-kind.yaml b/testdata/kind/same-named-kind.yaml new file mode 100644 index 0000000..f592e5b --- /dev/null +++ b/testdata/kind/same-named-kind.yaml @@ -0,0 +1,94 @@ +name: same-named-kind +description: test multiple Kinds with different APIVersions +fixtures: + - kind +tests: + - kube: + create: | + apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + metadata: + name: foos.example.com + spec: + group: example.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + a: + type: string + b: + type: integer + scope: Namespaced + names: + kind: Foo + plural: foos + singular: foo + - kube: + create: | + apiVersion: apiextensions.k8s.io/v1 + kind: CustomResourceDefinition + metadata: + name: foos.anotherexample.com + spec: + group: anotherexample.com + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + a: + type: string + b: + type: integer + scope: Namespaced + names: + kind: Foo + plural: foos + singular: foo + - kube.get: foos.example.com/nonexisting + assert: + notfound: true + - kube.get: foos.anotherexample.com/nonexisting + assert: + notfound: true + # Kube assumes the first Kind-matching resource type, so this should return a + # not found, not an unknown resource + - kube.get: foos/nonexisting + assert: + notfound: true + - kube: + create: | + apiVersion: anotherexample.com/v1 + kind: Foo + metadata: + name: fooexample + spec: + a: example + b: 42 + - kube.get: foos.anotherexample.com/fooexample + assert: + matches: + spec: + b: 42 + - kube.get: foos.v1.anotherexample.com/fooexample + assert: + matches: + spec: + b: 42 + - kube.delete: foos.anotherexample.com/fooexample + - kube.delete: crds/foos.example.com + - kube.delete: crds/foos.anotherexample.com diff --git a/testdata/kind/var-save-restore.yaml b/testdata/kind/var-save-restore.yaml new file mode 100644 index 0000000..3766a46 --- /dev/null +++ b/testdata/kind/var-save-restore.yaml @@ -0,0 +1,35 @@ +name: var-save-restore +description: scenario showing different variations of variable definition and references +fixtures: + - kind +defaults: + kube: + namespace: var-save-restore +tests: + - name: create-pod + kube: + create: ../manifests/nginx-pod.yaml + + - name: define a variable and populate it with the pod's IP address + kube: + get: pods/nginx + assert: + conditions: + ready: + status: true + var: + POD_IP: + from: $.status.podIP + POD_NAME: + from: $.metadata.name + + - name: get the pod's information and assert same values as variables + kube.get: pods/$$POD_NAME + assert: + matches: + status: + podIP: $$POD_IP + + - name: delete-pod + kube: + delete: pods/$$POD_NAME diff --git a/testdata/list-pods-empty.yaml b/testdata/list-pods-empty.yaml index dc837ad..fe5eaa5 100644 --- a/testdata/list-pods-empty.yaml +++ b/testdata/list-pods-empty.yaml @@ -1,10 +1,10 @@ name: list-pods-empty description: test empty list of Pod resources -fixtures: - - kind + defaults: kube: namespace: list-pods-empty + tests: - name: verify-no-pods kube: diff --git a/testdata/list-pods-with-labels.yaml b/testdata/list-pods-with-labels.yaml index 55817f4..b2fe157 100644 --- a/testdata/list-pods-with-labels.yaml +++ b/testdata/list-pods-with-labels.yaml @@ -1,14 +1,15 @@ name: list-pods-with-labels description: test list of Pod resources using label selector -fixtures: - - kind + defaults: kube: namespace: list-pods-with-labels + tests: - name: create-deployment kube: create: testdata/manifests/nginx-deployment.yaml + - name: verify-pods-with-app-nginx-label kube: get: @@ -17,6 +18,7 @@ tests: app: nginx assert: len: 2 + - name: verify-no-pods-with-app-noexist-label kube: get: @@ -25,6 +27,7 @@ tests: app: noexist assert: len: 0 + - name: delete-deployment kube: delete: deployments/nginx diff --git a/testdata/matches.yaml b/testdata/matches.yaml index 5964a1f..c261356 100644 --- a/testdata/matches.yaml +++ b/testdata/matches.yaml @@ -1,7 +1,5 @@ name: matches description: create a deployment and check the matches condition succeeds -fixtures: - - kind defaults: kube: namespace: matches @@ -9,6 +7,7 @@ tests: - name: create-deployment kube: create: testdata/manifests/nginx-deployment.yaml + - name: deployment-matches-expected-fields timeout: after: 20s @@ -24,6 +23,7 @@ tests: app: nginx status: readyReplicas: 2 + - name: delete-deployment kube: delete: deployments/nginx diff --git a/testdata/parse.yaml b/testdata/parse.yaml index 966f763..00055f8 100644 --- a/testdata/parse.yaml +++ b/testdata/parse.yaml @@ -1,5 +1,6 @@ name: parse description: a scenario with several well-formed kube test specs + tests: - name: create a pod from YAML using kube.create shortcut kube.create: | @@ -11,8 +12,9 @@ tests: containers: - name: nginx image: nginx:1.7.9 + - name: apply a pod from a file using kube.apply shortcut - kube.apply: testdata/manifests/nginx-pod.yaml + kube.apply: manifests/nginx-pod.yaml - name: create a pod from YAML kube: create: | @@ -24,36 +26,44 @@ tests: containers: - name: nginx image: nginx:1.7.9 + - name: delete a pod from a file kube: - delete: testdata/manifests/nginx-pod.yaml + delete: manifests/nginx-pod.yaml + - name: fetch a pod via kube.get shortcut kube.get: pods/name + - name: fetch a pod via long-form kube:get kube: get: pods/name + - name: fetch a pod via kube.get shortcut to long-form resource identifier with labels kube.get: type: pods labels: app: nginx + - name: fetch a pod via kube:get long-form resource identifier with labels kube: get: type: pods labels: app: nginx + - name: fetch a pod with envvar substitution kube: get: pods/${pod_name} assert: len: 0 + - name: define a gdt variable kube: get: pods/name var: POD: from: $.metadata.name + - name: fetch a pod with gdt variable system substitution kube: get: pods/$$POD diff --git a/testdata/parse/fail/more-than-one-kube-action.yaml b/testdata/parse/fail/more-than-one-kube-action.yaml index 2062821..044e2a6 100644 --- a/testdata/parse/fail/more-than-one-kube-action.yaml +++ b/testdata/parse/fail/more-than-one-kube-action.yaml @@ -2,5 +2,5 @@ name: more-than-one-kube-action description: invalid kube spec with more than one Kubernetes action in kube field tests: - kube: - create: testdata/manifests/nginx-pod.yaml - apply: testdata/manifests/nginx-pod.yaml + create: ../../manifests/nginx-pod.yaml + apply: ../../manifests/nginx-pod.yaml diff --git a/testdata/parse/fail/shortcut-and-long-kube.yaml b/testdata/parse/fail/shortcut-and-long-kube.yaml index ae09565..7a59f4d 100644 --- a/testdata/parse/fail/shortcut-and-long-kube.yaml +++ b/testdata/parse/fail/shortcut-and-long-kube.yaml @@ -1,7 +1,7 @@ name: shortcut-and-long-kube description: invalid kube spec with both shortcut and long-form kube tests: - - kube.create: testdata/manifests/nginx-pod.yaml + - kube.create: ../../manifests/nginx-pod.yaml # The kube object is redundant when there is a kube.create shortcut kube: - create: testdata/manifests/nginx-pod.yaml + create: ../../manifests/nginx-pod.yaml diff --git a/testdata/placement-spread.yaml b/testdata/placement-spread.yaml index ea24258..9c197b7 100644 --- a/testdata/placement-spread.yaml +++ b/testdata/placement-spread.yaml @@ -1,14 +1,18 @@ name: placement-spread description: check placement spread assertions + fixtures: - kind-three-workers-three-zones + defaults: kube: namespace: placement-spread + tests: - name: create-deployment kube: - create: testdata/manifests/nginx-deployment-spread-zones.yaml + create: manifests/nginx-deployment-spread-zones.yaml + - name: deployment-ready timeout: 40s kube: @@ -17,12 +21,14 @@ tests: matches: status: readyReplicas: 6 + - name: deployment-spread-evenly-across-hosts kube: get: deployments/nginx-spread-zones assert: placement: spread: topology.kubernetes.io/zone + - name: delete-deployment kube: delete: deployments/nginx-spread-zones diff --git a/testdata/same-named-kind.yaml b/testdata/same-named-kind.yaml index f592e5b..bec84f6 100644 --- a/testdata/same-named-kind.yaml +++ b/testdata/same-named-kind.yaml @@ -1,7 +1,6 @@ name: same-named-kind description: test multiple Kinds with different APIVersions -fixtures: - - kind + tests: - kube: create: | @@ -31,6 +30,7 @@ tests: kind: Foo plural: foos singular: foo + - kube: create: | apiVersion: apiextensions.k8s.io/v1 @@ -59,17 +59,21 @@ tests: kind: Foo plural: foos singular: foo + - kube.get: foos.example.com/nonexisting assert: notfound: true + - kube.get: foos.anotherexample.com/nonexisting assert: notfound: true + # Kube assumes the first Kind-matching resource type, so this should return a # not found, not an unknown resource - kube.get: foos/nonexisting assert: notfound: true + - kube: create: | apiVersion: anotherexample.com/v1 @@ -79,16 +83,21 @@ tests: spec: a: example b: 42 + - kube.get: foos.anotherexample.com/fooexample assert: matches: spec: b: 42 + - kube.get: foos.v1.anotherexample.com/fooexample assert: matches: spec: b: 42 + - kube.delete: foos.anotherexample.com/fooexample + - kube.delete: crds/foos.example.com + - kube.delete: crds/foos.anotherexample.com diff --git a/testdata/var-save-restore.yaml b/testdata/var-save-restore.yaml index 7782ae8..eecd8a4 100644 --- a/testdata/var-save-restore.yaml +++ b/testdata/var-save-restore.yaml @@ -1,16 +1,16 @@ name: var-save-restore description: scenario showing different variations of variable definition and references -fixtures: - - kind + defaults: kube: namespace: var-save-restore + tests: - name: create-pod kube: - create: testdata/manifests/nginx-pod.yaml + create: manifests/nginx-pod.yaml - - name: define a variable and populate it with the pod's IP address + - name: get-pod-ip-into-var kube: get: pods/nginx assert: @@ -23,13 +23,20 @@ tests: POD_NAME: from: $.metadata.name - - name: get the pod's information and assert same values as variables + - name: assert-pod-ip-matches-var kube.get: pods/$$POD_NAME assert: matches: status: podIP: $$POD_IP + - name: check-exec-plugin-accesses-var + exec: echo $$POD_IP + shell: sh + assert: + out: + contains: $$POD_IP + - name: delete-pod kube: delete: pods/$$POD_NAME diff --git a/var.go b/var.go index aea85be..53c71bc 100644 --- a/var.go +++ b/var.go @@ -8,19 +8,13 @@ import ( "context" "fmt" - "github.com/PaesslerAG/jsonpath" "github.com/gdt-dev/core/api" gdtjson "github.com/gdt-dev/core/assertion/json" "github.com/gdt-dev/core/debug" + "github.com/theory/jsonpath" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -var ( - // defining the JSONPath language here allows us to disaggregate parse - // errors from runtime errors when evaluating a JSONPath expression. - lang = jsonpath.Language() -) - type VarEntry struct { // From is a string that indicates where the value of the variable will be // sourced from. This string is a JSONPath expression that contains @@ -48,7 +42,7 @@ func saveVars( if err != nil { return err } - debug.Println(ctx, "save.vars: %s -> %v", varName, extracted) + debug.Printf(ctx, "save.vars: %s -> %v", varName, extracted) res.SetData(varName, extracted) } return nil @@ -72,11 +66,16 @@ func extractFrom(path string, out any) (any, error) { default: return nil, fmt.Errorf("unhandled extract type %T", out) } - got, err := jsonpath.Get(path, normalized) + p, err := jsonpath.Parse(path) if err != nil { - // Shouldn't happen since during parse we validate the JSONPath - // expression is valid, but double-check anyway. + // Not terminal because during parse we validate the JSONPath + // expression is valid. + return nil, gdtjson.JSONPathNotFound(path, err) + } + nodes := p.Select(normalized) + if len(nodes) == 0 { return nil, gdtjson.JSONPathNotFound(path, err) } + got := nodes[0] return got, nil }