How to make Ansible ignore changes in certain lines in file - ansible

I have a regular ubuntu VM. My ansible playbook is the following
---
- hosts: linux
tasks:
- name: "Creating a file file.txt"
become: yes
copy: src=./file.txt dest=~/file.txt
notify: restart some_app
handlers:
- name: restart some_app
become: yes
service:
name=some_app
state=restarted
And the contents of my file are
First line
Second line
After creating my VM I apply my ansible playbook. Then I am doing some work on ubuntu VM which could change the contents of file.txt (in Ubuntu VM, not in host). And when my work is done I apply anible playbook again. But I want to restart some_app only when changes are made to the First line in my file and ignore any changes made to the Second line (during some work in ubuntu VM). For example:
If after my work I have (on Ubuntu VM)
First line
Changed Second line
I want ansible to do nothing to this file (I don't want it to be replaced by initial file.txt on my host) and I don't want some_app to be started.
But if I have
Changed First line
Changed Second line
I want ansible to replace only First line and restart some_app, so after applying ansible playbook I would have
First line
Changed Second line
Is it possible to create Second line only once and then make ansible ignore it completly? How can I make this possible using ansible playbook? I can't split my file.txt into two different files or smth like this.

You can create backup and diff the first lines of the files.
For example, if the destination does not exist the module copy reports changed in the registered dictionary. The attribute backup_file is undefined. In this case, changed_when evaluates to True and the handler is notified
- copy:
src: file.txt
dest: ~/file.txt
backup: true
register: copy_result
changed_when:
- copy_result is changed
- copy_result.backup_file is undefined
notify: restart some_app
If the destination exists you want to notify the handler if the first line changes. Create the block below to test the first line. The tasks in the block will be executed only if a backup file is created
- block:
- script:
cmd: '{{ playbook_dir}}/diff_first_line {{ copy_result.backup_file }}'
executable: /bin/bash
register: diff_result
ignore_errors: true
changed_when: diff_result.stdout_lines.0|d('') == '1c1'
notify: restart some_app
- debug:
var: diff_result
when: debug|d(false)|bool
when:
- copy_result.backup_file is defined
Create the script below. The script returns the diff between the first lines of the destination ~/file.txt and the backup file
shell> cat diff_first_line
diff <(head -n 1 ~/file.txt) <(head -n 1 $1)
exit 0
Create the handler for testing
handlers:
- name: restart some_app
debug:
msg: First line of ~/file.txt changed
Example of a complete playbook for testing
shell> cat pb.yml
- hosts: test_24
tasks:
- copy:
src: file.txt
dest: ~/file.txt
backup: true
register: copy_result
changed_when:
- copy_result is changed
- copy_result.backup_file is undefined
notify: restart some_app
- debug:
var: copy_result
when: debug|d(false)|bool
- block:
- script:
cmd: '{{ playbook_dir}}/diff_first_line {{ copy_result.backup_file }}'
executable: /bin/bash
register: diff_result
ignore_errors: true
changed_when: diff_result.stdout_lines.0|d('') == '1c1'
notify: restart some_app
- debug:
var: diff_result
when: debug|d(false)|bool
when:
- copy_result.backup_file is defined
handlers:
- name: restart some_app
debug:
msg: First line of ~/file.txt changed
If the destination does not exist the file will be created and the handler will be notified
shell> ansible-playbook pb.yml
PLAY [test_24] *******************************************************************************
TASK [copy] **********************************************************************************
changed: [test_24]
TASK [debug] *********************************************************************************
skipping: [test_24]
TASK [script] ********************************************************************************
skipping: [test_24]
TASK [debug] *********************************************************************************
skipping: [test_24]
RUNNING HANDLER [restart some_app] ***********************************************************
ok: [test_24] =>
msg: First line of ~/file.txt changed
PLAY RECAP ***********************************************************************************
test_24: ok=2 changed=1 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
shell> ssh admin#test_24 sudo cat /root/file.txt
First line
Second line
If the destination exists and the first line changed
shell> ssh admin#test_24 sudo cat /root/file.txt
First line changed
Second line
The script in the block will compare the first lines of the files and will notify the handler. Running the playbook with the variable debug=true gives
shell> ansible-playbook pb.yml -e debug=true
PLAY [test_24] *******************************************************************************
TASK [copy] **********************************************************************************
ok: [test_24]
TASK [debug] *********************************************************************************
ok: [test_24] =>
copy_result:
backup_file: /root/file.txt.1520674.2023-01-05#11:00:41~
changed: false
checksum: e23b0534d0708167023aba6f65ef83afe38d52df
dest: /root/file.txt
diff: []
failed: false
gid: 0
group: root
md5sum: cbfa34ef619be185bfbe2b8c8f104d97
mode: '0644'
owner: root
secontext: system_u:object_r:admin_home_t:s0
size: 23
src: /home/admin/.ansible/tmp/ansible-tmp-1672934439.424632-1552928-229786621684419/source
state: file
uid: 0
TASK [script] ********************************************************************************
changed: [test_24]
TASK [debug] *********************************************************************************
ok: [test_24] =>
diff_result:
changed: true
failed: false
rc: 0
stderr: |-
Shared connection to test_24 closed.
stderr_lines:
- Shared connection to test_24 closed.
stdout: |-
1c1
< First line
---
> First line changed
stdout_lines:
- 1c1
- < First line
- '---'
- '> First line changed'
RUNNING HANDLER [restart some_app] ***********************************************************
ok: [test_24] =>
msg: First line of ~/file.txt changed
PLAY RECAP ***********************************************************************************
test_24: ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The handler won't be notified if the first line doesn't change
shell> ssh admin#test_24 sudo cat /root/file.txt
First line
Second line changed
shell> ansible-playbook pb.yml -e debug=true
PLAY [test_24] *******************************************************************************
TASK [copy] **********************************************************************************
ok: [test_24]
TASK [debug] *********************************************************************************
ok: [test_24] =>
copy_result:
backup_file: /root/file.txt.1522339.2023-01-05#11:02:50~
changed: false
checksum: e23b0534d0708167023aba6f65ef83afe38d52df
dest: /root/file.txt
diff: []
failed: false
gid: 0
group: root
md5sum: cbfa34ef619be185bfbe2b8c8f104d97
mode: '0644'
owner: root
secontext: system_u:object_r:admin_home_t:s0
size: 23
src: /home/admin/.ansible/tmp/ansible-tmp-1672934567.8696983-1553096-127236704493791/source
state: file
uid: 0
TASK [script] ********************************************************************************
ok: [test_24]
TASK [debug] *********************************************************************************
ok: [test_24] =>
diff_result:
changed: false
failed: false
rc: 0
stderr: |-
Shared connection to test_24 closed.
stderr_lines:
- Shared connection to test_24 closed.
stdout: ''
stdout_lines: []
PLAY RECAP ***********************************************************************************
test_24: ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

How to automate a DataStage job with Ansible?

*****************************[UPDATE]***************************************
I want to edit a script with Ansible. ✔ SOLVED
I found the replace module but in my attempt to run the playbook I get an error. ✔ SOLVED
I want for each value that passes to the variable "result" to do something different. ✘ UNSOLVED
- name: Automation of a job in DataStage
hosts: dshost
vars_prompt:
- name: project
prompt: 'Enter the project'
private: no
- name: j0b
prompt: 'Enter the job'
private: no
tasks:
- name: Copying file script to modify then
copy:
src: /home/ansible/Downloads/script.sh
dest: /home/dsadm/script.sh
- name: Replaces the variables that we've passed
replace:
path: /home/dsadm/script.sh
regexp: '{{ item.regexp1 }}'
replace: '{{ item.replace }}'
backup: yes
with_items:
- { regexp1: 'project', replace: '{{ project }}' }
- { regexp1: 'j0b', replace: '{{ j0b }}' }
- name: Running the script
command: sh /home/dsadm/script.sh
register: result
- name: Check if the script is executed correctly
debug:
msg: "The file script was executed without errors"
when: result.stdout == "\r\nStatus code = 0 \r\n"
The script :
#!/bin/bash
cd /opt/IBM/InformationServer/Server/DSEngine/
. ./dsenv
$DSHOME/bin/dsjob -run project j0b
rtn=$?
**************(UPDATE)********* The output is :
sudo ansible-playbook testJob.yml
[sudo] password for ansible:
Enter the project: dstage1
Enter the job: limits
PLAY [Automation of a job in DataStage] ****************************************************
TASK [Gathering Facts] *********************************************************************
ok: [172.16.2.112]
TASK [Copying file script to modify then] **************************************************
changed: [172.16.2.112]
TASK [Replaces the variables that we've passed] ********************************************
changed: [172.16.2.112] => (item={u'regexp1': u'project', u'replace': u'dstage1'})
changed: [172.16.2.112] => (item={u'regexp1': u'j0b', u'replace': u'limits'})
TASK [Running the script] ******************************************************************
changed: [172.16.2.112]
TASK [Check if the script is executed correctly] *******************************************
skipping: [172.16.2.112]
PLAY RECAP *********************************************************************************
172.16.2.112 : ok=4 changed=3 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
UPDATE 04/07/2019
How can I achive that my playbook to does what I want depending on the variable result?
Correct syntax is
- name: Step one
hosts: xxx.xx.x.xxx
vars_prompt:
...
This is the reason for the error
"ERROR! the field 'hosts' is required but was not set"

How do I handle rollback in case of failure using handlers in Ansible?

I wrote below yml file which will install the SSM and cloudwatch agent but I want to rollback the installation in case of any failures during the installation. I tried use FAIL but not working..Please advise..
---
# tasks file for SSMAgnetInstall
- name: status check
command: systemctl status amazon-ssm-agent
register: s_status
- debug:
msg: "{{ s_status }}"
- name: Get CPU architecture
command: getconf LONG_BIT
register: cpu_arch
changed_when: False
check_mode: no
when: s_status.stdout == ""
ignore_errors: true
- name: Install rpm file for Redhat Family (Amazon Linux, RHEL, and CentOS) 32/64-bit
yum:
name: "https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_386/amazon-ssm-agent.rpm"
state: present
when: s_status.stdout == ""
become: yes
ignore_errors: true
- name: cloud status check
command: systemctl status amazon-cloudwatch-agent
register: cld_status
become: yes
- debug:
msg: "{{ cld_status }}"
- name: Register to cloud watch service
become: yes
become_user: root
service:
name: amazon-ssm-agent
enabled: yes
state: started
- name: copy the output to a local file
copy:
content: "{{ myshell_output.stdout }}"
dest: "/home/ansible/rama/output.txt"
delegate_to: localhost
You should have a look at the the documentation on blocks, more specifically the error handling part. This is the general idea with an oversimplified example, you will have to adapt to your specific case.
The test.yml playbook
---
- hosts: localhost
gather_facts: false
tasks:
- block:
- name: I am a task that can fail
debug:
msg: "I {{ gen_fail | default(false) | bool | ternary('failed', 'succeeded') }}"
failed_when: gen_fail | default(false) | bool
- name: I am a task that will never fail
debug:
msg: I succeeded
rescue:
- name: I am a task in a block played when a failure happens
debug:
msg: rescue task
always:
- name: I am a task always played whatever happens
debug:
msg: always task
Played normally (no fail)
$ ansible-playbook test.yml
PLAY [localhost] ************************************************************************
TASK [I am a task that can fail] ********************************************************
ok: [localhost] => {
"msg": "I succeeded"
}
TASK [I am a task that will never fail] *************************************************
ok: [localhost] => {
"msg": "I succeeded"
}
TASK [I am a task always played whatever happens] ***************************************
ok: [localhost] => {
"msg": "always task"
}
PLAY RECAP ******************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Played forcing a fail
$ ansible-playbook test.yml -e gen_fail=true
PLAY [localhost] ************************************************************************
TASK [I am a task that can fail] ********************************************************
fatal: [localhost]: FAILED! => {
"msg": "I failed"
}
TASK [I am a task in a block played when a failure happens] *****************************
ok: [localhost] => {
"msg": "rescue task"
}
TASK [I am a task always played whatever happens] ***************************************
ok: [localhost] => {
"msg": "always task"
}
PLAY RECAP ******************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0

Ansible: move on to the next task if the task is completed on one host

In ansible what i require is to check for a file is available on two hosts. But if the file is available on even one host i need to cancel the task on other host and move onto the next task. The reason why i require this is because a the next task can only be done if that particular file is available and that file can be randomly written to any of the hosts.
The following play does exactly what you want:
---
- hosts:
- server1
- server2
gather_facts: False
vars:
file_name: 'foo.bar'
tasks:
- name: wait for file
wait_for:
path: '{{ file_name }}'
state: present
timeout: 30
ignore_errors: True
- name: stat
stat:
path: '{{ file_name }}'
register: result
- name: next
debug:
msg: "File {{ file_name }} available on {{ ansible_host }}"
when: result.stat.isreg is defined and result.stat.isreg
The output is:
PLAY [server1,server2] *********************************************************
TASK [wait for file] ***********************************************************
ok: [server1]
fatal: [server2]: FAILED! => {"changed": false, "elapsed": 3, "msg": "Timeout when waiting for file foo.bar"}
...ignoring
TASK [stat] ********************************************************************
ok: [server1]
ok: [server2]
TASK [next] ********************************************************************
skipping: [server2]
ok: [server1] => {
"msg": "File foo.bar available on server1"
}
PLAY RECAP *********************************************************************
server1 : ok=3 changed=0 unreachable=0 failed=0
server2 : ok=0 changed=0 unreachable=0 failed=0
You can use the stat module to check the status like below and for also you can add the serial:1 below hosts: in your playbook
stat:
path: /path/to/something
register: p
debug:
msg: "Path exists and is a directory"
when: p.stat.isdir is defined and p.stat.isdir
https://docs.ansible.com/ansible/latest/modules/stat_module.html for more details

Condition based execution in ansible

I have a scenario if first condition is successful then execute condition two and if condition two is successful then execute condition three.
Below is what i tried.
vi testme.yml
---
- hosts: all
tasks:
- name: run this command and ignore the result
shell: "grep -ci Hello /tmp/data.out"
register: pingout
ignore_errors: yes
- debug: msg="{{ pingout.rc }}"
- name: run the server if Hello is found in the above task
command: echo "The server is UP since `uptime`"
register: output1
when: pingout.rc == 0
- debug: "{{ output1.stdout }}"
When the string is found I was expecting to see this executed and shown in the output: The server is UP since uptime
However, I do not see this printed in the output.
ansible-playbook -i /tmp/myhost /root/testme.yml
Output:
PLAY [all] ******************************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************************
ok: [95.76.121.113]
TASK [run this command and ignore the result] *******************************************************************************************************************************
changed: [95.76.121.113]
TASK [debug] ****************************************************************************************************************************************************************
ok: [95.76.121.113] => {
"msg": "0"
}
TASK [run the server if Hello is found in the above task] *******************************************************************************************************************
changed: [95.76.121.113]
TASK [debug] ****************************************************************************************************************************************************************
ok: [95.76.121.113] => {
"msg": "Hello world!"
}
PLAY RECAP ******************************************************************************************************************************************************************
95.76.121.113 : ok=5 changed=2 unreachable=0 failed=0
You do not need to check rc. Ansible knows, when a command failed.
---
- hosts: localhost
connection: local
tasks:
- name: First
shell: "true"
register: first
ignore_errors: yes
- name: Second
shell: "true"
register: second
ignore_errors: yes
when: first is not failed
- name: Third
shell: "true"
register: third
ignore_errors: yes
when: second is not failed
Correct syntax is
- debug: msg="{{ output1.stdout }}"
, or
- debug: var=output1.stdout

ansible loop over shell command output

I am learning ansible and I would like to know how to iterate of the results of a shell command. Here is what I have tried. I have this playbook:
[root#d61311ae17e2 /]# cat loop.yaml
---
- name: Loop Example
hosts: localhost
tasks:
- name:
command: cat /vcs.txt
register: vcs
- name: Nonsense to demo loop
template:
src: /foo.j2
dest: /foo.{{ item.1 }}
with_indexed_items: "{{ vcs }}"
The file /vcs.txt contains this:
[root#d61311ae17e2 /]# cat vcs.txt
vc-one
vc-two
vc-three
vc-four
What I was hoping would happen was the creation of four files: foo.vc-one, foo.vc-two, foo.vc-three and foo.vc-four. But what happens instead when I run ansible-playbook loop.yaml is this:
PLAY [Loop Example] *********************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************
ok: [127.0.0.1]
TASK [command] **************************************************************************************************************************************************
changed: [127.0.0.1]
TASK [Nonsense to demo loop] ************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"msg": "with_indexed_items expects a list"}
to retry, use: --limit #/loop.retry
PLAY RECAP ******************************************************************************************************************************************************
127.0.0.1 : ok=2 changed=1 unreachable=0 failed=1
I needed to do this with_indexed_items: "{{ vcs.stdout.split('\n')}}"
If you need stdout on a line-by-line basis, with_indexed_items: "{{ vcs.stdout_lines }}" is equivalent to .split('\n') and likely simpler/clearer.

Resources