mirror of
https://github.com/imjasonh/client-go2
synced 2026-07-08 17:16:47 +00:00
trying to make the client simpler; doesn't work :(
This commit is contained in:
parent
f6db51af0f
commit
59f526eccf
3 changed files with 102 additions and 102 deletions
|
|
@ -1,8 +1,8 @@
|
|||
# Experimenting with `go-client` and Go generics
|
||||
|
||||
This is an experimental type-parameter-aware client that wraps [`k8s.io/client-go/dynamic`](https://pkg.go.dev/k8s.io/client-go/dynamic).
|
||||
This is an experimental type-parameter-aware client that wraps [`k8s.io/client-go/dynamic`](https://pkg.go.dev/k8s.io/client-go/dynamic) _(...for now)_.
|
||||
|
||||
Until Go 1.18 is released, use [`gotip`](https://pkg.go.dev/golang.org/dl/gotip)
|
||||
Until Go 1.18 is released, use [`gotip`](https://pkg.go.dev/golang.org/dl/gotip).
|
||||
|
||||
Assuming you've got a working kubeconfig (does `kubectl get pods` work?), you can run this code:
|
||||
|
||||
|
|
|
|||
83
generic/client.go
Normal file
83
generic/client.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package generic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
|
||||
type obj interface {
|
||||
GetObjectKind() schema.ObjectKind
|
||||
}
|
||||
|
||||
func NewClient[T obj](config *rest.Config) client[T] {
|
||||
return client[T] {
|
||||
dyn: dynamic.NewForConfigOrDie(config),
|
||||
}
|
||||
}
|
||||
|
||||
type client[T obj] struct {
|
||||
dyn dynamic.Interface // TODO: don't depend on dynamic client
|
||||
}
|
||||
|
||||
// gvr translates the T to a GVR, via GVK
|
||||
// GVK->GVR translation is a terrible hack; we need something better.
|
||||
func (c client[T]) gvr() schema.GroupVersionResource {
|
||||
// BOOM!
|
||||
// This panics because T is a nil *corev1.Pod; the runtime.Object interface is only satisfied by *corev1.Pod, not the instantiable corev1.Pod.
|
||||
// https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#pointer-method-example
|
||||
var t T
|
||||
gvk := t.GetObjectKind().GroupVersionKind()
|
||||
return schema.GroupVersionResource{
|
||||
Group: gvk.Group,
|
||||
Version: gvk.Version,
|
||||
Resource: strings.ToLower(gvk.Kind) + "s", // HACK
|
||||
}
|
||||
}
|
||||
|
||||
func (c client[T]) List(ctx context.Context, namespace string) ([]T, error) {
|
||||
ul, err := c.dyn.Resource(c.gvr()).Namespace(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []T
|
||||
for _, u := range ul.Items {
|
||||
var t T
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(u.Object); err != nil { return nil, err }
|
||||
if err := json.NewDecoder(&buf).Decode(t); err != nil { return nil, err }
|
||||
out = append(out, t)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c client[T]) Get(ctx context.Context, namespace, name string) (T, error) {
|
||||
var t T
|
||||
u, err := c.dyn.Resource(c.gvr()).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(u.Object); err != nil { return t, err }
|
||||
if err := json.NewDecoder(&buf).Decode(t); err != nil { return t, err }
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (c client[T]) Create(ctx context.Context, namespace string, t T) error {
|
||||
m := map[string]interface{}{}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(t); err != nil { return err }
|
||||
if err := json.NewDecoder(&buf).Decode(&m); err != nil { return err }
|
||||
u := &unstructured.Unstructured{Object:m}
|
||||
_, err := c.dyn.Resource(c.gvr()).Namespace(namespace).Create(ctx, u, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
117
main.go
117
main.go
|
|
@ -1,22 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"github.com/imjasonh/client-go2/generic"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This is load bearing...
|
||||
// Without this, while the client is in generic/, I get a mysterious compiler error:
|
||||
// "internal compiler error: missing import reader for json.NewEncoder"
|
||||
_ = json.NewEncoder(nil)
|
||||
|
||||
ctx := context.Background()
|
||||
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}).ClientConfig()
|
||||
if err != nil {
|
||||
|
|
@ -24,16 +24,18 @@ func main() {
|
|||
}
|
||||
|
||||
// List pods in kube-system.
|
||||
pods, err := NewClient[pod](config).List(ctx, "kube-system")
|
||||
if err != nil { log.Fatal("listing pods:", err) }
|
||||
pods, err := generic.NewClient[*corev1.Pod](config).List(ctx, "kube-system")
|
||||
if err != nil {
|
||||
log.Fatal("listing pods:", err)
|
||||
}
|
||||
log.Println("PODS")
|
||||
for _, p := range pods {
|
||||
log.Println("-", p.Name)
|
||||
}
|
||||
|
||||
// Create a ConfigMap, then list ConfigMaps.
|
||||
cmc := NewClient[cm](config)
|
||||
if err := cmc.Create(ctx, "kube-system",cm{
|
||||
cmc := generic.NewClient[*corev1.ConfigMap](config)
|
||||
if err := cmc.Create(ctx, "kube-system", &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "foo-",
|
||||
},
|
||||
|
|
@ -44,96 +46,11 @@ func main() {
|
|||
log.Fatal("creating configmap:", err)
|
||||
}
|
||||
cms, err := cmc.List(ctx, "kube-system")
|
||||
if err != nil { log.Fatal("listing configmaps:", err) }
|
||||
if err != nil {
|
||||
log.Fatal("listing configmaps:", err)
|
||||
}
|
||||
log.Println("CONFIGMAPS")
|
||||
for _, cm := range cms {
|
||||
log.Println("-", cm.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient[T obj](config *rest.Config) client[T] {
|
||||
return client[T] {
|
||||
dyn: dynamic.NewForConfigOrDie(config),
|
||||
}
|
||||
}
|
||||
|
||||
type client[T obj] struct {
|
||||
dyn dynamic.Interface
|
||||
}
|
||||
|
||||
func (c client[T]) List(ctx context.Context, namespace string) ([]T, error) {
|
||||
var t T
|
||||
ul, err := c.dyn.Resource(t.GVR()).Namespace(namespace).List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []T
|
||||
for _, u := range ul.Items {
|
||||
var t T
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(u.Object); err != nil { return nil, err }
|
||||
if err := json.NewDecoder(&buf).Decode(&t); err != nil { return nil, err }
|
||||
out = append(out, t)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c client[T]) Get(ctx context.Context, namespace, name string) (*T, error) {
|
||||
var t T
|
||||
u, err := c.dyn.Resource(t.GVR()).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(u.Object); err != nil { return nil, err }
|
||||
if err := json.NewDecoder(&buf).Decode(&t); err != nil { return nil, err }
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (c client[T]) Create(ctx context.Context, namespace string, t T) error {
|
||||
m := map[string]interface{}{}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(t); err != nil { return err }
|
||||
if err := json.NewDecoder(&buf).Decode(&m); err != nil { return err }
|
||||
u := &unstructured.Unstructured{Object:m}
|
||||
_, err := c.dyn.Resource(t.GVR()).Namespace(namespace).Create(ctx, u, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// THESE ARE HACKS
|
||||
//
|
||||
// Ideally, the Pod type (or any runtime.Object) would be able to give us its
|
||||
// GVR directly, then the obj interface / type constraint would just be
|
||||
// runtime.Object.
|
||||
//
|
||||
// Since it can't, we have to wrap runtime.Object and provide these per-type.
|
||||
// This would be really nice to fix.
|
||||
|
||||
type pod corev1.Pod
|
||||
func (pod pod) GetObjectKind() schema.ObjectKind { return pod.GetObjectKind() }
|
||||
func (pod pod) DeepCopyObject() runtime.Object { return pod.DeepCopyObject() }
|
||||
func (pod) GVR() schema.GroupVersionResource {
|
||||
return schema.GroupVersionResource{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
}
|
||||
}
|
||||
|
||||
type cm corev1.ConfigMap
|
||||
func (cm cm) GetObjectKind() schema.ObjectKind { return cm.GetObjectKind() }
|
||||
func (cm cm) DeepCopyObject() runtime.Object { return cm.DeepCopyObject() }
|
||||
func (cm) GVR() schema.GroupVersionResource {
|
||||
return schema.GroupVersionResource{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Resource: "configmaps",
|
||||
}
|
||||
}
|
||||
|
||||
type obj interface {
|
||||
runtime.Object
|
||||
|
||||
GVR() schema.GroupVersionResource
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue