Skip to content
This repository has been archived by the owner on May 6, 2022. It is now read-only.

Improve kubectl get experience for service catalog resources #2091

Merged

Conversation

luksa
Copy link
Contributor

@luksa luksa commented Jun 4, 2018

Instead of using custom-columns in OpenAPI schema, this commit adds
server-side column printing by registering TableConvertors.

@k8s-ci-robot k8s-ci-robot added 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. labels Jun 4, 2018
@luksa luksa force-pushed the kubectl_columns_tableconvertor branch 3 times, most recently from 0befdfe to a2bed79 Compare June 5, 2018 05:53
Copy link
Contributor

@pmorie pmorie left a comment

Choose a reason for hiding this comment

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

Looks like a promising start, but I'm not sure of kubectl column naming conventions. A more fundamental question is what information to show for ServiceInstance re request and matched service/plan and all the different ways you can request a service/plan.

[]metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Instance", Type: "string"},
{Name: "Secret", Type: "string"},
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be SecretName

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I've changed this to Secret-Name so it gets printed as SECRET-NAME, but I think a simple SECRET is perfectly fine. This is just the column header.

TableConvertor: tableconvertor.NewTableConvertor(
[]metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "External name", Type: "string"},
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this should be ExternalName

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to External-Name so it gets printed as EXTERNAL-NAME (otherwise, it would be printed as EXTERNALNAME).

{Name: "External name", Type: "string"},
{Name: "Broker", Type: "string"},
{Name: "Bindable", Type: "bool"},
{Name: "Plan updatable", Type: "bool"},
Copy link
Contributor

Choose a reason for hiding this comment

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

Hrm - reading these, I'm not certain what the naming convention is for kubectl columns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They use two different forms:

  • Cluster-IP, OS-Image, Kernel-Version, Container-Runtime, ...
  • Node Selector, Last Schedule, Access Modes,Reclaim Policy

See: https://github.com/kubernetes/kubernetes/blob/7eb88f11d23d2be7dc3a91f727a1a77a0abac5e8/pkg/printers/internalversion/printers.go#L76-L422

[]metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "External name", Type: "string"},
{Name: "Broker", Type: "string"},
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be ClusterServiceBrokerName

Copy link
Contributor

Choose a reason for hiding this comment

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

This will be the printed name, correct? I thought the point of tweaking the default printing was to get closer to what svcat does (and a huge part of that is making our field names shorter and more intuitive). What's wrong with just Broker?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't see the value of calling the column CLUSTER-SERVICE-BROKER-NAME, as it just makes the output unnecessarily wide:

NAME                                   EXTERNAL-NAME                        CLUSTER-SERVICE-BROKER-NAME   BINDABLE   PLAN-UPDATABLE   AGE
4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468   user-provided-service                ups-broker                    true       true             12m
5f6e6cf6-ffdd-425f-a2c7-3c9258ad2468   user-provided-service-single-plan    ups-broker                    true       true             12m
8a6229d4-239e-4790-ba1f-8367004d0473   user-provided-service-with-schemas   ups-broker                    true       true             12m

