Skip to content

Commit

Permalink
Merge pull request #10 from jaypipes/multi-kind
Browse files Browse the repository at this point in the history
add ability to pass a KinD config to fixture
  • Loading branch information
jaypipes committed May 27, 2024
2 parents c58e237 + 98c7ed0 commit cd9bc31
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 30 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,48 @@ tests:
- kube.get: pods/nginx
```

#### Passing a KinD configuration

You may want to pass a custom KinD configuration resource by using the
`fixtures.kind.WithConfigPath()` modifier:


```go
import (
"github.com/gdt-dev/gdt"
gdtkube "github.com/gdt-dev/kube"
gdtkind "github.com/gdt-dev/kube/fixtures/kind"
)
func TestExample(t *testing.T) {
s, err := gdt.From("path/to/test.yaml")
if err != nil {
t.Fatalf("failed to load tests: %s", err)
}
configPath := filepath.Join("testdata", "my-kind-config.yaml")
ctx := context.Background()
ctx = gdt.RegisterFixture(
ctx, "kind", gdtkind.New(),
gdtkind.WithConfigPath(configPath),
)
err = s.Run(ctx, t)
if err != nil {
t.Fatalf("failed to run tests: %s", err)
}
}
```

In your test file, you would list the "kind" fixture in the `fixtures` list:

```yaml
name: example-using-kind
fixtures:
- kind
tests:
- kube.get: pods/nginx
```
## Contributing and acknowledgements

`gdt` was inspired by [Gabbi](https://github.com/cdent/gabbi), the excellent
Expand Down
16 changes: 14 additions & 2 deletions action.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,12 @@ func (a *Action) doList(
// We already validated the label selector during parse-time
opts.LabelSelector = labels.Set(withlabels).String()
}
return c.client.Resource(res).Namespace(ns).List(
if c.resourceNamespaced(res) {
return c.client.Resource(res).Namespace(ns).List(
ctx, opts,
)
}
return c.client.Resource(res).List(
ctx, opts,
)
}
Expand All @@ -176,7 +181,14 @@ func (a *Action) doGet(
ns string,
name string,
) (*unstructured.Unstructured, error) {
return c.client.Resource(res).Namespace(ns).Get(
if c.resourceNamespaced(res) {
return c.client.Resource(res).Namespace(ns).Get(
ctx,
name,
metav1.GetOptions{},
)
}
return c.client.Resource(res).Get(
ctx,
name,
metav1.GetOptions{},
Expand Down
9 changes: 6 additions & 3 deletions assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,15 +274,18 @@ func (a *assertions) errorOK() bool {
// that has a 404 ErrStatus.Code in it
apierr, ok := a.err.(*apierrors.StatusError)
if ok {
if !a.expectsNotFound() {
if a.expectsNotFound() {
if http.StatusNotFound != int(apierr.ErrStatus.Code) {
msg := fmt.Sprintf("got status code %d", apierr.ErrStatus.Code)
a.Fail(ExpectedNotFound(msg))
return false
}
// "Swallow" the NotFound error since we expected it.
a.err = nil
} else {
a.Fail(apierr)
return false
}
// "Swallow" the NotFound error since we expected it.
a.err = nil
}
}
if exp.Error != "" && a.r != nil {
Expand Down
17 changes: 17 additions & 0 deletions connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,23 @@ func (c *connection) gvrFromGVK(
return r.Resource, nil
}

// resourceNamespaces returns true if the supplied schema.GroupVersionResource
// is namespaced, false otherwise
func (c *connection) resourceNamespaced(gvr schema.GroupVersionResource) bool {
apiResources, err := c.disco.ServerResourcesForGroupVersion(
gvr.GroupVersion().String(),
)
if err != nil {
panic("expected to find APIResource for GroupVersion " + gvr.GroupVersion().String())
}
for _, apiResource := range apiResources.APIResources {
if apiResource.Name == gvr.Resource {
return apiResource.Namespaced
}
}
panic("expected to find APIResource for GroupVersionResource " + gvr.Resource)
}

// connect returns a connection with a discovery client and a Kubernetes
// client-go DynamicClient to use in communicating with the Kubernetes API
// server configured for this Spec
Expand Down
39 changes: 14 additions & 25 deletions fixtures/kind/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,16 @@
package kind

import (
"bytes"
"strings"

gdttypes "github.com/gdt-dev/gdt/types"
"github.com/samber/lo"
"sigs.k8s.io/kind/pkg/cluster"
kindconst "sigs.k8s.io/kind/pkg/cluster/constants"
kubeyaml "sigs.k8s.io/yaml"

gdtkube "github.com/gdt-dev/kube"
)

const (
workdirNamePattern = "gdt-kube.kindfix.*"
)

// KindFixture implements `gdttypes.Fixture` and exposes connection/config
// information about a running KinD cluster.
type KindFixture struct {
Expand All @@ -36,6 +30,8 @@ type KindFixture struct {
// will use the default KinD context, which is "kind-{cluster_name}"
// See https://github.com/kubernetes-sigs/kind/blob/3610f606516ccaa88aa098465d8c13af70937050/pkg/cluster/internal/kubeconfig/internal/kubeconfig/helpers.go#L23-L26
Context string
// ConfigPath is a path to the v1alpha4 KinD configuration CR
ConfigPath string
}

func (f *KindFixture) Start() {
Expand All @@ -45,7 +41,11 @@ func (f *KindFixture) Start() {
if f.isRunning() {
return
}
if err := f.provider.Create(f.ClusterName); err != nil {
opts := []cluster.CreateOption{}
if f.ConfigPath != "" {
opts = append(opts, cluster.CreateWithConfigFile(f.ConfigPath))
}
if err := f.provider.Create(f.ClusterName, opts...); err != nil {
panic(err)
}
}
Expand Down Expand Up @@ -96,24 +96,6 @@ func (f *KindFixture) State(key string) interface{} {
return ""
}

// normYAML round trips yaml bytes through sigs.k8s.io/yaml to normalize them
// versus other kubernetes ecosystem yaml output
func normYAML(y []byte) ([]byte, error) {
var unstructured interface{}
if err := kubeyaml.Unmarshal(y, &unstructured); err != nil {
return nil, err
}
encoded, err := kubeyaml.Marshal(&unstructured)
if err != nil {
return nil, err
}
// special case: don't write anything when empty
if bytes.Equal(encoded, []byte("{}\n")) {
return []byte{}, nil
}
return encoded, nil
}

type KindFixtureModifier func(*KindFixture)

// WithClusterName modifies the KindFixture's cluster name
Expand All @@ -130,6 +112,13 @@ func WithContext(name string) KindFixtureModifier {
}
}

// WithConfigPath configures a path to a KinD configuration CR to use
func WithConfigPath(path string) KindFixtureModifier {
return func(f *KindFixture) {
f.ConfigPath = path
}
}

// New returns a fixture that exposes Kubernetes configuration/context
// information about a KinD cluster. If no such KinD cluster exists, one will
// be created. If the KinD cluster is created, it is destroyed at the end of
Expand Down
65 changes: 65 additions & 0 deletions fixtures/kind/kind_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.

package kind_test

import (
"os"
"path/filepath"
"testing"

"github.com/gdt-dev/gdt"
gdtcontext "github.com/gdt-dev/gdt/context"
kindfix "github.com/gdt-dev/kube/fixtures/kind"
"github.com/stretchr/testify/require"
)

func TestDefaultSingleControlPlane(t *testing.T) {
skipKind(t)
require := require.New(t)

fp := filepath.Join("testdata", "default-single-control-plane.yaml")

s, err := gdt.From(fp)
require.Nil(err)
require.NotNil(s)

ctx := gdtcontext.New()
ctx = gdtcontext.RegisterFixture(ctx, "kind", kindfix.New())

err = s.Run(ctx, t)
require.Nil(err)
}

func TestOneControlPlaneOneWorker(t *testing.T) {
skipKind(t)
require := require.New(t)

fp := filepath.Join("testdata", "one-control-plane-one-worker.yaml")

s, err := gdt.From(fp)
require.Nil(err)
require.NotNil(s)

kindCfgPath := filepath.Join("testdata", "kind-config-one-cp-one-worker.yaml")

ctx := gdtcontext.New()
ctx = gdtcontext.RegisterFixture(
ctx, "kind-one-cp-one-worker",
kindfix.New(
kindfix.WithClusterName("kind-one-cp-one-worker"),
kindfix.WithConfigPath(kindCfgPath),
),
)

err = s.Run(ctx, t)
require.Nil(err)
}

func skipKind(t *testing.T) {
_, found := os.LookupEnv("SKIP_KIND")
if found {
t.Skipf("skipping KinD-requiring test")
}
}
17 changes: 17 additions & 0 deletions fixtures/kind/testdata/default-single-control-plane.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: default-single-control-plane
description: test default KinD cluster has a single control plane node
fixtures:
- kind
tests:
- name: list-all-nodes
kube.get: nodes
assert:
len: 1
- name: single-control-plane-node
kube:
get:
type: nodes
labels:
node-role.kubernetes.io/control-plane: ""
assert:
len: 1
7 changes: 7 additions & 0 deletions fixtures/kind/testdata/kind-config-one-cp-one-worker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
labels:
role: worker
25 changes: 25 additions & 0 deletions fixtures/kind/testdata/one-control-plane-one-worker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: one-control-plane-one-worker
description: test default KinD cluster has one control plane node and one worker
fixtures:
- kind-one-cp-one-worker
tests:
- name: list-all-nodes
kube.get: nodes
assert:
len: 2
- name: one-control-plane-node
kube:
get:
type: nodes
labels:
node-role.kubernetes.io/control-plane: ""
assert:
len: 1
- name: one-worker-node
kube:
get:
type: nodes
labels:
role: worker
assert:
len: 1

0 comments on commit cd9bc31

Please sign in to comment.