mirror of https://github.com/k3s-io/k3s
320 lines
9.5 KiB
Go
320 lines
9.5 KiB
Go
/*
|
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"k8s.io/kubernetes/pkg/api/errors"
|
|
"k8s.io/kubernetes/pkg/api/meta"
|
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
|
"k8s.io/kubernetes/pkg/client/restclient"
|
|
"k8s.io/kubernetes/pkg/runtime"
|
|
"k8s.io/kubernetes/pkg/util/yaml"
|
|
"k8s.io/kubernetes/pkg/watch"
|
|
)
|
|
|
|
// ObjectRetriever abstracts the implementation for retrieving or setting generic
|
|
// objects. It is intended to be used to fake calls to a server by returning
|
|
// objects based on their kind and name.
|
|
type ObjectRetriever interface {
|
|
// Kind should return a resource or a list of resources (depending on the provided kind and
|
|
// name). It should return an error if the caller should communicate an error to the server.
|
|
Kind(gvk unversioned.GroupVersionKind, name string) (runtime.Object, error)
|
|
// Add adds a runtime object for test purposes into this object.
|
|
Add(runtime.Object) error
|
|
}
|
|
|
|
// ObjectScheme abstracts the implementation of common operations on objects.
|
|
type ObjectScheme interface {
|
|
runtime.ObjectCreater
|
|
runtime.ObjectCopier
|
|
runtime.ObjectTyper
|
|
}
|
|
|
|
// ObjectReaction returns a ReactionFunc that takes a generic action string of the form
|
|
// <verb>-<resource> or <verb>-<subresource>-<resource> and attempts to return a runtime
|
|
// Object or error that matches the requested action. For instance, list-replicationControllers
|
|
// should attempt to return a list of replication controllers. This method delegates to the
|
|
// ObjectRetriever interface to satisfy retrieval of lists or retrieval of single items.
|
|
// TODO: add support for sub resources
|
|
func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc {
|
|
return func(action Action) (bool, runtime.Object, error) {
|
|
resource := action.GetResource()
|
|
kind, err := mapper.KindFor(resource)
|
|
// This is a temporary fix. Because there is no internal resource, so
|
|
// the caller has no way to express that it expects to get an internal
|
|
// kind back. A more proper fix will be directly specify the Kind when
|
|
// build the action.
|
|
kind.Version = resource.Version
|
|
if err != nil {
|
|
return false, nil, fmt.Errorf("unrecognized action %s: %v", action.GetResource(), err)
|
|
}
|
|
|
|
// TODO: have mapper return a Kind for a subresource?
|
|
switch castAction := action.(type) {
|
|
case ListAction:
|
|
kind.Kind += "List"
|
|
resource, err := o.Kind(kind, "")
|
|
return true, resource, err
|
|
|
|
case GetAction:
|
|
resource, err := o.Kind(kind, castAction.GetName())
|
|
return true, resource, err
|
|
|
|
case DeleteAction:
|
|
resource, err := o.Kind(kind, castAction.GetName())
|
|
return true, resource, err
|
|
|
|
case CreateAction:
|
|
accessor, err := meta.Accessor(castAction.GetObject())
|
|
if err != nil {
|
|
return true, nil, err
|
|
}
|
|
resource, err := o.Kind(kind, accessor.GetName())
|
|
return true, resource, err
|
|
|
|
case UpdateAction:
|
|
accessor, err := meta.Accessor(castAction.GetObject())
|
|
if err != nil {
|
|
return true, nil, err
|
|
}
|
|
resource, err := o.Kind(kind, accessor.GetName())
|
|
return true, resource, err
|
|
|
|
default:
|
|
return false, nil, fmt.Errorf("no reaction implemented for %s", action)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddObjectsFromPath loads the JSON or YAML file containing Kubernetes API resources
|
|
// and adds them to the provided ObjectRetriever.
|
|
func AddObjectsFromPath(path string, o ObjectRetriever, decoder runtime.Decoder) error {
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data, err = yaml.ToJSON(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
obj, err := runtime.Decode(decoder, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := o.Add(obj); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type objects struct {
|
|
types map[string][]runtime.Object
|
|
last map[string]int
|
|
scheme ObjectScheme
|
|
decoder runtime.Decoder
|
|
}
|
|
|
|
var _ ObjectRetriever = &objects{}
|
|
|
|
// NewObjects implements the ObjectRetriever interface by introspecting the
|
|
// objects provided to Add() and returning them when the Kind method is invoked.
|
|
// If an api.List object is provided to Add(), each child item is added. If an
|
|
// object is added that is itself a list (PodList, ServiceList) then that is added
|
|
// to the "PodList" kind. If no PodList is added, the retriever will take any loaded
|
|
// Pods and return them in a list. If an api.Status is added, and the Details.Kind field
|
|
// is set, that status will be returned instead (as an error if Status != Success, or
|
|
// as a runtime.Object if Status == Success). If multiple PodLists are provided, they
|
|
// will be returned in order by the Kind call, and the last PodList will be reused for
|
|
// subsequent calls.
|
|
func NewObjects(scheme ObjectScheme, decoder runtime.Decoder) ObjectRetriever {
|
|
return objects{
|
|
types: make(map[string][]runtime.Object),
|
|
last: make(map[string]int),
|
|
scheme: scheme,
|
|
decoder: decoder,
|
|
}
|
|
}
|
|
|
|
func (o objects) Kind(kind unversioned.GroupVersionKind, name string) (runtime.Object, error) {
|
|
if len(kind.Version) == 0 {
|
|
kind.Version = runtime.APIVersionInternal
|
|
}
|
|
empty, err := o.scheme.New(kind)
|
|
nilValue := reflect.Zero(reflect.TypeOf(empty)).Interface().(runtime.Object)
|
|
|
|
arr, ok := o.types[kind.Kind]
|
|
if !ok {
|
|
if strings.HasSuffix(kind.Kind, "List") {
|
|
itemKind := kind.Kind[:len(kind.Kind)-4]
|
|
arr, ok := o.types[itemKind]
|
|
if !ok {
|
|
return empty, nil
|
|
}
|
|
out, err := o.scheme.New(kind)
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
if err := meta.SetList(out, arr); err != nil {
|
|
return nilValue, err
|
|
}
|
|
if out, err = o.scheme.Copy(out); err != nil {
|
|
return nilValue, err
|
|
}
|
|
return out, nil
|
|
}
|
|
return nilValue, errors.NewNotFound(unversioned.GroupResource{Group: kind.Group, Resource: kind.Kind}, name)
|
|
}
|
|
|
|
index := o.last[kind.Kind]
|
|
if index >= len(arr) {
|
|
index = len(arr) - 1
|
|
}
|
|
if index < 0 {
|
|
return nilValue, errors.NewNotFound(unversioned.GroupResource{Group: kind.Group, Resource: kind.Kind}, name)
|
|
}
|
|
out, err := o.scheme.Copy(arr[index])
|
|
if err != nil {
|
|
return nilValue, err
|
|
}
|
|
o.last[kind.Kind] = index + 1
|
|
|
|
if status, ok := out.(*unversioned.Status); ok {
|
|
if status.Details != nil {
|
|
status.Details.Kind = kind.Kind
|
|
}
|
|
if status.Status != unversioned.StatusSuccess {
|
|
return nilValue, &errors.StatusError{ErrStatus: *status}
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (o objects) Add(obj runtime.Object) error {
|
|
gvk, err := o.scheme.ObjectKind(obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kind := gvk.Kind
|
|
|
|
switch {
|
|
case meta.IsListType(obj):
|
|
if kind != "List" {
|
|
o.types[kind] = append(o.types[kind], obj)
|
|
}
|
|
|
|
list, err := meta.ExtractList(obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if errs := runtime.DecodeList(list, o.decoder); len(errs) > 0 {
|
|
return errs[0]
|
|
}
|
|
for _, obj := range list {
|
|
if err := o.Add(obj); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
if status, ok := obj.(*unversioned.Status); ok && status.Details != nil {
|
|
kind = status.Details.Kind
|
|
}
|
|
o.types[kind] = append(o.types[kind], obj)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func DefaultWatchReactor(watchInterface watch.Interface, err error) WatchReactionFunc {
|
|
return func(action Action) (bool, watch.Interface, error) {
|
|
return true, watchInterface, err
|
|
}
|
|
}
|
|
|
|
// SimpleReactor is a Reactor. Each reaction function is attached to a given verb,resource tuple. "*" in either field matches everything for that value.
|
|
// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions
|
|
type SimpleReactor struct {
|
|
Verb string
|
|
Resource string
|
|
|
|
Reaction ReactionFunc
|
|
}
|
|
|
|
func (r *SimpleReactor) Handles(action Action) bool {
|
|
verbCovers := r.Verb == "*" || r.Verb == action.GetVerb()
|
|
if !verbCovers {
|
|
return false
|
|
}
|
|
resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
|
|
if !resourceCovers {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (r *SimpleReactor) React(action Action) (bool, runtime.Object, error) {
|
|
return r.Reaction(action)
|
|
}
|
|
|
|
// SimpleWatchReactor is a WatchReactor. Each reaction function is attached to a given resource. "*" matches everything for that value.
|
|
// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions
|
|
type SimpleWatchReactor struct {
|
|
Resource string
|
|
|
|
Reaction WatchReactionFunc
|
|
}
|
|
|
|
func (r *SimpleWatchReactor) Handles(action Action) bool {
|
|
resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
|
|
if !resourceCovers {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (r *SimpleWatchReactor) React(action Action) (bool, watch.Interface, error) {
|
|
return r.Reaction(action)
|
|
}
|
|
|
|
// SimpleProxyReactor is a ProxyReactor. Each reaction function is attached to a given resource. "*" matches everything for that value.
|
|
// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions.
|
|
type SimpleProxyReactor struct {
|
|
Resource string
|
|
|
|
Reaction ProxyReactionFunc
|
|
}
|
|
|
|
func (r *SimpleProxyReactor) Handles(action Action) bool {
|
|
resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
|
|
if !resourceCovers {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (r *SimpleProxyReactor) React(action Action) (bool, restclient.ResponseWrapper, error) {
|
|
return r.Reaction(action)
|
|
}
|