diff --git a/pkg/karmadactl/interpret/check_test.go b/pkg/karmadactl/interpret/check_test.go new file mode 100644 index 000000000..80968ce0d --- /dev/null +++ b/pkg/karmadactl/interpret/check_test.go @@ -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: 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) + } + }) + } +} diff --git a/pkg/karmadactl/interpret/execute_test.go b/pkg/karmadactl/interpret/execute_test.go new file mode 100644 index 000000000..d46599b79 --- /dev/null +++ b/pkg/karmadactl/interpret/execute_test.go @@ -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) + } + }) + } +} diff --git a/pkg/karmadactl/interpret/interpret.go b/pkg/karmadactl/interpret/interpret.go index 1578e1b60..7a17a07b2 100644 --- a/pkg/karmadactl/interpret/interpret.go +++ b/pkg/karmadactl/interpret/interpret.go @@ -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 } diff --git a/pkg/karmadactl/interpret/interpret_test.go b/pkg/karmadactl/interpret/interpret_test.go new file mode 100644 index 000000000..aad809be5 --- /dev/null +++ b/pkg/karmadactl/interpret/interpret_test.go @@ -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) + } + }) + } +} diff --git a/pkg/karmadactl/interpret/testdata/customization.yml b/pkg/karmadactl/interpret/testdata/customization.yml new file mode 100644 index 000000000..816874c76 --- /dev/null +++ b/pkg/karmadactl/interpret/testdata/customization.yml @@ -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 diff --git a/pkg/karmadactl/interpret/testdata/customization_check.yml b/pkg/karmadactl/interpret/testdata/customization_check.yml new file mode 100644 index 000000000..2a504eb4f --- /dev/null +++ b/pkg/karmadactl/interpret/testdata/customization_check.yml @@ -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 diff --git a/pkg/karmadactl/interpret/testdata/customization_error.yml b/pkg/karmadactl/interpret/testdata/customization_error.yml new file mode 100644 index 000000000..089027027 --- /dev/null +++ b/pkg/karmadactl/interpret/testdata/customization_error.yml @@ -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 + + diff --git a/pkg/karmadactl/interpret/testdata/desired.yml b/pkg/karmadactl/interpret/testdata/desired.yml new file mode 100644 index 000000000..14826243e --- /dev/null +++ b/pkg/karmadactl/interpret/testdata/desired.yml @@ -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: {} diff --git a/pkg/karmadactl/interpret/testdata/observed.yml b/pkg/karmadactl/interpret/testdata/observed.yml new file mode 100644 index 000000000..5e65f3dbf --- /dev/null +++ b/pkg/karmadactl/interpret/testdata/observed.yml @@ -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 diff --git a/pkg/karmadactl/interpret/testdata/status.yml b/pkg/karmadactl/interpret/testdata/status.yml new file mode 100644 index 000000000..23b4ed84f --- /dev/null +++ b/pkg/karmadactl/interpret/testdata/status.yml @@ -0,0 +1,9 @@ +clusterName: cluster1 +status: + readyReplicas: 2 +applied: true +--- +clusterName: cluster2 +status: + readyReplicas: 3 +applied: true diff --git a/pkg/karmadactl/util/testing/fake.go b/pkg/karmadactl/util/testing/fake.go new file mode 100644 index 000000000..b9929a697 --- /dev/null +++ b/pkg/karmadactl/util/testing/fake.go @@ -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") +} diff --git a/vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go b/vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go new file mode 100644 index 000000000..44d877ecf --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go @@ -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", +) diff --git a/vendor/k8s.io/kube-openapi/pkg/util/proto/testing/openapi.go b/vendor/k8s.io/kube-openapi/pkg/util/proto/testing/openapi.go new file mode 100644 index 000000000..7a5dc2d86 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/util/proto/testing/openapi.go @@ -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 +} diff --git a/vendor/k8s.io/kube-openapi/pkg/util/proto/testing/openapi_v3.go b/vendor/k8s.io/kube-openapi/pkg/util/proto/testing/openapi_v3.go new file mode 100644 index 000000000..ccc7e53bb --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/util/proto/testing/openapi_v3.go @@ -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 +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/testing/fake.go b/vendor/k8s.io/kubectl/pkg/cmd/testing/fake.go new file mode 100644 index 000000000..34ef091e2 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/testing/fake.go @@ -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"}, + }, + }, + }, + } +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/testing/interfaces.go b/vendor/k8s.io/kubectl/pkg/cmd/testing/interfaces.go new file mode 100644 index 000000000..efb6a65de --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/testing/interfaces.go @@ -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 +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/testing/util.go b/vendor/k8s.io/kubectl/pkg/cmd/testing/util.go new file mode 100644 index 000000000..960fb6e13 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/testing/util.go @@ -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) + }) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/testing/zz_generated.deepcopy.go b/vendor/k8s.io/kubectl/pkg/cmd/testing/zz_generated.deepcopy.go new file mode 100644 index 000000000..f211f1040 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/testing/zz_generated.deepcopy.go @@ -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 +} diff --git a/vendor/k8s.io/kubectl/pkg/util/openapi/testing/openapi.go b/vendor/k8s.io/kubectl/pkg/util/openapi/testing/openapi.go new file mode 100644 index 000000000..78f819d3f --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/openapi/testing/openapi.go @@ -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 + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1fbc410e0..68e5e2d76 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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