Kubectl and OC Command Output

Posted by Mark DeNeve on Sunday, April 21, 2019

Introduction

After running an OpenShift or Kubernetes cluster for a little while you find that you need to create reports on specific data about the cluster itself. Reporting on things like Project owners, container images in use, and project quota are just some of the things you might be asked about. There are multiple ways to do this, such as writing your own application that queries the API, or creating a shell script that wraps a bunch of cli commands. For very complex reports, these tactics may be required. For simpler requests, there is another way, using the provided command line client such as “oc” or “kubectl” and a built-in feature which allows you to specify the output format for your query.

Before We Begin

If you want to try these commands out yourself, you will need a working Kubernetes or OpenShift cluster to connect to. If you want to stand up your own cluster, see some of my previous posts on creating your very own cluster such as OpenShift on Azure. In the remainder of this post I will be referring to the “oc” command and “OpenShift”, however what I outline here will work with a regular Kubernetes cluster as well. All you need to do is replace the “oc” command with the “kubectl” command. If you have a Kubernetes cluster and the kubectl command you can run the command with the same options, just substitute “kubectl” for “oc” in the following examples.

Starting with a purpose in mind

In order to demonstrate this process, we will start with a task in mind. We will use a similar scenario to what go me to look into this. Imagine we need to review 100s of OpenShift Routes across many Namespaces to find which routes are using HTTPS and which routes have Whitelists enabled. Whitelists in OpenShift are a way to restrict access to a route to a specific set of defined IP addresses. This can be done by specifying one or many IP addresses as an annotation on a route. This is a special feature of routes, so it is not normally printed when you run an “oc get route”. Additionally, we will format the output as CSV so that it can easily be imported into a spreadsheet for others to consume.

NOTE: If you are a Kubernetes user, Routes are similar to Ingress controllers. They are a way of getting traffic into your cluster to a specific service.

Listing out the Routes

To understand how we are going to create this report, we need to start with knowing what just one “route” looks like. Using the “oc” client, connect to your cluster as a “cluster admin” and let’s see what routes are defined in your cluster. Run the command oc get route --all-namespaces. This will list out all the routes across the entire cluster:

$ oc get route --all-namespaces
NAMESPACE                          NAME                HOST/PORT                                                      PATH      SERVICES            PORT      TERMINATION          WILDCARD
default                            docker-registry     docker-registry-default.apps.okd.example.net                               docker-registry     <all>     passthrough          None
default                            registry-console    registry-console-default.apps.okd.example.net                              registry-console    <all>     passthrough          None
kube-service-catalog               apiserver           apiserver-kube-service-catalog.apps.okd.example.net                        apiserver           secure    passthrough          None
openshift-ansible-service-broker   asb-1338            asb-1338-openshift-ansible-service-broker.apps.okd.example.net             asb                 1338      reencrypt            None
openshift-console                  console             console.apps.okd.example.net                                               console             https     reencrypt/Redirect   None
openshift-monitoring               alertmanager-main   alertmanager-main-openshift-monitoring.apps.okd.example.net                alertmanager-main   web       reencrypt            None
openshift-monitoring               grafana             grafana-openshift-monitoring.apps.okd.example.net                          grafana             https     reencrypt            None
openshift-monitoring               prometheus-k8s      prometheus-k8s-openshift-monitoring.apps.okd.example.net                   prometheus-k8s      web       reencrypt            None

Reviewing the output from above you can see we have multiple routes defined in this cluster. Let’s examine one more closely. We will output the data in YAML format using the “-o yaml” option oc get route grafana -namespace openshift-monitoring -o yaml:

$ oc get route grafana -namespace openshift-monitoring -o yaml
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  annotations:
    openshift.io/host.generated: "true"
    haproxy.router.openshift.io/ip_whitelist: "192.168.5.1"
  name: grafana
  namespace: openshift-monitoring
spec:
  host: grafana-openshift-monitoring.apps.okd.example.net
  port:
    targetPort: https
  tls:
    termination: reencrypt
  to:
    kind: Service
    name: grafana
    weight: 100
  wildcardPolicy: None
status:
  ingress:
  - conditions:
      type: Admitted
    host: grafana-openshift-monitoring.apps.okd.example.net
    routerName: router
    wildcardPolicy: None

If you have not seen YAML before it is standard configuration language that uses whitespace to create a hierarchical layout of information. We don’t need to understand all of the output above, we just need to center in on the information that we are most interested in. In this case, the “namespace”, the “name”, the “host”, if tls is enabled and if there is a whitelist defined. Using the above output as a guideline, let’s start to work on a report across the entire cluster.

Go Templating for Output

We are going to leverage the “go-template” output type which will allow us to specify the specific fields we are interested in and the exact format we want to output. Go Templates are a feature of the Go programming language. Don’t worry though, you don’t need to know the go programming language to use them. We will start simple, and print out a header, and two fields: “name” and “namespace”.

oc get routes --all-namespaces -o=go-template='{{"namespace,name\n"}}{{range .items}}{{.metadata.namespace}}{{","}}{{.metadata.name}}{{"\n"}}{{end}}

The output will look similar to this:

namespace,name
default,docker-registry
default,registry-console
kube-service-catalog,apiserver
openshift-ansible-service-broker,asb-1338
openshift-console,console
openshift-monitoring,alertmanager-main
openshift-monitoring,grafana
openshift-monitoring,prometheus-k8s

Let’s break down the command we just used. The first portion oc get routes --all-namespaces does just what it says and will (as previously seen) retrieve all the routes defined within this cluster across all namespaces. The real magic happens here -o=go-template='{{range...}}' so we will break this down into smaller chunks.

First, you will notice lots of curly-braces (ie. {{}}). These delineate individual actions to the Go template language. Next, anything inside the curly-braces and also in quotes will be printed out directly. This is how we get our header of “namespace,name”. Finally, “\n” is a symbol for “new line”. If we didn’t put that in there the output would be all strung together in one very long line, which would be very hard to read.

Moving on to the next section, we have {{range .items}}…{{end}}, which tells the template system to repeat over all the items that are returned and run the actions between the start and the final {{end}}. We then specify the items that we want the template to print out. If you compare the name “.metadata.namespace” to the YAML example above you can see that we are defining the path to the value you want, separating each level with a “.” We insert a comma between each field so that we end up with a “CSV” format by putting {{","}} between each field we are outputting.

Let’s add in the remaining fields (except whitelist for now) and see what we get:

oc get routes --all-namespaces -o=go-template='{{"namespace,name,hostname,tls\n"}}{{range .items}}{{.metadata.namespace}}{{","}}{{.metadata.name}}{{","}}{{.spec.host}}{{","}}{{.spec.tls.termination}}{{"\n"}}{{end}}

The output will look like so:

namespace,name,hostname,tls
docker-registry,docker-registry-default.apps.okd.example.net,passthrough
registry-console,registry-console-default.apps.okd.example.net,passthrough
apiserver,apiserver-kube-service-catalog.apps.okd.example.net,passthrough
asb-1338,asb-1338-openshift-ansible-service-broker.apps.okd.example.net,reencrypt
console,console.apps.okd.example.net,reencrypt
alertmanager-main,alertmanager-main-openshift-monitoring.apps.okd.example.net,reencrypt
grafana,grafana-openshift-monitoring.apps.okd.example.net,reencrypt
prometheus-k8s,prometheus-k8s-openshift-monitoring.apps.okd.example.net,reencrypt

With all of this command on one line, it can be difficult to read. Below is a multi-line pseudo-code version of the template which may be easier to read:

{{"namespace,name,hostname,tls\n"}}  # output our header
{{range .items}}  # loop over all the items
    {{.metadata.namespace}}{{","}} # print out the namespace this route is in
    {{.metadata.name}}{{","}} # print out the name of this route
    {{.spec.host}}{{","}} # print out the route hostname
    {{.spec.tls.termination}}{{"\n"}} # print out if the route has tls enabled
    {{"\n"}} # start a new line
{{end}}

Taking it up a notch

If you remember my original goal was to get not only the namespace, name, hostname and if tls was in use but also a lesser-known attribute called “WhiteList” and see if it was available. A whitelist shows up as an Annotation on the route with a key of “haproxy.router.openshift.io./ip_whitelist”. We want to output this as the last field on our line. Unfortunately its not as simple as just adding “{{.metadata.annotations.haproxy.router.openshift.io/ip_whitelist}}” and calling it a day. Annotations are a map (or array) of multiple fields. In order to get just the whitelist we will need to parse the annotations looking for the specific value we want. We can do this by creating a small “if” statement and then using the “index” operator to output all the values of the “haproxy.router.openshift.io/ip_whitelist” field.

oc get routes --all-namespaces -o=go-template='{{"namespace,name,hostname,tls,whitelist\n"}}{{range .items}}{{.metadata.namespace}}{{","}}{{.metadata.name}}{{","}}{{.spec.host}}{{","}}{{.spec.tls.termination}}{{","}}{{if .metadata.annotations}}{{index .metadata.annotations "haproxy.router.openshift.io/ip_whitelist"}}{{else}}{{"nil"}}{{end}}{{","}}{{"\n"}}{{end}}'

As before, all on one line, this is very hard to read and understand so let’s show exactly what this looks like in a pseudo-code layout:

{{"namespace,name,hostname,tls,whitelist,\n"}}  # output our header
{{range .items}}  # loop over all the items
    {{.metadata.namespace}}{{","}} # print out the namespace this route is in
    {{.metadata.name}}{{","}} # print out the name of this route
    {{.spec.host}}{{","}} # print out the route hostname
    {{.spec.tls.termination}}{{"\n"}} # print out if the route has tls enabled
    {{if .metadata.annotations }} # if there are annotations
        {{index .metadata.annotations "haproxy.router.openshift.io/ip_whitelist"}} # list out all the entries for ip_whitelist
    {{end}}{{","}} # end our if statement
    {{"\n"}} # start a new line
{{end}}

The output from this command is now what we set out to get:

namespace,name,hostname,tls,whitelist,
default,docker-registry,docker-registry-default.apps.okd.example.net,passthrough,<no value>,
default,registry-console,registry-console-default.apps.okd.example.net,passthrough,192.168.1.10,
kube-service-catalog,apiserver,apiserver-kube-service-catalog.apps.okd.example.net,passthrough,<no value>,
openshift-ansible-service-broker,asb-1338,asb-1338-openshift-ansible-service-broker.apps.okd.example.net,reencrypt,<no value>,
openshift-console,console,console.apps.okd.example.net,reencrypt,<no value>,
openshift-monitoring,alertmanager-main,alertmanager-main-openshift-monitoring.apps.okd.example.net,reencrypt,<no value>,
openshift-monitoring,grafana,grafana-openshift-monitoring.apps.okd.example.net,reencrypt,<no value>,
openshift-monitoring,prometheus-k8s,prometheus-k8s-openshift-monitoring.apps.okd.example.net,reencrypt,<no value>,

We can now take this output and import it into a spreadsheet application and share it with others. As an example, here is the CSV above opened in Google Sheets:

google sheets csv

Conclusion

The go-template system allows for much more than we have seen above. For example, the if statement can be used to compare values and only print things out when they match something you are looking for. If you want to learn more about using the go-template output format or the other options such as jsonpath check out the following links: