diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain.go b/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain.go index 7965250bb20..1cde900601b 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain.go @@ -73,8 +73,8 @@ type ExplainOptions struct { args []string - Mapper meta.RESTMapper - Schema openapi.Resources + Mapper meta.RESTMapper + openAPIGetter openapi.OpenAPIResourcesGetter // Name of the template to use with the openapiv3 template renderer. OutputFormat string @@ -123,17 +123,14 @@ func (o *ExplainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return err } - o.Schema, err = f.OpenAPISchema() - if err != nil { - return err - } - // Only openapi v3 needs the discovery client. o.OpenAPIV3Client, err = f.OpenAPIV3Client() if err != nil { return err } + // Lazy-load the OpenAPI V2 Resources, so they're not loaded when using OpenAPI V3. + o.openAPIGetter = f o.args = args return nil } @@ -224,7 +221,11 @@ func (o *ExplainOptions) renderOpenAPIV2( gvk = apiVersion.WithKind(gvk.Kind) } - schema := o.Schema.LookupResource(gvk) + resources, err := o.openAPIGetter.OpenAPISchema() + if err != nil { + return err + } + schema := resources.LookupResource(gvk) if schema == nil { return fmt.Errorf("couldn't find resource for %q", gvk) } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain_test.go index b63dbfd1daa..94c3ff8090b 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/explain/explain_test.go @@ -275,3 +275,44 @@ func runExplainTestCases(t *testing.T, cases []explainTestCase) { buf.Reset() } } + +// OpenAPI V2 specifications retrieval -- should never be called. +func panicOpenAPISchemaFn() (openapi.Resources, error) { + panic("should never be called") +} + +// OpenAPI V3 specifications retrieval does *not* retrieve V2 specifications. +func TestExplainOpenAPIV3DoesNotLoadOpenAPIV2Specs(t *testing.T) { + // Set up OpenAPI V3 specifications endpoint for explain. + fakeServer, err := clienttestutil.NewFakeOpenAPIV3Server(filepath.Join(testDataPath, "openapi", "v3")) + if err != nil { + t.Fatalf("error starting fake openapi server: %v", err.Error()) + } + defer fakeServer.HttpServer.Close() + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() + tf.OpenAPIV3ClientFunc = func() (openapiclient.Client, error) { + fakeDiscoveryClient := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: fakeServer.HttpServer.URL}) + return fakeDiscoveryClient.OpenAPIV3(), nil + } + // OpenAPI V2 specifications retrieval will panic if called. + tf.OpenAPISchemaFunc = panicOpenAPISchemaFn + + // Explain the following resources, validating the command does not panic. + cmd := explain.NewCmdExplain("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard()) + resources := []string{"pods", "services", "endpoints", "configmaps"} + for _, resource := range resources { + cmd.Run(cmd, []string{resource}) + } + // Verify retrieving OpenAPI V2 specifications will panic. + defer func() { + if panicErr := recover(); panicErr == nil { + t.Fatal("expecting panic for openapi v2 retrieval") + } + }() + // Set OpenAPI V2 output flag for explain. + if err := cmd.Flags().Set("output", "plaintext-openapiv2"); err != nil { + t.Fatal(err) + } + cmd.Run(cmd, []string{"pods"}) +}