You can use the field [+] notation to append fields in yq v3. So your command results in
yq --style double w -i myfile.yaml 'spec.template.spec.containers[0].command[+]' "c"
The above command appends the entry "c" to the array.
Or if you want to update the whole array command you could do in v4. Note that this creates the array entries in separate lines than what is shown in the OP. See YAML Multi-Line Arrays
yq e '.spec.template.spec.containers[0].command |= ["a", "b", "c","d"] | ..style="double"' yaml
Note that yq v4 is already out there and supports much powerful path expressions and operations. See Upgrading from v3 for instructions
Answer from Inian on Stack Overflow[question][solved]: how do I add to an array contents from another file
Syntax to write and append array with v4
How to Add / Replace array elements with "yq" conditionally - Stack Overflow
yq syntax to append a modified array element to a YAML file - Stack Overflow
Use this:
yq -i '.persons[0].city = "Barcelona"' yourfile.yaml
replacing “Barcelona” and yourfile.yaml as appropriate. This replaces the value of city inside the first element of the persons array (or creates it if there is none).
Alternatively, if you dont know the exact index, but know the name of the object you could try this:
yq -i '.persons[] |= select(.name == "mario").city = "Barcelona"' yourfile.yaml
works in yq version 4.25.2
UPDATE based on comments (Kusalananda), for versions 4.3x and 4.4x you need to use the following:
yq4407 -i '.persons |= map(select(.name == "mario").city = "Barcelona")' city.yaml
I don't have enough rep to comment on your answer @kev - but that is super neat. I'm going to clean it up a little and put it into the tips and tricks in the yq docs.
I've made it a little more generic and put it all in one command:
yq ea '.services = (
((.services[] | {.name: .}) as $item ireduce ({}; . * $item )) as $uniqueMap
| ( $uniqueMap | to_entries | .[]) as $item ireduce([]; . + $item.value)
) | select(fi==0)' *.yaml
Disclosure: I wrote yq
ireduce({}; . * $item)- create an objectireduce([]; . + $item)- create an array
yq ea '(.services[] | {.name: .url}) as $item ireduce ({}; . * $item ) | to_entries' Master.yaml service*.yaml |
yq e '{"services": .[] as $item ireduce([]; . + {"name": $item.key, "url": $item.value})}' -
read more
I just realized that my solution only worked for kislyuk/yq.
yq -y '.set += (.set | map(select(.x == "a") | .x = "A"))'
For mikefarah/yq go with @Fravadona's solution
yq '.set as $set | .set += ($set | map(select(.x=="a") | .x = "A"))'
Both output:
set:
- x: a
y: aaa
- x: b
y: bbb
- x: a
y: ccc
- x: A
y: aaa
- x: A
y: ccc
I opened an issue on github. Mike identified the cause as:
[yq] does not create a copy of the data structure when you call
.set[]. In the expression where you use it twice, the elements of the splat refer to the same location in memory, so updating one updates both.To get what you need in yq, you need to explicitly make a copy, you can do that by merging it in to an empty node
His suggestion changes my code:
old: yq '{"set":[.set[], ( .set[] |select(.x=="a")|.x="A")]}'
new: yq '{"set":[.set[], (({}*.set[])|select(.x=="a")|.x="A")]}'
^^^^^^^^^^^
Given that running kislyuk/yq on my original code produces the result I expected, I think the root cause of my problem is not having appropriate sequence points for use of / assignment to .set[].
You can use the following filter to make it work. It works by dynamically selecting the index of the object where your tag exists. On the selected object .value=51 will update the value as you wanted. You can also use the -i flag to do in-place modification of the original file.
yq -y '.releases[].set |= map(select(.name == "image.bar_proxy.tag").value=51)' yaml
See the underlying jq filter acting on the JSON object at jq-playground
Given the context of using Helmfile, there are a couple of ways you can approach this without necessarily editing the helmfile.yaml. Helmfile allows using the Go text/template language in many places, similarly to the underlying Helm tool, and has some other features that can help.
One of the easiest things you can do is take advantage of values: being a list, and of unknown values generally being ignored. You (or your CI/CD system) can write a separate YAML file that contains just the tags (JSON may be easier to write and is valid YAML)
# tags.yaml
image:
tag: 22
bar: {tag: 29}
bar_proxy: {tag: 46}
and then include this file as an additional file in the helmfile.yaml. (This would be equivalent to using helm install -f with multiple local values files, rather than helm install --set individual values.)
releases:
- name: foo
values:
- ../config/templates/foo-values.yaml.gotmpl
- tags.yaml
# no `set:`
- name: bar
values:
- ../config/templates/bar-values.yaml.gotmpl
- tags.yaml
- replicas: 1
# no `set:`
Helmfile's templating extensions also include env and requiredEnv to read ordinary environment variables from the host system. Helm proper does not have these to try to minimize the number of implicit inputs to a chart, but for Helmfile it's a possible way to provide values at deploy time.
releases:
- name: bar
set:
- name: image.bar_proxy.tag
value: {{ env "BAR_PROXY_TAG" | default "46" }}
- name: image.bar.tag
value: {{ requiredEnv "BAR_TAG" }}