ut for interpret command

Signed-off-by: yingjinhui <yingjinhui@didiglobal.com>
This commit is contained in:
yingjinhui 2022-11-29 14:15:15 +08:00
parent 32291dfc53
commit 36bc23183b
20 changed files with 2140 additions and 1 deletions

View File

@ -0,0 +1,106 @@
package interpret
import (
"testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
cmdtesting "github.com/karmada-io/karmada/pkg/karmadactl/util/testing"
"github.com/karmada-io/karmada/pkg/util/interpreter"
)
func TestOptions_runCheck(t *testing.T) {
tests := []struct {
name string
options *Options
want string
wantErr bool
}{
{
name: "Has errors in file",
options: &Options{
Rules: interpreter.AllResourceInterpreterCustomizationRules,
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization_error.yml"}},
Check: true,
},
wantErr: true,
want: `-----------------------------------
SOURCE: not-customization
not a ResourceInterpreterCustomization, got /v1, Kind=Pod
-----------------------------------
SOURCE: api-version-unset
target.apiVersion no set
-----------------------------------
SOURCE: kind-unset
target.kind no set
`,
},
{
name: "customization has error",
options: &Options{
Rules: interpreter.AllResourceInterpreterCustomizationRules,
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization_check.yml"}},
Check: true,
},
wantErr: true,
want: `-----------------------------------
SOURCE: customization-check
TARGET: apps/v1 Deployment
RULERS:
Retain: PASS
InterpretReplica: ERROR: <string> line:1(column:10) near 'format': parse error
ReviseReplica: UNSET
InterpretStatus: UNSET
AggregateStatus: UNSET
InterpretHealth: UNSET
InterpretDependency: UNSET
`,
},
{
name: "customization has no error",
options: &Options{
Rules: interpreter.AllResourceInterpreterCustomizationRules,
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Check: true,
},
wantErr: false,
want: `-----------------------------------
SOURCE: customization
TARGET: apps/v1 Deployment
RULERS:
Retain: PASS
InterpretReplica: PASS
ReviseReplica: PASS
InterpretStatus: PASS
AggregateStatus: PASS
InterpretHealth: PASS
InterpretDependency: PASS
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
tt.options.IOStreams = streams
if err := tt.options.Complete(tf, nil, nil); err != nil {
t.Fatal(err)
}
if err := tt.options.Validate(); err != nil {
t.Fatal(err)
}
err := tt.options.Run()
if (err != nil) != tt.wantErr {
t.Errorf("runCheck() error = %v, wantErr %v", err, tt.wantErr)
}
if got := buf.String(); got != tt.want {
t.Errorf("runCheck() = %q, want %q", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,209 @@
package interpret
import (
"testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
cmdtesting "github.com/karmada-io/karmada/pkg/karmadactl/util/testing"
"github.com/karmada-io/karmada/pkg/util/interpreter"
)
func TestOptions_runExecute(t *testing.T) {
tests := []struct {
name string
options *Options
want string
wantErr bool
}{
{
name: "execute retain",
options: &Options{
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Operation: "retain",
DesiredFile: "./testdata/desired.yml",
ObservedFile: "./testdata/observed.yml",
Rules: interpreter.AllResourceInterpreterCustomizationRules,
},
want: `---
# [1/1] retained:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
cluster: cluster1
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:alpine
name: nginx
`,
},
{
name: "execute interpretReplica",
options: &Options{
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Operation: "interpretReplica",
ObservedFile: "./testdata/observed.yml",
Rules: interpreter.AllResourceInterpreterCustomizationRules,
},
want: `---
# [1/2] replica:
3
---
# [2/2] requires:
resourceRequest:
cpu: 100m
`,
},
{
name: "execute reviseReplica",
options: &Options{
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Operation: "reviseReplica",
ObservedFile: "./testdata/observed.yml",
DesiredReplica: 2,
Rules: interpreter.AllResourceInterpreterCustomizationRules,
},
want: `---
# [1/1] revised:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
cluster: cluster1
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:alpine
name: nginx
resources:
limits:
cpu: 100m
status:
readyReplicas: 3
`,
},
{
name: "execute interpretStatus",
options: &Options{
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Operation: "interpretStatus",
ObservedFile: "./testdata/observed.yml",
Rules: interpreter.AllResourceInterpreterCustomizationRules,
},
want: `---
# [1/1] status:
readyReplicas: 3
`,
},
{
name: "execute interpretHealth",
options: &Options{
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Operation: "interpretHealth",
ObservedFile: "./testdata/observed.yml",
Rules: interpreter.AllResourceInterpreterCustomizationRules,
},
want: `---
# [1/1] healthy:
true
`,
},
{
name: "execute interpretDependency",
options: &Options{
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Operation: "interpretDependency",
ObservedFile: "./testdata/observed.yml",
Rules: interpreter.AllResourceInterpreterCustomizationRules,
},
want: `---
# [1/1] dependencies:
- apiVersion: v1
kind: ServiceAccount
name: nginx
`,
},
{
name: "execute aggregateStatus",
options: &Options{
FilenameOptions: resource.FilenameOptions{Filenames: []string{"./testdata/customization.yml"}},
Operation: "aggregateStatus",
ObservedFile: "./testdata/observed.yml",
StatusFile: "./testdata/status.yml",
Rules: interpreter.AllResourceInterpreterCustomizationRules,
},
want: `---
# [1/1] aggregatedStatus:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
cluster: cluster1
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:alpine
name: nginx
resources:
limits:
cpu: 100m
status:
readyReplicas: 5
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
tt.options.IOStreams = streams
if err := tt.options.Complete(tf, nil, nil); err != nil {
t.Fatal(err)
}
if err := tt.options.Validate(); err != nil {
t.Fatal(err)
}
err := tt.options.Run()
if (err != nil) != tt.wantErr {
t.Errorf("runExecute() error = %v, wantErr %v", err, tt.wantErr)
}
if got := buf.String(); got != tt.want {
t.Errorf("runExecute() = %q, want %q", got, tt.want)
}
})
}
}

