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

Add JSON and YAML output to svcat #1944

Merged
merged 5 commits into from
Apr 19, 2018

Conversation

jeremyrickard
Copy link
Contributor

Adding JSON and YAML output capabilities

Note: This is not 100% complete. Right now, we do not obtain the
metav1.TypeMeta within svcat. We will need to figure out how to
populate this.

See: kubernetes/client-go#308 (comment)

Fixes: #1814

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Apr 16, 2018
@jeremyrickard jeremyrickard changed the title Json output Add JSON and YAML output to svcat Apr 16, 2018
@carolynvs carolynvs self-requested a review April 16, 2018 23:10
@jeremyrickard
Copy link
Contributor Author

jeremyrickard commented Apr 16, 2018

Example output

$ ./bin/svcat/svcat get instance cosmosdb -o json
{
   "metadata": {
      "name": "cosmosdb",
      "namespace": "default",
      "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/namespaces/default/serviceinstances/cosmosdb",
      "uid": "dffb52b5-41a1-11e8-8f2d-0a580af4010c",
      "resourceVersion": "432",
      "generation": 1,
      "creationTimestamp": "2018-04-16T18:13:33Z",
      "finalizers": [
         "kubernetes-incubator/service-catalog"
      ]
   },
   "spec": {
      "clusterServiceClassExternalName": "azure-cosmosdb-mongo-account",
      "clusterServicePlanExternalName": "account",
      "clusterServiceClassRef": {
         "name": "8797a079-5346-4e84-8018-b7d5ea5c0e3a"
      },
      "clusterServicePlanRef": {
         "name": "86fdda05-78d7-4026-a443-1325928e7b02"
      },
      "parameters": {
         "location": "eastus"
      },
      "externalID": "dffb4fd6-41a1-11e8-8f2d-0a580af4010c",
      "updateRequests": 0
   },
   "status": {
      "conditions": [
         {
            "type": "Ready",
            "status": "True",
            "lastTransitionTime": "2018-04-16T18:17:50Z",
            "reason": "ProvisionedSuccessfully",
            "message": "The instance was provisioned successfully"
         }
      ],
      "asyncOpInProgress": false,
      "orphanMitigationInProgress": false,
      "reconciledGeneration": 1,
      "observedGeneration": 1,
      "externalProperties": {
         "clusterServicePlanExternalName": "account",
         "clusterServicePlanExternalID": "86fdda05-78d7-4026-a443-1325928e7b02",
         "parameters": {
            "location": "eastus"
         },
         "parameterChecksum": "d0ec58f6385d6c93695c4a23b5f3c7c81c91f31e0f8502f3189cf4a44e7d3095"
      },
      "provisionStatus": "Provisioned",
      "deprovisionStatus": "Required"
   }
}

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.

THIS MAKES ME SO HAPPY! 💃

Don't be overwhelmed by the # of my comments, looks great and with a few tweaks will be ready to rock.

@@ -45,7 +50,7 @@ func NewGetCmd(cxt *command.Context) *cobra.Command {
}

command.AddNamespaceFlags(cmd.Flags(), true)

command.AddOutputFlags(cmd.Flags())
Copy link
Contributor

@carolynvs carolynvs Apr 16, 2018

Choose a reason for hiding this comment

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

Because the --output flag is used by kubectl as well, you will need to add an explicit mapping for when svcat is used in plugin mode.

See https://github.com/kubernetes-incubator/service-catalog/blob/2e1ca7f8cf2b5d45b0542602f0d12cb18bb05f98/cmd/svcat/plugin/plugin.go#L60-L66 for an example of how we do that with the -v and --namespace flags.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does that need to happen for "--output" and "-o"? Or is it smart enough to handle that?

@@ -71,7 +76,7 @@ func (c *getCmd) getAll() error {
return err
}

output.WriteBindingList(c.Output, bindings.Items...)
output.WriteBindingList(c.Output, c.outputFormat, bindings)
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Yeah I get what you were saying before now about needing to switch this to use the root object and not just items.

@@ -81,7 +87,7 @@ func (c *getCmd) getAll() error {
return err
}

output.WriteClassList(c.Output, classes...)
output.WriteClassList(c.Output, c.outputFormat, classes...)
Copy link
Contributor

@carolynvs carolynvs Apr 16, 2018

Choose a reason for hiding this comment

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

This is my mistake. c.App.RetrieveClasses() should return the entire type and not just items, like bindings does. There may be a few other types that should standardize as well on returning the entire response and not just items but that is fine (probably best) to be a follow-on PR.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you go it!

format = strings.ToLower(format)