TableConvertor: tableconvertor.NewTableConvertor(
[]metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Class", Type: "string"},
Copy link
Contributor

Choose a reason for hiding this comment

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

We should think about how we want to differentiate ns/cluster scoped classes/plans and requested vs. matched service/plan here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suggest not differentiating for now and just printing both cluster and ns-scoped classes/plans in the same column. I definitely don't want to see two different columns for this, as that would make the output too wide.

But we could add a prefix to the values in the column (e.g. csc/class-name for clusterserviceclasses and sc/class-namefor serviceclasses)

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, a couple of us talked about what this should look like in svcat at the last F2F, here are notes:

#1826 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

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

After thinking about it more, I would much rather prefer that we display the namespace (and leave it empty for cluster level resources) than use the prefixes.

Copy link
Contributor Author

@luksa luksa Aug 9, 2018

Choose a reason for hiding this comment

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

Yeah, I don't like the short prefixes idea either. But I also don't like displaying the namespace, since a ServiceInstance never references a ServiceClass/Plan in a different namespace. It's either the same namespace as the ServiceInstance or no namespace.

Actually, I've just remembered that in core k8s, a RoleBinding can reference either a ClusterRole or a namespaced Role. This is how kubectl prints the reference:

$ kubectl get rolebinding -o wide
NAME       AGE       ROLE                               USERS     ...
test       1m        Role/my-namespaced-role            foo                 
test2      2s        ClusterRole/my-cluster-role        foo           

So, to be consistent, we need to prefix with ServiceClass/ and ClusterServiceClass/.

@luksa
Copy link
Contributor Author

luksa commented Jun 12, 2018

Here are the new outputs (these include the changes requested by @pmorie):

$ k get clusterservicebrokers
NAME         URL                                                         STATUS    AGE
ups-broker   http://ups-broker-ups-broker.ups-broker.svc.cluster.local   Ready     18m

$ k get clusterserviceclasses
NAME                                   EXTERNAL-NAME                        BROKER       BINDABLE   PLAN-UPDATABLE   AGE
4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468   user-provided-service                ups-broker   true       true             18m
5f6e6cf6-ffdd-425f-a2c7-3c9258ad2468   user-provided-service-single-plan    ups-broker   true       true             18m
8a6229d4-239e-4790-ba1f-8367004d0473   user-provided-service-with-schemas   ups-broker   true       true             18m

$ k get clusterserviceplans
NAME                                   EXTERNAL-NAME   BROKER       CLASS                                  AGE
4dbcd97c-c9d2-4c6b-9503-4401a789b558   default         ups-broker   8a6229d4-239e-4790-ba1f-8367004d0473   18m
86064792-7ea2-467b-af93-ac9694d96d52   default         ups-broker   4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468   18m
96064792-7ea2-467b-af93-ac9694d96d52   default         ups-broker   5f6e6cf6-ffdd-425f-a2c7-3c9258ad2468   18m
cc0d7529-18e8-416d-8946-6f7456acd589   premium         ups-broker   4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468   18m

$ k get serviceinstances
NAME           CLASS                   PLAN      STATUS    AGE
ups-instance   user-provided-service   default   Ready     18m

$ k get servicebindings
NAME          SERVICE-INSTANCE   SECRET-NAME   STATUS    AGE
ups-binding   ups-instance       ups-binding   Ready     18m

@jboyd01
Copy link
Contributor

jboyd01 commented Jun 13, 2018

LGTM. This only takes effect if the kubeclient is of a specific version right? Is that 1.10 or 1.11? And older clients get the older, non friendly output?

@luksa
Copy link
Contributor Author

luksa commented Jun 14, 2018

@jboyd01 yeah, the client needs to support server-side printing. Looks like this is only enabled by default in kubectl 1.11. In 1.10 you need to enable it explicitly with --experimental-server-print=true.

@carolynvs carolynvs added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 14, 2018
@carolynvs
Copy link
Contributor

carolynvs commented Jun 14, 2018

Due to a bug in our travis file, this PR must be rebased in order to get the Travis CI build to run for this PR.

@luksa luksa force-pushed the kubectl_columns_tableconvertor branch from d3ec96c to a5ab712 Compare June 15, 2018 08:32
@luksa
Copy link
Contributor Author

luksa commented Jun 15, 2018

Rebased.

@luksa luksa force-pushed the kubectl_columns_tableconvertor branch from a5ab712 to 5f39bc0 Compare June 15, 2018 09:09
@carolynvs carolynvs removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 15, 2018
{Name: "Status", Type: "string"},
{Name: "Age", Type: "string"},
},
func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

{Name: "Name", Type: "string", Format: "name"},
{Name: "External-Name", Type: "string"},
{Name: "Broker", Type: "string"},
{Name: "Bindable", Type: "bool"},
Copy link
Contributor

Choose a reason for hiding this comment

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

Bindable and plan-updatable aren't great candidates to promote to columns. Maybe I'm missing a use case but that's never information I'm looking for. Description seems more useful to promote up, that's what we display in the svcat cli.

