I'm trying to do a replace where I first escape all double quotes and then I want to use the result of this replace to updated a value. But in the last replace the backslashes are removed, why is this and how do I avoid that?
Example in bash:
>TEST_OBJECT='{"val1": "a", "val2": "b"}'
>ESCAPED_OBJ="$(echo $TEST_OBJECT | sed 's/"/\\"/g')"
>echo $ESCAPED_OBJ
{\"val1\": \"a\", \"val2\": \"b\"}
>echo 'value: "_REPLACE_ME"' | sed "s#_REPLACE_ME#$ESCAPED_OBJ#g"
value: "{"val1": "a", "val2": "b"}"
I'm expecting this on the last row:
value: "{\"val1\": \"a\", \"val2\": \"b\"}"
EDIT
I realize I presented the issue wrong, the reason why I do it in 2 steps is because the first replace happens in one step and then the second replace happens in a later step. This is part of a github workflow and the last replace actually replaces a string in a different yaml file.
sed "s#_REPLACE_ME#$ESCAPED_OBJ#g" > ${{ github.action_path }}/config/job.yml
So I don't think I can do the replace in one step, first I need to update the string and then replace a value in another file.
job.yml
...
env:
- name: CUSTOM_DATA_OBJECT
value: "_REPLACE_ME"
I need the value to be escaped so that it doesn't break the yaml.
Using sed
$ sed "s#_REPLACE_ME#${TEST_OBJECT//\"/\\\\\"}#" input_file
...
env:
- name: CUSTOM_DATA_OBJECT
value: "{\"val1\": \"a\", \"val2\": \"b\"}"
Maybe letting yq take care of the correct escaping for YAML?
TEST_OBJECT='{"val1": "a", "val2": "b"}' \
yq eval '.env[0].value = strenv(TEST_OBJECT)' file.yaml
env:
- name: CUSTOM_DATA_OBJECT
value: "{\"val1\": \"a\", \"val2\": \"b\"}"
I would like to replace a single value inside a yaml file (file1.yml) with the value of another yaml file (file2.yml). The key inside both file:
app:
key: 12345
So far here is what I have done using sed with no success:
#!/bin/bash
old_value=`cat file1.yml | grep key | head -n1 | cut -f 2 -d ':'`
new_value=`cat file2.yml | grep key | head -n1 | cut -f 2 -d ':'`
sed "s/$old_value/$new_value/g;" file1.yml
I guess I should not be sending the standard output with sed and should be using awk.
To manipulate yaml files, you should employ a yaml processor, like mikefarah/yq or kislyuk/yq.
Using mikefarah/yq:
new="$(yq '.key' file2.yml)" yq -i '.key = env(new)' file1.yml
Using kislyuk/yq:
yml="$(yq -y -s '.[0].key = .[1].key | .[0]' file1.yml file2.yml)"
cat <<< "$yml" > file1.yml
Because in a yaml file the same value may exist in multiple places you want to use sed to perform a full search and then replace the value you are looking for; or use an specialized tool like yq (a jq wrapper)
For example, this yaml file is valid
app1:
key: "1234"
app2:
key: "1234"
with sed you will run the following to change key: "1234" to key: "5678" in just app2
sed '/^app2:/{n;s/key:.*/key: "5678"/;}' file.yaml
But doing the same using yq using in-place edit would look like:
yq -i -y '.app2.key = "5678"' file.yml
I have a YAML file like this
apiVersion: "v1alpha1"
kind: "Druid"
metadata:
name: druid-dev-cluster
spec:
common.runtime.properties: |
# Zookeeper
druid.zk.service.host=cluster-zk-0.cluster-zk
druid.zk.paths.base=/druid
druid.zk.service.compress=false
I want to replace one of the properties in the common.runtime.properties. Is this supported using yq?
When I try normally it fails
property=.spec.common.runtime.properties.druid.zk.service.host=cluster-zk-0.cluster-zk
OVERLAY= deploy/overlays/aws/common-runtime-properties.yaml
yq e "${property}" "$OVERLAY"/"$propertyType"
Error: Parsing expression: Lexer error: could not match text starting at 1:55 failing at 1:57.
unmatched text: "ti"
This works for other properties like
apiVersion: "v1alpha1"
kind: "Druid"
metadata:
name: druid-dev-cluster
spec:
nodes:
brokers:
nodeType: "broker"
druid.port: 8088
ingressAnnotations:
kubernetes.io/ingress.class: "plb.v1"
The problems are:
| is a YAML literal block scalar. Its content is a single scalar as far as YAML is concerned and hence you cannot path-select into it from yq.
Also, the content of | is not YAML, but probably parsed as Java properties file. Therefore, you cannot parse it even with recursively calling yq on the scalar.
Also, the path to the scalar is not .spec.common.runtime.properties but .spec."common.runtime.properties" – note how the second part is written as a single scalar in the YAML content, which is not equivalent to having multiple child mappings (even though some Java folks seem to believe that).
That being said, you can of course do something like
export NEW_VALUE=droggeljug
UPDATED_CONTENT=$(\
yq e '.spec."common.runtime.properties"' test.yaml | \
sed -re 's/(druid.zk.service.host=)[^\n]*/\1'"$NEW_VALUE"'/g' \
) yq e '.spec."common.runtime.properties" = strenv(UPDATED_CONTENT)' test.yaml
This uses yq to select the scalar containing the properties (line 3), uses sed to update the value druid.zk.service.host to the content of NEW_VALUE (line 4), stores the result in UPDATED_CONTENT (line 2) and then calls yq again updating the value to be the content of UPDATED_CONTENT (line 5).
When I retrieve all records for a hosted zone in Cloudflare, e.g. of response, I need to create from it the following yaml structure:
name_zones: # this line we create
.zone_name: # the value is taken from the response
auth_key: XXX # this line we create
records: # this line we create
# iterate over all records
- name: .name
type: .type
priority: .priority # create line if value set|exist
content: .content
ttl: .ttl # create line if value set|exist
e.g. jq code which almost done this:
jq '.result[] | {name: .name, type: .type, content: .content, ttl: .ttl} + if has("priority") then {priority} else null end' | jq -n '.name_zone.zone_name.auth_key.records |= [inputs]' | yq r -P -
How to pass or create the value of zone_name and auth_key: XXX?
First, your two invocations of jq can be replaced by just one, as shown in the answer below.
Second, there are currently (at least) two yqs in the wild:
python-based yq (https://kislyuk.github.io/yq) - hereafter python-yq
go-based yq (https://github.com/mikefarah/yq)
For better and/or worse, version 4 of the go-based yq is significantly different from earlier versions, so if you want to use the current go-based version you may have to make adjustments accordingly. To simplify things (at least from my point of view), I will replace your yq r -P - by:
python-jq -y .
The following produces the output shown below.
< cf_response.json \
jq '{name_zones:
{zone_name: .result[0].zone_name,
auth_key: "XXX",
records:
[.result[]
| {name, type, content, ttl}
+ if has("priority")
then {priority}
else null end] }} '|
python-yq -y .
Output
name_zones:
zone_name: test.com
auth_key: XXX
records:
- name: test.com
type: A
content: 111.111.111.111
ttl: 1
- name: test.com
type: TXT
content: google-site-verification=content
ttl: 1
- name: test.com
type: MX
content: smtp.test.com
ttl: 1
priority: 0
auth_key
If you want to pass in the value of auth_key as a parameter to jq, you could use the command-line sequence --arg auth_key XXX, and then use $auth_key in the jq program.
I've got a playbook with the following tasks:
- set_fact:
asg_filter: >
.AutoScalingGroups[] |
select(.Tags[] | select(.Key == "Role").Value == "myrole")
- shell: aws autoscaling --region us-west-2 describe-auto-scaling-groups | jq --compact-output "{{ asg_filter }}"
register: asgs_result
- set_fact:
stale_instance_filter: >
.LaunchConfigurationName as $lc |
.Instances[] |
select(.LaunchConfigurationName != $lc) |
.InstanceId
Now I want to use stale_instance_filter on asgs_result.stdout. The following works:
- shell: echo '{{ asgs_result.stdout }}' | jq -r '{{ stale_instance_filter }}'
But this doesn't:
- shell: jq -r '{{ stale_instance_filter }}'
args:
stdin: "{{ asgs_result.stdout }}"
I get the following error message: parse error: Invalid numeric literal at line 1, column 23 (which I believe is from the account number in the ARN for the ASG.) I think it's a quoting issue (maybe something about the double quotes in the JSON), but I've also tried asgs_result.stdout | quote to no avail. I also tried the command module; it didn't help either. Of course this all works if I do it directly on the CLI.
I realize I could combine the two jq filters but I want to reuse asgs_result for other things and don't want to have to make the query multiple times. How can I fix this so I can use the stdin argument?
Edit: I was asked to provide an example of the value of asgs_result, well here you go, here's the stdout attribute in it (since I don't use anything else):
"stdout": "{\"AutoScalingGroupARN\":\"arn:aws:autoscaling:us-east-2:123456:autoScalingGroup:e75a213b-75fe-467c-8cf5-d7c51f76c471:autoScalingGroupName/myrole-dev\",\"TargetGroupARNs\":[],\"SuspendedProcesses\":[],\"DesiredCapacity\":4,\"Tags\":[{\"ResourceType\":\"auto-scaling-group\",\"ResourceId\":\"myrole-dev\",\"PropagateAtLaunch\":true,\"Value\":\"dev\",\"Key\":\"Dimension\"},{\"ResourceType\":\"auto-scaling-group\",\"ResouJceId\":\"myrole-dev\",\"PropagateAtLaunch\":true,\"Value\":\"true\",\"Key\":\"Monitored\"},{\"ResourceType\":\"auto-scaling-group\",\"ResourceId\":\"myrole-dev\",\"PropagateAtLaunch\":true,\"Value\":\"myrole\",\"Key\":\"Name\"},{\"ResourceType\":\"auto-scaling-group\",\"ResourceId\":\"myrole-dev\",\"PropagateAtLaunch\":true,\"Value\":\"myrole\",\"Key\":\"Role\"},{\"ResourceType\":\"auto-scaling-group\",\"ResourceId\":\"myrole-dev\",\"PropagateAtLaunch\":true,\"Value\":\"2035-09-30 18:55:31 +0000\",\"Key\":\"cleaner-destroy-after\"},{\"ResourceType\":\"auto-scaling-group\",\"ResourceId\":\"myrole-dev\",\"PropagateAtLaunch\":true,\"Value\":\"vpce-2c23ca45\",\"Key\":\"force_s3_endpoint_dependency\"},{\"ResourceType\":\"auto-scaling-group\",\"ResourceId\":\"myrole-dev\",\"PropagateAtLaunch\":true,\"Value\":\"owned\",\"Key\":\"kubernetes.io/cluster/dev\"}],\"EnabledMetrics\":[],\"LoadBalancerNames\":[],\"AutoScalingGroupName\":\"myrole-dev\",\"DefaultCooldown\":300,\"MinSize\":4,\"Instances\":[{\"ProtectedFromScaleIn\":false,\"AvailabilityZone\":\"us-east-2b\",\"InstanceId\":\"i-0141fd35e3cf3ad0a\",\"HealthStatus\":\"Healthy\",\"LifecycleState\":\"InService\",\"LaunchConfigurationName\":\"dev_myrole_20180511171410107500000002\"},{\"ProtectedFromScaleIn\":false,\"AvailabilityZone\":\"us-east-2c\",\"InstanceId\":\"i-01aec2b3546d75190\",\"HealthStatus\":\"Healthy\",\"LifecycleState\":\"InService\",\"LaunchConfigurationName\":\"dev_myrole_20180511171410107500000002\"},{\"ProtectedFromScaleIn\":false,\"AvailabilityZone\":\"us-east-2a\",\"InstanceId\":\"i-0830b227f034d2859\",\"HealthStatus\":\"Healthy\",\"LifecycleState\":\"InService\",\"LaunchConfigurationName\":\"dev_myrole_20180511171410107500000002\"},{\"ProtectedFromScaleIn\":false,\"AvailabilityZone\":\"us-east-2b\",\"InstanceId\":\"i-0f7d847e8c168040b\",\"HealthStatus\":\"Healthy\",\"LifecycleState\":\"InService\",\"LaunchConfigurationName\":\"dev_myrole_20180511171410107500000002\"}],\"MaxSize\":4,\"VPCZoneIdentifier\":\"subnet-c348988e,subnet-79743210,subnet-156ee36e\",\"HealthCheckGracePeriod\":300,\"TerminationPolicies\":[\"Default\"],\"LaunchConfigurationName\":\"dev_myrole_20180511171410107500000002\",\"CreatedTime\":\"2018-02-20T22:35:32.183Z\",\"AvailabilityZones\":[\"us-east-2a\",\"us-east-2b\",\"us-east-2c\"],\"HealthCheckType\":\"EC2\",\"NewInstancesProtectedFromScaleIn\":false}"
Sorry that it is all on one line but I don't want to make anyone think there is a newline in there, because there isn't.
The JSON content seems to be interpreted before sent to the stdin, so looks like simple quotes are sent (seen in verbose mode with -vvv):
"stdin": "{'AutoScalingGroupARN': 'arn:aws:autoscaling:us-east-2:123456:autoScalin
gGroup:e75a213b-75fe-467c-8cf5-d7c51f76c471:autoScalingGroupName/myrole-dev', ...,
'AvailabilityZones': ['us-east-2a', 'us-east-2b', 'us-east-2c']}"
Which is not JSON valid:
$ echo "{'AutoScalingGroupARN': 'arn:aws:autoscaling:us-east-2:123456:autoScalingGroup:e75a213b-75fe-467c-8cf5-d7c51f76c471:autoScalingGroupName/myrole-dev', 'HealthCheckGracePeriod': 300}" | jq
parse error: Invalid numeric literal at line 1, column 23
$ echo '{"AutoScalingGroupARN": "arn:aws:autoscaling:us-east-2:123456:autoScalingGroup:e75a213b-75fe-467c-8cf5-d7c51f76c471:autoScalingGroupName/myrole-dev", "HealthCheckGracePeriod": 300}' | jq
{
"AutoScalingGroupARN": "arn:aws:autoscaling:us-east-2:123456:autoScalingGroup:e75a213b-75fe-467c-8cf5-d7c51f76c471:autoScalingGroupName/myrole-dev",
"HealthCheckGracePeriod": 300
}
So, you need to "escape" it.
Unfortunately, the to_json filter, escape to much:
"stdin": "\"{\\\"AutoScalingGroupARN\\\":\\\"arn:aws:autosca...
But the string filter fits perfectly:
"stdin": "{\"AutoScalingGroupARN\":\"arn:aws:autosca...
So, the correct way with stdin is this:
- shell: jq -r '{{ stale_instance_filter }}'
args:
stdin: "{{ asgs_result.stdout | string }}"