switch format {
case "", "table":
Copy link
Contributor

Choose a reason for hiding this comment

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

Just wanted to agree that handling "" and "table" here instead of setting the default value on the flag is the correct thing to do because in plugin mode, the flag's default value isn't under our control. 👍

case "yaml":
return "yaml", nil
default:
return "", fmt.Errorf("unknown output format: %s", format)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would help to mention the flag and acceptable formats, maybe something like this?

invalid --output format %q, allowed values are table, json and yaml

// Retrieve the class as well because plans don't have the external class name
class, err := c.App.RetrieveClassByID(plan.Spec.ClusterServiceClassRef.Name)
if err != nil {
return err
}

output.WritePlanList(c.Output, []v1beta1.ClusterServicePlan{*plan}, []v1beta1.ClusterServiceClass{*class})
output.WritePlan(c.Output, c.outputFormat, *plan, *class)
Copy link
Contributor

Choose a reason for hiding this comment

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

⭐️

@@ -0,0 +1,129 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

haha this isn't yaml 😀

{name: "get class by name", cmd: "get class user-provided-service", golden: "output/get-class.txt"},
{name: "get class by uuid", cmd: "get class --uuid 4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468", golden: "output/get-class.txt"},
{name: "describe class by name", cmd: "describe class user-provided-service", golden: "output/describe-class.txt"},
{name: "describe class uuid", cmd: "describe class --uuid 4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468", golden: "output/describe-class.txt"},

{name: "list all plans", cmd: "get plans", golden: "output/get-plans.txt"},
{name: "list all plans (json)", cmd: "get plans -o json", golden: "output/get-plans.json"},
{name: "list all plans (yaml)", cmd: "get plans -o yaml", golden: "output/get-plans.yaml"},
{name: "get plan by name", cmd: "get plan default", golden: "output/get-plan.txt"},
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a missing testcase for outputting yaml/json for "get plan by name". No need to do it for uuid too.

{name: "describe broker", cmd: "describe broker ups-broker", golden: "output/describe-broker.txt"},

{name: "list all classes", cmd: "get classes", golden: "output/get-classes.txt"},
{name: "list all classes (json)", cmd: "get classes -o json", golden: "output/get-classes.json"},
{name: "list all classes (yaml)", cmd: "get classes -o yaml", golden: "output/get-classes.yaml"},
{name: "get class by name", cmd: "get class user-provided-service", golden: "output/get-class.txt"},
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a missing testcase for outputting yaml/json for "get class by name". No need to do it for uuid too.

@@ -88,13 +88,26 @@ tree:
- name: all-namespaces
desc: If present, list the requested object(s) across all namespaces. Namespace
in current context is ignored even if specified with --namespace
- name: output
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh gosh, I just realized that the doc I added to the dev guide explaining how to easily update the golden files went missing! 😝 I hope that didn't waste too much of your time.

The trick is this (i'll submit a PR in a minute to fix the doc)

  1. touch cmd/svcat/testdata/output/NEWFILE
  2. go test ./cmd/svcat/... -update

That will insert the test output into the golden file without you having to do it manually.

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 once the changes to the plugin file are reverted. Sorry for the wrong advice earlier!

case "yaml":
writeBindingListYAML(w, bindingList)
case "table":
case formatJSON:
Copy link
Contributor

Choose a reason for hiding this comment

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

This is sooooooooo much better!

excellent


func writeJSON(w io.Writer, obj interface{}, n int) {

if n == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ignoring the value they set seems a bit unexpected to me. If it's a *int then checking null would be less surprising than converting 0 -> 3. Though then it makes it hard to pass in an int... 🤔

Just something to think about, we can tweak later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For now, I just went ahead and set it to do three spaces. If we run into the need for variable indent on JSON output, let's revisit?

@@ -46,6 +46,9 @@ const (
// EnvPluginVerbose is the -v=LEVEL flag
EnvPluginVerbose = EnvPluginGlobalFlagPrefix + "_V"

// EnvPluginOutput is the -o=FORMAT flag
Copy link
Contributor

Choose a reason for hiding this comment

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

I just tested this in plugin mode and I apologize for sending you down the wrong path. It won't be sent to us as a global flag after all so the changes to this file can be reverted. Our default plugin wiring will handle setting it without this (because it will come in as an env var like this KUBECTL_PLUGINS_LOCAL_FLAG_OUTPUT=json).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image

This adds JSON/YAML output to svcat, but does not currently include
the Type Metadata, so we lose the Kind/GroupVersion info.
Note: This is not 100% complete. Right now, we do not obtain the
metav1.TypeMeta within svcat. We will need to figure out how to
populate this.

See: kubernetes/client-go#308 (comment)

Fixes: kubernetes-retired#1814
@jeremyrickard
Copy link
Contributor Author

@carolynvs ok! take a look again, all the changes we discussed should be done now!

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

Can't wait to use this! 💯

@n3wscott
Copy link
Contributor

Awesome work.

@n3wscott n3wscott added the LGTM2 label Apr 19, 2018
@MHBauer
Copy link
Contributor

MHBauer commented Apr 19, 2018

At the top it says Note: This is not 100% complete. Does it need to be complete to merge? Is this PR ready to merge now?

@carolynvs carolynvs merged commit 929c6e2 into kubernetes-retired:master Apr 19, 2018
@carolynvs
Copy link
Contributor

Reflecting and populating type metadata (i.e. kind) will be addressed in a follow-on since it's not really used, and would just be nice to have for completeness sake in comparison to the output of kubectl.

@carolynvs carolynvs added this to the 1.0.0 milestone May 21, 2018
# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. LGTM1 LGTM2 size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants