How to Insert a new string into telegraf.conf's inputs.ping using ansible regexp - regex

I'm trying to use ansible to update telegraf.conf's [[inputs.ping]].
telegraf.conf looks like the following:
[[inputs.ping]]
urls = ["tac-temp1","tac-temp2", "tac-temp3","tac-temp4"] #tac
count = 30
timeout = 15.0
[inputs.ping.tags]
name = "tac"
[[inputs.ping]]
urls = ["prod-temp1","prod-temp2", "prod-temp3","prod-temp4"] #prod
count = 30
timeout = 15.0
[inputs.ping.tags]
name = "prod"
[[inputs.ping]]
urls = ["test-temp1","test-temp2", "test-temp3","test-temp4"] #test
count = 30
timeout = 15.0
[inputs.ping.tags]
name = "test"
I'm trying to add ,"tac-temp10" after ,"tac-temp4" in line 2 shown above.
- hosts: Servers
become: yes
become_method: sudo
tasks:
- name: Loading telegraf.conf content for search
shell: cat /tmp/telegraf.conf
register: tele_lookup
- name: Adding Server to /tmp/telegraf.conf if does not exists
lineinfile:
path: /tmp/telegraf.conf
state: present
regexp: '^((.*)"] #tac$)'
line: ',"tac-temp10"'
backup: yes
when: tele_lookup.stdout.find('tac-temp10') != '0'
regexp: '^((.*)"] #tac$)' is replacing the whole line with ,"tac-temp10". Expected output:
[[inputs.ping]]
urls = ["tac-temp1","tac-temp2", "tac-temp3","tac-temp4","tac-temp10"] #tac
count = 30
timeout = 15.0
[inputs.ping.tags]
name = "tac"

Warning: Ugly regexp ahead. Beware of unpredictable understanding for next guys (including you after time passed by...) doing maintenance.
The following will add your server at the end of the list if it is not already present (anywhere in the list) with a single idempotent task.
- name: add our server if needed
lineinfile:
path: /tmp/test.conf
backup: yes
state: present
regexp: '^( *urls *= *\[)(("(?!tac-temp10)([a-zA-Z0-9_-]*)",? *)*)(\] #tac)$'
backrefs: yes
line: '\1\2, "tac-temp10"\5'
You need to use backreferences to put back on the line the already matched parts of the expression. I used backup: yes so I could easily come back to the original for my tests. Feel free to drop it.
As you can see (and as advised in my warning) this is pretty much impossible to understand for anyone having to quickly read the code. If you have to do anything more fancy/complicated, consider using a template and storing your server list in a variable somewhere.

Related

Insert lines at the end of a block with ansible.builtin.lineinfile

I'm trying to add a couple of config items to the default containerd config file. Here is a snippet of the file.
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
runtime_type = ""
runtime_engine = ""
runtime_root = ""
privileged_without_host_devices = false
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
runtime_type = ""
runtime_engine = ""
runtime_root = ""
privileged_without_host_devices = false
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v1"
runtime_engine = ""
runtime_root = ""
privileged_without_host_devices = false
[plugins."io.containerd.grpc.v1.cri".cni]
I want to insert the following two lines at the end of [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] block.
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
I've managed to insert them at the beginning of the block with the following code. It's messy and I'm manually counting the whitespaces which I want to avoid. Is there a better way of doing this?
---
- name: Replace line in file
hosts: control
gather_facts: false
tasks:
- name: Replacing first line
ansible.builtin.lineinfile:
path: /root/config.toml
insertafter: '^(\s+)\[plugins.+\w\.runc\]'
line: ' [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]'
- name: Replacing second line
ansible.builtin.lineinfile:
path: /root/config.toml
insertafter: '^(\s+)\[plugins.+\w\.runc\.options\]'
line: ' SystemdCgroup = true'
I tried working with backrefs but ended up replacing instead of adding.
It appears there is no logical way of doing this yet. The best solutions seem to be either to use a template (seems to be the preferred way) or use insertbefore.

Performing regex_findall and split() on the same line

Here is the output that I'm trying to parse:
hostname#show bgp vrf vrfname summary | i 1.1
BGP Route Distinguisher: 1.1.1.1:0
BGP router identifier 1.1.1.1, local AS number 2222
1.1.1.3 0 64512 349608 316062 896772 0 0 2w4d 1
I have the following regex that succesfully matches just the last line. Now I need to split that line and view the last index. In this case it is "1", but I will want to fail if that value is "0".
- name: debug test
debug:
msg: "{{show_bgp_sessions.data | regex_findall('\\d+\\.\\d+\\.\\d+\\.\\d+\\s\\s.*')}}"
I tried adding a split in a couple different formats at the end of the "msg" line so that I can grab the last index to compare it in the failed_when statement:
msg: "{{show_bgp_sessions.data | regex_findall('\\d+\\.\\d+\\.\\d+\\.\\d+\\s\\s.*') | split(' ')}}"
But I'm getting the following error msg:
"template error while templating string: no filter named 'split'. String:
I've also tried to use a few different forms of "ends_with" to verify the last index in the string as I've used that a lot in my python experience, but I can't get it to work in ansible.
I can't create a new task to parse the data and perform the split seperately because I need to run this verification through a loop.
When you select the line, reverse the string, and split the first item. For example
msg: "{{ (my_line|reverse).split()|first }}"
Possibly the regex provided by #Thefourthbird is a better solution.
But for your issue at hand, this is caused by the fact that there is indeed no filter split in Jinja, see the list there: https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters.
The reason why there is no such a filter is simple: split() is a function of the Python String, and since Jinja is Python, you can just use it as is.
Also mind that, since regex_findall is meant for multiple matches, you'll have to select the first element of the list, for example, with the filter first.
So your message ends up being:
msg: >-
{{
(
show_bgp_sessions.data
| regex_findall('\\d+\\.\\d+\\.\\d+\\.\\d+\\s\\s.*')
| first
).split()
}}
Given the playbook:
- hosts: all
gather_facts: no
vars:
show_bgp_sessions:
data: |
hostname#show bgp vrf vrfname summary | i 1.1
BGP Route Distinguisher: 1.1.1.1:0
BGP router identifier 1.1.1.1, local AS number 2222
1.1.1.3 0 64512 349608 316062 896772 0 0 2w4d 1
tasks:
- debug:
msg: >-
{{
(
show_bgp_sessions.data
| regex_findall('\\d+\\.\\d+\\.\\d+\\.\\d+\\s\\s.*')
| first
).split()
}}
Gives the recap:
TASK [debug] ***************************************************************
ok: [localhost] => {
"msg": [
"1.1.1.3",
"0",
"64512",
"349608",
"316062",
"896772",
"0",
"0",
"2w4d",
"1"
]
}

Azure Pipeline dynamic parameters to template file from YAML pipeline

