diff --git a/action.go b/action.go index d823e8c..adb49f5 100644 --- a/action.go +++ b/action.go @@ -13,8 +13,8 @@ import ( "os" "strings" + "github.com/gdt-dev/gdt/api" "github.com/gdt-dev/gdt/debug" - gdterrors "github.com/gdt-dev/gdt/errors" "github.com/gdt-dev/gdt/parse" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -226,7 +226,7 @@ func (a *Action) create( if err != nil { // This should never happen because we check during parse time // whether the file can be opened. - rterr := fmt.Errorf("%w: %s", gdterrors.RuntimeError, err) + rterr := fmt.Errorf("%w: %s", api.RuntimeError, err) return rterr } defer f.Close() @@ -245,7 +245,7 @@ func (a *Action) create( objs, err := unstructuredFromReader(r) if err != nil { - rterr := fmt.Errorf("%w: %s", gdterrors.RuntimeError, err) + rterr := fmt.Errorf("%w: %s", api.RuntimeError, err) return rterr } for _, obj := range objs { @@ -290,7 +290,7 @@ func (a *Action) apply( if err != nil { // This should never happen because we check during parse time // whether the file can be opened. - rterr := fmt.Errorf("%w: %s", gdterrors.RuntimeError, err) + rterr := fmt.Errorf("%w: %s", api.RuntimeError, err) return rterr } defer f.Close() @@ -309,7 +309,7 @@ func (a *Action) apply( objs, err := unstructuredFromReader(r) if err != nil { - rterr := fmt.Errorf("%w: %s", gdterrors.RuntimeError, err) + rterr := fmt.Errorf("%w: %s", api.RuntimeError, err) return rterr } for _, obj := range objs { @@ -358,13 +358,13 @@ func (a *Action) delete( if err != nil { // This should never happen because we check during parse time // whether the file can be opened. - rterr := fmt.Errorf("%w: %s", gdterrors.RuntimeError, err) + rterr := fmt.Errorf("%w: %s", api.RuntimeError, err) return rterr } defer f.Close() objs, err := unstructuredFromReader(f) if err != nil { - rterr := fmt.Errorf("%w: %s", gdterrors.RuntimeError, err) + rterr := fmt.Errorf("%w: %s", api.RuntimeError, err) return rterr } for _, obj := range objs { @@ -378,7 +378,7 @@ func (a *Action) delete( if ons == "" { ons = ns } - if err = a.doDelete(ctx, c, res, name, ns); err != nil { + if err = a.doDelete(ctx, c, res, name, ons); err != nil { return err } } diff --git a/assertions.go b/assertions.go index 96bb89b..ed5b059 100644 --- a/assertions.go +++ b/assertions.go @@ -12,9 +12,8 @@ import ( "net/http" "strings" + "github.com/gdt-dev/gdt/api" gdtjson "github.com/gdt-dev/gdt/assertion/json" - gdterrors "github.com/gdt-dev/gdt/errors" - gdttypes "github.com/gdt-dev/gdt/types" "gopkg.in/yaml.v3" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -166,8 +165,8 @@ type Expect struct { // conditionMatch is a struct with fields that we will match a resource's // `Condition` against. type conditionMatch struct { - Status *gdttypes.FlexStrings `yaml:"status,omitempty"` - Reason string `yaml:"reason,omitempty"` + Status *api.FlexStrings `yaml:"status,omitempty"` + Reason string `yaml:"reason,omitempty"` } // ConditionMatch can be a string (the ConditionStatus to match), a slice of @@ -182,7 +181,7 @@ type ConditionMatch struct { // ConditionMatch can be either a string, a slice of strings, or an object with . func (m *ConditionMatch) UnmarshalYAML(node *yaml.Node) error { if node.Kind == yaml.ScalarNode || node.Kind == yaml.SequenceNode { - var fs gdttypes.FlexStrings + var fs api.FlexStrings if err := node.Decode(&fs); err != nil { return ConditionMatchInvalid(node, err) } @@ -203,10 +202,10 @@ func (m *ConditionMatch) UnmarshalYAML(node *yaml.Node) error { type PlacementAssertion struct { // Spread contains zero or more topology keys that gdt-kube will assert an // even spread across. - Spread *gdttypes.FlexStrings `yaml:"spread,omitempty"` + Spread *api.FlexStrings `yaml:"spread,omitempty"` // Pack contains zero or more topology keys that gdt-kube will assert // bin-packing of resources within. - Pack *gdttypes.FlexStrings `yaml:"pack,omitempty"` + Pack *api.FlexStrings `yaml:"pack,omitempty"` } // assertions contains all assertions made for the exec test @@ -246,7 +245,7 @@ func (a *assertions) OK(ctx context.Context) bool { exp := a.exp if exp == nil { if a.err != nil { - a.Fail(gdterrors.UnexpectedError(a.err)) + a.Fail(api.UnexpectedError(a.err)) return false } return true @@ -309,16 +308,16 @@ func (a *assertions) errorOK() bool { } if exp.Error != "" && a.r != nil { if a.err == nil { - a.Fail(gdterrors.UnexpectedError(a.err)) + a.Fail(api.UnexpectedError(a.err)) return false } if !strings.Contains(a.err.Error(), exp.Error) { - a.Fail(gdterrors.NotIn(a.err.Error(), exp.Error)) + a.Fail(api.NotIn(a.err.Error(), exp.Error)) return false } } if a.err != nil { - a.Fail(gdterrors.UnexpectedError(a.err)) + a.Fail(api.UnexpectedError(a.err)) return false } return true @@ -329,36 +328,6 @@ func (a *assertions) expectsNotFound() bool { return (exp.Len != nil && *exp.Len == 0) || exp.NotFound } -// notFoundOK returns true if the supplied error and response matches the -// NotFound condition and the Len==0 condition, false otherwise -func (a *assertions) notFoundOK() bool { - if a.expectsNotFound() { - // First check if the error is like one returned from Get or Delete - // that has a 404 ErrStatus.Code in it - apierr, ok := a.err.(*apierrors.StatusError) - if ok { - if http.StatusNotFound != int(apierr.ErrStatus.Code) { - msg := fmt.Sprintf("got status code %d", apierr.ErrStatus.Code) - a.Fail(ExpectedNotFound(msg)) - return false - } - return true - } - // Next check to see if the supplied resp is a list of objects returned - // by the dynamic client and if so, is that an empty list. - list, ok := a.r.(*unstructured.UnstructuredList) - if ok { - if len(list.Items) != 0 { - msg := fmt.Sprintf("got %d items", len(list.Items)) - a.Fail(ExpectedNotFound(msg)) - return false - } - return true - } - } - return true -} - // lenOK returns true if the subject matches the Len condition, false otherwise func (a *assertions) lenOK() bool { exp := a.exp @@ -368,7 +337,7 @@ func (a *assertions) lenOK() bool { list, ok := a.r.(*unstructured.UnstructuredList) if ok && list != nil { if len(list.Items) != *exp.Len { - a.Fail(gdterrors.NotEqualLength(*exp.Len, len(list.Items))) + a.Fail(api.NotEqualLength(*exp.Len, len(list.Items))) return false } } @@ -402,7 +371,7 @@ func (a *assertions) matchesOK() bool { // for _, obj := range list.Items { // diff := compareResourceToMatchObject(obj, matchObj) // - // a.Fail(gdterrors.NotEqualLength(*exp.Len, len(list.Items))) + // a.Fail(api.NotEqualLength(*exp.Len, len(list.Items))) // return false // } //} @@ -435,7 +404,7 @@ func (a *assertions) conditionsOK() bool { // for _, obj := range list.Items { // diff := compareResourceToMatchObject(obj, matchObj) // - // a.Fail(gdterrors.NotEqualLength(*exp.Len, len(list.Items))) + // a.Fail(api.NotEqualLength(*exp.Len, len(list.Items))) // return false // } //} @@ -511,7 +480,7 @@ func newAssertions( exp *Expect, err error, r interface{}, -) gdttypes.Assertions { +) api.Assertions { return &assertions{ c: c, failures: []error{}, diff --git a/compare.go b/compare.go index 2bde545..7e081dd 100644 --- a/compare.go +++ b/compare.go @@ -6,7 +6,7 @@ package kube import ( "fmt" - "io/ioutil" + "os" "reflect" "strconv" "strings" @@ -42,7 +42,7 @@ func compareConditions( panic(msg) } if (!found || len(conds) == 0) && len(expected) != 0 { - for condType, _ := range expected { + for condType := range expected { d.Add(fmt.Sprintf("no condition with type %q found", condType)) } return d @@ -130,13 +130,13 @@ func compareConditions( // map[string]interface{} is the collection of resource fields that we will // match against. func matchObjectFromAny(m interface{}) map[string]interface{} { - switch m.(type) { + switch m := m.(type) { case string: var err error var b []byte - v := m.(string) + v := m if probablyFilePath(v) { - b, err = ioutil.ReadFile(v) + b, err = os.ReadFile(v) if err != nil { // NOTE(jaypipes): We already validated that the file exists at // parse time. If we get an error here, just panic cuz there's @@ -155,7 +155,7 @@ func matchObjectFromAny(m interface{}) map[string]interface{} { } return obj case map[string]interface{}: - return m.(map[string]interface{}) + return m } return map[string]interface{}{} } @@ -238,7 +238,7 @@ func collectFieldDifferences( } return case int, int8, int16, int32, int64: - switch subject.(type) { + switch subject := subject.(type) { case int, int8, int16, int32, int64: mv := toInt64(match) sv := toInt64(subject) @@ -261,8 +261,7 @@ func collectFieldDifferences( } case string: mv := toInt64(match) - ss := subject.(string) - sv, err := strconv.Atoi(ss) + sv, err := strconv.Atoi(subject) if err != nil { diff := fmt.Sprintf( "%s had different values. expected %v but found %v", @@ -296,8 +295,7 @@ func collectFieldDifferences( } case string: mv, _ := match.(string) - sv, _ := subject.(string) - if mv != sv { + if mv != subject { diff := fmt.Sprintf( "%s had different values. expected %v but found %v", fp, match, subject, @@ -362,34 +360,34 @@ func typesComparable(a, b interface{}) bool { // toUint64 takes an interface and returns a uint64 func toUint64(v interface{}) uint64 { - switch v.(type) { + switch v := v.(type) { case uint64: - return v.(uint64) + return v case uint8: - return uint64(v.(uint8)) + return uint64(v) case uint16: - return uint64(v.(uint16)) + return uint64(v) case uint32: - return uint64(v.(uint32)) + return uint64(v) case uint: - return uint64(v.(uint)) + return uint64(v) } return 0 } // toInt64 takes an interface and returns an int64 func toInt64(v interface{}) int64 { - switch v.(type) { + switch v := v.(type) { case int64: - return v.(int64) + return v case int8: - return int64(v.(int8)) + return int64(v) case int16: - return int64(v.(int16)) + return int64(v) case int32: - return int64(v.(int32)) + return int64(v) case int: - return int64(v.(int)) + return int64(v) } return 0 } diff --git a/defaults.go b/defaults.go index cdf2dcd..9405ad4 100644 --- a/defaults.go +++ b/defaults.go @@ -7,8 +7,7 @@ package kube import ( "os" - "github.com/gdt-dev/gdt/errors" - gdttypes "github.com/gdt-dev/gdt/types" + "github.com/gdt-dev/gdt/api" "gopkg.in/yaml.v3" ) @@ -39,21 +38,21 @@ type Defaults struct { func (d *Defaults) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(node) + return api.ExpectedMapAt(node) } // maps/structs are stored in a top-level Node.Content field which is a // concatenated slice of Node pointers in pairs of key/values. for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] if keyNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(keyNode) + return api.ExpectedScalarAt(keyNode) } key := keyNode.Value valNode := node.Content[i+1] switch key { case "kube": if valNode.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(valNode) + return api.ExpectedMapAt(valNode) } hd := kubeDefaults{} if err := valNode.Decode(&hd); err != nil { @@ -86,7 +85,7 @@ func (d *Defaults) validate() error { } // fromBaseDefaults returns an gdt-kube plugin-specific Defaults from a Spec -func fromBaseDefaults(base *gdttypes.Defaults) *Defaults { +func fromBaseDefaults(base *api.Defaults) *Defaults { if base == nil { return nil } diff --git a/errors.go b/errors.go index f6a4607..f5c3c9a 100644 --- a/errors.go +++ b/errors.go @@ -7,7 +7,7 @@ package kube import ( "fmt" - gdterrors "github.com/gdt-dev/gdt/errors" + "github.com/gdt-dev/gdt/api" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -20,7 +20,7 @@ var ( ErrExpectedMapOrYAMLString = fmt.Errorf( "%w: expected either map[string]interface{} "+ "or a string with embedded YAML", - gdterrors.ErrParse, + api.ErrParse, ) // ErrEitherShortcutOrKubeSpec is returned when the test author // included both a shortcut (e.g. `kube.create` or `kube.apply`) AND the @@ -28,7 +28,7 @@ var ( ErrEitherShortcutOrKubeSpec = fmt.Errorf( "%w: either specify a full KubeSpec in the `kube` field or specify "+ "one of the shortcuts (e.g. `kube.create` or `kube.apply`", - gdterrors.ErrParse, + api.ErrParse, ) // ErrMoreThanOneKubeAction is returned when the test author // included more than one Kubernetes action (e.g. `create` or `apply`) in @@ -36,46 +36,46 @@ var ( ErrMoreThanOneKubeAction = fmt.Errorf( "%w: you may only specify a single Kubernetes action field "+ "(e.g. `create`, `apply` or `delete`) in the `kube` object. ", - gdterrors.ErrParse, + api.ErrParse, ) // ErrKubeConfigNotFound is returned when a kubeconfig path points // to a file that does not exist. ErrKubeConfigNotFound = fmt.Errorf( "%w: specified kube config path not found", - gdterrors.ErrParse, + api.ErrParse, ) // ErrResourceSpecifier is returned when the test author uses a // resource specifier for the `kube.get` or `kube.delete` fields that is // not valid. ErrResourceSpecifierInvalid = fmt.Errorf( "%w: invalid resource specifier", - gdterrors.ErrParse, + api.ErrParse, ) // ErrResourceSpecifierOrFilepath is returned when the test author // uses a resource specifier for the `kube.delete` fields that is not valid // or is not a filepath. ErrResourceSpecifierInvalidOrFilepath = fmt.Errorf( "%w: invalid resource specifier or filepath", - gdterrors.ErrParse, + api.ErrParse, ) // ErrMatchesInvalid is returned when the `Kube.Assert.Matches` value is // malformed. ErrMatchesInvalid = fmt.Errorf( "%w: `kube.assert.matches` not well-formed", - gdterrors.ErrParse, + api.ErrParse, ) // ErrConditionMatchInvalid is returned when the `Kube.Assert.Conditions` // value is malformed. ErrConditionMatchInvalid = fmt.Errorf( "%w: `kube.assert.conditions` not well-formed", - gdterrors.ErrParse, + api.ErrParse, ) // ErrWithLabelsOnlyGetDelete is returned when the test author included // `kube.with.labels` but did not specify either `kube.get` or // `kube.delete`. ErrWithLabelsInvalid = fmt.Errorf( "%w: with labels invalid", - gdterrors.ErrParse, + api.ErrParse, ) // ErrWithLabelsOnlyGetDelete is returned when the test author included // `kube.with.labels` but did not specify either `kube.get` or @@ -83,7 +83,7 @@ var ( ErrWithLabelsOnlyGetDelete = fmt.Errorf( "%w: with labels may only be specified for "+ "`kube.get` or `kube.delete`", - gdterrors.ErrParse, + api.ErrParse, ) // ErrResourceUnknown is returned when an unknown resource kind is // specified for a create/apply/delete target. This is a runtime error @@ -91,32 +91,32 @@ var ( // kind is valid. ErrResourceUnknown = fmt.Errorf( "%w: resource unknown", - gdterrors.ErrFailure, + api.ErrFailure, ) // ErrExpectedNotFound is returned when we expected to get either a // NotFound response code (get) or an empty set of results (list) but did // not find that. ErrExpectedNotFound = fmt.Errorf( "%w: expected not found", - gdterrors.ErrFailure, + api.ErrFailure, ) // ErrMatchesNotEqual is returned when we failed to match a resource to an // object field in a `kube.assert.matches` object. ErrMatchesNotEqual = fmt.Errorf( "%w: match field not equal", - gdterrors.ErrFailure, + api.ErrFailure, ) // ErrConditionDoesNotMatch is returned when we failed to match a resource to an // Condition match expression in a `kube.assert.matches` object. ErrConditionDoesNotMatch = fmt.Errorf( "%w: condition does not match expectation", - gdterrors.ErrFailure, + api.ErrFailure, ) // ErrConnect is returned when we failed to create a client config to // connect to the Kubernetes API server. ErrConnect = fmt.Errorf( "%w: k8s connect failure", - gdterrors.RuntimeError, + api.RuntimeError, ) ) diff --git a/eval.go b/eval.go index 618e751..434984c 100644 --- a/eval.go +++ b/eval.go @@ -7,14 +7,13 @@ package kube import ( "context" - gdterrors "github.com/gdt-dev/gdt/errors" - "github.com/gdt-dev/gdt/result" + "github.com/gdt-dev/gdt/api" ) // Eval performs an action and evaluates the results of that action, returning // a Result that informs the Scenario about what failed or succeeded. A new // Kubernetes client request is made during this call. -func (s *Spec) Eval(ctx context.Context) (*result.Result, error) { +func (s *Spec) Eval(ctx context.Context) (*api.Result, error) { c, err := s.connect(ctx) if err != nil { return nil, ConnectError(err) @@ -25,16 +24,16 @@ func (s *Spec) Eval(ctx context.Context) (*result.Result, error) { var out interface{} err = s.Kube.Do(ctx, c, ns, &out) if err != nil { - if err == gdterrors.ErrTimeoutExceeded { - return result.New(result.WithFailures(gdterrors.ErrTimeoutExceeded)), nil + if err == api.ErrTimeoutExceeded { + return api.NewResult(api.WithFailures(api.ErrTimeoutExceeded)), nil } - if err == gdterrors.RuntimeError { + if err == api.RuntimeError { return nil, err } } a := newAssertions(c, s.Assert, err, out) if a.OK(ctx) { - return result.New(), nil + return api.NewResult(), nil } - return result.New(result.WithFailures(a.Failures()...)), nil + return api.NewResult(api.WithFailures(a.Failures()...)), nil } diff --git a/fixtures/kind/kind.go b/fixtures/kind/kind.go index 8b6e5a7..10cabb5 100644 --- a/fixtures/kind/kind.go +++ b/fixtures/kind/kind.go @@ -7,18 +7,28 @@ package kind import ( "context" "strings" + "time" + "github.com/cenkalti/backoff" + "github.com/gdt-dev/gdt/api" gdtcontext "github.com/gdt-dev/gdt/context" "github.com/gdt-dev/gdt/debug" - gdttypes "github.com/gdt-dev/gdt/types" "github.com/samber/lo" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/kind/pkg/cluster" kindconst "sigs.k8s.io/kind/pkg/cluster/constants" gdtkube "github.com/gdt-dev/kube" ) -// KindFixture implements `gdttypes.Fixture` and exposes connection/config +var ( + checkDefaultServiceAccountTimeout = time.Second * 15 +) + +// KindFixture implements `api.Fixture` and exposes connection/config // information about a running KinD cluster. type KindFixture struct { // provider is the KinD cluster provider @@ -68,7 +78,7 @@ func (f *KindFixture) Start(ctx context.Context) error { if f.isRunning() { debug.Println(ctx, "cluster %s already running", f.ClusterName) f.runningBeforeStart = true - return nil + return f.waitForDefaultServiceAccount(ctx) } opts := []cluster.CreateOption{} if f.ConfigPath != "" { @@ -86,7 +96,7 @@ func (f *KindFixture) Start(ctx context.Context) error { f.deleteOnStop = true debug.Println(ctx, "cluster %s will be deleted on stop", f.ClusterName) } - return nil + return f.waitForDefaultServiceAccount(ctx) } func (f *KindFixture) isRunning() bool { @@ -100,6 +110,62 @@ func (f *KindFixture) isRunning() bool { return lo.Contains(clusterNames, f.ClusterName) } +func (f *KindFixture) waitForDefaultServiceAccount(ctx context.Context) error { + // Sometimes it takes a little while for the default service account to + // exist for new clusters, and the default service account is required for + // a lot of testing, so we wait here until the default service account is + // ready to go... + cfg, err := f.provider.KubeConfig(f.ClusterName, false) + if err != nil { + return err + } + cc, err := clientcmd.Load([]byte(cfg)) + if err != nil { + return err + } + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, checkDefaultServiceAccountTimeout) + defer cancel() + overrides := &clientcmd.ConfigOverrides{} + rules := clientcmd.NewDefaultClientConfigLoadingRules() + ccfg, err := clientcmd.NewNonInteractiveClientConfig( + *cc, "", overrides, rules, + ).ClientConfig() + if err != nil { + return err + } + clientset, err := kubernetes.NewForConfig(ccfg) + if err != nil { + return err + } + bo := backoff.WithContext( + backoff.NewExponentialBackOff(), + ctx, + ) + ticker := backoff.NewTicker(bo) + attempts := 1 + for range ticker.C { + found := true + _, err = clientset.CoreV1().ServiceAccounts("default").Get(context.TODO(), "default", metav1.GetOptions{}) + if err != nil { + if !errors.IsNotFound(err) { + return err + } + found = false + } + debug.Println( + ctx, "check for default service account: attempt %d, found: %v", + attempts, found, + ) + attempts++ + if found { + ticker.Stop() + break + } + } + return nil +} + func (f *KindFixture) Stop(ctx context.Context) { ctx = gdtcontext.PushTrace(ctx, "fixtures.kind.stop") defer func() { @@ -216,7 +282,7 @@ func WithRetainOnStop() KindFixtureModifier { // - "kube.config" returns the path of the kubeconfig file to use with this // KinD cluster // - "kube.context" returns the kubecontext to use with this KinD cluster -func New(mods ...KindFixtureModifier) gdttypes.Fixture { +func New(mods ...KindFixtureModifier) api.Fixture { f := &KindFixture{ provider: cluster.NewProvider(), } diff --git a/go.mod b/go.mod index 9e4abb0..2176ca0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gdt-dev/kube go 1.21 require ( - github.com/gdt-dev/gdt v1.8.0 + github.com/gdt-dev/gdt v1.9.0 github.com/samber/lo v1.38.1 github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 643a84b..c11d950 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= -github.com/gdt-dev/gdt v1.8.0 h1:JWuRbvSmVdNMQ8qHw1LZqVsQZEL4oM3mr85Z1L48vwQ= -github.com/gdt-dev/gdt v1.8.0/go.mod h1:oph7/YpGDMhnOz2TkNyrllpAiwmGaMyFjUAXhAGNZHI= +github.com/gdt-dev/gdt v1.9.0 h1:OoWTEXaMEze4EMyx1l9bu3DPngA3iawWuExui+k8Kn0= +github.com/gdt-dev/gdt v1.9.0/go.mod h1:oph7/YpGDMhnOz2TkNyrllpAiwmGaMyFjUAXhAGNZHI= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/identifier.go b/identifier.go index 3c87baf..47184e7 100644 --- a/identifier.go +++ b/identifier.go @@ -8,10 +8,9 @@ import ( "path/filepath" "strings" + "github.com/gdt-dev/gdt/api" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/labels" - - gdterrors "github.com/gdt-dev/gdt/errors" ) // resourceIdentifierWithSelector is the full long-form resource identifier as @@ -56,7 +55,7 @@ func (r *ResourceIdentifier) Labels() map[string]string { // ResourceIdentifier can be either a string or a selector. func (r *ResourceIdentifier) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.ScalarNode && node.Kind != yaml.MappingNode { - return gdterrors.ExpectedScalarOrMapAt(node) + return api.ExpectedScalarOrMapAt(node) } var s string // A resource identifier can be a string of the form {type}/{name} or @@ -140,7 +139,7 @@ func (r *ResourceIdentifierOrFile) Labels() map[string]string { // ResourceIdentifierOrFile can be either a string or a selector. func (r *ResourceIdentifierOrFile) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.ScalarNode && node.Kind != yaml.MappingNode { - return gdterrors.ExpectedScalarOrMapAt(node) + return api.ExpectedScalarOrMapAt(node) } var s string // A resource identifier can be a filepath, a string of the form @@ -148,7 +147,7 @@ func (r *ResourceIdentifierOrFile) UnmarshalYAML(node *yaml.Node) error { if err := node.Decode(&s); err == nil { if probablyFilePath(s) { if !fileExists(s) { - return gdterrors.FileNotFound(s, node) + return api.FileNotFound(s, node) } r.fp = s return nil diff --git a/parse.go b/parse.go index 3e3b022..75fc6bc 100644 --- a/parse.go +++ b/parse.go @@ -7,16 +7,15 @@ package kube import ( "os" + "github.com/gdt-dev/gdt/api" gdtjson "github.com/gdt-dev/gdt/assertion/json" - "github.com/gdt-dev/gdt/errors" - gdttypes "github.com/gdt-dev/gdt/types" "github.com/samber/lo" "gopkg.in/yaml.v3" ) func (s *Spec) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(node) + return api.ExpectedMapAt(node) } // We do an initial pass over the shortcut fields, then all the // non-shortcut fields after that. @@ -27,14 +26,14 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] if keyNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(keyNode) + return api.ExpectedScalarAt(keyNode) } key := keyNode.Value valNode := node.Content[i+1] switch key { case "kube.get": if valNode.Kind != yaml.ScalarNode && valNode.Kind != yaml.MappingNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } if ks != nil { return MoreThanOneKubeActionAt(valNode) @@ -48,7 +47,7 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { s.Kube = ks case "kube.create": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } if ks != nil { return MoreThanOneKubeActionAt(valNode) @@ -56,7 +55,7 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { v := valNode.Value if probablyFilePath(v) { if !fileExists(v) { - return errors.FileNotFound(v, valNode) + return api.FileNotFound(v, valNode) } } ks = &KubeSpec{} @@ -64,7 +63,7 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { s.Kube = ks case "kube.apply": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } if ks != nil { return MoreThanOneKubeActionAt(valNode) @@ -75,7 +74,7 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { s.Kube = ks case "kube.delete": if valNode.Kind != yaml.ScalarNode && valNode.Kind != yaml.MappingNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } if ks != nil { return MoreThanOneKubeActionAt(valNode) @@ -93,14 +92,14 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] if keyNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(keyNode) + return api.ExpectedScalarAt(keyNode) } key := keyNode.Value valNode := node.Content[i+1] switch key { case "kube": if valNode.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(valNode) + return api.ExpectedMapAt(valNode) } if ks != nil { return EitherShortcutOrKubeSpecAt(valNode) @@ -111,7 +110,7 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { s.Kube = ks case "assert": if valNode.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(valNode) + return api.ExpectedMapAt(valNode) } var e *Expect if err := valNode.Decode(&e); err != nil { @@ -121,10 +120,10 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { case "kube.get", "kube.create", "kube.delete", "kube.apply": continue default: - if lo.Contains(gdttypes.BaseSpecFields, key) { + if lo.Contains(api.BaseSpecFields, key) { continue } - return errors.UnknownFieldAt(key, keyNode) + return api.UnknownFieldAt(key, keyNode) } } return nil @@ -132,30 +131,30 @@ func (s *Spec) UnmarshalYAML(node *yaml.Node) error { func (s *KubeSpec) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(node) + return api.ExpectedMapAt(node) } // maps/structs are stored in a top-level Node.Content field which is a // concatenated slice of Node pointers in pairs of key/values. for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] if keyNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(keyNode) + return api.ExpectedScalarAt(keyNode) } key := keyNode.Value valNode := node.Content[i+1] switch key { case "config": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } fp := valNode.Value if !fileExists(fp) { - return errors.FileNotFound(fp, valNode) + return api.FileNotFound(fp, valNode) } s.Config = fp case "context": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } // NOTE(jaypipes): We can't validate the kubectx exists yet because // fixtures may advertise a kube config and we look up the context @@ -163,14 +162,14 @@ func (s *KubeSpec) UnmarshalYAML(node *yaml.Node) error { s.Context = valNode.Value case "namespace": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } s.Namespace = valNode.Value case "get", "create", "apply", "delete": // Because Action is an embedded struct and we parse it below, just // ignore these fields in the top-level `kube:` field for now. default: - return errors.UnknownFieldAt(key, keyNode) + return api.UnknownFieldAt(key, keyNode) } } var a Action @@ -183,43 +182,43 @@ func (s *KubeSpec) UnmarshalYAML(node *yaml.Node) error { func (a *Action) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(node) + return api.ExpectedMapAt(node) } // maps/structs are stored in a top-level Node.Content field which is a // concatenated slice of Node pointers in pairs of key/values. for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] if keyNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(keyNode) + return api.ExpectedScalarAt(keyNode) } key := keyNode.Value valNode := node.Content[i+1] switch key { case "apply": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } v := valNode.Value if probablyFilePath(v) { if !fileExists(v) { - return errors.FileNotFound(v, valNode) + return api.FileNotFound(v, valNode) } } a.Apply = v case "create": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } v := valNode.Value if probablyFilePath(v) { if !fileExists(v) { - return errors.FileNotFound(v, valNode) + return api.FileNotFound(v, valNode) } } a.Create = v case "get": if valNode.Kind != yaml.ScalarNode && valNode.Kind != yaml.MappingNode { - return errors.ExpectedScalarOrMapAt(valNode) + return api.ExpectedScalarOrMapAt(valNode) } var v *ResourceIdentifier if err := valNode.Decode(&v); err != nil { @@ -228,7 +227,7 @@ func (a *Action) UnmarshalYAML(node *yaml.Node) error { a.Get = v case "delete": if valNode.Kind != yaml.ScalarNode && valNode.Kind != yaml.MappingNode { - return errors.ExpectedScalarOrMapAt(valNode) + return api.ExpectedScalarOrMapAt(valNode) } var v *ResourceIdentifierOrFile if err := valNode.Decode(&v); err != nil { @@ -245,21 +244,21 @@ func (a *Action) UnmarshalYAML(node *yaml.Node) error { func (e *Expect) UnmarshalYAML(node *yaml.Node) error { if node.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(node) + return api.ExpectedMapAt(node) } // maps/structs are stored in a top-level Node.Content field which is a // concatenated slice of Node pointers in pairs of key/values. for i := 0; i < len(node.Content); i += 2 { keyNode := node.Content[i] if keyNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(keyNode) + return api.ExpectedScalarAt(keyNode) } key := keyNode.Value valNode := node.Content[i+1] switch key { case "error": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } var v string if err := valNode.Decode(&v); err != nil { @@ -268,7 +267,7 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { e.Error = v case "len": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } var v *int if err := valNode.Decode(&v); err != nil { @@ -277,7 +276,7 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { e.Len = v case "unknown": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } var v bool if err := valNode.Decode(&v); err != nil { @@ -286,7 +285,7 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { e.Unknown = v case "notfound": if valNode.Kind != yaml.ScalarNode { - return errors.ExpectedScalarAt(valNode) + return api.ExpectedScalarAt(valNode) } var v bool if err := valNode.Decode(&v); err != nil { @@ -295,7 +294,7 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { e.NotFound = v case "json": if valNode.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(valNode) + return api.ExpectedMapAt(valNode) } var v *gdtjson.Expect if err := valNode.Decode(&v); err != nil { @@ -304,7 +303,7 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { e.JSON = v case "conditions": if valNode.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(valNode) + return api.ExpectedMapAt(valNode) } var v map[string]*ConditionMatch if err := valNode.Decode(&v); err != nil { @@ -328,7 +327,7 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { } if probablyFilePath(v) { if !fileExists(v) { - return errors.FileNotFound(v, valNode) + return api.FileNotFound(v, valNode) } } // inline YAML. check it can be unmarshaled into a @@ -343,7 +342,7 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { } case "placement": if valNode.Kind != yaml.MappingNode { - return errors.ExpectedMapAt(valNode) + return api.ExpectedMapAt(valNode) } var v *PlacementAssertion if err := valNode.Decode(&v); err != nil { @@ -351,30 +350,12 @@ func (e *Expect) UnmarshalYAML(node *yaml.Node) error { } e.Placement = v default: - return errors.UnknownFieldAt(key, keyNode) + return api.UnknownFieldAt(key, keyNode) } } return nil } -// expandShortcut looks at the shortcut fields (e.g. `kube.create`) and expands -// the shortcut into a full KubeSpec. -func expandShortcut(s *Spec) { - if s.Kube != nil { - return - } - ks := &KubeSpec{ - Action: Action{}, - } - if s.KubeCreate != "" { - ks.Create = s.KubeCreate - } - if s.KubeApply != "" { - ks.Apply = s.KubeApply - } - s.Kube = ks -} - // moreThanOneAction returns true if the test author has specified more than a // single action in the KubeSpec. func moreThanOneAction(a *Action) bool { diff --git a/parse_test.go b/parse_test.go index a64fedb..867f2e1 100644 --- a/parse_test.go +++ b/parse_test.go @@ -6,22 +6,15 @@ package kube_test import ( "path/filepath" - "runtime" "testing" "github.com/gdt-dev/gdt" - "github.com/gdt-dev/gdt/errors" - gdttypes "github.com/gdt-dev/gdt/types" + "github.com/gdt-dev/gdt/api" gdtkube "github.com/gdt-dev/kube" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func currentDir() string { - _, filename, _, _ := runtime.Caller(0) - return filepath.Dir(filename) -} - func TestFailureBadDefaults(t *testing.T) { assert := assert.New(t) require := require.New(t) @@ -30,7 +23,7 @@ func TestFailureBadDefaults(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) - assert.ErrorIs(err, errors.ErrExpectedMap) + assert.ErrorIs(err, api.ErrExpectedMap) require.Nil(s) } @@ -43,7 +36,7 @@ func TestFailureDefaultsConfigNotFound(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrKubeConfigNotFound) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -56,7 +49,7 @@ func TestFailureBothShortcutAndKubeSpec(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrEitherShortcutOrKubeSpec) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -69,7 +62,7 @@ func TestFailureMoreThanOneKubeAction(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrMoreThanOneKubeAction) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -82,7 +75,7 @@ func TestFailureInvalidResourceSpecifierNoMultipleResources(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrResourceSpecifierInvalid) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -95,7 +88,7 @@ func TestFailureInvalidResourceSpecifierMutipleForwardSlashes(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrResourceSpecifierInvalid) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -108,7 +101,7 @@ func TestFailureInvalidDeleteNotFilepathOrResourceSpecifier(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrResourceSpecifierInvalidOrFilepath) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -120,8 +113,8 @@ func TestFailureCreateFileNotFound(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) - assert.ErrorIs(err, errors.ErrFileNotFound) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrFileNotFound) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -133,8 +126,8 @@ func TestFailureDeleteFileNotFound(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) - assert.ErrorIs(err, errors.ErrFileNotFound) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrFileNotFound) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -146,8 +139,8 @@ func TestFailureBadMatchesFileNotFound(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) - assert.ErrorIs(err, errors.ErrFileNotFound) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrFileNotFound) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -160,7 +153,7 @@ func TestFailureBadMatchesInvalidYAML(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrMatchesInvalid) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -173,7 +166,7 @@ func TestFailureBadMatchesEmpty(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrExpectedMapOrYAMLString) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -186,7 +179,7 @@ func TestFailureBadMatchesNotMapAny(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrMatchesInvalid) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -198,8 +191,8 @@ func TestFailureBadPlacementNotObject(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) - assert.ErrorIs(err, errors.ErrExpectedMap) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrExpectedMap) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -212,7 +205,7 @@ func TestWithLabelsInvalid(t *testing.T) { s, err := gdt.From(fp) require.NotNil(err) assert.ErrorIs(err, gdtkube.ErrWithLabelsInvalid) - assert.ErrorIs(err, errors.ErrParse) + assert.ErrorIs(err, api.ErrParse) require.Nil(s) } @@ -242,12 +235,12 @@ spec: ` var zero int - expTests := []gdttypes.Evaluable{ + expTests := []api.Evaluable{ &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 0, Name: "create a pod from YAML using kube.create shortcut", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -256,10 +249,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 1, Name: "apply a pod from a file using kube.apply shortcut", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -268,10 +261,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 2, Name: "create a pod from YAML", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -280,10 +273,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 3, Name: "delete a pod from a file", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -295,10 +288,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 4, Name: "fetch a pod via kube.get shortcut", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -309,10 +302,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 5, Name: "fetch a pod via long-form kube:get", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -323,10 +316,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 6, Name: "fetch a pod via kube.get shortcut to long-form resource identifier with labels", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -339,10 +332,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 7, Name: "fetch a pod via kube:get long-form resource identifier with labels", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ @@ -355,10 +348,10 @@ spec: }, }, &gdtkube.Spec{ - Spec: gdttypes.Spec{ + Spec: api.Spec{ Index: 8, Name: "fetch a pod with envvar substitution", - Defaults: &gdttypes.Defaults{}, + Defaults: &api.Defaults{}, }, Kube: &gdtkube.KubeSpec{ Action: gdtkube.Action{ diff --git a/placement.go b/placement.go index 64deb53..8caf747 100644 --- a/placement.go +++ b/placement.go @@ -35,6 +35,9 @@ func getNodes( Kind: "Node", } res, err := c.gvrFromGVK(gvk) + if err != nil { + panic(err) + } opts := metav1.ListOptions{} list, err := c.client.Resource(res).Namespace("").List( ctx, opts, @@ -94,6 +97,9 @@ func getPods( Kind: "Pod", } res, err := c.gvrFromGVK(gvk) + if err != nil { + panic(err) + } opts := client.ListOptions{ LabelSelector: ls, Namespace: ns, diff --git a/plugin.go b/plugin.go index 8cb097a..de92724 100644 --- a/plugin.go +++ b/plugin.go @@ -6,7 +6,7 @@ package kube import ( "github.com/gdt-dev/gdt" - gdttypes "github.com/gdt-dev/gdt/types" + "github.com/gdt-dev/gdt/api" "gopkg.in/yaml.v3" ) @@ -27,13 +27,13 @@ const ( type plugin struct{} -func (p *plugin) Info() gdttypes.PluginInfo { - return gdttypes.PluginInfo{ +func (p *plugin) Info() api.PluginInfo { + return api.PluginInfo{ Name: pluginName, - Retry: &gdttypes.Retry{ + Retry: &api.Retry{ Exponential: true, }, - Timeout: &gdttypes.Timeout{ + Timeout: &api.Timeout{ After: DefaultTimeout, }, } @@ -43,11 +43,11 @@ func (p *plugin) Defaults() yaml.Unmarshaler { return &Defaults{} } -func (p *plugin) Specs() []gdttypes.Evaluable { - return []gdttypes.Evaluable{&Spec{}} +func (p *plugin) Specs() []api.Evaluable { + return []api.Evaluable{&Spec{}} } // Plugin returns the Kubernetes gdt plugin -func Plugin() gdttypes.Plugin { +func Plugin() api.Plugin { return &plugin{} } diff --git a/spec.go b/spec.go index 7dd44ef..75e3214 100644 --- a/spec.go +++ b/spec.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - gdttypes "github.com/gdt-dev/gdt/types" + "github.com/gdt-dev/gdt/api" ) // KubeSpec is the complex type containing all of the Kubernetes-specific @@ -36,7 +36,7 @@ type KubeSpec struct { // Spec describes a test of a *single* Kubernetes API request and response. type Spec struct { - gdttypes.Spec + api.Spec // Kube is the complex type containing all of the Kubernetes-specific // actions and assertions. Most users will use the `kube.create`, // `kube.apply` and `kube.describe` shortcut fields. @@ -82,6 +82,24 @@ type Spec struct { Assert *Expect `yaml:"assert,omitempty"` } +func (s *Spec) Retry() *api.Retry { + if s.Spec.Retry != nil { + // The user may have overridden in the test spec file... + return s.Spec.Retry + } + if s.Kube.Action.Get != nil { + // returning nil here means the plugin's default will be used... + return nil + } + // for apply/create/delete, we don't want to retry... + return api.NoRetry +} + +func (s *Spec) Timeout() *api.Timeout { + // returning nil here means the plugin's default will be used... + return nil +} + // Title returns a good name for the Spec func (s *Spec) Title() string { // If the user did not specify a name for the test spec, just default @@ -123,11 +141,11 @@ func probablyFilePath(subject string) bool { return strings.ContainsRune(subject, '.') } -func (s *Spec) SetBase(b gdttypes.Spec) { +func (s *Spec) SetBase(b api.Spec) { s.Spec = b } -func (s *Spec) Base() *gdttypes.Spec { +func (s *Spec) Base() *api.Spec { return &s.Spec }