View File

@ -220,7 +220,7 @@ func getUnstructuredObjectFromResult(result *resource.Result) (*unstructured.Uns
func asResourceInterpreterCustomization(o runtime.Object) (*configv1alpha1.ResourceInterpreterCustomization, error) {
c, ok := o.(*configv1alpha1.ResourceInterpreterCustomization)
if !ok {
return nil, fmt.Errorf("not a ResourceInterpreterCustomization: %#v", o)
return nil, fmt.Errorf("not a ResourceInterpreterCustomization, got %v", o.GetObjectKind().GroupVersionKind())
}
return c, nil
}

View File

@ -0,0 +1,28 @@
package interpret
import (
"testing"
)
func TestOptions_Validate(t *testing.T) {
tests := []struct {
name string
options *Options
wantErr bool
}{
{
name: "operation is unknown",
options: &Options{
Operation: "unknown-operation",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.options.Validate(); (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -0,0 +1,65 @@
apiVersion: config.karmada.io/v1alpha1
kind: ResourceInterpreterCustomization
metadata:
name: customization
spec:
target:
apiVersion: apps/v1
kind: Deployment
customizations:
retention:
luaScript: >
function Retain(desiredObj, runtimeObj)
desiredObj.metadata.annotations.cluster = runtimeObj.metadata.annotations.cluster
return desiredObj
end
replicaResource:
luaScript: >
function GetReplicas(obj)
replica = obj.spec.replicas
requirement = {
resourceRequest = obj.spec.template.spec.containers[1].resources.limits,
nodeClaim = {
nodeSelector = obj.spec.template.spec.nodeSelector,
tolerations = obj.spec.template.spec.tolerations
}
}
return replica, requirement
end
replicaRevision:
luaScript: >
function ReviseReplica(obj, desiredReplica)
obj.spec.replicas = desiredReplica
return obj
end
statusReflection:
luaScript: >
function ReflectStatus(observedObj)
return observedObj.status
end
statusAggregation:
luaScript: >
function AggregateStatus(desiredObj, items)
desiredObj.status.readyReplicas = 0
for i = 1, #items do
desiredObj.status.readyReplicas = desiredObj.status.readyReplicas + items[i].status.readyReplicas
end
return desiredObj
end
healthInterpretation:
luaScript: >
function InterpretHealth(observedObj)
return observedObj.status.readyReplicas == observedObj.spec.replicas
end
dependencyInterpretation:
luaScript: >
function GetDependencies(desiredObj)
dependencies = {}
dependencies[1] = {
apiVersion = "v1",
kind = "ServiceAccount",
name = desiredObj.metadata.name,
namespace = desiredObj.metadata.namespace,
}
return dependencies
end

View File

@ -0,0 +1,17 @@
apiVersion: config.karmada.io/v1alpha1
kind: ResourceInterpreterCustomization
metadata:
name: customization-check
spec:
target:
apiVersion: apps/v1
kind: Deployment
customizations:
retention:
luaScript: >
function Retain(desiredObj, runtimeObj)
return desiredObj
end
replicaResource:
luaScript: >
bad format

View File

@ -0,0 +1,23 @@
---
apiVersion: v1
kind: Pod
metadata:
name: not-customization
---
apiVersion: config.karmada.io/v1alpha1
kind: ResourceInterpreterCustomization
metadata:
name: api-version-unset
spec:
target:
kind: Deployment
---
apiVersion: config.karmada.io/v1alpha1
kind: ResourceInterpreterCustomization
metadata:
name: kind-unset
spec:
target:
apiVersion: apps/v1

View File

@ -0,0 +1,19 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
annotations: {}
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:alpine
name: nginx
status: {}

View File

@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
annotations:
cluster: cluster1
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:alpine
name: nginx
resources:
limits:
cpu: 100m
status:
readyReplicas: 3

View File

@ -0,0 +1,9 @@
clusterName: cluster1
status:
readyReplicas: 2
applied: true
---
clusterName: cluster2
status:
readyReplicas: 3
applied: true

View File

@ -0,0 +1,35 @@
package testing
import (
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
"github.com/karmada-io/karmada/pkg/karmadactl/util"
)
// TestFactory extends cmdutil.Factory
type TestFactory struct {
*cmdtesting.TestFactory
}
var _ util.Factory = (*TestFactory)(nil)
// NewTestFactory returns an initialized TestFactory instance
func NewTestFactory() *TestFactory {
return &TestFactory{
TestFactory: cmdtesting.NewTestFactory(),
}
}
// KarmadaClientSet returns a karmada clientset
func (t *TestFactory) KarmadaClientSet() (versioned.Interface, error) {
// TODO implement me
panic("implement me")
}
// FactoryForMemberCluster returns a cmdutil.Factory for the member cluster
func (t *TestFactory) FactoryForMemberCluster(clusterName string) (cmdutil.Factory, error) {
// TODO implement me
panic("implement me")
}

View File

@ -0,0 +1,171 @@
/*
Copyright 2018 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 testrestmapper
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
)
// TestOnlyStaticRESTMapper returns a union RESTMapper of all known types with priorities chosen in the following order:
// 1. legacy kube group preferred version, extensions preferred version, metrics preferred version, legacy
// kube any version, extensions any version, metrics any version, all other groups alphabetical preferred version,
// all other groups alphabetical.
//
// TODO callers of this method should be updated to build their own specific restmapper based on their scheme for their tests
// TODO the things being tested are related to whether various cases are handled, not tied to the particular types being checked.
func TestOnlyStaticRESTMapper(scheme *runtime.Scheme, versionPatterns ...schema.GroupVersion) meta.RESTMapper {
unionMapper := meta.MultiRESTMapper{}
unionedGroups := sets.NewString()
for _, enabledVersion := range scheme.PrioritizedVersionsAllGroups() {
if !unionedGroups.Has(enabledVersion.Group) {
unionedGroups.Insert(enabledVersion.Group)
unionMapper = append(unionMapper, newRESTMapper(enabledVersion.Group, scheme))
}
}
if len(versionPatterns) != 0 {
resourcePriority := []schema.GroupVersionResource{}
kindPriority := []schema.GroupVersionKind{}
for _, versionPriority := range versionPatterns {
resourcePriority = append(resourcePriority, versionPriority.WithResource(meta.AnyResource))
kindPriority = append(kindPriority, versionPriority.WithKind(meta.AnyKind))
}
return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority}
}
prioritizedGroups := []string{"", "extensions", "metrics"}
resourcePriority, kindPriority := prioritiesForGroups(scheme, prioritizedGroups...)
prioritizedGroupsSet := sets.NewString(prioritizedGroups...)
remainingGroups := sets.String{}
for _, enabledVersion := range scheme.PrioritizedVersionsAllGroups() {
if !prioritizedGroupsSet.Has(enabledVersion.Group) {
remainingGroups.Insert(enabledVersion.Group)
}
}
remainingResourcePriority, remainingKindPriority := prioritiesForGroups(scheme, remainingGroups.List()...)
resourcePriority = append(resourcePriority, remainingResourcePriority...)
kindPriority = append(kindPriority, remainingKindPriority...)
return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority}
}
// prioritiesForGroups returns the resource and kind priorities for a PriorityRESTMapper, preferring the preferred version of each group first,
// then any non-preferred version of the group second.
func prioritiesForGroups(scheme *runtime.Scheme, groups ...string) ([]schema.GroupVersionResource, []schema.GroupVersionKind) {
resourcePriority := []schema.GroupVersionResource{}
kindPriority := []schema.GroupVersionKind{}
for _, group := range groups {
availableVersions := scheme.PrioritizedVersionsForGroup(group)
if len(availableVersions) > 0 {
resourcePriority = append(resourcePriority, availableVersions[0].WithResource(meta.AnyResource))
kindPriority = append(kindPriority, availableVersions[0].WithKind(meta.AnyKind))
}
}
for _, group := range groups {
resourcePriority = append(resourcePriority, schema.GroupVersionResource{Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource})
kindPriority = append(kindPriority, schema.GroupVersionKind{Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind})
}
return resourcePriority, kindPriority
}
func newRESTMapper(group string, scheme *runtime.Scheme) meta.RESTMapper {
mapper := meta.NewDefaultRESTMapper(scheme.PrioritizedVersionsForGroup(group))
for _, gv := range scheme.PrioritizedVersionsForGroup(group) {
for kind := range scheme.KnownTypes(gv) {
if ignoredKinds.Has(kind) {
continue
}
scope := meta.RESTScopeNamespace
if rootScopedKinds[gv.WithKind(kind).GroupKind()] {
scope = meta.RESTScopeRoot
}
mapper.Add(gv.WithKind(kind), scope)
}
}
return mapper
}
// hardcoded is good enough for the test we're running
var rootScopedKinds = map[schema.GroupKind]bool{
{Group: "admission.k8s.io", Kind: "AdmissionReview"}: true,
{Group: "admissionregistration.k8s.io", Kind: "ValidatingWebhookConfiguration"}: true,
{Group: "admissionregistration.k8s.io", Kind: "MutatingWebhookConfiguration"}: true,
{Group: "authentication.k8s.io", Kind: "TokenReview"}: true,
{Group: "authorization.k8s.io", Kind: "SubjectAccessReview"}: true,
{Group: "authorization.k8s.io", Kind: "SelfSubjectAccessReview"}: true,
{Group: "authorization.k8s.io", Kind: "SelfSubjectRulesReview"}: true,
{Group: "certificates.k8s.io", Kind: "CertificateSigningRequest"}: true,
{Group: "", Kind: "Node"}: true,
{Group: "", Kind: "Namespace"}: true,
{Group: "", Kind: "PersistentVolume"}: true,
{Group: "", Kind: "ComponentStatus"}: true,
{Group: "extensions", Kind: "PodSecurityPolicy"}: true,
{Group: "policy", Kind: "PodSecurityPolicy"}: true,
{Group: "extensions", Kind: "PodSecurityPolicy"}: true,
{Group: "rbac.authorization.k8s.io", Kind: "ClusterRole"}: true,
{Group: "rbac.authorization.k8s.io", Kind: "ClusterRoleBinding"}: true,
{Group: "scheduling.k8s.io", Kind: "PriorityClass"}: true,
{Group: "storage.k8s.io", Kind: "StorageClass"}: true,
{Group: "storage.k8s.io", Kind: "VolumeAttachment"}: true,
{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}: true,
{Group: "apiserver.k8s.io", Kind: "AdmissionConfiguration"}: true,
{Group: "audit.k8s.io", Kind: "Event"}: true,
{Group: "audit.k8s.io", Kind: "Policy"}: true,
{Group: "apiregistration.k8s.io", Kind: "APIService"}: true,
{Group: "metrics.k8s.io", Kind: "NodeMetrics"}: true,
{Group: "wardle.example.com", Kind: "Fischer"}: true,
}
// hardcoded is good enough for the test we're running
var ignoredKinds = sets.NewString(
"ListOptions",
"DeleteOptions",
"Status",
"PodLogOptions",
"PodExecOptions",
"PodAttachOptions",
"PodPortForwardOptions",
"PodProxyOptions",
"NodeProxyOptions",
"ServiceProxyOptions",
)

View File

@ -0,0 +1,59 @@
/*
Copyright 2017 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 (
"io/ioutil"
"os"
"sync"
openapi_v2 "github.com/google/gnostic/openapiv2"
)
// Fake opens and returns a openapi swagger from a file Path. It will
// parse only once and then return the same copy everytime.
type Fake struct {
Path string
once sync.Once
document *openapi_v2.Document
err error
}
// OpenAPISchema returns the openapi document and a potential error.
func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) {
f.once.Do(func() {
_, err := os.Stat(f.Path)
if err != nil {
f.err = err
return
}
spec, err := ioutil.ReadFile(f.Path)
if err != nil {
f.err = err
return
}
f.document, f.err = openapi_v2.ParseDocument(spec)
})
return f.document, f.err
}
type Empty struct{}
func (Empty) OpenAPISchema() (*openapi_v2.Document, error) {
return nil, nil
}

View File

@ -0,0 +1,71 @@
/*
Copyright 2022 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 (
"io/ioutil"
"os"
"path/filepath"
"sync"
openapi_v3 "github.com/google/gnostic/openapiv3"
)
type FakeV3 struct {
Path string
lock sync.Mutex
documents map[string]*openapi_v3.Document
errors map[string]error
}
func (f *FakeV3) OpenAPIV3Schema(groupVersion string) (*openapi_v3.Document, error) {
f.lock.Lock()
defer f.lock.Unlock()
if existing, ok := f.documents[groupVersion]; ok {
return existing, nil
} else if existingError, ok := f.errors[groupVersion]; ok {
return nil, existingError
}
_, err := os.Stat(f.Path)
if err != nil {
return nil, err
}
spec, err := ioutil.ReadFile(filepath.Join(f.Path, groupVersion+".json"))
if err != nil {
return nil, err
}
if f.documents == nil {
f.documents = make(map[string]*openapi_v3.Document)
}
if f.errors == nil {
f.errors = make(map[string]error)
}
result, err := openapi_v3.ParseDocument(spec)
if err != nil {
f.errors[groupVersion] = err
return nil, err
}
f.documents[groupVersion] = result
return result, nil
}

845
vendor/k8s.io/kubectl/pkg/cmd/testing/fake.go generated vendored Normal file
View File

@ -0,0 +1,845 @@
/*
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 (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
diskcached "k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
fakedynamic "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/restmapper"
scaleclient "k8s.io/client-go/scale"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/openapi"
openapitesting "k8s.io/kubectl/pkg/util/openapi/testing"
"k8s.io/kubectl/pkg/validation"
openapi_v2 "github.com/google/gnostic/openapiv2"
)
// InternalType is the schema for internal type
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type InternalType struct {
Kind string
APIVersion string
Name string
}
// ExternalType is the schema for external type
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalType struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
}
// ExternalType2 is another schema for external type
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalType2 struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
}
// GetObjectKind returns the ObjectKind schema
func (obj *InternalType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the version and kind
func (obj *InternalType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns GroupVersionKind schema
func (obj *InternalType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalType2) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API version and obj kind from schema
func (obj *ExternalType2) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the FromAPIVersionAndKind schema
func (obj *ExternalType2) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// NewInternalType returns an initialized InternalType instance
func NewInternalType(kind, apiversion, name string) *InternalType {
item := InternalType{Kind: kind,
APIVersion: apiversion,
Name: name}
return &item
}
func convertInternalTypeToExternalType(in *InternalType, out *ExternalType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
func convertInternalTypeToExternalType2(in *InternalType, out *ExternalType2, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
func convertExternalTypeToInternalType(in *ExternalType, out *InternalType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
func convertExternalType2ToInternalType(in *ExternalType2, out *InternalType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
return nil
}
// InternalNamespacedType schema for internal namespaced types
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type InternalNamespacedType struct {
Kind string
APIVersion string
Name string
Namespace string
}
// ExternalNamespacedType schema for external namespaced types
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalNamespacedType struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
// ExternalNamespacedType2 schema for external namespaced types
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ExternalNamespacedType2 struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
// GetObjectKind returns the ObjectKind schema
func (obj *InternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API group and kind from schema
func (obj *InternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *InternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API version and kind from schema
func (obj *ExternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// GetObjectKind returns the ObjectKind schema
func (obj *ExternalNamespacedType2) GetObjectKind() schema.ObjectKind { return obj }
// SetGroupVersionKind sets the API version and kind from schema
func (obj *ExternalNamespacedType2) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind returns the GroupVersionKind schema
func (obj *ExternalNamespacedType2) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
// NewInternalNamespacedType returns an initialized instance of InternalNamespacedType
func NewInternalNamespacedType(kind, apiversion, name, namespace string) *InternalNamespacedType {
item := InternalNamespacedType{Kind: kind,
APIVersion: apiversion,
Name: name,
Namespace: namespace}
return &item
}
func convertInternalNamespacedTypeToExternalNamespacedType(in *InternalNamespacedType, out *ExternalNamespacedType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
func convertInternalNamespacedTypeToExternalNamespacedType2(in *InternalNamespacedType, out *ExternalNamespacedType2, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
func convertExternalNamespacedTypeToInternalNamespacedType(in *ExternalNamespacedType, out *InternalNamespacedType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
func convertExternalNamespacedType2ToInternalNamespacedType(in *ExternalNamespacedType2, out *InternalNamespacedType, s conversion.Scope) error {
out.Kind = in.Kind
out.APIVersion = in.APIVersion
out.Name = in.Name
out.Namespace = in.Namespace
return nil
}
// ValidVersion of API
var ValidVersion = "v1"
// InternalGV is the internal group version object
var InternalGV = schema.GroupVersion{Group: "apitest", Version: runtime.APIVersionInternal}
// UnlikelyGV is a group version object for unrecognised version
var UnlikelyGV = schema.GroupVersion{Group: "apitest", Version: "unlikelyversion"}
// ValidVersionGV is the valid group version object
var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion}
// NewExternalScheme returns required objects for ExternalScheme
func NewExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
scheme := runtime.NewScheme()
mapper, codec := AddToScheme(scheme)
return scheme, mapper, codec
}
func registerConversions(s *runtime.Scheme) error {
if err := s.AddConversionFunc((*InternalType)(nil), (*ExternalType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalTypeToExternalType(a.(*InternalType), b.(*ExternalType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*InternalType)(nil), (*ExternalType2)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalTypeToExternalType2(a.(*InternalType), b.(*ExternalType2), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalType)(nil), (*InternalType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalTypeToInternalType(a.(*ExternalType), b.(*InternalType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalType2)(nil), (*InternalType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalType2ToInternalType(a.(*ExternalType2), b.(*InternalType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*InternalNamespacedType)(nil), (*ExternalNamespacedType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalNamespacedTypeToExternalNamespacedType(a.(*InternalNamespacedType), b.(*ExternalNamespacedType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*InternalNamespacedType)(nil), (*ExternalNamespacedType2)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertInternalNamespacedTypeToExternalNamespacedType2(a.(*InternalNamespacedType), b.(*ExternalNamespacedType2), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalNamespacedType)(nil), (*InternalNamespacedType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalNamespacedTypeToInternalNamespacedType(a.(*ExternalNamespacedType), b.(*InternalNamespacedType), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*ExternalNamespacedType2)(nil), (*InternalNamespacedType)(nil), func(a, b interface{}, scope conversion.Scope) error {
return convertExternalNamespacedType2ToInternalNamespacedType(a.(*ExternalNamespacedType2), b.(*InternalNamespacedType), scope)
}); err != nil {
return err
}
return nil
}
// AddToScheme adds required objects into scheme
func AddToScheme(scheme *runtime.Scheme) (meta.RESTMapper, runtime.Codec) {
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{})
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{})
utilruntime.Must(registerConversions(scheme))
codecs := serializer.NewCodecFactory(scheme)
codec := codecs.LegacyCodec(UnlikelyGV)
mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV})
for _, gv := range []schema.GroupVersion{UnlikelyGV, ValidVersionGV} {
for kind := range scheme.KnownTypes(gv) {
gvk := gv.WithKind(kind)
scope := meta.RESTScopeNamespace
mapper.Add(gvk, scope)
}
}
return mapper, codec
}
type FakeCachedDiscoveryClient struct {
discovery.DiscoveryInterface
Groups []*metav1.APIGroup
Resources []*metav1.APIResourceList
PreferredResources []*metav1.APIResourceList
Invalidations int
}
func NewFakeCachedDiscoveryClient() *FakeCachedDiscoveryClient {
return &FakeCachedDiscoveryClient{
Groups: []*metav1.APIGroup{},
Resources: []*metav1.APIResourceList{},
PreferredResources: []*metav1.APIResourceList{},
Invalidations: 0,
}
}
func (d *FakeCachedDiscoveryClient) Fresh() bool {
return true
}
func (d *FakeCachedDiscoveryClient) Invalidate() {
d.Invalidations++
}
func (d *FakeCachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return d.Groups, d.Resources, nil
}
func (d *FakeCachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
groupList := &metav1.APIGroupList{Groups: []metav1.APIGroup{}}
for _, g := range d.Groups {
groupList.Groups = append(groupList.Groups, *g)
}
return groupList, nil
}
func (d *FakeCachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return d.PreferredResources, nil
}
// TestFactory extends cmdutil.Factory
type TestFactory struct {
cmdutil.Factory
kubeConfigFlags *genericclioptions.TestConfigFlags
Client RESTClient
ScaleGetter scaleclient.ScalesGetter
UnstructuredClient RESTClient
ClientConfigVal *restclient.Config
FakeDynamicClient *fakedynamic.FakeDynamicClient
tempConfigFile *os.File
UnstructuredClientForMappingFunc resource.FakeClientFunc
OpenAPISchemaFunc func() (openapi.Resources, error)
FakeOpenAPIGetter discovery.OpenAPISchemaInterface
}
// NewTestFactory returns an initialized TestFactory instance
func NewTestFactory() *TestFactory {
// specify an optionalClientConfig to explicitly use in testing
// to avoid polluting an existing user config.
tmpFile, err := ioutil.TempFile(os.TempDir(), "cmdtests_temp")
if err != nil {
panic(fmt.Sprintf("unable to create a fake client config: %v", err))
}
loadingRules := &clientcmd.ClientConfigLoadingRules{
Precedence: []string{tmpFile.Name()},
MigrationRules: map[string]string{},
}
overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmdapi.Cluster{Server: "http://localhost:8080"}}
fallbackReader := bytes.NewBuffer([]byte{})
clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, fallbackReader)
configFlags := genericclioptions.NewTestConfigFlags().
WithClientConfig(clientConfig).
WithRESTMapper(testRESTMapper())
restConfig, err := clientConfig.ClientConfig()
if err != nil {
panic(fmt.Sprintf("unable to create a fake restclient config: %v", err))
}
return &TestFactory{
Factory: cmdutil.NewFactory(configFlags),
kubeConfigFlags: configFlags,
FakeDynamicClient: fakedynamic.NewSimpleDynamicClient(scheme.Scheme),
tempConfigFile: tmpFile,
ClientConfigVal: restConfig,
}
}
// WithNamespace is used to mention namespace reactively
func (f *TestFactory) WithNamespace(ns string) *TestFactory {
f.kubeConfigFlags.WithNamespace(ns)
return f
}
// WithClientConfig sets the client config to use
func (f *TestFactory) WithClientConfig(clientConfig clientcmd.ClientConfig) *TestFactory {
f.kubeConfigFlags.WithClientConfig(clientConfig)
return f
}
func (f *TestFactory) WithDiscoveryClient(discoveryClient discovery.CachedDiscoveryInterface) *TestFactory {
f.kubeConfigFlags.WithDiscoveryClient(discoveryClient)
return f
}
// Cleanup cleans up TestFactory temp config file
func (f *TestFactory) Cleanup() {
if f.tempConfigFile == nil {
return
}
os.Remove(f.tempConfigFile.Name())
}
// ToRESTConfig is used to get ClientConfigVal from a TestFactory
func (f *TestFactory) ToRESTConfig() (*restclient.Config, error) {
return f.ClientConfigVal, nil
}
// ClientForMapping is used to Client from a TestFactory
func (f *TestFactory) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
return f.Client, nil
}
// PathOptions returns a new PathOptions with a temp file
func (f *TestFactory) PathOptions() *clientcmd.PathOptions {
pathOptions := clientcmd.NewDefaultPathOptions()
pathOptions.GlobalFile = f.tempConfigFile.Name()
pathOptions.EnvVar = ""
return pathOptions
}
// PathOptionsWithConfig writes a config to a temp file and returns PathOptions
func (f *TestFactory) PathOptionsWithConfig(config clientcmdapi.Config) (*clientcmd.PathOptions, error) {
pathOptions := f.PathOptions()
err := clientcmd.WriteToFile(config, pathOptions.GlobalFile)
if err != nil {
return nil, err
}
return pathOptions, nil
}
// UnstructuredClientForMapping is used to get UnstructuredClient from a TestFactory
func (f *TestFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
if f.UnstructuredClientForMappingFunc != nil {
return f.UnstructuredClientForMappingFunc(mapping.GroupVersionKind.GroupVersion())
}
return f.UnstructuredClient, nil
}
// Validator returns a validation schema
func (f *TestFactory) Validator(validateDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error) {
return validation.NullSchema{}, nil
}
// OpenAPISchema returns openapi resources
func (f *TestFactory) OpenAPISchema() (openapi.Resources, error) {
if f.OpenAPISchemaFunc != nil {
return f.OpenAPISchemaFunc()
}
return openapitesting.EmptyResources{}, nil
}
type EmptyOpenAPI struct{}
func (EmptyOpenAPI) OpenAPISchema() (*openapi_v2.Document, error) {
return &openapi_v2.Document{}, nil
}
func (f *TestFactory) OpenAPIGetter() discovery.OpenAPISchemaInterface {
if f.FakeOpenAPIGetter != nil {
return f.FakeOpenAPIGetter
}
client, err := f.ToDiscoveryClient()
if err != nil {
return EmptyOpenAPI{}
}
return client
}
// NewBuilder returns an initialized resource.Builder instance
func (f *TestFactory) NewBuilder() *resource.Builder {
return resource.NewFakeBuilder(
func(version schema.GroupVersion) (resource.RESTClient, error) {
if f.UnstructuredClientForMappingFunc != nil {
return f.UnstructuredClientForMappingFunc(version)
}
if f.UnstructuredClient != nil {
return f.UnstructuredClient, nil
}
return f.Client, nil
},
f.ToRESTMapper,
func() (restmapper.CategoryExpander, error) {
return resource.FakeCategoryExpander, nil
},
)
}
// KubernetesClientSet initializes and returns the Clientset using TestFactory
func (f *TestFactory) KubernetesClientSet() (*kubernetes.Clientset, error) {
fakeClient := f.Client.(*fake.RESTClient)
clientset := kubernetes.NewForConfigOrDie(f.ClientConfigVal)
clientset.CoreV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AuthorizationV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AutoscalingV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AutoscalingV2beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.BatchV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.CertificatesV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.CertificatesV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.ExtensionsV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.RbacV1alpha1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.RbacV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.StorageV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.StorageV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AppsV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AppsV1beta2().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.AppsV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.PolicyV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.PolicyV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.DiscoveryClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
return clientset, nil
}
// DynamicClient returns a dynamic client from TestFactory
func (f *TestFactory) DynamicClient() (dynamic.Interface, error) {
if f.FakeDynamicClient != nil {
return f.FakeDynamicClient, nil
}
return f.Factory.DynamicClient()
}
// RESTClient returns a REST client from TestFactory
func (f *TestFactory) RESTClient() (*restclient.RESTClient, error) {
// Swap out the HTTP client out of the client with the fake's version.
fakeClient := f.Client.(*fake.RESTClient)
restClient, err := restclient.RESTClientFor(f.ClientConfigVal)
if err != nil {
panic(err)
}
restClient.Client = fakeClient.Client
return restClient, nil
}
// DiscoveryClient returns a discovery client from TestFactory
func (f *TestFactory) DiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
fakeClient := f.Client.(*fake.RESTClient)
cacheDir := filepath.Join("", ".kube", "cache", "discovery")
cachedClient, err := diskcached.NewCachedDiscoveryClientForConfig(f.ClientConfigVal, cacheDir, "", time.Duration(10*time.Minute))
if err != nil {
return nil, err
}
cachedClient.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
return cachedClient, nil
}
func testRESTMapper() meta.RESTMapper {
groupResources := testDynamicResources()
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
// for backwards compatibility with existing tests, allow rest mappings from the scheme to show up
// TODO: make this opt-in?
mapper = meta.FirstHitRESTMapper{
MultiRESTMapper: meta.MultiRESTMapper{
mapper,
testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme),
},
}
fakeDs := NewFakeCachedDiscoveryClient()
expander := restmapper.NewShortcutExpander(mapper, fakeDs)
return expander
}
// ScaleClient returns the ScalesGetter from a TestFactory
func (f *TestFactory) ScaleClient() (scaleclient.ScalesGetter, error) {
return f.ScaleGetter, nil
}
func testDynamicResources() []*restmapper.APIGroupResources {
return []*restmapper.APIGroupResources{
{
Group: metav1.APIGroup{
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "pods", Namespaced: true, Kind: "Pod"},
{Name: "services", Namespaced: true, Kind: "Service"},
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
{Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"},
{Name: "nodes", Namespaced: false, Kind: "Node"},
{Name: "secrets", Namespaced: true, Kind: "Secret"},
{Name: "configmaps", Namespaced: true, Kind: "ConfigMap"},
{Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"},
{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
{Name: "resourcequotas", Namespaced: true, Kind: "ResourceQuota"},
},
},
},
{
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"},
{Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"},
},
},
},
{
Group: metav1.APIGroup{
Name: "apps",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v1beta2"},
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1beta1": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"},
},
"v1beta2": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
},
"v1": {
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
{Name: "replicasets", Namespaced: true, Kind: "ReplicaSet"},
},
},
},
{
Group: metav1.APIGroup{
Name: "batch",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1beta1": {
{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
},
"v1": {
{Name: "jobs", Namespaced: true, Kind: "Job"},
},
},
},
{
Group: metav1.APIGroup{
Name: "autoscaling",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1"},
{Version: "v2beta1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v2beta1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"},
},
"v2beta1": {
{Name: "horizontalpodautoscalers", Namespaced: true, Kind: "HorizontalPodAutoscaler"},
},
},
},
{
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: "rbac.authorization.k8s.io",
Versions: []metav1.GroupVersionForDiscovery{
{Version: "v1beta1"},
{Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "clusterroles", Namespaced: false, Kind: "ClusterRole"},
},
"v1beta1": {
{Name: "clusterrolebindings", Namespaced: false, Kind: "ClusterRoleBinding"},
},
},
},
{
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"},
},
},
},
{
Group: metav1.APIGroup{
Name: "unit-test.test.com",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "unit-test.test.com/v1", Version: "v1"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "unit-test.test.com/v1",
Version: "v1"},
},
VersionedResources: map[string][]metav1.APIResource{
"v1": {
{Name: "widgets", Namespaced: true, Kind: "Widget"},
},
},
},
{
Group: metav1.APIGroup{
Name: "apitest",
Versions: []metav1.GroupVersionForDiscovery{
{GroupVersion: "apitest/unlikelyversion", Version: "unlikelyversion"},
},
PreferredVersion: metav1.GroupVersionForDiscovery{
GroupVersion: "apitest/unlikelyversion",
Version: "unlikelyversion"},
},
VersionedResources: map[string][]metav1.APIResource{
"unlikelyversion": {
{Name: "types", SingularName: "type", Namespaced: false, Kind: "Type"},
},
},
},
}
}

32
vendor/k8s.io/kubectl/pkg/cmd/testing/interfaces.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
/*
Copyright 2014 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 (
"k8s.io/apimachinery/pkg/types"
client "k8s.io/client-go/rest"
)
// RESTClient is a client helper for dealing with RESTful resources
// in a generic way.
type RESTClient interface {
Get() *client.Request
Post() *client.Request
Patch(types.PatchType) *client.Request
Delete() *client.Request
Put() *client.Request
}

181
vendor/k8s.io/kubectl/pkg/cmd/testing/util.go generated vendored Normal file
View File

@ -0,0 +1,181 @@
/*
Copyright 2018 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 (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
)
var (
grace = int64(30)
enableServiceLinks = corev1.DefaultEnableServiceLinks
)
func DefaultHeader() http.Header {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return header
}
func DefaultClientConfig() *restclient.Config {
return &restclient.Config{
APIPath: "/api",
ContentConfig: restclient.ContentConfig{
NegotiatedSerializer: scheme.Codecs,
ContentType: runtime.ContentTypeJSON,
GroupVersion: &corev1.SchemeGroupVersion,
},
}
}
func ObjBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
}
func BytesBody(bodyBytes []byte) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader(bodyBytes))
}
func StringBody(body string) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(body)))
}
func TestData() (*corev1.PodList, *corev1.ServiceList, *corev1.ReplicationControllerList) {
pods := &corev1.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []corev1.Pod{
{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &corev1.PodSecurityContext{},
EnableServiceLinks: &enableServiceLinks,
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &corev1.PodSecurityContext{},
EnableServiceLinks: &enableServiceLinks,
},
},
},
}
svc := &corev1.ServiceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "16",
},
Items: []corev1.Service{
{
ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
Spec: corev1.ServiceSpec{
SessionAffinity: "None",
Type: corev1.ServiceTypeClusterIP,
},
},
},
}
one := int32(1)
rc := &corev1.ReplicationControllerList{
ListMeta: metav1.ListMeta{
ResourceVersion: "17",
},
Items: []corev1.ReplicationController{
{
ObjectMeta: metav1.ObjectMeta{Name: "rc1", Namespace: "test", ResourceVersion: "18"},
Spec: corev1.ReplicationControllerSpec{
Replicas: &one,
},
},
},
}
return pods, svc, rc
}
// EmptyTestData returns no pod, service, or replication controller
func EmptyTestData() (*corev1.PodList, *corev1.ServiceList, *corev1.ReplicationControllerList) {
pods := &corev1.PodList{
ListMeta: metav1.ListMeta{
ResourceVersion: "15",
},
Items: []corev1.Pod{},
}
svc := &corev1.ServiceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "16",
},
Items: []corev1.Service{},
}
rc := &corev1.ReplicationControllerList{
ListMeta: metav1.ListMeta{
ResourceVersion: "17",
},
Items: []corev1.ReplicationController{},
}
return pods, svc, rc
}
func SubresourceTestData() *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &corev1.PodSecurityContext{},
EnableServiceLinks: &enableServiceLinks,
},
Status: corev1.PodStatus{
Phase: corev1.PodPending,
},
}
}
func GenResponseWithJsonEncodedBody(bodyStruct interface{}) (*http.Response, error) {
jsonBytes, err := json.Marshal(bodyStruct)
if err != nil {
return nil, err
}
return &http.Response{StatusCode: http.StatusOK, Header: DefaultHeader(), Body: BytesBody(jsonBytes)}, nil
}
func InitTestErrorHandler(t *testing.T) {
cmdutil.BehaviorOnFatal(func(str string, code int) {
t.Errorf("Error running command (exit code %d): %s", code, str)
})
}

View File

@ -0,0 +1,170 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package testing
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalNamespacedType) DeepCopyInto(out *ExternalNamespacedType) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalNamespacedType.
func (in *ExternalNamespacedType) DeepCopy() *ExternalNamespacedType {
if in == nil {
return nil
}
out := new(ExternalNamespacedType)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ExternalNamespacedType) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalNamespacedType2) DeepCopyInto(out *ExternalNamespacedType2) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalNamespacedType2.
func (in *ExternalNamespacedType2) DeepCopy() *ExternalNamespacedType2 {
if in == nil {
return nil
}
out := new(ExternalNamespacedType2)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ExternalNamespacedType2) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalType) DeepCopyInto(out *ExternalType) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalType.
func (in *ExternalType) DeepCopy() *ExternalType {
if in == nil {
return nil
}
out := new(ExternalType)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ExternalType) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExternalType2) DeepCopyInto(out *ExternalType2) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalType2.
func (in *ExternalType2) DeepCopy() *ExternalType2 {
if in == nil {
return nil
}
out := new(ExternalType2)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ExternalType2) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InternalNamespacedType) DeepCopyInto(out *InternalNamespacedType) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalNamespacedType.
func (in *InternalNamespacedType) DeepCopy() *InternalNamespacedType {
if in == nil {
return nil
}
out := new(InternalNamespacedType)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *InternalNamespacedType) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InternalType) DeepCopyInto(out *InternalType) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalType.
func (in *InternalType) DeepCopy() *InternalType {
if in == nil {
return nil
}
out := new(InternalType)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *InternalType) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,71 @@
/*
Copyright 2017 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 (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kube-openapi/pkg/util/proto/testing"
"k8s.io/kubectl/pkg/util/openapi"
)
// FakeResources is a wrapper to directly load the openapi schema from a
// file, and get the schema for given GVK. This is only for test since
// it's assuming that the file is there and everything will go fine.
type FakeResources struct {
fake testing.Fake
}
var _ openapi.Resources = &FakeResources{}
// NewFakeResources creates a new FakeResources.
func NewFakeResources(path string) *FakeResources {
return &FakeResources{
fake: testing.Fake{Path: path},
}
}
// LookupResource will read the schema, parse it and return the
// resources. It doesn't return errors and will panic instead.
func (f *FakeResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
s, err := f.fake.OpenAPISchema()
if err != nil {
panic(err)
}
resources, err := openapi.NewOpenAPIData(s)
if err != nil {
panic(err)
}
return resources.LookupResource(gvk)
}
// EmptyResources implement a Resources that just doesn't have any resources.
type EmptyResources struct{}
var _ openapi.Resources = EmptyResources{}
// LookupResource will always return nil. It doesn't have any resources.
func (f EmptyResources) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
return nil
}
// CreateOpenAPISchemaFunc returns a function useful for the TestFactory.
func CreateOpenAPISchemaFunc(path string) func() (openapi.Resources, error) {
return func() (openapi.Resources, error) {
return NewFakeResources(path), nil
}
}

4
vendor/modules.txt vendored
View File

@ -840,6 +840,7 @@ k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextension
k8s.io/apimachinery/pkg/api/equality
k8s.io/apimachinery/pkg/api/errors
k8s.io/apimachinery/pkg/api/meta
k8s.io/apimachinery/pkg/api/meta/testrestmapper
k8s.io/apimachinery/pkg/api/resource
k8s.io/apimachinery/pkg/api/validation
k8s.io/apimachinery/pkg/api/validation/path
@ -1455,6 +1456,7 @@ k8s.io/kube-openapi/pkg/schemamutation
k8s.io/kube-openapi/pkg/spec3
k8s.io/kube-openapi/pkg/util
k8s.io/kube-openapi/pkg/util/proto
k8s.io/kube-openapi/pkg/util/proto/testing
k8s.io/kube-openapi/pkg/util/proto/validation
k8s.io/kube-openapi/pkg/util/sets
k8s.io/kube-openapi/pkg/validation/spec
@ -1468,6 +1470,7 @@ k8s.io/kubectl/pkg/cmd/describe
k8s.io/kubectl/pkg/cmd/exec
k8s.io/kubectl/pkg/cmd/get
k8s.io/kubectl/pkg/cmd/logs
k8s.io/kubectl/pkg/cmd/testing
k8s.io/kubectl/pkg/cmd/util
k8s.io/kubectl/pkg/cmd/util/editor
k8s.io/kubectl/pkg/cmd/util/editor/crlf
@ -1486,6 +1489,7 @@ k8s.io/kubectl/pkg/util/fieldpath
k8s.io/kubectl/pkg/util/i18n
k8s.io/kubectl/pkg/util/interrupt
k8s.io/kubectl/pkg/util/openapi
k8s.io/kubectl/pkg/util/openapi/testing
k8s.io/kubectl/pkg/util/openapi/validation
k8s.io/kubectl/pkg/util/podutils
k8s.io/kubectl/pkg/util/prune