I am currently working with Azure Devops Build Pipelines, and am trying to call a template file to do some tasks from my build yaml.
I am facing some difficulties to pass parameters to the template file. Let assume that this is my template file (simplified) which works fine :
parameters:
iterations: []
steps:
- ${{ each i in parameters.iterations }}:
- task: PowerShell#2
displayName: "Get key values ${{i}}"
name: getKeyValues_${{i}}
inputs:
targetType: 'inline'
script: |
$item = "${{i}}"
Write-Host "item : $($item)"
$keyVal = $item -split "_"
Write-Host $keyVal
Write-Host "key: $($keyVal[0]) | value: $($keyVal[1])"
echo "##vso[task.setvariable variable=key;isOutput=true]$($keyVal[0])"
echo "##vso[task.setvariable variable=value;isOutput=true]$($keyVal[1])"
So I want my iterations parameter contain something like this :
iterations: ["1_60", "2_40"]
Inside my Yaml pipeline I have the following code(also simplified) :
Not working scenario
- task: PowerShell#2
displayName: Calculate iterations for $(copies) copies
name: calculateIterations
inputs:
targetType: 'inline'
script: |
# Do some stuf here to get the arrow below from int value = 100
$iterations = ["1_60, "2_40"]
echo "##vso[task.setvariable variable=iterations;isOutput=true]$($iterations)"
- template: container-template.yml
parameters:
iterations: $(calculateIterations.iterations)
Working scenario
- task: PowerShell#2
displayName: Calculate iterations for $(copies) copies
name: calculateIterations
inputs:
targetType: 'inline'
script: |
# Do some stuf here to get the arrow below from int value = 100
$iterations = ["1_60, "2_40"]
echo "##vso[task.setvariable variable=iterations;isOutput=true]$($iterations)"
- template: container-template.yml
parameters:
iterations: ["1_60, "2_40"]
As you can see, the problem is that I am unable to use the output variable of my script to pass it as parameter to my template.
When I run the not working scenario I have the following error :
I have found this post, but no solutions yet...
As what 4c74356b41 said, this is the dilemma at present. In another word, the Not working scenario you mentioned does not support to achieve until now.
Now, we must let the template know the clear text at compile time. Because during this compile time, we have difficulty to do 2 or more things in one step at same time, especially contain compiling variable value, passing to corresponding template dynamic arguments and etc.
Expected a sequence or mapping. Actual value
'$(calculateIterations.iterations)'
More detailed, during compile time (after you click Run but before pipeline start truely):
1) Firstly we map the value that come from YAML pipeline, to make sure the - ${{ each i in parameters.iterations }} has clear value to start.
2) After it is done, then parse exact value on name: getKeyValues_${{i}} in script order.
In your scenario, it cannot even satisfy the first step since what you passed is a variable, and we do not has parse value process here. That's why you saw error said Expected a sequence or mapping. Actual value '$(calculateIterations.iterations)'.
Another expression of this error message is: we(template) are looking forward to exact value(s) to map our dynamic parameters, but what you give is a unrecognized content $(calculateIterations.iterations). Sorry, we cannot start to run.

regex ansible lineinfile subsection

I am fairly new to ansible and have been solving the following problem with a shell script, but the proper way I believe is to use the lineinfile module, just not sure how to accomplish this.
Let's say I have a file with the following text in it.
<cpu>
<alarm>
active = yes
</alarm>
</cpu>
<disk>
<alarm>
active = yes
<fixed>
<#>
active = yes
description = File system /
<inode_error>
active = yes
threshold = 2
message = InodeError
</inode_error>
</#>
<#boot>
active = yes
description = File system /boot
percent = yes
<error>
active = yes
threshold = 5
message = DiskError
</error>
</#boot>
</fixed>
</alarm>
</disk>
I want to make sure the following section is set correctly.
<disk><alarm><fixed><#boot><error>"threshold = 2"</error></#boot></fixed></alarm></disk>
is there a way to only (modify/make sure exists) that line, normally this file is much larger with many more sections, but I erased some so the question is readable.
Update: Modifying this as it is not valid XML and the XML module will not parse the file correctly.
Thanks!
lineinfile scans file per line, so you can't define complex multiline regexp for context definition.
replace module support multiline regexp.
If you have threshold = X in the file and want to be sure it is set to specific value, you can use this regexp:
- replace:
path: ./test.txt
regexp: '(<disk>[\s\S]*<alarm>[\s\S]*<#boot>[\s\S]*<error>[\s\S]*)(threshold\s*=\s*\d+)([\s\S]*?<\/error>)'
replace: '\1threshold = 2\3'
It searches for line threshold\s*=\s*\d+ inside <disk>...<alarm>...<#boot>...<error>....
This code is idempotent – so if threshold = 2, then nothing is done.
But if there is no threshold = X string, it will fail. You should construct more complex regular expression for that case.
you could use the lineinfile module (http://docs.ansible.com/ansible/latest/lineinfile_module.html) where you could write a regex to modify/add the line and use the validate function to run a command to ensure that the xml file has the proper syntax.
If you are on Ansible 2.4 you can use the xml module (https://docs.ansible.com/ansible/2.4/xml_module.html) and use the attribute parameter to check if the xpath in xml file is set, like that:
- name: Read attribute value
xml:
path: /foo/bar.xml
xpath: /business/website/validxhtml
content: attribute
attribute: validatedon
register: xmlresp
regards

Rows in a group remove

I'm using the replace module of Ansible (http://docs.ansible.com/ansible/replace_module.html).
My file is:
...
net route-domain /Common/0 {
id 0
vlans {
/thisrow/AAAA_yyyyy
/Common/http-tunnel
/Common/socks-tunnel
/Common/BIGIP-HA
/thisrow/AAAA_xxxxx
}
}
...
I need to remove all rows containing /thisrow/ inside vlans.
I'm using this regex: (^ vlans )(?P<vlanrow>){([^}]*)}{0}.*vasgk.*\n but I don't know how to remove ALL thisrow from vlanrow group
Thanks,
Riccardo
This is not a dupl. Ansible is not the problem. The problem is the regular expression matching just 1 time thisrow. Try it on https://regex101.com/r/n3rRsl/1
I've came up with the following playbook, using a little modified regexp of yours and the sample data from regex101 you provided.
playbook.yml
- hosts: localhost
tasks:
- replace:
dest: /home/user/config.conf
regexp: '(^ vlans )(?P<vlanrow>){([^}]*)}{0}(\s{8}/vasgk.*)\n'
replace: '\1\2{\3'
register: result
until: result.changed == False
retries: 4094 # you can't have more vlans!
This is the result:
net route-domain /Common/0 {
id 0
vlans {
/Common/http-tunnel
/Common/socks-tunnel
/Common/BIGIP-HA
}
}
It seems to be quite slow though, but should give you an idea. Hope that helps!
Edit:
changed
(^ vlans )(?P<vlanrow>){([^}]*)}{0}(.*/vasgk.*)\n
to (^ vlans )(?P<vlanrow>){([^}]*)}{0}(\s{8}/vasgk.*)\n, this fixed problems with spacing.