Skip to content

Commit cd9bc31

Browse files
authored
Merge pull request #10 from jaypipes/multi-kind
add ability to pass a KinD config to fixture
2 parents c58e237 + 98c7ed0 commit cd9bc31

File tree

9 files changed

+207
-30
lines changed

9 files changed

+207
-30
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,48 @@ tests:
775775
- kube.get: pods/nginx
776776
```
777777

778+
#### Passing a KinD configuration
779+
780+
You may want to pass a custom KinD configuration resource by using the
781+
`fixtures.kind.WithConfigPath()` modifier:
782+
783+
784+
```go
785+
import (
786+
"github.com/gdt-dev/gdt"
787+
gdtkube "github.com/gdt-dev/kube"
788+
gdtkind "github.com/gdt-dev/kube/fixtures/kind"
789+
)
790+
791+
func TestExample(t *testing.T) {
792+
s, err := gdt.From("path/to/test.yaml")
793+
if err != nil {
794+
t.Fatalf("failed to load tests: %s", err)
795+
}
796+
797+
configPath := filepath.Join("testdata", "my-kind-config.yaml")
798+
799+
ctx := context.Background()
800+
ctx = gdt.RegisterFixture(
801+
ctx, "kind", gdtkind.New(),
802+
gdtkind.WithConfigPath(configPath),
803+
)
804+
err = s.Run(ctx, t)
805+
if err != nil {
806+
t.Fatalf("failed to run tests: %s", err)
807+
}
808+
}
809+
```
810+
811+
In your test file, you would list the "kind" fixture in the `fixtures` list:
812+
813+
```yaml
814+
name: example-using-kind
815+
fixtures:
816+
- kind
817+
tests:
818+
- kube.get: pods/nginx
819+
```
778820
## Contributing and acknowledgements
779821

780822
`gdt` was inspired by [Gabbi](https://github.com/cdent/gabbi), the excellent

action.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,12 @@ func (a *Action) doList(
162162
// We already validated the label selector during parse-time
163163
opts.LabelSelector = labels.Set(withlabels).String()
164164
}
165-
return c.client.Resource(res).Namespace(ns).List(
165+
if c.resourceNamespaced(res) {
166+
return c.client.Resource(res).Namespace(ns).List(
167+
ctx, opts,
168+
)
169+
}
170+
return c.client.Resource(res).List(
166171
ctx, opts,
167172
)
168173
}
@@ -176,7 +181,14 @@ func (a *Action) doGet(
176181
ns string,
177182
name string,
178183
) (*unstructured.Unstructured, error) {
179-
return c.client.Resource(res).Namespace(ns).Get(
184+
if c.resourceNamespaced(res) {
185+
return c.client.Resource(res).Namespace(ns).Get(
186+
ctx,
187+
name,
188+
metav1.GetOptions{},
189+
)
190+
}
191+
return c.client.Resource(res).Get(
180192
ctx,
181193
name,
182194
metav1.GetOptions{},

assertions.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,18 @@ func (a *assertions) errorOK() bool {
274274
// that has a 404 ErrStatus.Code in it
275275
apierr, ok := a.err.(*apierrors.StatusError)
276276
if ok {
277-
if !a.expectsNotFound() {
277+
if a.expectsNotFound() {
278278
if http.StatusNotFound != int(apierr.ErrStatus.Code) {
279279
msg := fmt.Sprintf("got status code %d", apierr.ErrStatus.Code)
280280
a.Fail(ExpectedNotFound(msg))
281281
return false
282282
}
283+
// "Swallow" the NotFound error since we expected it.
284+
a.err = nil
285+
} else {
286+
a.Fail(apierr)
287+
return false
283288
}
284-
// "Swallow" the NotFound error since we expected it.
285-
a.err = nil
286289
}
287290
}
288291
if exp.Error != "" && a.r != nil {

connect.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,23 @@ func (c *connection) gvrFromGVK(
156156
return r.Resource, nil
157157
}
158158

159+
// resourceNamespaces returns true if the supplied schema.GroupVersionResource
160+
// is namespaced, false otherwise
161+
func (c *connection) resourceNamespaced(gvr schema.GroupVersionResource) bool {
162+
apiResources, err := c.disco.ServerResourcesForGroupVersion(
163+
gvr.GroupVersion().String(),
164+
)
165+
if err != nil {
166+
panic("expected to find APIResource for GroupVersion " + gvr.GroupVersion().String())
167+
}
168+
for _, apiResource := range apiResources.APIResources {
169+
if apiResource.Name == gvr.Resource {
170+
return apiResource.Namespaced
171+
}
172+
}
173+
panic("expected to find APIResource for GroupVersionResource " + gvr.Resource)
174+
}
175+
159176
// connect returns a connection with a discovery client and a Kubernetes
160177
// client-go DynamicClient to use in communicating with the Kubernetes API
161178
// server configured for this Spec

fixtures/kind/kind.go

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,16 @@
55
package kind
66

77
import (
8-
"bytes"
98
"strings"
109

1110
gdttypes "github.com/gdt-dev/gdt/types"
1211
"github.com/samber/lo"
1312
"sigs.k8s.io/kind/pkg/cluster"
1413
kindconst "sigs.k8s.io/kind/pkg/cluster/constants"
15-
kubeyaml "sigs.k8s.io/yaml"
1614

1715
gdtkube "github.com/gdt-dev/kube"
1816
)
1917

20-
const (
21-
workdirNamePattern = "gdt-kube.kindfix.*"
22-
)
23-
2418
// KindFixture implements `gdttypes.Fixture` and exposes connection/config
2519
// information about a running KinD cluster.
2620
type KindFixture struct {
@@ -36,6 +30,8 @@ type KindFixture struct {
3630
// will use the default KinD context, which is "kind-{cluster_name}"
3731
// See https://github.com/kubernetes-sigs/kind/blob/3610f606516ccaa88aa098465d8c13af70937050/pkg/cluster/internal/kubeconfig/internal/kubeconfig/helpers.go#L23-L26
3832
Context string
33+
// ConfigPath is a path to the v1alpha4 KinD configuration CR
34+
ConfigPath string
3935
}
4036

4137
func (f *KindFixture) Start() {
@@ -45,7 +41,11 @@ func (f *KindFixture) Start() {
4541
if f.isRunning() {
4642
return
4743
}
48-
if err := f.provider.Create(f.ClusterName); err != nil {
44+
opts := []cluster.CreateOption{}
45+
if f.ConfigPath != "" {
46+
opts = append(opts, cluster.CreateWithConfigFile(f.ConfigPath))
47+
}
48+
if err := f.provider.Create(f.ClusterName, opts...); err != nil {
4949
panic(err)
5050
}
5151
}
@@ -96,24 +96,6 @@ func (f *KindFixture) State(key string) interface{} {
9696
return ""
9797
}
9898

99-
// normYAML round trips yaml bytes through sigs.k8s.io/yaml to normalize them
100-
// versus other kubernetes ecosystem yaml output
101-
func normYAML(y []byte) ([]byte, error) {
102-
var unstructured interface{}
103-
if err := kubeyaml.Unmarshal(y, &unstructured); err != nil {
104-
return nil, err
105-
}
106-
encoded, err := kubeyaml.Marshal(&unstructured)
107-
if err != nil {
108-
return nil, err
109-
}
110-
// special case: don't write anything when empty
111-
if bytes.Equal(encoded, []byte("{}\n")) {
112-
return []byte{}, nil
113-
}
114-
return encoded, nil
115-
}
116-
11799
type KindFixtureModifier func(*KindFixture)
118100

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

115+
// WithConfigPath configures a path to a KinD configuration CR to use
116+
func WithConfigPath(path string) KindFixtureModifier {
117+
return func(f *KindFixture) {
118+
f.ConfigPath = path
119+
}
120+
}
121+
133122
// New returns a fixture that exposes Kubernetes configuration/context
134123
// information about a KinD cluster. If no such KinD cluster exists, one will
135124
// be created. If the KinD cluster is created, it is destroyed at the end of

fixtures/kind/kind_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Use and distribution licensed under the Apache license version 2.
2+
//
3+
// See the COPYING file in the root project directory for full text.
4+
5+
package kind_test
6+
7+
import (
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/gdt-dev/gdt"
13+
gdtcontext "github.com/gdt-dev/gdt/context"
14+
kindfix "github.com/gdt-dev/kube/fixtures/kind"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestDefaultSingleControlPlane(t *testing.T) {
19+
skipKind(t)
20+
require := require.New(t)
21+
22+
fp := filepath.Join("testdata", "default-single-control-plane.yaml")
23+
24+
s, err := gdt.From(fp)
25+
require.Nil(err)
26+
require.NotNil(s)
27+
28+
ctx := gdtcontext.New()
29+
ctx = gdtcontext.RegisterFixture(ctx, "kind", kindfix.New())
30+
31+
err = s.Run(ctx, t)
32+
require.Nil(err)
33+
}
34+
35+
func TestOneControlPlaneOneWorker(t *testing.T) {
36+
skipKind(t)
37+
require := require.New(t)
38+
39+
fp := filepath.Join("testdata", "one-control-plane-one-worker.yaml")
40+
41+
s, err := gdt.From(fp)
42+
require.Nil(err)
43+
require.NotNil(s)
44+
45+
kindCfgPath := filepath.Join("testdata", "kind-config-one-cp-one-worker.yaml")
46+
47+
ctx := gdtcontext.New()
48+
ctx = gdtcontext.RegisterFixture(
49+
ctx, "kind-one-cp-one-worker",
50+
kindfix.New(
51+
kindfix.WithClusterName("kind-one-cp-one-worker"),
52+
kindfix.WithConfigPath(kindCfgPath),
53+
),
54+
)
55+
56+
err = s.Run(ctx, t)
57+
require.Nil(err)
58+
}
59+
60+
func skipKind(t *testing.T) {
61+
_, found := os.LookupEnv("SKIP_KIND")
62+
if found {
63+
t.Skipf("skipping KinD-requiring test")
64+
}
65+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: default-single-control-plane
2+
description: test default KinD cluster has a single control plane node
3+
fixtures:
4+
- kind
5+
tests:
6+
- name: list-all-nodes
7+
kube.get: nodes
8+
assert:
9+
len: 1
10+
- name: single-control-plane-node
11+
kube:
12+
get:
13+
type: nodes
14+
labels:
15+
node-role.kubernetes.io/control-plane: ""
16+
assert:
17+
len: 1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
kind: Cluster
2+
apiVersion: kind.x-k8s.io/v1alpha4
3+
nodes:
4+
- role: control-plane
5+
- role: worker
6+
labels:
7+
role: worker
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: one-control-plane-one-worker
2+
description: test default KinD cluster has one control plane node and one worker
3+
fixtures:
4+
- kind-one-cp-one-worker
5+
tests:
6+
- name: list-all-nodes
7+
kube.get: nodes
8+
assert:
9+
len: 2
10+
- name: one-control-plane-node
11+
kube:
12+
get:
13+
type: nodes
14+
labels:
15+
node-role.kubernetes.io/control-plane: ""
16+
assert:
17+
len: 1
18+
- name: one-worker-node
19+
kube:
20+
get:
21+
type: nodes
22+
labels:
23+
role: worker
24+
assert:
25+
len: 1

0 commit comments

Comments
 (0)