Merge pull request #98194 from julianvmodesto/dry-run-openapi

Cache the OpenAPI schema for kubectl server-side dry run
This commit is contained in:
Kubernetes Prow Robot 2021-03-04 07:44:09 -08:00 committed by GitHub
commit da85ff10cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 196 additions and 252 deletions

View File

@ -27,17 +27,10 @@ import (
"k8s.io/client-go/dynamic"
)
// VerifyDryRun returns nil if a resource group-version-kind supports
// server-side dry-run. Otherwise, an error is returned.
func VerifyDryRun(gvk schema.GroupVersionKind, dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) error {
verifier := NewDryRunVerifier(dynamicClient, discoveryClient)
return verifier.HasSupport(gvk)
}
func NewDryRunVerifier(dynamicClient dynamic.Interface, discoveryClient discovery.DiscoveryInterface) *DryRunVerifier {
func NewDryRunVerifier(dynamicClient dynamic.Interface, openAPIGetter discovery.OpenAPISchemaInterface) *DryRunVerifier {
return &DryRunVerifier{
finder: NewCRDFinder(CRDFromDynamic(dynamicClient)),
openAPIGetter: discoveryClient,
openAPIGetter: openAPIGetter,
}
}

View File

@ -178,11 +178,7 @@ func (o *AnnotateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -218,11 +218,7 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(o.DynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(o.DynamicClient, f.OpenAPIGetter())
o.FieldManager = GetApplyFieldManagerFlag(cmd, o.ServerSideApply)
if o.ForceConflicts && !o.ServerSideApply {

View File

@ -128,11 +128,7 @@ func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command)
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.output = cmdutil.GetFlagString(cmd, "output")
o.shortOutput = o.output == "name"

View File

@ -29,6 +29,7 @@ import (
"strings"
"testing"
openapi_v2 "github.com/googleapis/gnostic/openapiv2"
"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
@ -41,6 +42,7 @@ import (
sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
dynamicfakeclient "k8s.io/client-go/dynamic/fake"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
@ -52,21 +54,38 @@ import (
)
var (
fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "swagger.json")}
testingOpenAPISchemaFns = []func() (openapi.Resources, error){nil, AlwaysErrorOpenAPISchemaFn, openAPISchemaFn}
AlwaysErrorOpenAPISchemaFn = func() (openapi.Resources, error) {
return nil, errors.New("cannot get openapi spec")
fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "swagger.json")}
testingOpenAPISchemas = []testOpenAPISchema{{OpenAPIGetter: &fakeSchema}, AlwaysErrorsOpenAPISchema, FakeOpenAPISchema}
AlwaysErrorsOpenAPISchema = testOpenAPISchema{
OpenAPISchemaFn: func() (openapi.Resources, error) {
return nil, errors.New("cannot get openapi spec")
},
OpenAPIGetter: &alwaysErrorsOpenAPISchema{},
}
openAPISchemaFn = func() (openapi.Resources, error) {
s, err := fakeSchema.OpenAPISchema()
if err != nil {
return nil, err
}
return openapi.NewOpenAPIData(s)
FakeOpenAPISchema = testOpenAPISchema{
OpenAPISchemaFn: func() (openapi.Resources, error) {
s, err := fakeSchema.OpenAPISchema()
if err != nil {
return nil, err
}
return openapi.NewOpenAPIData(s)
},
OpenAPIGetter: &fakeSchema,
}
codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
)
type testOpenAPISchema struct {
OpenAPISchemaFn func() (openapi.Resources, error)
OpenAPIGetter discovery.OpenAPISchemaInterface
}
type alwaysErrorsOpenAPISchema struct{}
func (o *alwaysErrorsOpenAPISchema) OpenAPISchema() (*openapi_v2.Document, error) {
return nil, errors.New("cannot get openapi schema")
}
func TestApplyExtraArgsFail(t *testing.T) {
f := cmdtesting.NewTestFactory()
defer f.Cleanup()
@ -518,7 +537,7 @@ func TestApplyObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply when a local object is specified", func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -540,7 +559,8 @@ func TestApplyObject(t *testing.T) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -566,7 +586,7 @@ func TestApplyPruneObjects(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply returns correct output", func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -588,7 +608,8 @@ func TestApplyPruneObjects(t *testing.T) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -631,7 +652,7 @@ func TestApplyObjectOutput(t *testing.T) {
t.Fatal(err)
}
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply returns correct output", func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -653,7 +674,8 @@ func TestApplyObjectOutput(t *testing.T) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -680,7 +702,7 @@ func TestApplyRetry(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply retries on conflict error", func(t *testing.T) {
firstPatch := true
retry := false
@ -714,7 +736,8 @@ func TestApplyRetry(t *testing.T) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -860,7 +883,7 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC)
pathSVC := "/namespaces/test/services/" + nameSVC
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply on multiple objects", func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -889,7 +912,8 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -941,7 +965,7 @@ func TestApplyNULLPreservation(t *testing.T) {
verifiedPatch := false
deploymentBytes := readDeploymentFromFile(t, filenameDeployObjServerside)
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply preserves NULL fields", func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -984,7 +1008,8 @@ func TestApplyNULLPreservation(t *testing.T) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -1016,7 +1041,7 @@ func TestUnstructuredApply(t *testing.T) {
verifiedPatch := false
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply works correctly with unstructured objects", func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -1050,7 +1075,8 @@ func TestUnstructuredApply(t *testing.T) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -1084,7 +1110,7 @@ func TestUnstructuredIdempotentApply(t *testing.T) {
}
path := "/namespaces/test/widgets/widget"
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test repeated apply operations on an unstructured object", func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
@ -1115,7 +1141,8 @@ func TestUnstructuredIdempotentApply(t *testing.T) {
}
}),
}
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
ioStreams, _, buf, errBuf := genericclioptions.NewTestIOStreams()
@ -1275,7 +1302,7 @@ func TestForceApply(t *testing.T) {
"post": 1,
}
for _, fn := range testingOpenAPISchemaFns {
for _, testingOpenAPISchema := range testingOpenAPISchemas {
t.Run("test apply with --force", func(t *testing.T) {
deleted := false
isScaledDownToZero := false
@ -1357,7 +1384,8 @@ func TestForceApply(t *testing.T) {
}
fakeDynamicClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
tf.FakeDynamicClient = fakeDynamicClient
tf.OpenAPISchemaFunc = fn
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
tf.FakeOpenAPIGetter = testingOpenAPISchema.OpenAPIGetter
tf.Client = tf.UnstructuredClient
tf.ClientConfigVal = &restclient.Config{}

View File

@ -147,7 +147,7 @@ func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.builder = f.NewBuilder()
o.scaleKindResolver = scale.NewDiscoveryScaleKindResolver(discoveryClient)

View File

@ -206,11 +206,7 @@ func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
@ -387,11 +383,7 @@ func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)

View File

@ -131,11 +131,7 @@ func (o *ClusterRoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Comma
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -151,11 +151,7 @@ func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {

View File

@ -156,11 +156,7 @@ func (o *CreateDeploymentOptions) Complete(f cmdutil.Factory, cmd *cobra.Command
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -62,7 +62,7 @@ var (
Create an ingress with the specified name.`))
ingressExample = templates.Examples(i18n.T(`
# Create a single ingress called 'simple' that directs requests to foo.com/bar to svc
# Create a single ingress called 'simple' that directs requests to foo.com/bar to svc
# svc1:8080 with a tls secret "my-cert"
kubectl create ingress simple --rule="foo.com/bar=svc1:8080,tls=my-cert"
@ -75,7 +75,7 @@ var (
--annotation ingress.annotation2=bla
# Create an ingress with the same host and multiple paths
kubectl create ingress multipath --class=default \
kubectl create ingress multipath --class=default \
--rule="foo.com/=svc:port" \
--rule="foo.com/admin/=svcadmin:portadmin"
@ -88,11 +88,11 @@ var (
kubectl create ingress ingtls --class=default \
--rule="foo.com/=svc:https,tls" \
--rule="foo.com/path/subpath*=othersvc:8080"
# Create an ingress with TLS enabled using a specific secret and pathType as Prefix
kubectl create ingress ingsecret --class=default \
--rule="foo.com/*=svc:8080,tls=secret1"
# Create an ingress with a default backend
kubectl create ingress ingdefault --class=default \
--default-backend=defaultsvc:http \
@ -198,11 +198,7 @@ func (o *CreateIngressOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -146,11 +146,7 @@ func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {

View File

@ -138,11 +138,7 @@ func (o *PriorityClassOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -136,11 +136,7 @@ func (o *QuotaOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []strin
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resourcecli.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resourcecli.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {

View File

@ -250,11 +250,7 @@ func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.OutputFormat = cmdutil.GetFlagString(cmd, "output")
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)

View File

@ -140,11 +140,7 @@ func (o *RoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicCient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicCient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {

View File

@ -118,11 +118,7 @@ func (o *ServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -128,11 +128,7 @@ func (o *ServiceAccountOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {

View File

@ -188,11 +188,7 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Co
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
if len(o.Raw) == 0 {
r := f.NewBuilder().

View File

@ -487,7 +487,7 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(o.DynamicClient, o.DiscoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(o.DynamicClient, f.OpenAPIGetter())
o.CmdNamespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {

View File

@ -222,11 +222,7 @@ func (o *DrainCmdOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.drainer.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.drainer.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
if o.drainer.Client, err = f.KubernetesClientSet(); err != nil {
return err

View File

@ -178,11 +178,7 @@ func (o *ExposeServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) e
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -175,11 +175,7 @@ func (o *LabelOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {

View File

@ -167,11 +167,7 @@ func (o *PatchOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
return nil
}

View File

@ -156,11 +156,7 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -114,11 +114,7 @@ func (o *UndoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
if o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace(); err != nil {
return err

View File

@ -226,11 +226,7 @@ func (o *RunOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
attachFlag := cmd.Flags().Lookup("attach")
if !attachFlag.Changed && o.Interactive {

View File

@ -157,11 +157,7 @@ func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {

View File

@ -229,11 +229,7 @@ func (o *EnvOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.dryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -149,11 +149,7 @@ func (o *SetImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.Output = cmdutil.GetFlagString(cmd, "output")
o.ResolveImage = resolveImageFunc

View File

@ -161,11 +161,7 @@ func (o *SetResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, ar
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -140,11 +140,7 @@ func (o *SetSelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.resources, o.selector, err = getResourcesAndSelector(args)
if err != nil {

View File

@ -142,11 +142,7 @@ func (o *SetServiceAccountOptions) Complete(f cmdutil.Factory, cmd *cobra.Comman
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
o.output = cmdutil.GetFlagString(cmd, "output")
o.updatePodSpecForObject = polymorphichelpers.UpdatePodSpecForObjectFn

View File

@ -132,11 +132,7 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()

View File

@ -147,11 +147,7 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
// retrieves resource and taint args from args

View File

@ -50,6 +50,8 @@ import (
"k8s.io/kubectl/pkg/util/openapi"
openapitesting "k8s.io/kubectl/pkg/util/openapi/testing"
"k8s.io/kubectl/pkg/validation"
openapi_v2 "github.com/googleapis/gnostic/openapiv2"
)
// InternalType is the schema for internal type
@ -398,6 +400,7 @@ type TestFactory struct {
UnstructuredClientForMappingFunc resource.FakeClientFunc
OpenAPISchemaFunc func() (openapi.Resources, error)
FakeOpenAPIGetter discovery.OpenAPISchemaInterface
}
// NewTestFactory returns an initialized TestFactory instance
@ -502,6 +505,23 @@ func (f *TestFactory) OpenAPISchema() (openapi.Resources, error) {
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(

View File

@ -20,6 +20,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
@ -61,6 +62,8 @@ type Factory interface {
// Returns a schema that can validate objects stored on disk.
Validator(validate bool) (validation.Schema, error)
// OpenAPISchema returns the schema openapi schema definition
// OpenAPISchema returns the parsed openapi schema definition
OpenAPISchema() (openapi.Resources, error)
// OpenAPIGetter returns a getter for the openapi schema document
OpenAPIGetter() discovery.OpenAPISchemaInterface
}

View File

@ -19,6 +19,7 @@ limitations under the License.
package util
import (
"errors"
"sync"
corev1 "k8s.io/api/core/v1"
@ -38,20 +39,17 @@ import (
type factoryImpl struct {
clientGetter genericclioptions.RESTClientGetter
// openAPIGetter loads and caches openapi specs
openAPIGetter openAPIGetter
}
type openAPIGetter struct {
once sync.Once
getter openapi.Getter
// Caches OpenAPI document and parsed resources
openAPIParser *openapi.CachedOpenAPIParser
openAPIGetter *openapi.CachedOpenAPIGetter
parser sync.Once
getter sync.Once
}
func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory {
if clientGetter == nil {
panic("attempt to instantiate client_access_factory with nil clientGetter")
}
f := &factoryImpl{
clientGetter: clientGetter,
}
@ -159,19 +157,32 @@ func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) {
}, nil
}
// OpenAPISchema returns metadata and structural information about Kubernetes object definitions.
// OpenAPISchema returns metadata and structural information about
// Kubernetes object definitions.
func (f *factoryImpl) OpenAPISchema() (openapi.Resources, error) {
discovery, err := f.clientGetter.ToDiscoveryClient()
if err != nil {
return nil, err
openAPIGetter := f.OpenAPIGetter()
if openAPIGetter == nil {
return nil, errors.New("no openapi getter")
}
// Lazily initialize the OpenAPIGetter once
f.openAPIGetter.once.Do(func() {
// Create the caching OpenAPIGetter
f.openAPIGetter.getter = openapi.NewOpenAPIGetter(discovery)
// Lazily initialize the OpenAPIParser once
f.parser.Do(func() {
// Create the caching OpenAPIParser
f.openAPIParser = openapi.NewOpenAPIParser(f.OpenAPIGetter())
})
// Delegate to the OpenAPIGetter
return f.openAPIGetter.getter.Get()
// Delegate to the OpenAPIPArser
return f.openAPIParser.Parse()
}
func (f *factoryImpl) OpenAPIGetter() discovery.OpenAPISchemaInterface {
discovery, err := f.clientGetter.ToDiscoveryClient()
if err != nil {
return nil
}
f.getter.Do(func() {
f.openAPIGetter = openapi.NewOpenAPIGetter(discovery)
})
return f.openAPIGetter
}

View File

@ -19,47 +19,64 @@ package openapi
import (
"sync"
openapi_v2 "github.com/googleapis/gnostic/openapiv2"
"k8s.io/client-go/discovery"
)
// synchronizedOpenAPIGetter fetches the openapi schema once and then caches it in memory
type synchronizedOpenAPIGetter struct {
// CachedOpenAPIGetter fetches the openapi schema once and then caches it in memory
type CachedOpenAPIGetter struct {
openAPIClient discovery.OpenAPISchemaInterface
// Cached results
sync.Once
openAPISchema Resources
openAPISchema *openapi_v2.Document
err error
openAPIClient discovery.OpenAPISchemaInterface
}
var _ Getter = &synchronizedOpenAPIGetter{}
// Getter is an interface for fetching openapi specs and parsing them into an Resources struct
type Getter interface {
// OpenAPIData returns the parsed OpenAPIData
Get() (Resources, error)
}
var _ discovery.OpenAPISchemaInterface = &CachedOpenAPIGetter{}
// NewOpenAPIGetter returns an object to return OpenAPIDatas which reads
// from a server, and then stores in memory for subsequent invocations
func NewOpenAPIGetter(openAPIClient discovery.OpenAPISchemaInterface) Getter {
return &synchronizedOpenAPIGetter{
func NewOpenAPIGetter(openAPIClient discovery.OpenAPISchemaInterface) *CachedOpenAPIGetter {
return &CachedOpenAPIGetter{
openAPIClient: openAPIClient,
}
}
// Resources implements Getter
func (g *synchronizedOpenAPIGetter) Get() (Resources, error) {
// OpenAPISchema implements OpenAPISchemaInterface.
func (g *CachedOpenAPIGetter) OpenAPISchema() (*openapi_v2.Document, error) {
g.Do(func() {
s, err := g.openAPIClient.OpenAPISchema()
if err != nil {
g.err = err
return
}
g.openAPISchema, g.err = NewOpenAPIData(s)
g.openAPISchema, g.err = g.openAPIClient.OpenAPISchema()
})
// Return the save result
// Return the saved result.
return g.openAPISchema, g.err
}
type CachedOpenAPIParser struct {
openAPIClient discovery.OpenAPISchemaInterface
// Cached results
sync.Once
openAPIResources Resources
err error
}
func NewOpenAPIParser(openAPIClient discovery.OpenAPISchemaInterface) *CachedOpenAPIParser {
return &CachedOpenAPIParser{
openAPIClient: openAPIClient,
}
}
func (p *CachedOpenAPIParser) Parse() (Resources, error) {
p.Do(func() {
oapi, err := p.openAPIClient.OpenAPISchema()
if err != nil {
p.err = err
return
}
p.openAPIResources, p.err = NewOpenAPIData(oapi)
})
return p.openAPIResources, p.err
}

View File

@ -40,12 +40,12 @@ func (f *FakeCounter) OpenAPISchema() (*openapi_v2.Document, error) {
var _ = Describe("Getting the Resources", func() {
var client FakeCounter
var instance openapi.Getter
var instance *openapi.CachedOpenAPIParser
var expectedData openapi.Resources
BeforeEach(func() {
client = FakeCounter{}
instance = openapi.NewOpenAPIGetter(&client)
instance = openapi.NewOpenAPIParser(openapi.NewOpenAPIGetter(&client))
var err error
expectedData, err = openapi.NewOpenAPIData(nil)
Expect(err).To(BeNil())
@ -55,12 +55,12 @@ var _ = Describe("Getting the Resources", func() {
It("should return the same data for multiple calls", func() {
Expect(client.Calls).To(Equal(0))
result, err := instance.Get()
result, err := instance.Parse()
Expect(err).To(BeNil())
Expect(result).To(Equal(expectedData))
Expect(client.Calls).To(Equal(1))
result, err = instance.Get()
result, err = instance.Parse()
Expect(err).To(BeNil())
Expect(result).To(Equal(expectedData))
// No additional client calls expected
@ -73,11 +73,11 @@ var _ = Describe("Getting the Resources", func() {
Expect(client.Calls).To(Equal(0))
client.Err = fmt.Errorf("expected error")
_, err := instance.Get()
_, err := instance.Parse()
Expect(err).To(Equal(client.Err))
Expect(client.Calls).To(Equal(1))
_, err = instance.Get()
_, err = instance.Parse()
Expect(err).To(Equal(client.Err))
// No additional client calls expected
Expect(client.Calls).To(Equal(1))