Search code examples
jqkubectl

Updating an array element identified by other fields in the object using jq


Goal

I'd like to add a proxy-url field to the currently active clusters entry of my kubeconfig file. The "active" cluster is identified by the "active" context, which is itself identified by a top-level key current-context. Simplified, the JSON object looks something like:

{
  "clusters":[
    {
      "name":"cluster1",
      "field":"field1"
    },
    {
      "name":"cluster2",
      "field":"field2"
    }
  ],
  "contexts":[
    {
      "name":"context1",
      "context": {
        "cluster":"cluster1"
      }
    },
    {
      "name":"context2",
      "context": {
        "cluster":"cluster2"
      }
    }
  ],
  "current-context": "context1"
}

And I'd like to update the clusters entry for cluster1 from:

{
  "name":"cluster1",
  "field":"field1"
}

to

{
  "name":"cluster1",
  "field":"field1",
  "proxy-url":"my-url"
}

First attempt

jq '. as $o
  | $o."current-context" as $current_context_name
  | $o.contexts[] | select(.name == $current_context_name) as $context
  | $o.clusters[] | select(.name == $context.context.cluster)
  | .proxy_id |= "my-url"'

gives me

{
  "name": "cluster1",
  "field": "field1",
  "proxy_id": "my-url"
}

-- great! But I need the rest of the object too.

Parentheses almost work

With parentheses, I can get the whole object back & add a "proxy-url" field to the active context, but I can't take it one step further to update the active cluster. This filter:

jq '(. as $o
  | $o."current-context" as $current_context_name
  | $o.contexts[] | select(.name == $current_context_name)
  | ."proxy-url")
  |= "my-url"'

works mint:

{
  "clusters": [...],  // omitted for brevity, unchanged
  "contexts": [
    {
      "name": "context1",
      "context": {
        "cluster": "cluster1"
      },
      "proxy-url": "my-url"  // tada!
    },
    {...}  // omitted for brevity, unchanged
  ],
  "current-context": "context1"
}

Trying to take it one step further (to update the cluster identified by that context, instead):

jq '(. as $o
  | $o."current-context" as $current_context_name
  | $o.contexts[] | select(.name == $current_context_name) as $context
  | $o.clusters[] | select(.name == $context.context.cluster)
  | ."proxy-url")
  |= "my-url"'

gives me the following error:

jq: error (at <stdin>:26): Invalid path expression near attempt to access element "clusters" of {"clusters":[{"name":"clus...
exit status 5

How can I use the $context.context.cluster result to update the relevant clusters entry? I don't understand why this approach works for adding something to contexts but not to clusters.

Dirty solution

I can kludge together a new clusters entry & merge that with the top-level object:

jq '. as $o
  | $o."current-context" as $current_context_name
  | $o.contexts[] | select(.name == $current_context_name) as $context
  | $o + {"clusters": [($o.clusters[] | select(.name == $context.context.cluster)."proxy-url" |= "my-url")]}

but this feels a bit fragile.


Solution

  • This solution retrieves the active cluster using an INDEX construction, then just sets the new field directly without modifying the context:

    jq '
      INDEX(.contexts[]; .name)[."current-context"].context.cluster as $cluster
      | (.clusters[] | select(.name == $cluster))."proxy-url" = "my-url"
    '
    
    {
      "clusters": [
        {
          "name": "cluster1",
          "field": "field1",
          "proxy-url": "my-url"
        },
        {
          "name": "cluster2",
          "field": "field2"
        }
      ],
      "contexts": [
        {
          "name": "context1",
          "context": {
            "cluster": "cluster1"
          }
        },
        {
          "name": "context2",
          "context": {
            "cluster": "cluster2"
          }
        }
      ],
      "current-context": "context1"
    }
    

    Demo