Skip to content

Commit 08ad4c6

Browse files
authored
Merge pull request #76 from ecordell/dynfake
Add new dynamic fake that supports server-side apply / field managers
2 parents b98da97 + 386c596 commit 08ad4c6

File tree

9 files changed

+96780
-181
lines changed

9 files changed

+96780
-181
lines changed

client/fake/README.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Fake Dynamic Client
2+
3+
Enhanced fake dynamic client for testing Kubernetes controllers with Custom Resource Definitions (CRDs).
4+
5+
## Features
6+
7+
- **Server-Side Apply**: Full field management and strategic merge patch support
8+
- **CRD Support**: Automatic registration and schema validation
9+
- **Multi-Version CRDs**: Support for CRDs with multiple API versions
10+
- **Embedded CRDs**: Load CRDs from `go:embed` byte data
11+
- **Flexible API**: Functional options for easy configuration
12+
13+
## Quick Start
14+
15+
### Basic Usage
16+
17+
```go
18+
import "github.com/authzed/controller-idioms/client/fake"
19+
20+
// Basic client
21+
client := fake.NewClient(scheme)
22+
23+
// With CRDs and initial objects
24+
client := fake.NewClient(scheme,
25+
fake.WithCRDs(crd1, crd2),
26+
fake.WithObjects(existingObject),
27+
)
28+
29+
// With embedded CRD files
30+
client := fake.NewClient(scheme,
31+
fake.WithCRDBytes(embeddedCRDs1, embeddedCRDs2),
32+
)
33+
```
34+
35+
### Using go:embed
36+
37+
```go
38+
//go:embed testdata/my-crds.yaml
39+
var embeddedCRDs []byte
40+
41+
func TestMyController(t *testing.T) {
42+
client := fake.NewClient(scheme, fake.WithCRDBytes(embeddedCRDs))
43+
44+
// Create custom resources
45+
myResource := &unstructured.Unstructured{
46+
Object: map[string]interface{}{
47+
"apiVersion": "example.com/v1",
48+
"kind": "MyResource",
49+
"metadata": map[string]interface{}{"name": "test"},
50+
"spec": map[string]interface{}{"replicas": 3},
51+
},
52+
}
53+
54+
gvr := schema.GroupVersionResource{
55+
Group: "example.com", Version: "v1", Resource: "myresources",
56+
}
57+
58+
created, err := client.Resource(gvr).Namespace("default").Create(
59+
context.TODO(), myResource, metav1.CreateOptions{},
60+
)
61+
// Test your controller logic...
62+
}
63+
```
64+
65+
## API Reference
66+
67+
### NewClient (Recommended)
68+
69+
```go
70+
func NewClient(scheme *runtime.Scheme, opts ...ClientOption) dynamic.Interface
71+
```
72+
73+
Main constructor with functional options:
74+
75+
- `WithCRDs(crds...)` - Add CRD objects
76+
- `WithCRDBytes(data...)` - Add CRDs from YAML/JSON bytes
77+
- `WithCustomGVRMappings(mappings)` - Custom GVR to ListKind mappings
78+
- `WithOpenAPISpec(path)` - Custom OpenAPI spec file
79+
- `WithObjects(objects...)` - Initial objects in the client
80+
81+
### Other Constructors
82+
83+
These constructors are provided to mirror the upstream dynamic client interface,
84+
but they are less flexible than `NewClient`:
85+
86+
```go
87+
func NewFakeDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) dynamic.Interface
88+
func NewFakeDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) dynamic.Interface
89+
```
90+
91+
## CRD Format
92+
93+
Supports YAML and JSON, single or multi-document:
94+
95+
```yaml
96+
---
97+
apiVersion: apiextensions.k8s.io/v1
98+
kind: CustomResourceDefinition
99+
metadata:
100+
name: widgets.example.com
101+
spec:
102+
group: example.com
103+
names:
104+
kind: Widget
105+
plural: widgets
106+
# ... rest of CRD spec
107+
---
108+
apiVersion: apiextensions.k8s.io/v1
109+
kind: CustomResourceDefinition
110+
metadata:
111+
name: gadgets.example.com
112+
# ... second CRD
113+
```
114+
115+
## Examples
116+
117+
### Multiple CRD Sources
118+
119+
```go
120+
client := fake.NewClient(scheme,
121+
fake.WithCRDs(parsedCRD), // From CRD objects
122+
fake.WithCRDBytes(widgetCRDs, gadgetCRDs), // From embedded bytes
123+
fake.WithObjects(existingResources...), // Pre-existing objects
124+
)
125+
```
126+
127+
### Server-Side Apply
128+
129+
```go
130+
applied, err := client.Resource(gvr).Namespace("default").Apply(
131+
context.TODO(), "resource-name", resource,
132+
metav1.ApplyOptions{
133+
FieldManager: "my-controller",
134+
Force: true,
135+
},
136+
)
137+
```
138+
139+
### Multi-Version CRDs
140+
141+
```go
142+
// Different versions of the same resource
143+
v1alpha1GVR := schema.GroupVersionResource{Group: "example.com", Version: "v1alpha1", Resource: "apps"}
144+
v1GVR := schema.GroupVersionResource{Group: "example.com", Version: "v1", Resource: "apps"}
145+
146+
// Both work independently
147+
client.Resource(v1alpha1GVR).Create(...)
148+
client.Resource(v1GVR).Create(...)
149+
```

0 commit comments

Comments
 (0)