I am trying to set an Ansible variable based on the count returned by the grep command.
The code is as follows.
tasks:
- name: Check for 12.1 databases
shell: "grep -c /u1/app/oracle/product/12.1.0/dbhome_1 /etc/oratab"
register: grep_output
- name: Print
debug:
msg="grep_output.stdout"
- name: Set the variable
set_fact:
oracle_version_12_1_0: "true"
when: "grep_output.stdout > 0"
- name: Print variable
command: echo "{{ oracle_version_12_1_0 }}"
The code errors out as follows.
PLAY [oradbpoc1] ***********************************************************************************************************************************************
TASK [Gathering Facts] *********************************************************************************************************
ok: [oradbpoc1]
TASK [Check for 12.1 databases] *********************************************************************************************************
fatal: [oradbpoc1]: FAILED! => {"changed": true, "cmd": "grep -c /u1/app/oracle/product/12.1.0/dbhome_1 /etc/oratab", "delta": "0:00:00.003425", "end": "2020-08-06 18:33:04.476299", "msg": "non-zero return code", "rc": 1, "start": "2020-08-06 18:33:04.472874", "stderr": "", "stderr_lines": [], "stdout": "0", "stdout_lines": ["0"]}
PLAY RECAP *********************************************************************************************************
oradbpoc1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
PS: Also, is there a way to assign a default value ("false") to the oracle_version_12_1_0 variable?
Thanks
FR
With regards to the Fail condition, it is because a grep -c PATTERN FILE will return a non-zero exit code if the PATTERN is not found in a file.
Quick example:
# grep -c root /etc/passwd; echo $?
2
0
# grep -c rootNO /etc/passwd; echo $?
0
1
So, I believe there are two options:
Just base upon the exit code
Set to ignore the error, and deal with the value
Since the count should be sufficient, Option 2 is shown here:
- hosts: localhost
tasks:
- name: get value
shell:
cmd: grep -c root /etc/passwd
register: grep_output
ignore_errors: yes
changed_when: false
- name: Print
debug:
msg: "{{ inventory_hostname }}: '{{ grep_output.stdout }}'"
- name : Set the variable
set_fact:
oracle_version_12_1_0: "{% if grep_output.stdout == '0' %}false{% else %}true{% endif %}"
- name: Print
debug:
msg: "val: {{ oracle_version_12_1_0 }}
Adjust root to rootNo (for example) to see the difference in true/false.
Also, depending upon exactly what you are wishing to do, it is also possible to register the variable as indicated in the get value, and then use it in a when check (i.e., avoid setting the variable):
- name : task name
...
when: grep_output.stdout != '0'
Whether this latter approach is helpful or not depends on the overall use case.
Related
I currently have a playbook which includes a task file. In that task file, I would like to check for a condition. If the exit code of that condition is not equal to 0, all steps in the task file should be repeated. I have tried a few variations with block and loops but I have not figured out a way to make it do what I described above.
Currently I have something like this:
tasks:
- name: call task file
include: task_file.yml
In task_file.yml,
- name: perform an operations
shell: do A
- name: check
shell: do B
register: result
Next, I would like to tell the main playbook that if result.rc != 0, please repeat task_file.yml until result.rc == 0.
Any pointers would be greatly appreciated
The playbook seems to end no matter what the exit code.
There's no direct way to reach your goal as include_tasks does not support the retry/until loop keywords.
There is an attempt to circumvent that limitation by teaching ansible a new loop_control.until keyword for loops which could be used for includes. Unfortunately, the pull request has been opened since Sep. 2019 and has still not reached a realease.
The good news is you can implement that with some work by using include recursion with a block. The below example is largely inspired by a blog article on https://dev.to. I adapted to the current context, fixed some good practice and added features like flexible retries number and delay between retries. Here we go:
The tasks to be retried go in task_file.yml
---
- name: group of tasks to repeat until success
block:
- name: increment attempts counter
ansible.builtin.set_fact:
attempt_number: "{{ attempt_number | d(0) | int + 1 }}"
- name: dummy task
ansible.builtin.debug:
msg: "I'm a dummy task"
- name: task to check for success.
# Just for the example. Will return success on attempt number 3
ansible.builtin.command: "[ {{ attempt_number | int }} -eq 3 ]"
changed_when: false
rescue:
- name: "Fail if we reached the max of {{ max_attempts | d(3) }} attempts"
# Default will be 3 attempts if max_attempts is not passed as a parameter
ansible.builtin.fail:
msg: Maximum number of attempts reached
when: attempt_number | int == max_attempts | int | d(3)
- ansible.builtin.debug:
msg: "group of tasks failed on attempt {{ attempt_number }}. Retrying"
- name: add delay if needed
# no delay if retry_delay is not passed as parameter
ansible.builtin.wait_for:
timeout: "{{ retry_delay | int | d(omit) }}"
when: retry_delay is defined
# include ourselves to retry.
- ansible.builtin.include_tasks: task_file.yml
As you can see, the file includes itself again in case of failure until success of max attempts is reached. Also, note that a retry will happen if any task fails inside the block, not only the last one. If you have a more complex scenario, you can implement more checks for fail/not fail in the rescue section an even add an always section if needed. See anbile blocks
Then you can call this file from your playbook:
---
- hosts: localhost
gather_facts: false
tasks:
- name: Include tasks to retry. 7 attempts max with 1 second delay
ansible.builtin.include_tasks: task_file.yml
vars:
max_attempts: 7
retry_delay: 1
Playing this example succeeds on third attempt as hardcoded and expected. (You can play around with the parameters to test a fail scenario)
$ ansible-playbook playbook.yml
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Include tasks to retry] **********************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": ["[", "1", "-eq", "3", "]"], "delta": "0:00:00.002104", "end": "2022-12-08 14:16:27.850578", "msg": "non-zero return code", "rc": 1, "start": "2022-12-08 14:16:27.848474", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Fail if we reached the max of 7 attempts] ****************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.builtin.debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "group of tasks failed on attempt 1. Retrying"
}
TASK [add delay if needed] *************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.builtin.include_tasks] ***************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "cmd": ["[", "2", "-eq", "3", "]"], "delta": "0:00:00.004009", "end": "2022-12-08 14:16:29.496509", "msg": "non-zero return code", "rc": 1, "start": "2022-12-08 14:16:29.492500", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Fail if we reached the max of 7 attempts] ****************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [ansible.builtin.debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "group of tasks failed on attempt 2. Retrying"
}
TASK [add delay if needed] *************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [ansible.builtin.include_tasks] ***************************************************************************************************************************************************************************************************
included: /tmp/toto/task_file.yml for localhost
TASK [increment attempts counter] ******************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [dummy task] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "I'm a dummy task"
}
TASK [task to check for success.] *******************************************************************************************************************************************************************************************************
ok: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=14 changed=0 unreachable=0 failed=0 skipped=2 rescued=2 ignored=0
Below is my ansible playbook that checks for file/dir permission and should exit the loop (BUT not stop the play) with the details of the file/dir not meeting the permission check (others should not have execute, or write permissions)
---
- name: "Play 1"
hosts: localhost
serial: true
any_errors_fatal: false
tasks:
- name: Checking permissions with include task
include_tasks: "{{ playbook_dir }}/internalcheckperm.yml"
loop:
- "~/.ssh/known_hosts"
- "~/.ssh/authorized_keys"
- "~/"
- "~/.ssh"
when: permchangeflag | default(false) == false
- name: Send Email
debug:
msg: "Sending out email and other stuff below"
cat internalcheckperm.yml
- stat:
path: "{{ item }}"
register: fp
- set_fact:
permchangeflag: true
when: fp.stat.exists == true and fp.stat.xoth == true and fp.stat.woth == true and fp.stat.isdir == false
- debug:
msg: "permchangeflag value: {{ permchangeflag | default(false) }} on {{ inventory_hostname }} for file {{ item }}"
when: fp.stat.exists == true
Run as ansible-playbook test.yml
Requirement: if ~/.ssh/known_hosts is permission 777 then the condition when: permchangeflag | default(false) == false should fail and loop should not run (skip) for the remaining files/dir in the loop.
However, despite permchangeflag becoming true as evident from the output below the loop continues to run for all the files.
TASK [debug] *******************************************************************************************************************
Tuesday 15 November 2022 08:12:17 -0600 (0:00:00.031) 0:00:02.454 ******
ok: [myremote7] => {
"msg": "permchangeflag value: True on myremote7 for file ~/.ssh/known_hosts and when not True"
From the output it seems the when condition does not kick for one element of the loop at a time but all at once which is something I don't want.
TASK [Checking permissions with include task] ******************************************************************************************************************************************************************
included: /root/internalcheckperm.yml for localhost => (item=~/.ssh/known_hosts)
included: /root/internalcheckperm.yml for localhost => (item=~/.ssh/authorized_keys)
included: /root/internalcheckperm.yml for localhost => (item=~/)
included: /root/internalcheckperm.yml for localhost => (item=~/.ssh)
Can you please suggest how can i break/skip the loop as soon as the first when permission condition is not met?
Goal: set ~20 variables that are stored in a JSON file into the $GITHUBENV during GHA job.
Currently, I am hardcoding this with the parsing of the JSON file with jq:
- name: Set env variables from variables json
run: |
echo "NAME=$(jq -r '.name' variables.json)" >> $GITHUB_ENV
echo "AGE=$(jq -r '.age' variables.json)" >> $GITHUB_ENV
echo "WEIGHT=$(jq -r '.weight' variables.json)" >> $GITHUB_ENV
...etc
How can I "for loop" through this process?
Afterwards, I want to check if the variables entered manually through a workflow_dispatch run of the job match what are in the variables.json file. If they don't match, I want to update the json file with the new manually entered value:
- name: Set age if dispatching
shell: bash -l {0}
if: github.event.inputs.age != env.AGE
run: echo "$( jq '.name = ${{github.event.inputs.age}}' variables.json )" > variables.json
Similarly, how do I loop through this process?
I suggest you to use the JSON to variables GitHub Actions. From the doc:
test.json
{
"value": "value",
"array": [
{
"value": "value 0"
},
"value 1"
],
"obj": {
"value1": "value1",
"value2": "value2"
}
}
workflow.main
- name: JSON to variables
uses: antifree/json-to-variables#v1.0.1
with:
filename: 'test.json'
prefix: test
- name: Show output
run: echo "The time was ${{ env.test_value }}, ${{ env.test_array_0_value }}, ${{ env.test_obj_value1 }}"
I have made an ansible script to collect IP in the nginx log and then the IP that has been collected will be analyzed, if a malicious IP is detected it will be blocked.
The idea is that in the ansible script each IP analyzed is not created individually but using a loop function
But I'm having a hard time making a looping script in ansible, can anyone help?
Here is the script that i have created
- name: Execute a command using the shell module
shell: awk '{print $1}' /var/log/nginx/*.log | sort | uniq -c | sort -nr| head -5
register: results
- debug: var=results.stdout_lines
- name: Write Output
local_action: shell echo "{{ results.stdout_lines }}" > /tmp/output
#Collect IP
- name: Check 1st IP
local_action: shell cat /tmp/output | awk '{ print $3 }'| sed 's/.$//'|sed 's/.$//'
register: ip1
- debug: var=ip1.stdout
- name: Check 2nd IP
local_action: shell cat /tmp/output | awk '{ print $6 }'| sed 's/.$//'|sed 's/.$//'
register: ip2
- debug: var=ip2.stdout
- name: Check 3rd IP
local_action: shell cat /tmp/output | awk '{ print $9 }'| sed 's/.$//'|sed 's/.$//'
register: ip3
- debug: var=ip3.stdout
- name: Check 4th IP
local_action: shell cat /tmp/output | awk '{ print $12 }'| sed 's/.$//'|sed 's/.$//'
register: ip4
- debug: var=ip4.stdout
- name: Check 5th IP
local_action: shell cat /tmp/output | awk '{ print $15 }'| sed 's/.$//'|sed 's/.$//'
register: ip5
- debug: var=ip5.stdout
#Anlyze IP
- name: Analyze 1st IP
local_action: shell /usr/bin/abuseipdb -C {{ ip1.stdout }} -o plaintext
register: vb1
- debug: var=vb1.stdout
- name: Analyze 2nd IP
local_action: shell /usr/bin/abuseipdb -C {{ ip2.stdout }} -o plaintext
register: vb2
- debug: var=vb2.stdout
- name: Analyze 3rd IP
local_action: shell /usr/bin/abuseipdb -C {{ ip3.stdout }} -o plaintext
register: vb3
- debug: var=vb3.stdout
- name: Analyze 4th IP
local_action: shell /usr/bin/abuseipdb -C {{ ip4.stdout }} -o plaintext
register: vb4
- debug: var=vb4.stdout
- name: Analyze 5th IP
local_action: shell /usr/bin/abuseipdb -C {{ ip5.stdout }} -o plaintext
register: vb5
- debug: var=vb5.stdout
##Block IP if score more than 25
- name: Check 1rst IP and Block IP if score more than 25
shell: /sbin/route add "{{ ip1.stdout }}" gw 127.0.0.1 lo
when: "{{ vb1.stdout }} >= 25"
- name: Check 2nd IP and Block IP if score more than 25
shell: /sbin/route add "{{ ip2.stdout }}" gw 127.0.0.1 lo
when: "{{ vb2.stdout }} >= 25"
- name: Check 3rd IP and Block IP if score more than 25
shell: /sbin/route add "{{ ip3.stdout }}" gw 127.0.0.1 lo
when: "{{ vb3.stdout }} >= 25"
- name: Check 4th IP and Block IP if score more than 25
shell: /sbin/route add "{{ ip4.stdout }}" gw 127.0.0.1 lo
when: "{{ vb4.stdout }} >= 25"
- name: Check 5th IP and Block IP if score more than 25
shell: /sbin/route add "{{ ip5.stdout }}" gw 127.0.0.1 lo
when: "{{ vb5.stdout }} >= 25"
#Check Route
- name: Make Sure the IP has been blocked
shell: /sbin/route -n
register: route
- debug: var=route.stdout_lines
Here is the /tmp/output
└─$ cat /tmp/output
[' 40545 87.250.224.147', ' 20873 87.250.224.126', ' 16665 213.180.203.67', ' 15420 87.250.224.142', ' 14503 13.81.52.25']
Here is the output when running
└─$ cat /tmp/output | awk '{ print $3 }'| sed 's/.$//'|sed 's/.$//'
87.250.224.147
└─$ cat /tmp/output | awk '{ print $6 }'| sed 's/.$//'|sed 's/.$//'
87.250.224.126
└─$ cat /tmp/output | awk '{ print $9 }'| sed 's/.$//'|sed 's/.$//'
213.180.203.67
└─$ cat /tmp/output | awk '{ print $12 }'| sed 's/.$//'|sed 's/.$//'
87.250.224.142
└─$ cat /tmp/output | awk '{ print $15 }'| sed 's/.$//'|sed 's/.$//'
13.81.52.25
It is probably better to generate a JAML file from your Nginx log in order to use include_vars after that.
Btw: The idea of Ansible is to be platform independent. Because of that, it is better to have most of the platform specific stuff in an external script and not in the playbook. When you change the platform, you just have to migrate the external script, instead of rewriting the playbook.
Another alternative is to use scripted inventories. You can create a dynamic inventory by reading the IP addresses to block from your Nginx log. If you put the hosts to block into a group, you can define a single task for the group and delegate it to the firewall, where you block the hosts. You do not need to care about iterations. It is the job of Ansible to iterate.
This is much easier to maintain, than the playbook in your question. And it is extremely inefficient to put every line of a shell script into one local action. Don't do it.
PS: Yes you can program in a playbook, somehow. But try to avoid it. A playbook is not a useful programming language. You can not abstract with procedures and it has almost no scope. When you work with Ansible you have to change the way you think. Do not think about procedures. Instead thing about data. Take your data and arrange the data in a way, that is suitable for Ansible. If the data does not fit, use Jinja filters to rearrange the data until it fits. But avoid "programming" in a playbook by all means. And if you need to program, better do it in Jinja statements or Python.
so you could use range to loop over your command shell, i show you an example: when you loop with register, it records all output in list.
- name: "tips2"
hosts: localhost
tasks:
- name: Check 1st IP
local_action: shell echo {{ item }}
register: result
loop: "{{ range(0, 4 + 1, 2)|list }}" #loop from 0 to 4 with a step 2
- debug: var=result
#then you build your final list
- set_fact:
ips: "{{ ips | d([]) + item.stdout_lines }}"
loop: "{{ result.results }}"
- debug: var=ips
display variable result:
ok: [localhost] => {
"result": {
"changed": true,
"msg": "All items completed",
"results": [
{
"ansible_loop_var": "item",
:
:
}
},
"item": 0,
"rc": 0,
"start": "2022-01-21 09:38:12.092426",
"stderr": "",
"stderr_lines": [],
"stdout": "0",
"stdout_lines": [
"0"
]
},
{
"ansible_loop_var": "item",
:
:
}
},
"item": 2,
"rc": 0,
"start": "2022-01-21 09:38:12.348007",
"stderr": "",
"stderr_lines": [],
"stdout": "2",
"stdout_lines": [
"2"
]
},
{
"ansible_loop_var": "item",
:
:
}
},
"item": 4,
"rc": 0,
"start": "2022-01-21 09:38:12.607555",
"stderr": "",
"stderr_lines": [],
"stdout": "4",
"stdout_lines": [
"4"
]
}
]
}
}
and result final ips after set_fact:
ok: [localhost] => {
"ips": [
"0",
"2",
"4"
]
}
just adapt the solution to your case: as i dont know your output, check the value of register...
- name: Check All IPs
local_action: shell cat /tmp/output | awk '{ print ${{item}} }'| sed 's/.$//'|sed 's/.$//'
register: result
loop: "{{ range(3, 15 + 1, 3)|list }}" #loop from 3 to 15 with a step 3
- set_fact:
ips: "{{ ips | d([]) + item.stdout_lines }}"
loop: "{{ result.results }}"
- name: Analyze All IPs
local_action: shell /usr/bin/abuseipdb -C {{ item }} -o plaintext
register: vbresult
loop: "{{ ips }}"
- debug: var=vbresult
another solution will be to trap alls your ips from your file in one task..
but you have to know your output, but the solution i show is easily adaptable to your case quickly
EDITED:
following your output, you could work directly from your result.stdout_lines:
- name: simulate your output result.stdout_lines
set_fact:
values:
- ' 40545 87.250.224.147'
- ' 20873 87.250.224.126'
- ' 16665 213.180.203.67'
- ' 15420 87.250.224.142'
- ' 14503 13.81.52.25'
- name: trap ips values
set_fact:
ips: "{{ ips | d([]) + [_ip] }}"
loop: "{{ values }}"
vars:
_ip: "{{ (item | trim).split(' ') | last }}"
- debug:
var: ips
result:
ok: [localhost] => {
"ips": [
"87.250.224.147",
"87.250.224.126",
"213.180.203.67",
"87.250.224.142",
"13.81.52.25"
]
}
I need to skip the value null from a list in ansible loop. I am using the when condition, still the null value gets printed.
Below is my playbook:
- hosts: localhost
vars:
show:
- read
- write
- null
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: item != "null"
- name: Print
debug:
msg: "{{val}}"
Output:
TASK [Print] ***
ok: [localhost] => {
"msg": [
"read",
"write",
null,
"test"
]
}
Please advise.
Quoting from YAML 10.2.1.1. Null
"Represents the lack of a value. This is typically bound to a native null-like value (e.g., undef in Perl, None in Python). ..."
There are more options on how to test null.
Compare to Python None
when: item != None
Use Jinja test none
when: item is not none
If you for whatever reason have to compare to a string the Jinja filter string converts null to string 'None'
when: item|string != 'None'
The most efficient way is to remove null values from the list before the iteration
loop: "{{ show|reject('none')|list }}"
I don't know if you want to check if the variable is "null" as a text or if the variable doesn't have a value and it's nullified so I will write both examples :), make null value a string and compare as a string:
- hosts: localhost
vars:
show:
- read
- write
- "null"
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: "'{{item}}' != 'null'"
- name: Print
debug:
msg: "{{val}}"
Test if the value is nullified and it really doesn't have any value we will use, is none for that example:
- hosts: localhost
vars:
show:
- read
- write
- null
- test
val: []
tasks:
- name: Fact
set_fact:
val: "{{val+[item]}}"
loop: "{{show}}"
when: item is not none
- name: Print
debug:
msg: "{{val}}"