ut for interpret command
Signed-off-by: yingjinhui <yingjinhui@didiglobal.com>
This commit is contained in:
parent
32291dfc53
commit
36bc23183b
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
@ -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: {}
|
|
@ -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
|
|
@ -0,0 +1,9 @@
|
|||
clusterName: cluster1
|
||||
status:
|
||||
readyReplicas: 2
|
||||
applied: true
|
||||
---
|
||||
clusterName: cluster2
|
||||
status:
|
||||
readyReplicas: 3
|
||||
applied: true
|
|
@ -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")
|
||||
}
|
171
vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go
generated
vendored
Normal file
171
vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper/test_restmapper.go
generated
vendored
Normal 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",
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue