Mastering Cisco ACI API using icurl and jq. Part 1


I’ve been a bit of a fan of using Cisco’s moquery utility for ACI for querying objects, and have written and talked about that before.

But moquery has bugs – many of the -x options that make most sense when used with specific objects (like -x query-target=children) simply will not work with moquery. (See the discussion here).

So even though moquery is pretty easy to use, I’ve moved on from moquery to use the more functional icurl utility – also included on the Cisco APIC and switches.

I’ll present this in three parts. In today’s post I’ll discuss how you can use jq (the JSON Query utility) to make the output of  icurl look much more better than the raw JSON format that icurl produces.  I’ll cover five lessons where I dig a little deeper into jq each time.  Here are the lesson topics and the icurl | jq combination I’ll discuss in each lesson for those of you who just want to get the answers without reading the whole discussion:

In the second part, I’ll look at getting the most out of icurl using the Cisco ACI API REST options, and in part #3, I’ll give you a ton of practical examples

Part #1 – JSON Query (jq) – icurl’s best friend

Lesson #1.1 – pretty print output using jq

The output from the icurl command can be delivered in either xml (using icurl http://localhost/api/node/class/fvAEPg.xml) or JSON format.  I’ll be using JSON format for the rest of this article because the ACI APIC (and switches) come with the jq command-line JSON processor built in.

The trick to use jq to make the JSON output from icurl more readable – the simplest way is to simply pipe the output of the icurl command into jq

Tip:RedPoint In the examples that follow, I used http:// to invoke icurl – if you want to use https:// – then add a -k flag to the icurl command:
icurl -k https://localhost/api/... etc
I’ll also use a -s flag to the icurl command to suppress the output of the status information that would otherwise clutter up the output
T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json|jq
{
  "totalCount": "8",
  "imdata": [
    {
      "fvAEPg": {
        "attributes": {
          "annotation": "",
          "childAction": "",
          "configIssues": "",
          "configSt": "applied",
          "descr": "",
          "dn": "uni/tn-infra/ap-ave-ctrl/epg-ave-ctrl",
          "exceptionTag": "",
          "extMngdBy": "",
          "floodOnEncap": "disabled",
          "fwdCtrl": "",
          "hasMcastSource": "no",
          "isAttrBasedEPg": "no",
          "isSharedSrvMsiteEPg": "no",
          "lcOwn": "local",
          "matchT": "AtleastOne",
          "modTs": "2024-03-14T15:08:33.838+11:00",
          "monPolDn": "uni/tn-common/monepg-default",
          "name": "ave-ctrl",
          "nameAlias": "",
          "pcEnfPref": "unenforced",
          "pcTag": "32770",
          "pcTagAllocSrc": "idmanager",
          "prefGrMemb": "exclude",
          "prio": "unspecified",
          "scope": "3014656",
          "shutdown": "no",
          "status": "",
          "triggerSt": "triggerable",
          "txId": "5764607523034234882",
          "uid": "0",
          "userdom": "all"
        }
      }
    },
<...snip...>

We can make jq work a lot harder than that.

Lesson #1.2 – Harnessing the Power of jq – selecting keys

Challenge: Extract the EPG name and pcTag values for all EPGs in the system.

JSON query has the power to extract the values of any key, so looking at the output above, the full path to the name key we are interested in is:

  • .imdata[].fvAEPg.attributes.name

To reference this specific key in jq simply requires the path above to be added to the jq command.

So to get just the name values:

T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | 
jq '.imdata[].fvAEPg.attributes.name'
"ave-ctrl"
"default"
"SharedServices_EPG"
"AppServers_EPG"
"WebServers_EPG"
"AppServers_EPG"
"WebServers_EPG"
"DB.Servers_EPG"
Tip:RedPoint If you don’t want the quotes symbols, add a -r (raw) option to the jq command:
jq -r .imdata[].fvAEPg.attributes.name
ave-ctrl
default
SharedServices_EPG
... etc

That’s great.  But the challenge was to get the EPG name and pcTag values.  To do that, I need to modify the jq query to

jq '.imdata[].fvAEPg.attributes | .name, .pcTag'

Note the use of the pipe | and the comma , between the key indexes.  Here’s the result from my lab

T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | 
jq '.imdata[].fvAEPg.attributes|.name,.pcTag'
"ave-ctrl"
"32770"
"default"
"16387"
"SharedServices_EPG"
"10930"
"AppServers_EPG"
"49153"
"WebServers_EPG"
"32770"
"AppServers_EPG"
"49154"
"WebServers_EPG"
"49153"
"BD.Servers_EPG"
"49155""

Now that we’ve got icurl and jq producing the values we want, you’ll have noticed that the field names have disappeared.  But don’t worry, you can add your own and produce your own JSON output by modifying the field selector portion – i.e. the bit that reads

.name,.pcTag

Lesson #1.3 – Making  jq work for you – creating JSON output

Challenge: Add labels for  the EPG name and pcTag from the previous output.

This is done by enclosing the key list in braces {} and adding your own keys – like this:

{EPG: .name, Class_ID: .pcTag}

Now your output will look like:

T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json |
jq '.imdata[].fvAEPg.attributes |{EPG: .name, Class_ID: .pcTag}'
{
  "EPG": "ave-ctrl",
  "Class_ID": "32770"
}
{
  "EPG": "default",
  "Class_ID": "16387"
}
{
  "EPG": "SharedServices_EPG",
  "Class_ID": "10930"
}
{
  "EPG": "AppServers_EPG",
  "Class_ID": "49153"
}
{
  "EPG": "WebServers_EPG",
  "Class_ID": "32770"
}
{
  "EPG": "AppServers_EPG",
  "Class_ID": "49154"
}
{
  "EPG": "WebServers_EPG",
  "Class_ID": "49153"
}
{
  "EPG": "BD.Servers_EPG",
  "Class_ID": "49155"
}
Tip:RedPoint The keen observer will realise that the output above is not strictly in JSON format – there are commas missing. If you want, you can massage the output to turn it into proper JSON by piping it to jq -s
jq '.imdata[].fvAEPg.attributes|{EPG: .name, Class_ID: .pcTag}'|jq -s

But given the same EPG names occur more than once, perhaps a little more information would be nice. Like the Tenant name and the Application Profile name.

And if you look at the output of the original command, you’ll see the dn key has all the information you need. E.g.

 "dn": "uni/tn-infra/ap-ave-ctrl/epg-ave-ctrl",

Lesson #1.4 – Caressing jq – parsing keys

Challenge: Extract the Tenant, Application Profile and EPG names from the dn for all EPGs in the system.

Using the fact that the Tenant name, Application Profile name and EPG name always appears after a tn-, ap- or epg- prefix, and are always followed by a
/ character or end of line, I can use regex and jq‘s sub-command capture to extract exactly what we want between tn- and /ap,  etc like this:

T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json |
jq '.imdata[].fvAEPg.attributes |
(.dn|capture("tn-(?<T>.*)/ap").T),
(.dn|capture("ap-(?<A>.*)/epg").A),
(.dn|capture("epg-(?<E>.*)").E)'
"infra"
"ave-ctrl"
"ave-ctrl"
"infra"
"access"
"default"
"common"
"SharedServices_AP"
"SharedServices_EPG"
"Tenant01"
"2Tier_AP"
"AppServers_EPG"
"Tenant01"
"2Tier_AP"
"WebServers_EPG"
"Tenant17"
"2Tier_AP"
"AppServers_EPG"
"Tenant17"
"2Tier_AP"
"WebServers_EPG"
"Tenant17"
"2Tier_AP"
"BD.Servers_EPG"

In the example above T, A and E are just a variable names.

But the output is still a bit messy. So why not tidy it up by putting the output back into JSON format!  The logic is the same as in Lesson #1.3, it is just a little more convoluted because of the all the captures.

So how about:

T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json | 
jq '.imdata[].fvAEPg.attributes |
{tenant: (.dn|capture("tn-(?<T>.*)/ap").T),
AP: (.dn|capture("ap-(?<A>.*)/epg").A),
EPG: (.dn|capture("epg-(?<E>.*)").E)}'
{
  "tenant": "infra",
  "AP": "ave-ctrl",
  "EPG": "ave-ctrl"
}
{
  "tenant": "infra",
  "AP": "access",
  "EPG": "default"
}
{
  "tenant": "common",
  "AP": "SharedServices_AP",
  "EPG": "SharedServices_EPG"
}
{
  "tenant": "Tenant01",
  "AP": "2Tier_AP",
  "EPG": "AppServers_EPG"
}
{
  "tenant": "Tenant01",
  "AP": "2Tier_AP",
  "EPG": "WebServers_EPG"
}
{
  "tenant": "Tenant17",
  "AP": "2Tier_AP",
  "EPG": "AppServers_EPG"
}
{
  "tenant": "Tenant17",
  "AP": "2Tier_AP",
  "EPG": "WebServers_EPG"
}
{
  "tenant": "Tenant17",
  "AP": "2Tier_AP",
  "EPG": "BD.Servers_EPG"
}

If only it were possible to group all the Application Profiles and EPGs within each tenant…

Lesson #1.5 – Deep dive into jq – advanced features

Challenge: Extract the Application Profile and EPG names for each Tenant, and display them grouped by Tenant.

To achieve this, I’ll need to dig deep into the powers of jq. See if you can figure how this works!

T17@apic1:~> icurl -s http://localhost/api/node/class/fvAEPg.json |
jq '.imdata[].fvAEPg.attributes |
{tenant: (.dn|capture("tn-(?<T>.*)/ap").T),
AP: (.dn|capture("ap-(?<A>.*)/epg").A),
EPG: (.dn|capture("epg-(?<E>.*)").E)}' |
jq -s 'group_by(.tenant) | 
map({key: (.[0].tenant), value: [.[] | {AP: .AP, EPG: .EPG}] }) | 
from_entries'
{
  "Tenant01": [
    {
      "AP": "2Tier_AP",
      "EPG": "AppServers_EPG"
    },
    {
      "AP": "2Tier_AP",
      "EPG": "WebServers_EPG"
    }
  ],
  "Tenant17": [
    {
      "AP": "2Tier_AP",
      "EPG": "AppServers_EPG"
    },
    {
      "AP": "2Tier_AP",
      "EPG": "WebServers_EPG"
    },
    {
      "AP": "2Tier_AP",
      "EPG": "BD.Servers_EPG"
    }
  ],
  "common": [
    {
      "AP": "SharedServices_AP",
      "EPG": "SharedServices_EPG"
    }
  ],
  "infra": [
    {
      "AP": "ave-ctrl",
      "EPG": "ave-ctrl"
    },
    {
      "AP": "access",
      "EPG": "default"
    }
  ]
}

Wrap up

In this post I’ve shown how jq can be used in its simplest form to just pretty-print the output from an icurl command to a full-blown massage of the data to produce an elegant display of useful data.

In the next post, I’ll look more closely at how to get the data you want using some of the many Cisco ACI API REST options.

Until then, happy querying

RedNectar

 

Unknown's avatar

About RedNectar Chris Welsh

Professional IT Instructor. All things TCP/IP, Cisco or Data Centre
This entry was posted in ACI, ACI API, ACI Tutorial, APIC, Cisco and tagged . Bookmark the permalink.

8 Responses to Mastering Cisco ACI API using icurl and jq. Part 1

  1. Karol's avatar Karol says:

    Hi Chris,

    Again a great post. My question is, how to paste the command with question mark into APIC CLI?

    Unfortunately, the APIC CLI removes the ? when I paste the command directly. To work around I use:

    1. Paste the command without the ? characters.
    2. Manually type each ? using Ctrl+V followed by ?

    But I hope there is a better solution.

    Thank you

    • Hi Karol,

      The simplest answer is to make sure that you are at the bash prompt (as shown in all the examples – T17@apic1:-> ), rather than the apic1# prompt

      To get to the bash prompt, simply type the command bash at the apic1# prompt

  2. Pingback: moquery vs icurl bake off | RedNectar's Blog

  3. Pingback: Mastering Cisco ACI API using icurl and jq. Part 3 | RedNectar's Blog

  4. Pingback: Mastering Cisco ACI API using icurl and jq. Part 2 | RedNectar's Blog

  5. Oz's avatar Oz says:

    Hi,

    Thank you for your blogpost, it is very informative.

    I have question here because I don’t get it why it doesn’t work.

    As an example dn:
    “dn”: “uni/tn-TST-CUSTOMER2/ap-TST-CUSTOMER2/epg-VLAN401”,

    for the given dn following should capture the TST-CUSTOMER2
    icurl -k -s “https://localhost/api/node/class/fvAEPg.json” | jq ‘.imdata[].fvAEPg.attributes | {tenant: (.dn|capture(“tn-(.*)/ap”).T)}’

    but instead I just get nothing returned. Do you habe may be an idea?

    Thanks in advance!

    Regards,

    Oz.

    • Hi Oz,

      I think stupid WordPress has reformatted your question and removed the < and > symbols and the characters between them. AND reformatted your quotes symbols!!!

      Apart from that – I think you missed a question-mark – try

      icurl -k -s "https://localhost/api/node/class/fvAEPg.jso" | jq '.imdata[].fvAEPg.attributes | {tenant: (.dn|capture("tn-(?<T>.*)/ap").T)}'

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.