Merge pull request #129784 from soltysh/refactor_explain

Refactors explain command to split flags from options
This commit is contained in:
Kubernetes Prow Robot 2025-01-23 22:47:21 -08:00 committed by GitHub
commit f6f06806cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 60 deletions

View File

@ -98,14 +98,10 @@ var (
`) `)
) )
func boundObjectKindToAPIVersions() map[string]string { var boundObjectKinds = map[string]string{
kinds := map[string]string{ "Pod": "v1",
"Pod": "v1", "Secret": "v1",
"Secret": "v1", "Node": "v1",
"Node": "v1",
}
return kinds
} }
func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions { func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions {
@ -149,7 +145,7 @@ func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericiooptions.IOStreams)
cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.") cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.")
cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+ cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+
"Supported kinds are "+strings.Join(sets.List(sets.KeySet(boundObjectKindToAPIVersions())), ", ")+". "+ "Supported kinds are "+strings.Join(sets.List(sets.KeySet(boundObjectKinds)), ", ")+". "+
"If set, --bound-object-name must be provided.") "If set, --bound-object-name must be provided.")
cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+ cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+
"The token will expire when the object is deleted. "+ "The token will expire when the object is deleted. "+
@ -226,8 +222,8 @@ func (o *TokenOptions) Validate() error {
return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided") return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided")
} }
} else { } else {
if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok { if _, ok := boundObjectKinds[o.BoundObjectKind]; !ok {
return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.List(sets.KeySet(boundObjectKindToAPIVersions())), ", ")) return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.List(sets.KeySet(boundObjectKinds)), ", "))
} }
if len(o.BoundObjectName) == 0 { if len(o.BoundObjectName) == 0 {
return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided") return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided")
@ -250,7 +246,7 @@ func (o *TokenOptions) Run() error {
if len(o.BoundObjectKind) > 0 { if len(o.BoundObjectKind) > 0 {
request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{ request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
Kind: o.BoundObjectKind, Kind: o.BoundObjectKind,
APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind], APIVersion: boundObjectKinds[o.BoundObjectKind],
Name: o.BoundObjectName, Name: o.BoundObjectName,
UID: types.UID(o.BoundObjectUID), UID: types.UID(o.BoundObjectUID),
} }

View File

@ -56,44 +56,77 @@ var (
# Get the documentation of a specific field of a resource # Get the documentation of a specific field of a resource
kubectl explain pods.spec.containers kubectl explain pods.spec.containers
# Get the documentation of resources in different format # Get the documentation of resources in different format
kubectl explain deployment --output=plaintext-openapiv2`)) kubectl explain deployment --output=plaintext-openapiv2`))
)
const (
plaintextTemplateName = "plaintext" plaintextTemplateName = "plaintext"
plaintextOpenAPIV2TemplateName = "plaintext-openapiv2" plaintextOpenAPIV2TemplateName = "plaintext-openapiv2"
) )
type ExplainOptions struct { // ExplainFlags directly reflect the information that CLI is gathering via flags.
genericiooptions.IOStreams // They will be converted to Options, which reflect the runtime requirements for
// the command.
CmdParent string type ExplainFlags struct {
APIVersion string APIVersion string
Recursive bool
args []string
Mapper meta.RESTMapper
openAPIGetter openapi.OpenAPIResourcesGetter
// Name of the template to use with the openapiv3 template renderer.
OutputFormat string OutputFormat string
Recursive bool
// Client capable of fetching openapi documents from the user's cluster genericiooptions.IOStreams
OpenAPIV3Client openapiclient.Client
} }
func NewExplainOptions(parent string, streams genericiooptions.IOStreams) *ExplainOptions { // NewExplainFlags returns a default ExplainFlags
return &ExplainOptions{ func NewExplainFlags(streams genericiooptions.IOStreams) *ExplainFlags {
IOStreams: streams, return &ExplainFlags{
CmdParent: parent,
OutputFormat: plaintextTemplateName, OutputFormat: plaintextTemplateName,
IOStreams: streams,
} }
} }
// AddFlags registers flags for a cli
func (flags *ExplainFlags) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&flags.Recursive, "recursive", flags.Recursive, "Print the fields of fields (Currently only 1 level deep)")
cmd.Flags().StringVar(&flags.APIVersion, "api-version", flags.APIVersion, "Get different explanations for particular API version (API group/version)")
cmd.Flags().StringVar(&flags.OutputFormat, "output", plaintextTemplateName, "Format in which to render the schema (plaintext, plaintext-openapiv2)")
}
// ToOptions converts from CLI inputs to runtime input
func (flags *ExplainFlags) ToOptions(f cmdutil.Factory, parent string, args []string) (*ExplainOptions, error) {
mapper, err := f.ToRESTMapper()
if err != nil {
return nil, err
}
// Only openapi v3 needs the discovery client.
openAPIV3Client, err := f.OpenAPIV3Client()
if err != nil {
return nil, err
}
o := &ExplainOptions{
IOStreams: flags.IOStreams,
Recursive: flags.Recursive,
APIVersion: flags.APIVersion,
OutputFormat: flags.OutputFormat,
CmdParent: parent,
args: args,
Mapper: mapper,
openAPIGetter: f,
OpenAPIV3Client: openAPIV3Client,
}
return o, nil
}
// NewCmdExplain returns a cobra command for swagger docs // NewCmdExplain returns a cobra command for swagger docs
func NewCmdExplain(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { func NewCmdExplain(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
o := NewExplainOptions(parent, streams) flags := NewExplainFlags(streams)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "explain TYPE [--recursive=FALSE|TRUE] [--api-version=api-version-group] [-o|--output=plaintext|plaintext-openapiv2]", Use: "explain TYPE [--recursive=FALSE|TRUE] [--api-version=api-version-group] [-o|--output=plaintext|plaintext-openapiv2]",
@ -102,37 +135,34 @@ func NewCmdExplain(parent string, f cmdutil.Factory, streams genericiooptions.IO
Long: explainLong + "\n\n" + cmdutil.SuggestAPIResources(parent), Long: explainLong + "\n\n" + cmdutil.SuggestAPIResources(parent),
Example: explainExamples, Example: explainExamples,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args)) o, err := flags.ToOptions(f, parent, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run()) cmdutil.CheckErr(o.Run())
}, },
} }
cmd.Flags().BoolVar(&o.Recursive, "recursive", o.Recursive, "When true, print the name of all the fields recursively. Otherwise, print the available fields with their description.")
cmd.Flags().StringVar(&o.APIVersion, "api-version", o.APIVersion, "Use given api-version (group/version) of the resource.")
// Only enable --output as a valid flag if the feature is enabled flags.AddFlags(cmd)
cmd.Flags().StringVarP(&o.OutputFormat, "output", "o", plaintextTemplateName, "Format in which to render the schema. Valid values are: (plaintext, plaintext-openapiv2).")
return cmd return cmd
} }
func (o *ExplainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { type ExplainOptions struct {
var err error genericiooptions.IOStreams
o.Mapper, err = f.ToRESTMapper()
if err != nil {
return err
}
// Only openapi v3 needs the discovery client. Recursive bool
o.OpenAPIV3Client, err = f.OpenAPIV3Client() APIVersion string
if err != nil { // Name of the template to use with the openapiv3 template renderer.
return err OutputFormat string
}
// Lazy-load the OpenAPI V2 Resources, so they're not loaded when using OpenAPI V3. CmdParent string
o.openAPIGetter = f args []string
o.args = args
return nil Mapper meta.RESTMapper
openAPIGetter openapi.OpenAPIResourcesGetter
// Client capable of fetching openapi documents from the user's cluster
OpenAPIV3Client openapiclient.Client
} }
func (o *ExplainOptions) Validate() error { func (o *ExplainOptions) Validate() error {

View File

@ -57,9 +57,9 @@ func TestExplainInvalidArgs(t *testing.T) {
tf := cmdtesting.NewTestFactory() tf := cmdtesting.NewTestFactory()
defer tf.Cleanup() defer tf.Cleanup()
opts := explain.NewExplainOptions("kubectl", genericiooptions.NewTestIOStreamsDiscard()) flags := explain.NewExplainFlags(genericiooptions.NewTestIOStreamsDiscard())
cmd := explain.NewCmdExplain("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
err := opts.Complete(tf, cmd, []string{}) opts, err := flags.ToOptions(tf, "kubectl", []string{})
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }
@ -69,7 +69,7 @@ func TestExplainInvalidArgs(t *testing.T) {
t.Error("unexpected non-error") t.Error("unexpected non-error")
} }
err = opts.Complete(tf, cmd, []string{"resource1", "resource2"}) opts, err = flags.ToOptions(tf, "kubectl", []string{"resource1", "resource2"})
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }
@ -84,9 +84,9 @@ func TestExplainNotExistResource(t *testing.T) {
tf := cmdtesting.NewTestFactory() tf := cmdtesting.NewTestFactory()
defer tf.Cleanup() defer tf.Cleanup()
opts := explain.NewExplainOptions("kubectl", genericiooptions.NewTestIOStreamsDiscard()) flags := explain.NewExplainFlags(genericiooptions.NewTestIOStreamsDiscard())
cmd := explain.NewCmdExplain("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
err := opts.Complete(tf, cmd, []string{"foo"}) opts, err := flags.ToOptions(tf, "kubectl", []string{"foo"})
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }