Skip to content

Commit

Permalink
Merge pull request #3 from gdt-dev/action-parse
Browse files Browse the repository at this point in the history
rework label selector and resource identifier type
  • Loading branch information
a-hilaly committed Aug 15, 2023
2 parents 35a5d58 + 42953f0 commit 0190c8b
Show file tree
Hide file tree
Showing 18 changed files with 598 additions and 329 deletions.
46 changes: 30 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,22 +141,17 @@ matches some expectation:
allows you to override the `defaults.namespace` value from the test scenario.
* `kube`: (optional) an object containing actions and assertions the test takes
against the Kubernetes API server.
* `kube.get`: (optional) string containing either a resource specifier (e.g.
`pods`, `po/nginx` or a file path to a YAML manifest containing resources
that will be read from the Kubernetes API server.
* `kube.get`: (optional) string or object containing a resource identifier
(e.g. `pods`, `po/nginx` or label selector for resources that will be read
from the Kubernetes API server.
* `kube.create`: (optional) string containing either a file path to a YAML
manifest or a string of raw YAML containing the resource(s) to create.
* `kube.apply`: (optional) string containing either a file path to a YAML
manifest or a string of raw YAML containing the resource(s) for which
`gdt-kube` will perform a Kubernetes Apply call.
* `kube.delete`: (optional) string containing either a resource specifier (e.g.
`pods`, `po/nginx` or a file path to a YAML manifest containing resources
that will be deleted.
* `kube.with`: (optional) object containing selectors with which to filter
`get` and `delete` operations.
* `kube.with.labels`: (optional) `map[string]string` containing the label keys
and values to use in constructing an equality label selector (for all listed
labels)
* `kube.delete`: (optional) string or object containing either a resource
identifier (e.g. `pods`, `po/nginx` , a file path to a YAML manifest, or a
label selector for resources that will be deleted.
* `assert`: (optional) object containing assertions to make about the
action performed by the test.
* `assert.error`: (optional) string to match a returned error from the
Expand Down Expand Up @@ -236,14 +231,34 @@ Testing that there are two Pods having the label `app:nginx`:
```yaml
name: list-pods-with-labels
tests:
# You can use the shortcut kube.get
- name: verify-pods-with-app-nginx-label
kube.get:
type: pods
labels:
app: nginx
assert:
len: 2
# Or the long-form kube:get
- name: verify-pods-with-app-nginx-label
kube:
get: pods
with:
get:
type: pods
labels:
app: nginx
assert:
len: 2
# Like "kube.get", you can pass a label selector for "kube.delete"
- kube.delete:
type: pods
labels:
app: nginx
# And you can use the long-form kube:delete as well
- kube:
delete:
type: pods
labels:
app: nginx
```

Testing that a Pod with the name `nginx` exists by the specified timeout
Expand All @@ -253,9 +268,8 @@ the timeout):
```yaml
name: test-nginx-pod-exists-within-1-minute
tests:
- kube:
get: pods/nginx
timeout: 1m
- kube.get: pods/nginx
timeout: 1m
```

Testing creation and subsequent fetch then delete of a Pod, specifying the Pod
Expand Down
40 changes: 40 additions & 0 deletions action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.

package kube

// Action describes the the Kubernetes-specific action that is performed by the
// test.
type Action struct {
// Create is a string containing a file path or raw YAML content describing
// a Kubernetes resource to call `kubectl create` with.
Create string `yaml:"create,omitempty"`
// Apply is a string containing a file path or raw YAML content describing
// a Kubernetes resource to call `kubectl apply` with.
Apply string `yaml:"apply,omitempty"`
// Delete is a string or object containing arguments to `kubectl delete`.
//
// It must be one of the following:
//
// - a file path to a manifest that will be read and the resources
// described in the manifest will be deleted
// - a resource kind or kind alias, e.g. "pods", "po", followed by one of
// the following:
// * a space or `/` character followed by the resource name to delete
// only a resource with that name.
// - an object with a `type` and optional `labels` field containing a label
// selector that should be used to select that `type` of resource.
Delete *ResourceIdentifierOrFile `yaml:"delete,omitempty"`
// Get is a string or object containing arguments to `kubectl get`.
//
// It must be one of the following:
//
// - a string with a resource kind or kind alias, e.g. "pods", "po",
// followed by one of the following:
// * a space or `/` character followed by the resource name to get only a
// resource with that name.
// - an object with a `type` and optional `labels` field containing a label
// selector that should be used to select that `type` of resource.
Get *ResourceIdentifier `yaml:"get,omitempty"`
}
5 changes: 0 additions & 5 deletions connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ func (s *Spec) Config(ctx context.Context) (*rest.Config, error) {
).ClientConfig()
}

type groupVersion struct {
group string
version string
}

// connection is a struct containing a discovery client and a dynamic client
// that the Spec uses to communicate with Kubernetes.
type connection struct {
Expand Down
47 changes: 33 additions & 14 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ var (
"or a string with embedded YAML",
gdterrors.ErrParse,
)
// ErrMoreThanOneShortcut is returned when the test author included
// more than one shortcut (e.g. `kube.create` or `kube.apply`) in the same
// test spec.
ErrMoreThanOneShortcut = fmt.Errorf(
"%w: you may only specify a single shortcut field (e.g. "+
"`kube.create` or `kube.apply`",
gdterrors.ErrParse,
)
// ErrEitherShortcutOrKubeSpec is returned when the test author
// included both a shortcut (e.g. `kube.create` or `kube.apply`) AND the
// long-form `kube` object in the same test spec.
Expand Down Expand Up @@ -128,6 +120,24 @@ var (
)
)

// EitherShortcutOrKubeSpecAt returns ErrEitherShortcutOrKubeSpec for a given
// YAML node
func EitherShortcutOrKubeSpecAt(node *yaml.Node) error {
return fmt.Errorf(
"%w at line %d, column %d",
ErrEitherShortcutOrKubeSpec, node.Line, node.Column,
)
}

// MoreThanOneKubeActionAt returns ErrMoreThanOneKubeAction for a given YAML
// node
func MoreThanOneKubeActionAt(node *yaml.Node) error {
return fmt.Errorf(
"%w at line %d, column %d",
ErrMoreThanOneKubeAction, node.Line, node.Column,
)
}

// ExpectedMapOrYAMLStringAt returns ErrExpectedMapOrYAMLString for a given
// YAML node
func ExpectedMapOrYAMLStringAt(node *yaml.Node) error {
Expand All @@ -144,22 +154,31 @@ func KubeConfigNotFound(path string) error {

// InvalidResourceSpecifier returns ErrResourceSpecifier for a given
// supplied resource specifier.
func InvalidResourceSpecifier(subject string) error {
return fmt.Errorf("%w: %s", ErrResourceSpecifierInvalid, subject)
func InvalidResourceSpecifier(subject string, node *yaml.Node) error {
return fmt.Errorf(
"%w: %s at line %d, column %d",
ErrResourceSpecifierInvalid, subject, node.Line, node.Column,
)
}

// InvalidResourceSpecifierOrFilepath returns
// ErrResourceSpecifierOrFilepath for a given supplied subject.
func InvalidResourceSpecifierOrFilepath(subject string) error {
func InvalidResourceSpecifierOrFilepath(
subject string, node *yaml.Node,
) error {
return fmt.Errorf(
"%w: %s", ErrResourceSpecifierInvalidOrFilepath, subject,
"%w: %s at line %d, column %d",
ErrResourceSpecifierInvalidOrFilepath, subject, node.Line, node.Column,
)
}

// InvalidWithLabels returns ErrWithLabels with an error containing more
// context.
func InvalidWithLabels(err error) error {
return fmt.Errorf("%w: %s", ErrWithLabelsInvalid, err)
func InvalidWithLabels(err error, node *yaml.Node) error {
return fmt.Errorf(
"%w: %s at line %d, column %d",
ErrWithLabelsInvalid, err, node.Line, node.Column,
)
}

// ResourceUnknown returns ErrRuntimeResourceUnknown for a given kind
Expand Down
30 changes: 17 additions & 13 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ func (s *Spec) Eval(ctx context.Context, t *testing.T) *result.Result {
}
var res *result.Result
t.Run(s.Title(), func(t *testing.T) {
if s.Kube.Get != "" {
if s.Kube.Get != nil {
res = s.get(ctx, t, c)
}
if s.Kube.Create != "" {
res = s.create(ctx, t, c)
}
if s.Kube.Delete != "" {
if s.Kube.Delete != nil {
res = s.delete(ctx, t, c)
}
if s.Kube.Apply != "" {
Expand Down Expand Up @@ -83,7 +83,7 @@ func (s *Spec) get(
t *testing.T,
c *connection,
) *result.Result {
kind, name := splitKindName(s.Kube.Get)
kind, name := s.Kube.Get.KindName()
gvk := schema.GroupVersionKind{
Kind: kind,
}
Expand Down Expand Up @@ -146,12 +146,10 @@ func (s *Spec) doList(
namespace string,
) gdttypes.Assertions {
opts := metav1.ListOptions{}
with := s.Kube.With
if with != nil {
if with.Labels != nil {
// We already validated the label selector during parse-time
opts.LabelSelector = labels.Set(with.Labels).String()
}
withlabels := s.Kube.Get.Labels()
if withlabels != nil {
// We already validated the label selector during parse-time
opts.LabelSelector = labels.Set(withlabels).String()
}
list, err := c.client.Resource(res).Namespace(namespace).List(
ctx, opts,
Expand Down Expand Up @@ -346,8 +344,8 @@ func (s *Spec) delete(
t *testing.T,
c *connection,
) *result.Result {
if probablyFilePath(s.Kube.Delete) {
path := s.Kube.Delete
if s.Kube.Delete.FilePath() != "" {
path := s.Kube.Delete.FilePath()
f, err := os.Open(path)
if err != nil {
// This should never happen because we check during parse time
Expand Down Expand Up @@ -385,7 +383,7 @@ func (s *Spec) delete(
return result.New()
}

kind, name := splitKindName(s.Kube.Delete)
kind, name := s.Kube.Delete.KindName()
gvk := schema.GroupVersionKind{
Kind: kind,
}
Expand Down Expand Up @@ -428,10 +426,16 @@ func (s *Spec) doDeleteCollection(
res schema.GroupVersionResource,
namespace string,
) *result.Result {
listOpts := metav1.ListOptions{}
withlabels := s.Kube.Delete.Labels()
if withlabels != nil {
// We already validated the label selector during parse-time
listOpts.LabelSelector = labels.Set(withlabels).String()
}
err := c.client.Resource(res).Namespace(namespace).DeleteCollection(
ctx,
metav1.DeleteOptions{},
metav1.ListOptions{},
listOpts,
)
a := newAssertions(s.Assert, err, nil)
return result.New(result.WithFailures(a.Failures()...))
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.19

require (
github.com/cenkalti/backoff/v4 v4.2.1
github.com/gdt-dev/gdt v1.1.1
github.com/gdt-dev/gdt v1.2.1
github.com/samber/lo v1.38.1
github.com/stretchr/testify v1.8.4
gopkg.in/yaml.v3 v3.0.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/gdt-dev/gdt v1.1.1 h1:863WjQr2Oa+1eVKJspw1SRqW72S0Y3UydN0j8WwPYJU=
github.com/gdt-dev/gdt v1.1.1/go.mod h1:StnyGjC/67u59La2u6fh3HwW9MmodVhKdXcLlkgvNSY=
github.com/gdt-dev/gdt v1.2.1 h1:tNIpBPLatk8Rb0YFSK+FOzKIhHPYgmLpXQL8qottNcI=
github.com/gdt-dev/gdt v1.2.1/go.mod h1:StnyGjC/67u59La2u6fh3HwW9MmodVhKdXcLlkgvNSY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down
Loading

0 comments on commit 0190c8b

Please sign in to comment.