2016-10-18 22:53:26 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
|
|
|
|
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 testing
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"github.com/emicklei/go-restful/swagger"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
|
2017-01-11 14:09:48 +00:00
|
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
2017-01-25 19:00:30 +00:00
|
|
|
"k8s.io/client-go/discovery"
|
2017-01-19 18:27:59 +00:00
|
|
|
restclient "k8s.io/client-go/rest"
|
2017-01-24 15:00:24 +00:00
|
|
|
"k8s.io/client-go/rest/fake"
|
2016-12-15 18:10:33 +00:00
|
|
|
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset"
|
2016-10-18 22:53:26 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
"k8s.io/kubernetes/pkg/api/testapi"
|
|
|
|
"k8s.io/kubernetes/pkg/api/validation"
|
|
|
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
|
|
|
"k8s.io/kubernetes/pkg/kubectl"
|
|
|
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
|
|
|
"k8s.io/kubernetes/pkg/kubectl/resource"
|
2017-02-19 22:39:43 +00:00
|
|
|
"k8s.io/kubernetes/pkg/printers"
|
2016-10-18 22:53:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type InternalType struct {
|
|
|
|
Kind string
|
|
|
|
APIVersion string
|
|
|
|
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
type ExternalType struct {
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
APIVersion string `json:"apiVersion"`
|
|
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ExternalType2 struct {
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
APIVersion string `json:"apiVersion"`
|
|
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
}
|
|
|
|
|
2016-11-21 02:55:31 +00:00
|
|
|
func (obj *InternalType) GetObjectKind() schema.ObjectKind { return obj }
|
|
|
|
func (obj *InternalType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
2016-10-18 22:53:26 +00:00
|
|
|
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
|
|
|
}
|
2016-11-21 02:55:31 +00:00
|
|
|
func (obj *InternalType) GroupVersionKind() schema.GroupVersionKind {
|
|
|
|
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
2016-11-21 02:55:31 +00:00
|
|
|
func (obj *ExternalType) GetObjectKind() schema.ObjectKind { return obj }
|
|
|
|
func (obj *ExternalType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
2016-10-18 22:53:26 +00:00
|
|
|
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
|
|
|
}
|
2016-11-21 02:55:31 +00:00
|
|
|
func (obj *ExternalType) GroupVersionKind() schema.GroupVersionKind {
|
|
|
|
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
2016-11-21 02:55:31 +00:00
|
|
|
func (obj *ExternalType2) GetObjectKind() schema.ObjectKind { return obj }
|
|
|
|
func (obj *ExternalType2) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
2016-10-18 22:53:26 +00:00
|
|
|
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
|
|
|
}
|
2016-11-21 02:55:31 +00:00
|
|
|
func (obj *ExternalType2) GroupVersionKind() schema.GroupVersionKind {
|
|
|
|
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewInternalType(kind, apiversion, name string) *InternalType {
|
|
|
|
item := InternalType{Kind: kind,
|
|
|
|
APIVersion: apiversion,
|
|
|
|
Name: name}
|
|
|
|
return &item
|
|
|
|
}
|
|
|
|
|
2017-01-22 22:11:33 +00:00
|
|
|
type InternalNamespacedType struct {
|
|
|
|
Kind string
|
|
|
|
APIVersion string
|
|
|
|
|
|
|
|
Name string
|
|
|
|
Namespace string
|
|
|
|
}
|
|
|
|
|
|
|
|
type ExternalNamespacedType struct {
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
APIVersion string `json:"apiVersion"`
|
|
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
Namespace string `json:"namespace"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ExternalNamespacedType2 struct {
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
APIVersion string `json:"apiVersion"`
|
|
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
Namespace string `json:"namespace"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (obj *InternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
|
|
|
|
func (obj *InternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
|
|
|
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
|
|
|
}
|
|
|
|
func (obj *InternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
|
|
|
|
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
|
|
|
}
|
|
|
|
func (obj *ExternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
|
|
|
|
func (obj *ExternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
|
|
|
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
|
|
|
}
|
|
|
|
func (obj *ExternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
|
|
|
|
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
|
|
|
}
|
|
|
|
func (obj *ExternalNamespacedType2) GetObjectKind() schema.ObjectKind { return obj }
|
|
|
|
func (obj *ExternalNamespacedType2) SetGroupVersionKind(gvk schema.GroupVersionKind) {
|
|
|
|
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
|
|
|
|
}
|
|
|
|
func (obj *ExternalNamespacedType2) GroupVersionKind() schema.GroupVersionKind {
|
|
|
|
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewInternalNamespacedType(kind, apiversion, name, namespace string) *InternalNamespacedType {
|
|
|
|
item := InternalNamespacedType{Kind: kind,
|
|
|
|
APIVersion: apiversion,
|
|
|
|
Name: name,
|
|
|
|
Namespace: namespace}
|
|
|
|
return &item
|
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
var versionErr = errors.New("not a version")
|
|
|
|
|
|
|
|
func versionErrIfFalse(b bool) error {
|
|
|
|
if b {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return versionErr
|
|
|
|
}
|
|
|
|
|
2017-01-12 18:17:43 +00:00
|
|
|
var ValidVersion = api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version
|
2016-11-21 02:55:31 +00:00
|
|
|
var InternalGV = schema.GroupVersion{Group: "apitest", Version: runtime.APIVersionInternal}
|
|
|
|
var UnlikelyGV = schema.GroupVersion{Group: "apitest", Version: "unlikelyversion"}
|
|
|
|
var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion}
|
2016-10-18 22:53:26 +00:00
|
|
|
|
|
|
|
func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
|
|
|
|
scheme := runtime.NewScheme()
|
2017-01-22 22:11:33 +00:00
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
scheme.AddKnownTypeWithName(InternalGV.WithKind("Type"), &InternalType{})
|
|
|
|
scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("Type"), &ExternalType{})
|
|
|
|
//This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name.
|
|
|
|
scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("Type"), &ExternalType2{})
|
|
|
|
|
2017-01-22 22:11:33 +00:00
|
|
|
scheme.AddKnownTypeWithName(InternalGV.WithKind("NamespacedType"), &InternalNamespacedType{})
|
|
|
|
scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("NamespacedType"), &ExternalNamespacedType{})
|
|
|
|
//This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name.
|
|
|
|
scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("NamespacedType"), &ExternalNamespacedType2{})
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
codecs := serializer.NewCodecFactory(scheme)
|
|
|
|
codec := codecs.LegacyCodec(UnlikelyGV)
|
2016-11-21 02:55:31 +00:00
|
|
|
mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV}, func(version schema.GroupVersion) (*meta.VersionInterfaces, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return &meta.VersionInterfaces{
|
|
|
|
ObjectConvertor: scheme,
|
|
|
|
MetadataAccessor: meta.NewAccessor(),
|
|
|
|
}, versionErrIfFalse(version == ValidVersionGV || version == UnlikelyGV)
|
|
|
|
})
|
2016-11-21 02:55:31 +00:00
|
|
|
for _, gv := range []schema.GroupVersion{UnlikelyGV, ValidVersionGV} {
|
2016-10-18 22:53:26 +00:00
|
|
|
for kind := range scheme.KnownTypes(gv) {
|
|
|
|
gvk := gv.WithKind(kind)
|
|
|
|
|
|
|
|
scope := meta.RESTScopeNamespace
|
|
|
|
mapper.Add(gvk, scope)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return scheme, mapper, codec
|
|
|
|
}
|
|
|
|
|
2016-10-24 10:58:47 +00:00
|
|
|
type fakeCachedDiscoveryClient struct {
|
|
|
|
discovery.DiscoveryInterface
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *fakeCachedDiscoveryClient) Fresh() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *fakeCachedDiscoveryClient) Invalidate() {
|
|
|
|
}
|
|
|
|
|
2017-02-13 17:36:12 +00:00
|
|
|
func (d *fakeCachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
|
|
|
|
return []*metav1.APIResourceList{}, nil
|
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
type TestFactory struct {
|
2017-01-22 18:45:30 +00:00
|
|
|
Mapper meta.RESTMapper
|
|
|
|
Typer runtime.ObjectTyper
|
|
|
|
Client kubectl.RESTClient
|
|
|
|
UnstructuredClient kubectl.RESTClient
|
2017-02-19 22:39:43 +00:00
|
|
|
Describer printers.Describer
|
|
|
|
Printer printers.ResourcePrinter
|
|
|
|
CommandPrinter printers.ResourcePrinter
|
2017-01-22 18:45:30 +00:00
|
|
|
Validator validation.Schema
|
|
|
|
Namespace string
|
|
|
|
ClientConfig *restclient.Config
|
|
|
|
Err error
|
2017-02-12 20:24:03 +00:00
|
|
|
Command string
|
2017-02-19 22:39:43 +00:00
|
|
|
GenericPrinter bool
|
2017-02-03 18:51:24 +00:00
|
|
|
|
|
|
|
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
|
|
|
UnstructuredClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type FakeFactory struct {
|
|
|
|
tf *TestFactory
|
|
|
|
Codec runtime.Codec
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTestFactory() (cmdutil.Factory, *TestFactory, runtime.Codec, runtime.NegotiatedSerializer) {
|
|
|
|
scheme, mapper, codec := newExternalScheme()
|
|
|
|
t := &TestFactory{
|
|
|
|
Validator: validation.NullSchema{},
|
|
|
|
Mapper: mapper,
|
|
|
|
Typer: scheme,
|
|
|
|
}
|
2016-10-12 20:55:28 +00:00
|
|
|
negotiatedSerializer := serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
|
2016-10-18 22:53:26 +00:00
|
|
|
return &FakeFactory{
|
|
|
|
tf: t,
|
|
|
|
Codec: codec,
|
|
|
|
}, t, codec, negotiatedSerializer
|
|
|
|
}
|
|
|
|
|
2016-10-24 10:58:47 +00:00
|
|
|
func (f *FakeFactory) DiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
|
|
|
discoveryClient, err := discovery.NewDiscoveryClientForConfig(f.tf.ClientConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &fakeCachedDiscoveryClient{DiscoveryInterface: discoveryClient}, nil
|
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
func (f *FakeFactory) FlagSet() *pflag.FlagSet {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
|
|
|
|
priorityRESTMapper := meta.PriorityRESTMapper{
|
|
|
|
Delegate: f.tf.Mapper,
|
2016-11-21 02:55:31 +00:00
|
|
|
ResourcePriority: []schema.GroupVersionResource{
|
2016-10-18 22:53:26 +00:00
|
|
|
{Group: meta.AnyGroup, Version: "v1", Resource: meta.AnyResource},
|
|
|
|
},
|
2016-11-21 02:55:31 +00:00
|
|
|
KindPriority: []schema.GroupVersionKind{
|
2016-10-18 22:53:26 +00:00
|
|
|
{Group: meta.AnyGroup, Version: "v1", Kind: meta.AnyKind},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return priorityRESTMapper, f.tf.Typer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) {
|
2016-11-02 16:29:01 +00:00
|
|
|
groupResources := testDynamicResources()
|
|
|
|
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured)
|
|
|
|
typer := discovery.NewUnstructuredObjectTyper(groupResources)
|
|
|
|
|
2017-02-13 17:36:12 +00:00
|
|
|
fakeDs := &fakeCachedDiscoveryClient{}
|
|
|
|
expander, err := cmdutil.NewShortcutExpander(mapper, fakeDs)
|
|
|
|
return expander, typer, err
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) Decoder(bool) runtime.Decoder {
|
|
|
|
return f.Codec
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) JSONEncoder() runtime.Encoder {
|
|
|
|
return f.Codec
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) RESTClient() (*restclient.RESTClient, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) ClientSet() (*internalclientset.Clientset, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) ClientConfig() (*restclient.Config, error) {
|
|
|
|
return f.tf.ClientConfig, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2016-12-16 21:08:46 +00:00
|
|
|
func (f *FakeFactory) BareClientConfig() (*restclient.Config, error) {
|
|
|
|
return f.tf.ClientConfig, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2017-02-03 18:51:24 +00:00
|
|
|
func (f *FakeFactory) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
|
|
|
|
if f.tf.ClientForMappingFunc != nil {
|
|
|
|
return f.tf.ClientForMappingFunc(mapping)
|
|
|
|
}
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Client, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2016-12-15 18:10:33 +00:00
|
|
|
func (f *FakeFactory) FederationClientSetForVersion(version *schema.GroupVersion) (fedclientset.Interface, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
func (f *FakeFactory) FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
func (f *FakeFactory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (*internalclientset.Clientset, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersion) (*restclient.Config, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2017-02-03 18:51:24 +00:00
|
|
|
func (f *FakeFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
|
|
|
|
if f.tf.UnstructuredClientForMappingFunc != nil {
|
|
|
|
return f.tf.UnstructuredClientForMappingFunc(mapping)
|
|
|
|
}
|
2017-01-22 18:45:30 +00:00
|
|
|
return f.tf.UnstructuredClient, f.tf.Err
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
2017-02-19 22:39:43 +00:00
|
|
|
func (f *FakeFactory) Describer(*meta.RESTMapping) (printers.Describer, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Describer, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2017-02-19 22:39:43 +00:00
|
|
|
func (f *FakeFactory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) {
|
|
|
|
return f.tf.CommandPrinter, f.tf.GenericPrinter, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Printer, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) Scaler(*meta.RESTMapping) (kubectl.Scaler, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) Reaper(*meta.RESTMapping) (kubectl.Reaper, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) HistoryViewer(*meta.RESTMapping) (kubectl.HistoryViewer, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) Rollbacker(*meta.RESTMapping) (kubectl.Rollbacker, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) StatusViewer(*meta.RESTMapping) (kubectl.StatusViewer, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) MapBasedSelectorForObject(runtime.Object) (string, error) {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) PortsForObject(runtime.Object) ([]string, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) ProtocolsForObject(runtime.Object) (map[string]string, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) LabelsForObject(runtime.Object) (map[string]string, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) LogsForObject(object, options runtime.Object) (*restclient.Request, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2016-11-14 11:53:27 +00:00
|
|
|
func (f *FakeFactory) Pauser(info *resource.Info) ([]byte, error) {
|
|
|
|
return nil, nil
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
2016-11-14 11:53:27 +00:00
|
|
|
func (f *FakeFactory) Resumer(info *resource.Info) ([]byte, error) {
|
|
|
|
return nil, nil
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
2016-09-20 12:19:01 +00:00
|
|
|
func (f *FakeFactory) ResolveImage(name string) (string, error) {
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
func (f *FakeFactory) Validator(validate bool, cacheDir string) (validation.Schema, error) {
|
|
|
|
return f.tf.Validator, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2016-11-21 02:55:31 +00:00
|
|
|
func (f *FakeFactory) SwaggerSchema(schema.GroupVersionKind) (*swagger.ApiDeclaration, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) DefaultNamespace() (string, bool, error) {
|
|
|
|
return f.tf.Namespace, false, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2016-10-27 22:05:26 +00:00
|
|
|
func (f *FakeFactory) Generators(cmdName string) map[string]kubectl.Generator {
|
|
|
|
var generator map[string]kubectl.Generator
|
|
|
|
switch cmdName {
|
|
|
|
case "run":
|
|
|
|
generator = map[string]kubectl.Generator{
|
|
|
|
cmdutil.DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return generator
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
2016-11-21 02:55:31 +00:00
|
|
|
func (f *FakeFactory) CanBeExposed(schema.GroupKind) error {
|
2016-10-18 22:53:26 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-21 02:55:31 +00:00
|
|
|
func (f *FakeFactory) CanBeAutoscaled(schema.GroupKind) error {
|
2016-10-18 22:53:26 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) AttachablePodForObject(ob runtime.Object) (*api.Pod, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) UpdatePodSpecForObject(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) EditorEnvs() []string {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) PrintObjectSpecificMessage(obj runtime.Object, out io.Writer) {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) Command() string {
|
2017-02-12 20:24:03 +00:00
|
|
|
return f.tf.Command
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) BindFlags(flags *pflag.FlagSet) {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) BindExternalFlags(flags *pflag.FlagSet) {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-02-19 22:39:43 +00:00
|
|
|
func (f *FakeFactory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Printer, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) NewBuilder() *resource.Builder {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *kubectl.PrintOptions {
|
|
|
|
return &kubectl.PrintOptions{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FakeFactory) DefaultResourceFilterFunc() kubectl.Filters {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-21 02:55:31 +00:00
|
|
|
func (f *FakeFactory) SuggestedPodTemplateResources() []schema.GroupResource {
|
|
|
|
return []schema.GroupResource{}
|
2016-10-24 13:17:55 +00:00
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
type fakeMixedFactory struct {
|
|
|
|
cmdutil.Factory
|
|
|
|
tf *TestFactory
|
|
|
|
apiClient resource.RESTClient
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeMixedFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
|
|
|
|
var multiRESTMapper meta.MultiRESTMapper
|
|
|
|
multiRESTMapper = append(multiRESTMapper, f.tf.Mapper)
|
|
|
|
multiRESTMapper = append(multiRESTMapper, testapi.Default.RESTMapper())
|
|
|
|
priorityRESTMapper := meta.PriorityRESTMapper{
|
|
|
|
Delegate: multiRESTMapper,
|
2016-11-21 02:55:31 +00:00
|
|
|
ResourcePriority: []schema.GroupVersionResource{
|
2016-10-18 22:53:26 +00:00
|
|
|
{Group: meta.AnyGroup, Version: "v1", Resource: meta.AnyResource},
|
|
|
|
},
|
2016-11-21 02:55:31 +00:00
|
|
|
KindPriority: []schema.GroupVersionKind{
|
2016-10-18 22:53:26 +00:00
|
|
|
{Group: meta.AnyGroup, Version: "v1", Kind: meta.AnyKind},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return priorityRESTMapper, runtime.MultiObjectTyper{f.tf.Typer, api.Scheme}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) {
|
|
|
|
if m.ObjectConvertor == api.Scheme {
|
|
|
|
return f.apiClient, f.tf.Err
|
|
|
|
}
|
2017-02-03 18:51:24 +00:00
|
|
|
if f.tf.ClientForMappingFunc != nil {
|
|
|
|
return f.tf.ClientForMappingFunc(m)
|
|
|
|
}
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Client, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewMixedFactory(apiClient resource.RESTClient) (cmdutil.Factory, *TestFactory, runtime.Codec) {
|
2016-11-02 16:29:01 +00:00
|
|
|
f, t, c, _ := NewAPIFactory()
|
2016-10-18 22:53:26 +00:00
|
|
|
return &fakeMixedFactory{
|
|
|
|
Factory: f,
|
|
|
|
tf: t,
|
|
|
|
apiClient: apiClient,
|
|
|
|
}, t, c
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeAPIFactory struct {
|
|
|
|
cmdutil.Factory
|
|
|
|
tf *TestFactory
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) {
|
|
|
|
return testapi.Default.RESTMapper(), api.Scheme
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) {
|
|
|
|
groupResources := testDynamicResources()
|
|
|
|
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured)
|
|
|
|
typer := discovery.NewUnstructuredObjectTyper(groupResources)
|
2017-02-13 17:36:12 +00:00
|
|
|
fakeDs := &fakeCachedDiscoveryClient{}
|
|
|
|
expander, err := cmdutil.NewShortcutExpander(mapper, fakeDs)
|
|
|
|
return expander, typer, err
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 05:01:20 +00:00
|
|
|
func (f *fakeAPIFactory) Decoder(bool) runtime.Decoder {
|
|
|
|
return testapi.Default.Codec()
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) JSONEncoder() runtime.Encoder {
|
|
|
|
return testapi.Default.Codec()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) ClientSet() (*internalclientset.Clientset, error) {
|
2016-10-30 01:25:02 +00:00
|
|
|
// Swap the HTTP client out of the REST client with the fake
|
|
|
|
// version.
|
2016-10-18 22:53:26 +00:00
|
|
|
fakeClient := f.tf.Client.(*fake.RESTClient)
|
2016-10-30 01:25:02 +00:00
|
|
|
clientset := internalclientset.NewForConfigOrDie(f.tf.ClientConfig)
|
2016-11-02 04:59:51 +00:00
|
|
|
clientset.CoreClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.AuthenticationClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.AuthorizationClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.AutoscalingClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.BatchClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.CertificatesClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.ExtensionsClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.RbacClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.StorageClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.AppsClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.PolicyClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
|
|
|
clientset.DiscoveryClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
2016-10-30 01:25:02 +00:00
|
|
|
return clientset, f.tf.Err
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) RESTClient() (*restclient.RESTClient, error) {
|
|
|
|
// Swap out the HTTP client out of the client with the fake's version.
|
|
|
|
fakeClient := f.tf.Client.(*fake.RESTClient)
|
|
|
|
restClient, err := restclient.RESTClientFor(f.tf.ClientConfig)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
restClient.Client = fakeClient.Client
|
|
|
|
return restClient, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) ClientConfig() (*restclient.Config, error) {
|
|
|
|
return f.tf.ClientConfig, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2017-02-03 18:51:24 +00:00
|
|
|
func (f *fakeAPIFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) {
|
|
|
|
if f.tf.ClientForMappingFunc != nil {
|
|
|
|
return f.tf.ClientForMappingFunc(m)
|
|
|
|
}
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Client, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2017-02-03 18:51:24 +00:00
|
|
|
func (f *fakeAPIFactory) UnstructuredClientForMapping(m *meta.RESTMapping) (resource.RESTClient, error) {
|
|
|
|
if f.tf.UnstructuredClientForMappingFunc != nil {
|
|
|
|
return f.tf.UnstructuredClientForMappingFunc(m)
|
|
|
|
}
|
2017-01-22 18:45:30 +00:00
|
|
|
return f.tf.UnstructuredClient, f.tf.Err
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
|
2017-02-19 22:39:43 +00:00
|
|
|
func (f *fakeAPIFactory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) {
|
|
|
|
return f.tf.CommandPrinter, f.tf.GenericPrinter, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (printers.Describer, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Describer, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2017-02-19 22:39:43 +00:00
|
|
|
func (f *fakeAPIFactory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Printer, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) LogsForObject(object, options runtime.Object) (*restclient.Request, error) {
|
2016-10-24 14:58:05 +00:00
|
|
|
c, err := f.ClientSet()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2016-10-18 22:53:26 +00:00
|
|
|
|
|
|
|
switch t := object.(type) {
|
|
|
|
case *api.Pod:
|
|
|
|
opts, ok := options.(*api.PodLogOptions)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("provided options object is not a PodLogOptions")
|
|
|
|
}
|
2016-10-24 14:58:05 +00:00
|
|
|
return c.Core().Pods(f.tf.Namespace).GetLogs(t.Name, opts), nil
|
2016-10-18 22:53:26 +00:00
|
|
|
default:
|
|
|
|
fqKinds, _, err := api.Scheme.ObjectKinds(object)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("cannot get the logs from %v", fqKinds[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-24 14:28:52 +00:00
|
|
|
func (f *fakeAPIFactory) AttachablePodForObject(object runtime.Object) (*api.Pod, error) {
|
|
|
|
switch t := object.(type) {
|
|
|
|
case *api.Pod:
|
|
|
|
return t, nil
|
|
|
|
default:
|
|
|
|
gvks, _, err := api.Scheme.ObjectKinds(object)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("cannot attach to %v: not implemented", gvks[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
func (f *fakeAPIFactory) Validator(validate bool, cacheDir string) (validation.Schema, error) {
|
|
|
|
return f.tf.Validator, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) DefaultNamespace() (string, bool, error) {
|
|
|
|
return f.tf.Namespace, false, f.tf.Err
|
|
|
|
}
|
|
|
|
|
2017-02-12 20:24:03 +00:00
|
|
|
func (f *fakeAPIFactory) Command() string {
|
|
|
|
return f.tf.Command
|
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
func (f *fakeAPIFactory) Generators(cmdName string) map[string]kubectl.Generator {
|
|
|
|
return cmdutil.DefaultGenerators(cmdName)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error {
|
|
|
|
gvks, _, err := api.Scheme.ObjectKinds(obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
mapping, err := mapper.RESTMapping(gvks[0].GroupKind())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
printer, err := f.PrinterForMapping(cmd, mapping, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return printer.PrintObj(obj, out)
|
|
|
|
}
|
|
|
|
|
2017-02-19 22:39:43 +00:00
|
|
|
func (f *fakeAPIFactory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) {
|
2016-10-18 22:53:26 +00:00
|
|
|
return f.tf.Printer, f.tf.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fakeAPIFactory) NewBuilder() *resource.Builder {
|
|
|
|
mapper, typer := f.Object()
|
|
|
|
|
|
|
|
return resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true))
|
|
|
|
}
|
|
|
|
|
2016-11-21 02:55:31 +00:00
|
|
|
func (f *fakeAPIFactory) SuggestedPodTemplateResources() []schema.GroupResource {
|
|
|
|
return []schema.GroupResource{}
|
2016-10-24 13:17:55 +00:00
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
func NewAPIFactory() (cmdutil.Factory, *TestFactory, runtime.Codec, runtime.NegotiatedSerializer) {
|
|
|
|
t := &TestFactory{
|
|
|
|
Validator: validation.NullSchema{},
|
|
|
|
}
|
|
|
|
rf := cmdutil.NewFactory(nil)
|
|
|
|
return &fakeAPIFactory{
|
|
|
|
Factory: rf,
|
|
|
|
tf: t,
|
|
|
|
}, t, testapi.Default.Codec(), testapi.Default.NegotiatedSerializer()
|
2016-10-25 04:34:28 +00:00
|
|
|
}
|
|
|
|
|
2016-10-18 22:53:26 +00:00
|
|
|
func testDynamicResources() []*discovery.APIGroupResources {
|
|
|
|
return []*discovery.APIGroupResources{
|
|
|
|
{
|
2016-12-03 18:57:26 +00:00
|
|
|
Group: metav1.APIGroup{
|
|
|
|
Versions: []metav1.GroupVersionForDiscovery{
|
2016-10-18 22:53:26 +00:00
|
|
|
{Version: "v1"},
|
|
|
|
},
|
2016-12-03 18:57:26 +00:00
|
|
|
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
|
2016-10-18 22:53:26 +00:00
|
|
|
},
|
2016-12-03 18:57:26 +00:00
|
|
|
VersionedResources: map[string][]metav1.APIResource{
|
2016-10-18 22:53:26 +00:00
|
|
|
"v1": {
|
|
|
|
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
|
|
|
{Name: "services", Namespaced: true, Kind: "Service"},
|
|
|
|
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
|
2016-11-02 16:29:01 +00:00
|
|
|
{Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"},
|
|
|
|
{Name: "nodes", Namespaced: false, Kind: "Node"},
|
2017-01-27 14:18:48 +00:00
|
|
|
{Name: "secrets", Namespaced: true, Kind: "Secret"},
|
|
|
|
{Name: "configmaps", Namespaced: true, Kind: "ConfigMap"},
|
2016-11-02 16:29:01 +00:00
|
|
|
{Name: "type", Namespaced: false, Kind: "Type"},
|
2017-01-22 22:11:33 +00:00
|
|
|
{Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"},
|
2016-10-18 22:53:26 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2017-01-23 22:17:15 +00:00
|
|
|
{
|
|
|
|
Group: metav1.APIGroup{
|
|
|
|
Name: "extensions",
|
|
|
|
Versions: []metav1.GroupVersionForDiscovery{
|
|
|
|
{Version: "v1beta1"},
|
|
|
|
},
|
|
|
|
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1beta1"},
|
|
|
|
},
|
|
|
|
VersionedResources: map[string][]metav1.APIResource{
|
|
|
|
"v1beta1": {
|
|
|
|
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2017-02-13 00:31:06 +00:00
|
|
|
{
|
|
|
|
Group: metav1.APIGroup{
|
|
|
|
Name: "storage.k8s.io",
|
|
|
|
Versions: []metav1.GroupVersionForDiscovery{
|
|
|
|
{Version: "v1beta1"},
|
|
|
|
{Version: "v0"},
|
|
|
|
},
|
|
|
|
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1beta1"},
|
|
|
|
},
|
|
|
|
VersionedResources: map[string][]metav1.APIResource{
|
|
|
|
"v1beta1": {
|
|
|
|
{Name: "storageclasses", Namespaced: false, Kind: "StorageClass"},
|
|
|
|
},
|
|
|
|
// bogus version of a known group/version/resource to make sure kubectl falls back to generic object mode
|
|
|
|
"v0": {
|
|
|
|
{Name: "storageclasses", Namespaced: false, Kind: "StorageClass"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Group: metav1.APIGroup{
|
|
|
|
Name: "company.com",
|
|
|
|
Versions: []metav1.GroupVersionForDiscovery{
|
|
|
|
{Version: "v1"},
|
|
|
|
},
|
|
|
|
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
|
|
|
|
},
|
|
|
|
VersionedResources: map[string][]metav1.APIResource{
|
|
|
|
"v1": {
|
|
|
|
{Name: "bars", Namespaced: true, Kind: "Bar"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2016-10-18 22:53:26 +00:00
|
|
|
}
|
|
|
|
}
|