TableConvertor: tableconvertor.NewTableConvertor(
[]metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Class", Type: "string"},
Copy link
Contributor

Choose a reason for hiding this comment

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

After thinking about it more, I would much rather prefer that we display the namespace (and leave it empty for cluster level resources) than use the prefixes.

instance := obj.(*servicecatalog.ServiceInstance)
cells := []interface{}{
name,
instance.Spec.GetSpecifiedServiceClass(),
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, @eriknelson 's first pass on this resulted in two separate functions being created: one for cluster level classes/plans and another for namespaced. We spoke at the time about a follow-on PR that consolidated the two when being used by the svcat CLI so that we don't need awkward if/else statements because our helpers aren't helpful enough.

Just wanted you to be aware that this won't work the way you expect right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@carolynvs Thanks. Looks like we still don't have a function like GetSpecifiedClusterServiceClassOrServiceClass(), so I created one myself. I've named it GetSpecifiedClass() (and GetSpecifiedPlan()) for now. Any better ideas?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevermind. I'll throw that function out, since I no longer need it. I'll let someone else deal with the pain of naming these two methods :)

@luksa luksa force-pushed the kubectl_columns_tableconvertor branch from 5f39bc0 to 9c6c9fa Compare July 16, 2018 09:19
@jboyd01
Copy link
Contributor

jboyd01 commented Jul 24, 2018

/retest

@jboyd01
Copy link
Contributor

jboyd01 commented Jul 24, 2018

@luksa looks like this needs a rebase and updating, seeing unit test failures now.

@luksa luksa force-pushed the kubectl_columns_tableconvertor branch 3 times, most recently from ded0b59 to a26eca5 Compare August 9, 2018 13:48
Instead of using custom-columns in OpenAPI schema, this commit adds
server-side column printing by registering TableConvertors.
@luksa luksa force-pushed the kubectl_columns_tableconvertor branch from a26eca5 to 892ce28 Compare August 9, 2018 14:09
@luksa
Copy link
Contributor Author

luksa commented Aug 9, 2018

This is what the ServiceInstance columns look like after my latest changes:

$ kubectl get serviceinstances
NAME                 CLASS                                       PLAN                         STATUS    AGE
local-ups-instance   ServiceClass/user-provided-service          ServicePlan/default          Ready     6m
ups-instance         ClusterServiceClass/user-provided-service   ClusterServicePlan/default   Ready     12m

This is now consistent with RoleBindings:

$ kubectl get rolebinding -o wide
NAME       AGE       ROLE                               USERS     ...
test       1m        Role/my-namespaced-role            foo                 
test2      2s        ClusterRole/my-cluster-role        foo           

Not sure how I feel about the duplication (ServiceClass & ServicePlan). We could maybe omit the prefix in the PLAN column?

@carolynvs
Copy link
Contributor

@luksa In svcat I added a namespace column to the output so that if it's set, you know it's a ServicePlan, and if it's empty its a ClusterServicePlan. Maybe something like that would help?

@luksa
Copy link
Contributor Author

luksa commented Aug 10, 2018

@carolynvs Not sure if we're talking about the same thing. There are two different problems (I'll focus only on classes here to keep things simple; the same rules should apply to plans):

  • when running svcat get classes, the output needs to show whether each row represents a ClusterServiceClass or a ServiceClass.
  • when running svcat get instances or kubectl get serviceinstances, the output needs to show whether the referenced class is a ClusterServiceClass or a ServiceClass.

The first problem only applies to svcat. There's no such problem in kubectl, since you use two different commands (kubectl get clusterserviceclasses vs kubectl get serviceclasses). I'm only trying to solve the second problem.

Because a ServiceInstance can only reference a ServiceClass in the same namespace or a ClusterServiceClass, we only need to show whether the referenced class is namespaced or not. If it is, it's obviously in the same namespace as the instance.

I don't like using a NAMESPACE column to show whether an instance is referencing a namespaced or a global class for two reasons:

  • a namespace column might imply that an instance could also reference a class in a different namespace
  • kubectl get instances --all-namespaces already shows a NAMESPACE column, so we'd have to name our column REFERENCED-CLASS-NAMESPACE (or something similar) and it would only repeat what's shown in the NAMESPACE column (or show an empty value when referencing global classes).

BTW: I don't see svcat print the name of the referenced class/plan, when they are namespaced:

$ svcat get instances
         NAME          NAMESPACE           CLASS            PLAN     STATUS  
+--------------------+-----------+-----------------------+---------+--------+
  local-ups-instance   test-ns                                       Ready   
  ups-instance         test-ns     user-provided-service   default   Ready   

But it does print out both namespaced and global classes:

$ svcat get classes
                 NAME                  NAMESPACE         DESCRIPTION        
+------------------------------------+-----------+-------------------------+
  user-provided-service                            A user provided service  
  user-provided-service-single-plan                A user provided service  
  user-provided-service-with-schemas               A user provided service  
  user-provided-service                test-ns     A user provided service  
  user-provided-service-single-plan    test-ns     A user provided service  
  user-provided-service-with-schemas   test-ns     A user provided service  

@carolynvs
Copy link
Contributor

Thanks for the clarification! Yes I was mixing up two different things.

FYI, svcat hasn't been updated all the way through to support namespaced brokers. So any weirdness, is just because we aren't done yet. 😀

@luksa
Copy link
Contributor Author

luksa commented Aug 10, 2018

@carolynvs :) So, what's your opinion about prefixing just the CLASS column, and leaving the PLAN column without it? I'm now very confident we should use the prefix for the CLASS column (to keep things consistent with how kubectl prints out RoleBindings). I think we can also safely skip the prefix in the PLAN column, because of the parent-child relationship between classes and plans (since plans are part of the class, it's redundant to repeat the information).

So, this is what I'm suggesting:

$ kubectl get serviceinstances
NAME                 CLASS                                       PLAN      STATUS    AGE
local-ups-instance   ServiceClass/user-provided-service          default   Ready     6m
ups-instance         ClusterServiceClass/user-provided-service   default   Ready     12m

@carolynvs
Copy link
Contributor

I think that is solid, consistent and understandable. :shipit:

@luksa luksa force-pushed the kubectl_columns_tableconvertor branch from 892ce28 to ce86709 Compare August 10, 2018 15:41
@luksa
Copy link
Contributor Author

luksa commented Aug 10, 2018

@carolynvs Thanks. Done.

Copy link
Contributor

@carolynvs carolynvs left a comment

Choose a reason for hiding this comment

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

/lgtm

I am officially awarding you 1 MILLION ✨ internet points for getting this through. THANK YOU! ❤️

@k8s-ci-robot k8s-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Aug 10, 2018
@jboyd01
Copy link
Contributor

jboyd01 commented Aug 10, 2018

I agree with #2091 (review) - a lot of work to get this in, it will make a great difference to those using kubectl vs svcat.
LGTM
/approve

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jboyd01

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

The pull request process is described 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-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 10, 2018
@jboyd01
Copy link
Contributor

jboyd01 commented Aug 10, 2018

Travis failed with this:

Verify that vendor/ is in sync with Gopkg.lock

docker run --security-opt label:disable --rm -v /home/travis/build/kubernetes-incubator/service-catalog:/go/src/github.com/kubernetes-incubator/service-catalog -v /home/travis/build/kubernetes-incubator/service-catalog/.cache:/root/.cache/ -v /home/travis/build/kubernetes-incubator/service-catalog/.pkg:/go/pkg --env AZURE_STORAGE_CONNECTION_STRING scbuildimage build/verify-vendor.sh
grouped write of manifest, lock and vendor: error while writing out vendor tree: failed to write dep tree: failed to export github.com/PuerkitoBio/urlesc: https://github.com/PuerkitoBio/urlesc does not exist in the local cache and fetching failed: unable to get repository: Cloning into '/go/pkg/dep/sources/https---github.com-PuerkitoBio-urlesc'...
fatal: unable to access 'https://github.com/PuerkitoBio/urlesc/': GnuTLS recv error (-54): Error in the pull function.

I've restarted the job.

@jboyd01
Copy link
Contributor

jboyd01 commented Aug 10, 2018

hmm, different failure this time:

Step 3/12 : RUN curl -sSL -o /usr/local/bin/dep https://github.com/golang/dep/releases/download/$DEP_VERSION/dep-linux-amd64 && chmod +x /usr/local/bin/dep
---> Running in dcd2953075f1
curl: (56) SSL read: error:00000000:lib(0):func(0):reason(0), errno 104
The command '/bin/sh -c curl -sSL -o /usr/local/bin/dep https://github.com/golang/dep/releases/download/$DEP_VERSION/dep-linux-amd64 && chmod +x /usr/local/bin/dep' returned a non-zero code: 56
make: *** [.scBuildImage] Error 56

restarted again

@jboyd01
Copy link
Contributor

jboyd01 commented Aug 10, 2018

travis, travis, travis...

Running full build
mkdir -p .cache
mkdir -p .pkg
sed "s/GO_VERSION/1.10/g" < build/build-image/Dockerfile |
docker build -t scbuildimage -f - .
Sending build context to Docker daemon 12.98MB
Step 1/12 : FROM golang:1.10
1.10: Pulling from library/golang
No output has been received in the last 10m0s, this potentially indicates a stalled build or something wrong with the build itself.
Check the details on how to adjust your build configuration on: https://docs.travis-ci.com/user/common-build-problems/#Build-times-out-because-no-output-was-received
The build has been terminated

@k8s-ci-robot k8s-ci-robot merged commit 7e4c88e into kubernetes-retired:master Aug 10, 2018
@luksa luksa deleted the kubectl_columns_tableconvertor branch August 11, 2018 06:54
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. awaiting-ci cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants