Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

JSON & YAML output for kubectl api-resources #126338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
Loading
from

Conversation

dharmit
Copy link

@dharmit dharmit commented Jul 24, 2024

What type of PR is this?

/kind feature

What this PR does / why we need it:

JSON & YAML output for kubectl api-resources

Which issue(s) this PR fixes:

Fixes kubernetes/kubectl#1382

Special notes for your reviewer:

Does this PR introduce a user-facing change?

Adds machine readable output options (JSON & YAML) to kubectl api-resources

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:


@k8s-ci-robot k8s-ci-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. kind/feature Categorizes issue or PR as related to a new feature. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Jul 24, 2024
@k8s-ci-robot
Copy link
Contributor

This issue is currently awaiting triage.

If a SIG or subproject determines this is a relevant issue, they will accept it by applying the triage/accepted label and provide further guidance.

The triage/accepted label can be added by org members by writing /triage accepted in a comment.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added the needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. label Jul 24, 2024
@k8s-ci-robot
Copy link
Contributor

Welcome @dharmit!

It looks like this is your first PR to kubernetes/kubernetes 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes/kubernetes has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot
Copy link
Contributor

Hi @dharmit. Thanks for your PR.

I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. labels Jul 24, 2024
@k8s-ci-robot k8s-ci-robot added area/kubectl sig/cli Categorizes an issue or PR as relevant to SIG CLI. and removed do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Jul 24, 2024
@dharmit
Copy link
Author

dharmit commented Jul 24, 2024

Unit tests fail like below:
$ make test WHAT=./staging/src/k8s.io/kubectl/pkg/cmd/apiresources
+++ [0724 21:02:23] Set GOMAXPROCS automatically to 24
+++ [0724 21:02:23] Running tests without code coverage and with -race
--- FAIL: TestAPIResourcesRun (0.02s)
    --- FAIL: TestAPIResourcesRun/no_headers (0.00s)
        apiresources_test.go:389: unexpected output: bars            v1             true    Bar
            bars            v1             true    Bar
            foos     f,fo   v1             false   Foo
            foos     f,fo   v1             false   Foo
            bazzes   b      somegroup/v1   true    Baz
            bazzes   b      somegroup/v1   true    Baz

            expected: bars            v1             true    Bar
            foos     f,fo   v1             false   Foo
            bazzes   b      somegroup/v1   true    Baz
    --- FAIL: TestAPIResourcesRun/specific_api_group (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bazzes   b            somegroup/v1   true         Baz
    --- FAIL: TestAPIResourcesRun/output_wide (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND   VERBS                    CATEGORIES
            bars                  v1             true         Bar    get,list,create
            bars                  v1             true         Bar    get,list,create
            bars                  v1             true         Bar    get,list,create
            foos     f,fo         v1             false        Foo    get,list                 some-category
            foos     f,fo         v1             false        Foo    get,list                 some-category
            foos     f,fo         v1             false        Foo    get,list                 some-category
            bazzes   b            somegroup/v1   true         Baz    get,list,create,delete   some-category,another-category
            bazzes   b            somegroup/v1   true         Baz    get,list,create,delete   some-category,another-category
            bazzes   b            somegroup/v1   true         Baz    get,list,create,delete   some-category,another-category
            bazzes   b            somegroup/v1   true         Baz    get,list,create,delete   some-category,another-category

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND   VERBS                    CATEGORIES
            bars                  v1             true         Bar    get,list,create
            foos     f,fo         v1             false        Foo    get,list                 some-category
            bazzes   b            somegroup/v1   true         Baz    get,list,create,delete   some-category,another-category
    --- FAIL: TestAPIResourcesRun/output_name (0.00s)
        apiresources_test.go:389: unexpected output: bars
            bars
            bars
            bars
            foos
            foos
            foos
            foos
            bazzes.somegroup
            bazzes.somegroup
            bazzes.somegroup
            bazzes.somegroup
            bazzes.somegroup

            expected: bars
            foos
            bazzes.somegroup
    --- FAIL: TestAPIResourcesRun/output_json (0.00s)
        apiresources_test.go:389: unexpected output: {
                "kind": "v1",
                "apiVersion": "APIResourceList",
                "groupVersion": "",
                "resources": [
                    {
                        "name": "foos",
                        "singularName": "",
                        "namespaced": false,
                        "kind": "Foo",
                        "verbs": [
                            "get",
                            "list"
                        ],
                        "shortNames": [
                            "f",
                            "fo"
                        ],
                        "categories": [
                            "some-category"
                        ]
                    },
                    {
                        "name": "bars",
                        "singularName": "",
                        "namespaced": true,
                        "kind": "Bar",
                        "verbs": [
                            "get",
                            "list",
                            "create"
                        ]
                    },
                    {
                        "name": "bazzes",
                        "singularName": "",
                        "namespaced": true,
                        "kind": "Baz",
                        "verbs": [
                            "get",
                            "list",
                            "create",
                            "delete"
                        ],
                        "shortNames": [
                            "b"
                        ],
                        "categories": [
                            "some-category",
                            "another-category"
                        ]
                    },
                    {
                        "name": "NoVerbs",
                        "singularName": "",
                        "namespaced": true,
                        "kind": "NoVerbs",
                        "verbs": [],
                        "shortNames": [
                            "b"
                        ]
                    }
                ]
            }

            expected: {
            	"kind": "v1",
            	"apiVersion": "APIResourceList",
            	"groupVersion": "",
            	"resources": [
            		{
            			"name": "foos",
            			"singularName": "",
            			"namespaced": false,
            			"kind": "Foo",
            			"verbs": [
            				"get",
            				"list"
            			],
            			"shortNames": [
            				"f",
            				"fo"
            			],
            			"categories": [
            				"some-category"
            			]
            		},
            		{
            			"name": "bars",
            			"singularName": "",
            			"namespaced": true,
            			"kind": "Bar",
            			"verbs": [
            				"get",
            				"list",
            				"create"
            			]
            		},
            		{
            			"name": "bazzes",
            			"singularName": "",
            			"namespaced": true,
            			"kind": "Baz",
            			"verbs": [
            				"get",
            				"list",
            				"create",
            				"delete"
            			],
            			"shortNames": [
            				"b"
            			],
            			"categories": [
            				"some-category",
            				"another-category"
            			]
            		},
            		{
            			"name": "NoVerbs",
            			"singularName": "",
            			"namespaced": true,
            			"kind": "NoVerbs",
            			"verbs": [],
            			"shortNames": [
            				"b"
            			]
            		}
            	]
            }
    --- FAIL: TestAPIResourcesRun/namespaced (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bazzes   b            somegroup/v1   true         Baz
    --- FAIL: TestAPIResourcesRun/single_verb (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bazzes   b            somegroup/v1   true         Baz
    --- FAIL: TestAPIResourcesRun/multiple_verbs (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bazzes   b            somegroup/v1   true         Baz
    --- FAIL: TestAPIResourcesRun/single_category (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
    --- FAIL: TestAPIResourcesRun/multiple_categories (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bazzes   b            somegroup/v1   true         Baz
    --- FAIL: TestAPIResourcesRun/sort_by_name (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bazzes   b            somegroup/v1   true         Baz
            foos     f,fo         v1             false        Foo
    --- FAIL: TestAPIResourcesRun/sort_by_kind (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bazzes   b            somegroup/v1   true         Baz
            foos     f,fo         v1             false        Foo
    --- FAIL: TestAPIResourcesRun/cached (0.00s)
        apiresources_test.go:389: unexpected output: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz
            bazzes   b            somegroup/v1   true         Baz

            expected: NAME     SHORTNAMES   APIVERSION     NAMESPACED   KIND
            bars                  v1             true         Bar
            foos     f,fo         v1             false        Foo
            bazzes   b            somegroup/v1   true         Baz
FAIL
FAIL	k8s.io/kubectl/pkg/cmd/apiresources	0.227s
FAIL
make: *** [Makefile:192: test] Error 1

@brianpursley
Copy link
Member

brianpursley commented Jul 24, 2024

@dharmit I'll take a closer look at this PR later, but to answer your question about the failing unit test, it appears to be due to resources being a global variable.

Adding this before for _, r := range lists should prevent the error:

resources = []groupResource{}

@brianpursley
Copy link
Member

/ok-to-test

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Jul 24, 2024
Copy link
Member

@brianpursley brianpursley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dharmit I left a few comments for you to look into.

Ping me if you need clarification or if you make changes and want me to take another look.

name += "." + r.APIGroup
}
if _, err := fmt.Fprintf(writer, "%s\n", name); err != nil {
//errs = append(errs, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the original code, errors were collected and an aggregate error was returned.

Can this code do the same thing as the original code, in order to preserve the existing behavior?

Copy link
Author

@dharmit dharmit Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I wanted an opinion on, so I left this part commented instead of removing it.

The only way I could think of preserving the existing behaviour was by introducing a goroutine and using an error channel to put each error on. Would that be reasonable? Or does it sound like over-engineering?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it should need channels.

It should be able to return an aggregate error, something like this:

        if len(errs) > 0 {
		return errors.NewAggregate(errs)
	}

}
for _, r := range lists {
rl.APIResources = append(rl.APIResources, r.APIResources...)
}

for _, list := range lists {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this entire for loop be contained within APIResourcesPrinter?

It only applies to when you are not printing JSON or YAML, right?

Then I think that solves the resources global variable problem, because resources can remain a local variable inside APIResourcesPrinter now, if I am understanding this correctly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually the part that took me the longest while working on this issue. So I hope I understood things correctly.

So lists populated by the call

lists, err := o.discoveryClient.ServerPreferredResources()

is a slice of type metav1.APIResourceList. Initially, I intended to use it directly in the respective PrintObj methods. But while metav1.APIResourceList itself implements the runtime.Object interface, a slice of it doesn't. Hence, it is how you see it right now.

I could possibly be missing something here. So please correct me if I am wrong. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think I understand the reason.

You're re-purposing metav1.APIResourceList so that you have something that implements runtime.Object for the printer interface to work.

However, this doesn't seem like a good idea to me. It is really a list of lists, so merging them all into a single APIResourceList is losing some necessary details. For example, I notice in the unit test for json, it is missing the group version (v1 for Foo and Bar, and somegroup/v1 for Baz and NoVerbs).

return err
}
}

sort.Stable(sortableResource{resources, o.SortBy})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my previous comment, I think this can be moved inside APIResourcesPrinter because it does not apply when you are printing JSON or YAML.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to keep it here, since there's no straightforward way of accessing the o.SortBy within APIResourcePrinter as it is right now. Of course, we could modify APIResourcePrinter to also include the sortBy field and then this could go there. I'll do as you suggest.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think sorting would be an implementation detail of the printer, so it makes sense to have a sortBy field on the printer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, there is no need sorting for json and yaml. We can add validation to not allow this and sorting can be moved under custom (default) printer.

@dharmit
Copy link
Author

dharmit commented Jul 25, 2024

Hi @dharmit I left a few comments for you to look into.

@brianpursley thanks a lot for your feedback. 🙏🏽

Ping me if you need clarification or if you make changes and want me to take another look.

I have made a few changes and have a few questions for things I haven't changed. Your responses to how I could approach this would be greatly helpful.

@brianpursley
Copy link
Member

OK, it looks like the problem is that you've got lists, which is of type []*metav1.APIResourceList, but like you said, it doesn't implement runtime.Object so it can't be passed to PrintObj.

I'm trying to think of a good way to get around this situation...

🤔

@brianpursley
Copy link
Member

I wonder if it even makes sense to try to use the ResourcePrinter interface for this command. And I say this because these records aren't actually resources, they are metadata about the known resource types, so I feel like this might be a case of trying to make something fit when it is not an exact fit.

Some other commands have alternate printers, like kubectl top and kubectl explain.

I wonder if this command could have its own printer that doesn't require a runtime.Object and uses []*metav1.APIResourceList instead.

Check out metrics_printer.go which kubectl top uses. It's not exactly the same situation, but it is an example of what I'm talking out with a printer that does not implement ResourcePrinter.

@dharmit
Copy link
Author

dharmit commented Jul 28, 2024

I wonder if it even makes sense to try to use the ResourcePrinter interface for this command. And I say this because these records aren't actually resources, they are metadata about the known resource types, so I feel like this might be a case of trying to make something fit when it is not an exact fit.

I get your point about trying to make something fit that's not an exact fit. What's also messed up in the current JSON output is that apiVersion shows as APIResourceList and kind shows v1. 🤦🏽‍♂️

Some other commands have alternate printers, like kubectl top and kubectl explain.

Neither of these have JSON output. OTOH, kubectl get seems to have a repurposed printer too. Could that be a better reference? I have only skimmed through it while working on this one. Will check it in more detail now.

I'm literally brainstorming here so that I don't end up going down a rabbit hole, because I spent a few weeks before opening this PR. 😉

Also, @ardaguclu WDYT? Pinging you because you reviewed an earlier PR for this feature - #116926.

@@ -58,22 +59,25 @@ var (
kubectl api-resources --api-group=rbac.authorization.k8s.io`)
)

var resources []groupResource
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes everything harder, there shouldn't be any global variable for this.

@@ -151,6 +156,23 @@ func (o *APIResourceOptions) Complete(restClientGetter genericclioptions.RESTCli
o.groupChanged = cmd.Flags().Changed("api-group")
o.nsChanged = cmd.Flags().Changed("namespaced")

var printer printers.ResourcePrinter
if o.PrintFlags.OutputFormat != nil && len(*o.PrintFlags.OutputFormat) > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in this function looks correct.

return err
}
}

sort.Stable(sortableResource{resources, o.SortBy})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, there is no need sorting for json and yaml. We can add validation to not allow this and sorting can be moved under custom (default) printer.

noHeaders bool
}

type PrintFlags struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to have PrintFlags in here, built-in PrintFlags already achieves this for us.

OutputFormat *string
}

func (f *PrintFlags) AllowedFormats() []string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to have AllowedFormats in here and probably the reason of the usage of JSONYamlPrintFlags. We can manually check this in the code.

return p, nil
}

func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, no need

`,
expectedInvalidations: 1,
},
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this unit test. But we definitely need more.

@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Oct 18, 2024
@dharmit
Copy link
Author

dharmit commented Oct 18, 2024

This mechanism looks good to me. We definitely need unit tests and integration test to verify the behavior.

@ardaguclu I have added unit tests for JSON & YAML output and also one more that I thought was relevant to cover. PTAL. Please also suggest me if more unit test needs to be added. I couldn't think of more.

As for integration tests, can you point me where I should look for example tests for kubectl api-resources? I couldn't find any in test/e2e/kubectl/ or under test/integration.

@dharmit
Copy link
Author

dharmit commented Oct 18, 2024

@ardaguclu I have updated the PR based on your feedback and added unit tests. Just want to point out that @brianpursley mentioned in the past that the JSON/YAML outputs should preferably have that.

However, we're creating that groupVersion info here. I don't see how we could have it in the current approach of flattening the []metav1.APIResourceList.

Otherwise, I think, this PR is ready for review.

@dharmit dharmit marked this pull request as ready for review October 18, 2024 09:14
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 18, 2024
@dharmit
Copy link
Author

dharmit commented Oct 18, 2024

Regarding the failing linter hints. The first error is:

ERROR: staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources.go:239:5: S1002: should omit comparison to bool constant, can be simplified to `!o.NoHeaders` (gosimple)
ERROR: 	if o.NoHeaders == false && *o.PrintFlags.OutputFormat == "" || (*o.PrintFlags.OutputFormat == "wide" && !o.NoHeaders) { 

We're checking o.NoHeaders == false already even outside this PR. Wonder why it's failing now. 🤔

And the other three errors are with unit tests:

ERROR: staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources_test.go:206:20: Error return value of `(*github.com/spf13/pflag.FlagSet).Set` is not checked (errcheck)
ERROR: 				cmd.Flags().Set("output", "wide")
ERROR: 				               ^
ERROR: staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources_test.go:207:20: Error return value of `(*github.com/spf13/pflag.FlagSet).Set` is not checked (errcheck)
ERROR: 				cmd.Flags().Set("no-headers", "true")
ERROR: 				               ^
ERROR: staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources_test.go:318:20: Error return value of `(*github.com/spf13/pflag.FlagSet).Set` is not checked (errcheck)
ERROR: 				cmd.Flags().Set("output", "json")
ERROR: 				               ^
ERROR: staging/src/k8s.io/kubectl/pkg/cmd/apiresources/apiresources_test.go:380:20: Error return value of `(*github.com/spf13/pflag.FlagSet).Set` is not checked (errcheck)
ERROR: 				cmd.Flags().Set("output", "yaml")
ERROR: 				               ^

But the error return value of other test cases is also not being checked, AFAICS.

EDIT: Just noticed similar linter errors for other PRs too (#125489, #127341).

What am I missing here? I'll change the first one from o.NoHeaders == false to !o.NoHeaders, but what do I need to do about the issue it's pointing out in the unit tests?

What's also confusing me is that both make verify and make test complete just fine without any errors on my local system. Should I run any other tests locally?

Also, what is the meaning of expectedInvalidations in the test struct?

}
}

if o.NoHeaders == false && o.Output != "name" {
if err = printContextHeaders(w, o.Output); err != nil {
if o.NoHeaders == false && *o.PrintFlags.OutputFormat == "" || (*o.PrintFlags.OutputFormat == "wide" && !o.NoHeaders) {
Copy link
Member

@MikeSpreitzer MikeSpreitzer Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if o.NoHeaders == false && *o.PrintFlags.OutputFormat == "" || (*o.PrintFlags.OutputFormat == "wide" && !o.NoHeaders) {
if !o.NoHeaders && (*o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide") {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. My local branch had the !o.NoHeaders part of it already, but not the correct OR check that you provided. Done.

@MikeSpreitzer
Copy link
Member

In the unit tests I do not see API group and version. Those are part of the existing output formats, and should be present in the JSON and YAML formats too.

@dharmit
Copy link
Author

dharmit commented Nov 7, 2024

In the unit tests I do not see API group and version. Those are part of the existing output formats, and should be present in the JSON and YAML formats too.

To generate the JSON and YAML outputs, we are flattening the slice of APIResourceList we get here:

lists, err := o.discoveryClient.ServerPreferredResources()

We are flattening it because each element of that slice contains a slice of APIResource.

Now, the GroupVersion information is available per APIResourceList whereas the JSON and YAML outputs in the PR's current form are giving us APIResource. And the reason we are doing this is that we want to reuse existing JSONYAMLPrinter instead of implementing a custom struct. Ref: #116926 (review). A []APIResourceList doesn't implement the required interface, whereas APIResourceList does.

From a user perspective, I understand that this might not be ideal output. But I can't think of a way here without creating a new struct that also implements the ResourcePrinter interface, which requires implementing the runtime.Object interface:

// ResourcePrinter is an interface that knows how to print runtime objects.
type ResourcePrinter interface {
// PrintObj receives a runtime object, formats it and prints it to a writer.
PrintObj(runtime.Object, io.Writer) error
}

PS: I don't expect to have done a good job at explaining the dilemma here, but I'm willing to make changes that seem necessary here. @ardaguclu could you please chime in? :)

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Dec 14, 2024
Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>
Signed-off-by: Dharmit Shah <shahdharmit@gmail.com>
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Dec 19, 2024
@k8s-ci-robot
Copy link
Contributor

@dharmit: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-kubernetes-linter-hints 7053a1d link false /test pull-kubernetes-linter-hints

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Copy link
Contributor

@soltysh soltysh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reviewing the entire PR I'm more convinced that rather than using entire PrintFlags struct, you should only add json/yaml printer, since you're not using anything else other than just those two printers. Even more, rather than wiring the flags structure, I'd advise you to just inject the yaml/json printer directly based on the --output flag value provided by the user. I think this should simplify your implementation a bit.

supportedOutputTypes := sets.New[string]("", "wide", "name")
if !supportedOutputTypes.Has(o.Output) {
return fmt.Errorf("--output %v is not available", o.Output)
supportedOutputTypes := sets.NewString("", "wide", "name", "json", "yaml")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've decided to use entire PrintFlags which includes json/yaml printer, name printer and also template printer. Each one of them adds its own flags, but also has the ability to validate the contents of --output flag. With this check you're basically eliminating that last printer which I'm not sure we want here in the first place.

So my question for you is, which approach you prefer to have all of the printers (including the templating ones as well) or only the json/yaml and name printers? In either case this check is not necessary, since when creating the printer you'll get appropriate error if --output flag is not one of the accepted ones.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So my question for you is, which approach you prefer to have all of the printers (including the templating ones as well) or only the json/yaml and name printers?

Based on what you mentioned, I'd go with only the JSON/YAML printers and skip the rest.

In either case this check is not necessary, since when creating the printer you'll get appropriate error if --output flag is not one of the accepted ones.

But there already was a check for sets.New[string]("", "wide", "name"). Don't we need these? If no, how do we validate this?

Besides, we don't have OutputFormat field any more if we go with JSONYamlPrintFlags. What I can't wrap my head around is how do we make the user choose from these options:

  • wide
  • name
  • json
  • yaml

The reason I can't wrap my head around it is that while user can do -o wide and -o name, these are not delegated to any of the printers in staging/src/k8s.io/cli-runtime/pkg/printers, but implemented using switch..case:

switch o.Output {
case "name":
name := r.APIResource.Name
if len(r.APIGroup) > 0 {
name += "." + r.APIGroup
}
if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
errs = append(errs, err)
}
case "wide":
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\t%v\n",
r.APIResource.Name,
strings.Join(r.APIResource.ShortNames, ","),
r.APIGroupVersion,
r.APIResource.Namespaced,
r.APIResource.Kind,
strings.Join(r.APIResource.Verbs, ","),
strings.Join(r.APIResource.Categories, ",")); err != nil {
errs = append(errs, err)
}
case "":

Copy link
Contributor

@soltysh soltysh May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you mentioned, I'd go with only the JSON/YAML printers and skip the rest.

Sounds about right.

But there already was a check for sets.New[string]("", "wide", "name"). Don't we need these? If no, how do we validate this?

See my above answer.

Besides, we don't have OutputFormat field any more if we go with JSONYamlPrintFlags. What I can't wrap my head around is how do we make the user choose from these options:

I believe your proposal from this comment answers that question. I really like your idea of doing something similar to get flags, where you'll be able to embed JSONYamlPrintFlags, NameFlags and most likely copy parts from get's HumanReadableFlags. This will allow you to get rid of this entire check, and rely on the o.PrintFlags.ToPrinter() invocation which will give you an error if someone tries to use an unknown output format. Hope that helps 😄

var printer printers.ResourcePrinter
if o.PrintFlags.OutputFormat != nil && len(*o.PrintFlags.OutputFormat) > 0 &&
(*o.PrintFlags.OutputFormat == "json" || *o.PrintFlags.OutputFormat == "yaml") {
printer, err = o.PrintFlags.ToPrinter()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what I wrote above it's sufficient to squash this condition to just:

if o.PrintFlags.OutputFormat != nil {
    printer, err = o.PrintFlags.ToPrinter()
    ...

since the rest will be handled by the printers, including the validation I wrote above.

@@ -205,18 +228,36 @@ func (o *APIResourceOptions) RunAPIResources() error {
APIGroupVersion: gv.String(),
APIResource: resource,
})
apiResources = append(apiResources, resource)
}
if len(apiResources) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't filter out empty groups, this will cause that you won't print groups that don't have any resources. Filtering is happening earlier (there are continue blocks ensuring you'll skip group you don't care about).

@@ -171,6 +185,7 @@ func (o *APIResourceOptions) RunAPIResources() error {
}

resources := []groupResource{}
var filteredList []*metav1.APIResourceList
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you rename the resources to allResources, so it's clear which one is what.

}
}

if o.NoHeaders == false && o.Output != "name" {
if err = printContextHeaders(w, o.Output); err != nil {
if !o.NoHeaders && (*o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if !o.NoHeaders && (*o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide") {
if !o.NoHeaders && (o.PrintFlags.OutputFormat == nil || *o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide") {

return err
}
}

if *o.PrintFlags.OutputFormat == "json" || *o.PrintFlags.OutputFormat == "yaml" {
flatList := &metav1.APIResourceList{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason you can't create this flatList already when creating filteredList? With appropriate comment above that variable.

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: dharmit, hashim21223445
Once this PR has been reviewed and has the lgtm label, please assign seans3 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-triage-robot
Copy link

The Kubernetes project currently lacks enough contributors to adequately respond to all PRs.

This bot triages PRs according to the following rules:

  • After 90d of inactivity, lifecycle/stale is applied
  • After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied
  • After 30d of inactivity since lifecycle/rotten was applied, the PR is closed

You can:

  • Mark this PR as fresh with /remove-lifecycle stale
  • Close this PR with /close
  • Offer to help out with Issue Triage

Please send feedback to sig-contributor-experience at kubernetes/community.

/lifecycle stale

@k8s-ci-robot k8s-ci-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Apr 10, 2025
@dharmit
Copy link
Author

dharmit commented Apr 10, 2025

/remove-lifecycle stale

Will get onto this soon. Still very much want to get this merged.

@k8s-ci-robot k8s-ci-robot removed the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Apr 10, 2025
@dharmit
Copy link
Author

dharmit commented May 13, 2025

@soltysh besides asking this question (#126338 (comment)) I have pushed a separate branch which contains this commit dharmit@52f7ccf. It uses a separate PrintFlags within apiresources.go similar to how kubectl get does it. I feel it's a cleaner way of implementing the asked feature. I can add tests to it if you think it makes more sense than that PR.

Let me know what you think.

cc @ardaguclu @brianpursley @MikeSpreitzer

@soltysh
Copy link
Contributor

soltysh commented May 15, 2025

@soltysh besides asking this question (#126338 (comment)) I have pushed a separate branch which contains this commit dharmit@52f7ccf. It uses a separate PrintFlags within apiresources.go similar to how kubectl get does it. I feel it's a cleaner way of implementing the asked feature. I can add tests to it if you think it makes more sense than that PR.

Let me know what you think.

I really like your approach!

I think the only suggestion I have is to add something similar to HumanReadableFlags to handle both the name and wide options. Just to make it more composable.

@dharmit
Copy link
Author

dharmit commented May 16, 2025

@soltysh Adding the HumanReadableFlags and NamePrintFlags to the mix is causing an issue with the flatList in that both the printers are exiting with the error indicating metav1.Object interface is not implemented. And I'm feeling lost as to how to address this.

Can you help me figure what I'm doing wrong or what I could try? I have pushed the code as a separate commit dharmit@4232182.

@soltysh
Copy link
Contributor

soltysh commented May 16, 2025

Can you help me figure what I'm doing wrong or what I could try? I have pushed the code as a separate commit dharmit@4232182.

As mentioned on slack:

  1. don't re-use HumanReadableFlags, I've meant more to copy the pattern, once we have the shape, we can consider moving that printer to shared printers, but I doubt it, since the one from get will differ from the one in apiresources;
  2. very likely similar problem is with NamePrintFlags, which is not quite usable in apiresources;

I haven't looked too closely into what kind of error you're getting, but I'm suspecting this is coming from that. Ideally I'd suggest either opening entirely new PR or update the existing one with that code and let's continue discussion there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/kubectl cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/feature Categorizes issue or PR as related to a new feature. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/cli Categorizes an issue or PR as relevant to SIG CLI. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

Add machine readable --output options to kubectl api-resources
7 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.