Search code examples
yamljqyq

How to replace value recursively in yq depend on key and value type?


I wish I could use yq to iterate a yaml recursively, find all key-value pairs, whose key is image and value type is string(!!str), then add a prefix to the original value.

Input:

serving:
  template:
    image: "docker.io/serving"
custom:
  template:
    image: 
      type: string
      description: "..."
eventing:
  image: "docker.io/eventing"

Add a prefix private.io/ to all image field if its value type is string(!!str) recursively.

Expected output:

serving:
  template:
    image: "private.io/docker.io/serving"
custom:
  template:
    image: 
      type: string
      description: "..."
eventing:
  image: "private.io/docker.io/eventing"

For a equivalent json, I can use the walk operator to do such thing:

jq 'walk(if type == "object" and has("image") and (.image|type) == "string" then .image = "private.io/" + .image else . end)' input.json

Is there any equivalent operator like walk, I have tried the .. operator

yq '.. | select(tag == "!!str" and key == "image") |= "private.io/" + . ' input.json

But this command add some entries to the yaml, result in:

serving:
  template:
    image: "private.io/docker.io/serving"
custom:
  template:
    image:
      type: string
      description: "..."
eventing:
  image: "private.io/docker.io/eventing"
template:
  image: "private.io/docker.io/serving"
image: "private.io/docker.io/serving"
private.io/docker.io/serving
template:
  image:
    type: string
    description: "..."
image:
  type: string
  description: "..."
type: string
description: "..."
string
...
image: "private.io/docker.io/eventing"
private.io/docker.io/eventing

Any ideas or walk around ? Thanks, sincerely~


Solution

  • There is, and you almost had it. Just set parentheses to retain the context:

    yq '(.. | select(key == "image" and type == "!!str")) |= "private.io/" + .'
    
    serving:
      template:
        image: "private.io/docker.io/serving"
    custom:
      template:
        image:
          type: string
          description: "..."
    eventing:
      image: "private.io/docker.io/eventing"
    

    Tested with mikefarah/yq v4.31.1


    Note: With kislyuk/yq or itchyny/gojq you can also use your original walk filter on your YAML input. Both tools can process YAML files using proper jq syntax.

    yq -y 'walk(
      if type == "object" and has("image") and (.image|type) == "string" 
      then .image |= "private.io/" + . else . end
    )'
    
    # or
    
    gojq --yaml-input --yaml-output 'walk(
      if type == "object" and has("image") and (.image|type) == "string" 
      then .image |= "private.io/" + . else . end
    )'