mirror of
https://github.com/imjasonh/client-go2
synced 2026-07-18 14:46:27 +00:00
Add expansion methods and missing client-go operations
This PR adds support for resource-specific expansion methods and implements
missing standard client-go operations to achieve feature parity.
## New Features
### Expansion Methods
- Added PodClient with methods: GetLogs, Bind, Evict, ProxyGet
- Added ServiceClient with method: ProxyGet
- Both clients implement the official k8s.io/client-go expansion interfaces
- Runtime type assertions ensure type safety (panic if wrong type)
### Missing Standard Methods
- Watch: Watch resources with optional label/field selectors
- DeleteCollection: Delete multiple resources matching criteria
- UpdateStatus: Update only the status subresource
### Other Improvements
- Refactored all tests to use inline configuration pattern
- Added comprehensive e2e tests for new functionality
- Updated documentation with examples
- Fixed linting error in stream.Close() handling
## Design Decisions
The expansion methods follow a namespace-scoped pattern where calling
`client.PodClient("namespace")` returns a client that implements the
standard client-go PodExpansion interface. This maintains full API
compatibility while providing type safety through runtime assertions.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
63505cf9fd
commit
e79b66caf5
9 changed files with 1458 additions and 366 deletions
108
README.md
108
README.md
|
|
@ -2,19 +2,121 @@
|
|||
|
||||
[](https://github.com/imjasonh/client-go2/actions/workflows/build.yaml)
|
||||
|
||||
This is an experimental type-parameter-aware client that wraps [`k8s.io/client-go/rest`](https://pkg.go.dev/k8s.io/client-go/rest), in [about 500 lines of mostly-vibe-coded Go](./generic/client.go).
|
||||
This is an experimental type-parameter-aware client that wraps [`k8s.io/client-go/rest`](https://pkg.go.dev/k8s.io/client-go/rest), in [about 570 lines of mostly-vibe-coded Go](./generic/client.go).
|
||||
|
||||
## Features
|
||||
|
||||
- **Type-safe generic client** - Work with strongly-typed Kubernetes objects instead of `unstructured.Unstructured`
|
||||
- **Zero code generation** - Uses Go generics instead of code generation
|
||||
- **Full CRUD operations** - List, Get, Create, Update, Delete, and Patch support
|
||||
- **Full CRUD operations** - List, Get, Create, Update, Delete, Patch, Watch, DeleteCollection, and UpdateStatus support
|
||||
- **Informer support** - Watch for changes with type-safe event handlers
|
||||
- **Automatic GVR inference** - No need to manually specify GroupVersionResource for standard Kubernetes types
|
||||
- **Expansion methods** - Resource-specific operations like Pod.GetLogs() and Service.ProxyGet()
|
||||
- **Label/Field selectors** - Filter resources using Kubernetes selectors
|
||||
- **SubResource access** - Generic method to access any subresource
|
||||
|
||||
## Usage
|
||||
|
||||
See [the example](./main.go)
|
||||
See [the example](./main.go) for comprehensive usage examples.
|
||||
|
||||
### Quick Examples
|
||||
|
||||
#### Basic CRUD Operations
|
||||
```go
|
||||
// Create a client with automatic GVR inference
|
||||
client, err := generic.NewClient[*corev1.Pod](config)
|
||||
|
||||
// List pods with label selector
|
||||
pods, err := client.List(ctx, "default", &metav1.ListOptions{
|
||||
LabelSelector: "app=nginx",
|
||||
})
|
||||
|
||||
// Get a specific pod
|
||||
pod, err := client.Get(ctx, "default", "my-pod", nil)
|
||||
```
|
||||
|
||||
#### Expansion Methods for Pods (client-go compatible)
|
||||
```go
|
||||
// Start with a generic client for pods
|
||||
client, err := generic.NewClient[*corev1.Pod](config)
|
||||
|
||||
// Get a namespace-scoped PodClient that implements typedcorev1.PodExpansion
|
||||
podClient := client.PodClient("default") // Will panic if T is not *corev1.Pod
|
||||
|
||||
// Get pod logs (matches client-go API)
|
||||
req := podClient.GetLogs("my-pod", &corev1.PodLogOptions{
|
||||
Container: "nginx",
|
||||
TailLines: &tailLines,
|
||||
})
|
||||
logs, err := req.DoRaw(ctx)
|
||||
|
||||
// Bind pod to node
|
||||
err = podClient.Bind(ctx, binding, metav1.CreateOptions{})
|
||||
|
||||
// Evict pod
|
||||
err = podClient.Evict(ctx, eviction)
|
||||
|
||||
// For cluster-scoped operations, use the generic client directly
|
||||
pods, err := client.List(ctx, "default", nil)
|
||||
```
|
||||
|
||||
#### Expansion Methods for Services (client-go compatible)
|
||||
```go
|
||||
// Start with a generic client for services
|
||||
client, err := generic.NewClient[*corev1.Service](config)
|
||||
|
||||
// Get a namespace-scoped ServiceClient that implements typedcorev1.ServiceExpansion
|
||||
serviceClient := client.ServiceClient("default") // Will panic if T is not *corev1.Service
|
||||
|
||||
// Use proxy to access service endpoints
|
||||
req := serviceClient.ProxyGet("http", "my-service", "80", "api/health", nil)
|
||||
resp, err := req.DoRaw(ctx)
|
||||
```
|
||||
|
||||
#### Generic SubResource Access
|
||||
```go
|
||||
// Access any subresource using the generic method
|
||||
req := client.SubResource("default", "my-pod", "status")
|
||||
status, err := req.DoRaw(ctx)
|
||||
```
|
||||
|
||||
#### Watch Resources
|
||||
```go
|
||||
// Watch for pod changes with label selector
|
||||
watcher, err := client.Watch(ctx, "default", &metav1.ListOptions{
|
||||
LabelSelector: "app=nginx",
|
||||
})
|
||||
|
||||
defer watcher.Stop()
|
||||
|
||||
for event := range watcher.ResultChan() {
|
||||
pod := event.Object.(*corev1.Pod)
|
||||
fmt.Printf("Event: %s Pod: %s\n", event.Type, pod.Name)
|
||||
}
|
||||
```
|
||||
|
||||
#### Delete Collection
|
||||
```go
|
||||
// Delete all pods with specific label
|
||||
err = client.DeleteCollection(ctx, "default", nil, &metav1.ListOptions{
|
||||
LabelSelector: "app=test",
|
||||
})
|
||||
|
||||
// Delete all pods in namespace
|
||||
err = client.DeleteCollection(ctx, "default", nil, nil)
|
||||
```
|
||||
|
||||
#### Update Status
|
||||
```go
|
||||
// Update only the status subresource
|
||||
pod.Status.Phase = corev1.PodRunning
|
||||
pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{
|
||||
Type: corev1.PodReady,
|
||||
Status: corev1.ConditionTrue,
|
||||
})
|
||||
|
||||
updated, err := client.UpdateStatus(ctx, "default", pod, nil)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue