Skip to content

Commit c6d495d

Browse files
authored
Merge pull request #70 from pablochacin/generic-k8s-interface
Generic k8s interface
2 parents 0c01c2c + dea1bf7 commit c6d495d

File tree

7 files changed

+693
-59
lines changed

7 files changed

+693
-59
lines changed

README.md

+106-17
Original file line numberDiff line numberDiff line change
@@ -36,39 +36,131 @@ cd xk6-kubernetes
3636
make
3737
```
3838

39+
Using the `k6` binary with `xk6-kubernetes`, run the k6 test as usual:
40+
41+
```bash
42+
./k6 run k8s-test-script.js
43+
44+
```
45+
# Usage
46+
47+
The API assumes a `kubeconfig` configuration is available at any of the following default locations:
48+
* at the location pointed by the `KUBECONFIG` environment variable
49+
* at `$HOME/.kube`
50+
51+
52+
# API
3953

40-
## Example
54+
## Generic API
4155

56+
This API offers methods for creating, retrieving, listing and deleting resources of any of the supported kinds.
57+
58+
| Method | Parameters| Description |
59+
| ------------ | ---| ------ |
60+
| apply | manifest string| creates a Kubernetes resource given a YAML manifest |
61+
| create | spec object | creates a Kubernetes resource given its specification |
62+
| delete | kind | removes the named resource |
63+
| | name |
64+
| | namespace|
65+
| get | kind| returns the named resource |
66+
| | name |
67+
| | namespace |
68+
| list | kind| returns a collection of resources of a given kind
69+
| | namespace |
70+
71+
72+
The kinds of resources currently supported are:
73+
* ConfigMap
74+
* Deployment
75+
* Job
76+
* Namespace
77+
* Node
78+
* PersistentVolume
79+
* PersistentVolumeClaim
80+
* Pod
81+
* Secret
82+
* Service
83+
* StatefulSet
84+
85+
### Examples
86+
87+
#### Creating a pod using a specification
4288
```javascript
4389
import { Kubernetes } from 'k6/x/kubernetes';
44-
90+
91+
const podSpec = {
92+
apiVersion: "v1",
93+
kind: "Pod",
94+
metadata: {
95+
name: "busybox",
96+
namespace: "testns"
97+
},
98+
spec: {
99+
containers: [
100+
{
101+
name: "busybox",
102+
image: "busybox",
103+
command: ["sh", "-c", "sleep 30"]
104+
}
105+
]
106+
}
107+
}
108+
45109
export default function () {
46-
const kubernetes = new Kubernetes({
47-
// config_path: "/path/to/kube/config", ~/.kube/config by default
48-
});
110+
const kubernetes = new Kubernetes();
111+
112+
kubernetes.create(pod)
49113
50-
const pods = kubernetes.pods.list();
114+
const pods = kubernetes.list("Pod", "testns");
51115
52116
console.log(`${pods.length} Pods found:`);
53117
pods.map(function(pod) {
54-
console.log(` ${pod.name}`)
118+
console.log(` ${pod.metadata.name}`)
55119
});
56120
}
57121
```
58122
59-
Using the `k6` binary with `xk6-kubernetes`, run the k6 test as usual:
123+
#### Creating a job using a YAML manifest
124+
```javascript
125+
import { Kubernetes } from 'k6/x/kubernetes';
60126
61-
```bash
62-
./k6 run k8s-test-script.js
127+
const manifest = `
128+
apiVersion: batch/v1
129+
kind: Job
130+
metadata:
131+
name: busybox
132+
namespace: testns
133+
spec:
134+
template:
135+
spec:
136+
containers:
137+
- name: busybox
138+
image: busybox
139+
command: ["sleep", "300"]
140+
restartPolicy: Never
141+
`
63142
64-
...
65-
INFO[0001] 16 Pods found: source=console
66-
...
143+
export default function () {
144+
const kubernetes = new Kubernetes();
67145
146+
kubernetes.apply(manifest)
147+
148+
const jobs = kubernetes.list("Job", "testns");
149+
150+
console.log(`${jobs.length} Jobs found:`);
151+
pods.map(function(job) {
152+
console.log(` ${job.metadata.name}`)
153+
});
154+
}
68155
```
69156
157+
## Resource kind helpers
70158
71-
## APIs
159+
This API offers a helper for each kind of Kubernetes resources supported (Pods, Deployments, Secrets, et cetera). For each one, an interface for creating, getting, listing and deleting objects is offered.
160+
161+
>⚠️ This interface is deprecated and will be removed soon
162+
> -
163+
</br>
72164
73165
74166
### Create a client: `new Kubernetes(config)`
@@ -89,9 +181,6 @@ export default function () {
89181
}
90182
```
91183
92-
93-
94-
95184
### `Client.config_maps`
96185
97186
| Method | Description | |
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Kubernetes } from 'k6/x/kubernetes'
2+
import { sleep } from 'k6'
3+
4+
const k8s = new Kubernetes()
5+
6+
const podSpec = {
7+
apiVersion: "v1",
8+
kind: "Pod",
9+
metadata: {
10+
name: "busybox",
11+
namespace: "default"
12+
},
13+
spec: {
14+
containers: [
15+
{
16+
name: "busybox",
17+
image: "busybox",
18+
command: ["sh", "-c", "sleep 30"]
19+
}
20+
]
21+
}
22+
}
23+
24+
export default function(){
25+
var created = k8s.create(podSpec)
26+
console.log("pod '" + created.metadata.name +"' created")
27+
28+
var pod = k8s.get(podSpec.kind, podSpec.metadata.name, podSpec.metadata.namespace)
29+
if (podSpec.metadata.name != pod.metadata.name) {
30+
throw new Error("fetch by name did not return the Pod. Expected: " + podSpec.metadata.name + " but got: " + fetched.name)
31+
}
32+
33+
const pods = k8s.list(podSpec.kind, podSpec.metadata.namespace)
34+
if (pods === undefined || pods.length < 1) {
35+
throw new Error("expected listing with 1 Pod")
36+
}
37+
38+
k8s.delete(podSpec.kind, podSpec.metadata.name, podSpec.metadata.namespace)
39+
40+
// give time for the pod to be deleted
41+
sleep(5)
42+
43+
if (k8s.list(podSpec.kind, podSpec.metadata.namespace).length != 0) {
44+
throw new Error("deletion failed to remove pod")
45+
}
46+
}

internal/testutils/fake.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package testutils
2+
3+
import (
4+
"k8s.io/apimachinery/pkg/runtime"
5+
dynamicfake "k8s.io/client-go/dynamic/fake"
6+
"k8s.io/client-go/kubernetes/fake"
7+
)
8+
9+
// NewFakeDynamic creates a new instance of a fake dynamic client with a default scheme
10+
func NewFakeDynamic() (*dynamicfake.FakeDynamicClient, error) {
11+
scheme := runtime.NewScheme()
12+
err := fake.AddToScheme(scheme)
13+
if err != nil {
14+
return nil, err
15+
}
16+
return dynamicfake.NewSimpleDynamicClient(scheme), nil
17+
}

kubernetes.go

+33
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go.k6.io/k6/js/common"
1212
"k8s.io/client-go/rest"
1313

14+
"github.com/grafana/xk6-kubernetes/pkg/api"
1415
"github.com/grafana/xk6-kubernetes/pkg/configmaps"
1516
"github.com/grafana/xk6-kubernetes/pkg/deployments"
1617
"github.com/grafana/xk6-kubernetes/pkg/ingresses"
@@ -25,6 +26,7 @@ import (
2526

2627
"go.k6.io/k6/js/modules"
2728
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/client-go/dynamic"
2830
"k8s.io/client-go/kubernetes"
2931
_ "k8s.io/client-go/plugin/pkg/client/auth" // Required for access to GKE and AKS
3032
"k8s.io/client-go/tools/clientcmd"
@@ -44,10 +46,13 @@ type ModuleInstance struct {
4446
vu modules.VU
4547
// clientset enables injection of a pre-configured Kubernetes environment for unit tests
4648
clientset kubernetes.Interface
49+
// dynamic enables injection of a fake dynamic client for unit tests
50+
dynamic dynamic.Interface
4751
}
4852

4953
// Kubernetes is the exported object used within JavaScript.
5054
type Kubernetes struct {
55+
api.Kubernetes
5156
client kubernetes.Interface
5257
metaOptions metaV1.ListOptions
5358
ctx context.Context
@@ -100,6 +105,7 @@ func (mi *ModuleInstance) newClient(c goja.ConstructorCall) *goja.Object {
100105
obj := &Kubernetes{}
101106
var config *rest.Config
102107

108+
// if clientset was not injected for unit testing
103109
if mi.clientset == nil {
104110
var options KubeConfig
105111
err := rt.ExportTo(c.Argument(0), &options)
@@ -121,6 +127,33 @@ func (mi *ModuleInstance) newClient(c goja.ConstructorCall) *goja.Object {
121127
obj.client = mi.clientset
122128
}
123129

130+
// If dynamic client was not injected for unit testing
131+
// It is assumed rest config is set
132+
if mi.dynamic == nil {
133+
k8s, err := api.NewFromConfig(
134+
api.KubernetesConfig{
135+
Config: config,
136+
Context: ctx,
137+
},
138+
)
139+
if err != nil {
140+
common.Throw(rt, err)
141+
}
142+
obj.Kubernetes = k8s
143+
} else {
144+
// Pre-configured dynamic client is injected for unit testing
145+
k8s, err := api.NewFromConfig(
146+
api.KubernetesConfig{
147+
Client: mi.dynamic,
148+
Context: ctx,
149+
},
150+
)
151+
if err != nil {
152+
common.Throw(rt, err)
153+
}
154+
obj.Kubernetes = k8s
155+
}
156+
124157
obj.metaOptions = metaV1.ListOptions{}
125158
obj.ctx = ctx
126159

0 commit comments

Comments
 (0)