mirror of https://github.com/k3s-io/k3s
311 lines
9.1 KiB
Go
311 lines
9.1 KiB
Go
package objectclient
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rancher/norman/restwatch"
|
|
"github.com/sirupsen/logrus"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
json2 "k8s.io/apimachinery/pkg/runtime/serializer/json"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/watch"
|
|
"k8s.io/client-go/rest"
|
|
restclientwatch "k8s.io/client-go/rest/watch"
|
|
)
|
|
|
|
type ObjectFactory interface {
|
|
Object() runtime.Object
|
|
List() runtime.Object
|
|
}
|
|
|
|
type UnstructuredObjectFactory struct {
|
|
}
|
|
|
|
func (u *UnstructuredObjectFactory) Object() runtime.Object {
|
|
return &unstructured.Unstructured{}
|
|
}
|
|
|
|
func (u *UnstructuredObjectFactory) List() runtime.Object {
|
|
return &unstructured.UnstructuredList{}
|
|
}
|
|
|
|
type GenericClient interface {
|
|
UnstructuredClient() GenericClient
|
|
GroupVersionKind() schema.GroupVersionKind
|
|
Create(o runtime.Object) (runtime.Object, error)
|
|
GetNamespaced(namespace, name string, opts metav1.GetOptions) (runtime.Object, error)
|
|
Get(name string, opts metav1.GetOptions) (runtime.Object, error)
|
|
Update(name string, o runtime.Object) (runtime.Object, error)
|
|
DeleteNamespaced(namespace, name string, opts *metav1.DeleteOptions) error
|
|
Delete(name string, opts *metav1.DeleteOptions) error
|
|
List(opts metav1.ListOptions) (runtime.Object, error)
|
|
Watch(opts metav1.ListOptions) (watch.Interface, error)
|
|
DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error
|
|
Patch(name string, o runtime.Object, patchType types.PatchType, data []byte, subresources ...string) (runtime.Object, error)
|
|
ObjectFactory() ObjectFactory
|
|
}
|
|
|
|
type ObjectClient struct {
|
|
restClient rest.Interface
|
|
resource *metav1.APIResource
|
|
gvk schema.GroupVersionKind
|
|
ns string
|
|
Factory ObjectFactory
|
|
}
|
|
|
|
func NewObjectClient(namespace string, restClient rest.Interface, apiResource *metav1.APIResource, gvk schema.GroupVersionKind, factory ObjectFactory) *ObjectClient {
|
|
return &ObjectClient{
|
|
restClient: restClient,
|
|
resource: apiResource,
|
|
gvk: gvk,
|
|
ns: namespace,
|
|
Factory: factory,
|
|
}
|
|
}
|
|
|
|
func (p *ObjectClient) UnstructuredClient() GenericClient {
|
|
return &ObjectClient{
|
|
restClient: p.restClient,
|
|
resource: p.resource,
|
|
gvk: p.gvk,
|
|
ns: p.ns,
|
|
Factory: &UnstructuredObjectFactory{},
|
|
}
|
|
}
|
|
|
|
func (p *ObjectClient) GroupVersionKind() schema.GroupVersionKind {
|
|
return p.gvk
|
|
}
|
|
|
|
func (p *ObjectClient) getAPIPrefix() string {
|
|
if p.gvk.Group == "" {
|
|
return "api"
|
|
}
|
|
return "apis"
|
|
}
|
|
|
|
func (p *ObjectClient) Create(o runtime.Object) (runtime.Object, error) {
|
|
ns := p.ns
|
|
obj, ok := o.(metav1.Object)
|
|
if ok && obj.GetNamespace() != "" {
|
|
ns = obj.GetNamespace()
|
|
}
|
|
|
|
if ok {
|
|
labels := obj.GetLabels()
|
|
if labels == nil {
|
|
labels = make(map[string]string)
|
|
}
|
|
labels["cattle.io/creator"] = "norman"
|
|
obj.SetLabels(labels)
|
|
}
|
|
|
|
if t, err := meta.TypeAccessor(o); err == nil {
|
|
if t.GetKind() == "" {
|
|
t.SetKind(p.gvk.Kind)
|
|
}
|
|
if t.GetAPIVersion() == "" {
|
|
apiVersion, _ := p.gvk.ToAPIVersionAndKind()
|
|
t.SetAPIVersion(apiVersion)
|
|
}
|
|
}
|
|
result := p.Factory.Object()
|
|
logrus.Debugf("REST CREATE %s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, ns, p.resource.Name)
|
|
err := p.restClient.Post().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
NamespaceIfScoped(ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
Body(o).
|
|
Do().
|
|
Into(result)
|
|
return result, err
|
|
}
|
|
|
|
func (p *ObjectClient) GetNamespaced(namespace, name string, opts metav1.GetOptions) (runtime.Object, error) {
|
|
result := p.Factory.Object()
|
|
req := p.restClient.Get().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version)
|
|
if namespace != "" {
|
|
req = req.Namespace(namespace)
|
|
}
|
|
err := req.
|
|
Resource(p.resource.Name).
|
|
VersionedParams(&opts, metav1.ParameterCodec).
|
|
Name(name).
|
|
Do().
|
|
Into(result)
|
|
logrus.Debugf("REST GET %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, namespace, p.resource.Name, name)
|
|
return result, err
|
|
|
|
}
|
|
|
|
func (p *ObjectClient) Get(name string, opts metav1.GetOptions) (runtime.Object, error) {
|
|
result := p.Factory.Object()
|
|
err := p.restClient.Get().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
NamespaceIfScoped(p.ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
VersionedParams(&opts, metav1.ParameterCodec).
|
|
Name(name).
|
|
Do().
|
|
Into(result)
|
|
logrus.Debugf("REST GET %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, p.ns, p.resource.Name, name)
|
|
return result, err
|
|
}
|
|
|
|
func (p *ObjectClient) Update(name string, o runtime.Object) (runtime.Object, error) {
|
|
ns := p.ns
|
|
if obj, ok := o.(metav1.Object); ok && obj.GetNamespace() != "" {
|
|
ns = obj.GetNamespace()
|
|
}
|
|
result := p.Factory.Object()
|
|
if len(name) == 0 {
|
|
return result, errors.New("object missing name")
|
|
}
|
|
logrus.Debugf("REST UPDATE %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, ns, p.resource.Name, name)
|
|
err := p.restClient.Put().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
NamespaceIfScoped(ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
Name(name).
|
|
Body(o).
|
|
Do().
|
|
Into(result)
|
|
return result, err
|
|
}
|
|
|
|
func (p *ObjectClient) DeleteNamespaced(namespace, name string, opts *metav1.DeleteOptions) error {
|
|
req := p.restClient.Delete().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version)
|
|
if namespace != "" {
|
|
req = req.Namespace(namespace)
|
|
}
|
|
logrus.Debugf("REST DELETE %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, namespace, p.resource.Name, name)
|
|
return req.Resource(p.resource.Name).
|
|
Name(name).
|
|
Body(opts).
|
|
Do().
|
|
Error()
|
|
}
|
|
|
|
func (p *ObjectClient) Delete(name string, opts *metav1.DeleteOptions) error {
|
|
logrus.Debugf("REST DELETE %s/%s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, p.ns, p.resource.Name, name)
|
|
return p.restClient.Delete().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
NamespaceIfScoped(p.ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
Name(name).
|
|
Body(opts).
|
|
Do().
|
|
Error()
|
|
}
|
|
|
|
func (p *ObjectClient) List(opts metav1.ListOptions) (runtime.Object, error) {
|
|
result := p.Factory.List()
|
|
logrus.Debugf("REST LIST %s/%s/%s/%s/%s", p.getAPIPrefix(), p.gvk.Group, p.gvk.Version, p.ns, p.resource.Name)
|
|
return result, p.restClient.Get().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
NamespaceIfScoped(p.ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
VersionedParams(&opts, metav1.ParameterCodec).
|
|
Do().
|
|
Into(result)
|
|
}
|
|
|
|
func (p *ObjectClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
|
|
restClient := p.restClient
|
|
if watchClient, ok := restClient.(restwatch.WatchClient); ok {
|
|
restClient = watchClient.WatchClient()
|
|
}
|
|
|
|
r, err := restClient.Get().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
Prefix("watch").
|
|
NamespaceIfScoped(p.ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
VersionedParams(&opts, metav1.ParameterCodec).
|
|
Stream()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
embeddedDecoder := &structuredDecoder{
|
|
factory: p.Factory,
|
|
}
|
|
streamDecoder := streaming.NewDecoder(json2.Framer.NewFrameReader(r), embeddedDecoder)
|
|
decoder := restclientwatch.NewDecoder(streamDecoder, embeddedDecoder)
|
|
return watch.NewStreamWatcher(decoder), nil
|
|
}
|
|
|
|
type structuredDecoder struct {
|
|
factory ObjectFactory
|
|
}
|
|
|
|
func (d *structuredDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
|
|
if into == nil {
|
|
into = d.factory.Object()
|
|
}
|
|
|
|
err := json.Unmarshal(data, &into)
|
|
if err != nil {
|
|
status := &metav1.Status{}
|
|
if err := json.Unmarshal(data, status); err == nil && strings.ToLower(status.Kind) == "status" {
|
|
return status, defaults, nil
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
|
|
if _, ok := into.(*metav1.Status); !ok && strings.ToLower(into.GetObjectKind().GroupVersionKind().Kind) == "status" {
|
|
into = &metav1.Status{}
|
|
err := json.Unmarshal(data, into)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
return into, defaults, err
|
|
}
|
|
|
|
func (p *ObjectClient) DeleteCollection(deleteOptions *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
|
|
return p.restClient.Delete().
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
NamespaceIfScoped(p.ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
VersionedParams(&listOptions, metav1.ParameterCodec).
|
|
Body(deleteOptions).
|
|
Do().
|
|
Error()
|
|
}
|
|
|
|
func (p *ObjectClient) Patch(name string, o runtime.Object, patchType types.PatchType, data []byte, subresources ...string) (runtime.Object, error) {
|
|
ns := p.ns
|
|
if obj, ok := o.(metav1.Object); ok && obj.GetNamespace() != "" {
|
|
ns = obj.GetNamespace()
|
|
}
|
|
result := p.Factory.Object()
|
|
if len(name) == 0 {
|
|
return result, errors.New("object missing name")
|
|
}
|
|
err := p.restClient.Patch(patchType).
|
|
Prefix(p.getAPIPrefix(), p.gvk.Group, p.gvk.Version).
|
|
NamespaceIfScoped(ns, p.resource.Namespaced).
|
|
Resource(p.resource.Name).
|
|
SubResource(subresources...).
|
|
Name(name).
|
|
Body(data).
|
|
Do().
|
|
Into(result)
|
|
return result, err
|
|
}
|
|
|
|
func (p *ObjectClient) ObjectFactory() ObjectFactory {
|
|
return p.Factory
|
|
}
|