Ansible showing task changed but the task has condition (creates: ) and does not actually execute - asynchronous

My ansible-playbook is running some long running task with async tag and also utilizes "creates:" condition, so it is run only once on the server. When I was writing the playbook yesterday, I am pretty sure, the task was skipped when the log set in "creates:" tag existed.
It shows changed now though, everytime I run it.
I am confused as I do not think I did change anything and I'd like to set up my registered varaible correctly as unchanged, when the condition is true.
Output of ansible-play (debug section shows the task is changed: true):
TASK [singleserver : Install Assure1 SingleServer role] *********************************************************************************************************************************
changed: [crassure1]
TASK [singleserver : Debug] *************************************************************************************************************************************************************
ok: [crassure1] => {
"msg": {
"ansible_job_id": "637594935242.28556",
"changed": true,
"failed": false,
"finished": 0,
"results_file": "/root/.ansible_async/637594935242.28556",
"started": 1
}
}
But if I check the actual results file on the target maschine, it correctly resolved condition and did not actually execute the shell script, so the task should be unchanged (shows message the task is skipped as the log exists):
[root#crassure1 assure1]# cat "/root/.ansible_async/637594935242.28556"
{"invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": true, "strip_empty_ends": true, "_raw_params": "/opt/install/install_command.sh", "removes": null, "argv": null, "creates": "/opt/assure1/logs/SetupWizard.log", "chdir": null, "stdin_add_newline": true, "stdin": null}}, "cmd": "/opt/install/install_command.sh", "changed": false, "rc": 0, "stdout": "skipped, since /opt/assure1/logs/SetupWizard.log exists"}[root#crassure1 assure1]# Connection reset by 172.24.36.123 port 22
My playbook section looks like this:
- name: Install Assure1 SingleServer role
shell:
#cmd: "/opt/assure1/bin/SetupWizard -a --Depot /opt/install/:a1-local --First --WebFQDN crassure1.tspdata.local --Roles All"
cmd: "/opt/install/install_command.sh"
async: 7200
poll: 0
register: Assure1InstallWait
args:
creates: /opt/assure1/logs/SetupWizard.log
- name: Debug
debug:
msg: "{{ Assure1InstallWait }}"
- name: Check on Installation status every 15 minutes
async_status:
jid: "{{ Assure1InstallWait.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 900
when: Assure1InstallWait is changed
Is there something I am missing, or is that some kind of a bug?
I am limited by Ansible version available in configured trusted repo, so I am using ansible 2.9.25

Q: "The module shell shows changed every time I run it"
A: In async mode the task can't be skipped immediately. First, the module shell must find out whether the file /opt/assure1/logs/SetupWizard.log exists at the remote host or not. Then, if the file exists the module will decide to skip the execution of the command. But, you run the task asynchronously. In this case, Ansible starts the module and returns without waiting for the module to complete. That's what the registered variable Assure1InstallWait says. The task started but didn't finish yet.
"msg": {
"ansible_job_id": "637594935242.28556",
"changed": true,
"failed": false,
"finished": 0,
"results_file": "/root/.ansible_async/637594935242.28556",
"started": 1
}
The decision to set such a task changed is correct, I think because the execution on the remote host is going on.
Print the registered result of the module async. You'll see, that the command was skipped because the file exists (you've printed the async file at the remote instead). Here the attribute changed is set false because now we know the command didn't execute
job_result:
...
attempts: 1
changed: false
failed: false
finished: 1
msg: Did not run command since '/tmp/SetupWizard.log' exists
rc: 0
...

Related

How do I get just the STDOUT of a salt state?

my output now
I'm learning salt stack right now and I was wondering if there was a way to get the stdout of a salt state and put it into a document and then send it to the master. Or is there a better way to do this?
To achieve this, we'll have to save the execution of the script in a variable. It will contain a hash containing keys that are showing up under changes:. Then the contents of this variable (stdout) can be written to a file.
{% set script_res = salt['cmd.script']('salt://test.sh') %}
create-stdout-file:
file.managed:
- name: /tmp/script-stdout.txt
- contents: {{ script_res.stdout }}
The output is already going to the master. It would be better to actually output in json and query down to the data you want in your document on the master.
such as the following
Normal output
$ sudo salt salt00\* state.apply tests.test3
salt00.wolfnet.bad4.us:
----------
ID: test_run
Function: cmd.run
Name: echo test
Result: True
Comment: Command "echo test" run
Started: 10:39:51.103057
Duration: 18.281 ms
Changes:
----------
pid:
8661
retcode:
0
stderr:
stdout:
test
Summary for salt00.wolfnet.bad4.us
------------
Succeeded: 1 (changed=1)
Failed: 0
------------
Total states run: 1
Total run time: 18.281 ms
json output
$ sudo salt salt00\* state.apply tests.test3 --out json
{
"salt00.wolfnet.bad4.us": {
"cmd_|-test_run_|-echo test_|-run": {
"name": "echo test",
"changes": {
"pid": 9057,
"retcode": 0,
"stdout": "test",
"stderr": ""
},
"result": true,
"comment": "Command \"echo test\" run",
"__sls__": "tests.test3",
"__run_num__": 0,
"start_time": "10:40:55.582273",
"duration": 19.374,
"__id__": "test_run"
}
}
}
json parsed down with jq to just the stdout
$ sudo salt salt00\* state.apply tests.test3 --out=json | jq '.|.[]|."cmd_|-test_run_|-echo test_|-run"|.changes.stdout'
"test"
Also, for the record it is considered bad practice to put code that changes the system into jinja. Jinja always runs when a template is rendered and there is no way to control if it happens so just running test=true tests will still run the jinja code that makes changes which could be very harmful to your systems.

ncclient timed out when using junos_vlan

I use ansible for network automation.
Usually everything works great, but when trying to create vlan at a particular Juniper switch, I get ncclient timed out while waiting for an rpc reply error. I use junos_vlan module.
I tried extending the timeout periods at ansible.cfg, switching ansible_connection from network_cli to netconf and so on, but none of that helped.
Can something be done from either server's or switch's side?
I read about a guy finding a workaround editing the module file:
Ansible, Juniper CLI commands. Timeout Error?
Can I reach a desired effect with the same approach?
I use ansible 2.8.1 and python 3.6.3. The device has JUNOS 14.1X53-D47.3 firmware.
Any suggestions?
Here is the output of the failure:
TASK [Setting vlan description and giving vlanID] **********************************************
task path: /opt/ansible/roles/juniper/tasks/add_vlan_sw.yml:2
META: noop
META: noop
<x.x.x.x> ESTABLISH NETCONF SSH CONNECTION FOR USER: Ansible on PORT 22 TO x.x.x.x
<x.x.x.x> ESTABLISH LOCAL CONNECTION FOR USER: root
<x.x.x.x> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /root/.ansible/tmp/ansible-local-233162pe2oyh4/ansible-tmp-1565773542.046394-83274056275477 `" && echo ansible-tmp-1565773542.046394-83274056275477="` echo /root/.ansible/tmp/ansible-local-233162pe2oyh4/ansible-tmp-1565773542.046394-83274056275477 `" ) && sleep 0'
Using module file /opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/ansible/modules/network/junos/junos_vlan.py
<x.x.x.x> PUT /root/.ansible/tmp/ansible-local-233162pe2oyh4/tmpyr3gfpgt TO /root/.ansible/tmp/ansible-local-233162pe2oyh4/ansible-tmp-1565773542.046394-83274056275477/AnsiballZ_junos_vlan.py
<x.x.x.x> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-local-233162pe2oyh4/ansible-tmp-1565773542.046394-83274056275477/ /root/.ansible/tmp/ansible-local-233162pe2oyh4/ansible-tmp-1565773542.046394-83274056275477/AnsiballZ_junos_vlan.py && sleep 0'
<x.x.x.x> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-local-233162pe2oyh4/ansible-tmp-1565773542.046394-83274056275477/AnsiballZ_junos_vlan.py && sleep 0'
<x.x.x.x> EXEC /bin/sh -c 'rm -f -r /root/.ansible/tmp/ansible-local-233162pe2oyh4/ansible-tmp-1565773542.046394-83274056275477/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
WARNING: The below traceback may *not* be related to the actual failure.
File "/tmp/ansible_junos_vlan_payload_a3tXYK/ansible_junos_vlan_payload.zip/ansible/module_utils/network/junos/junos.py", line 204, in unlock_configuration
response = conn.unlock()
File "/tmp/ansible_junos_vlan_payload_a3tXYK/ansible_junos_vlan_payload.zip/ansible/module_utils/network/common/netconf.py", line 76, in __rpc__
return self.parse_rpc_error(to_bytes(rpc_error, errors='surrogate_then_replace'))
File "/tmp/ansible_junos_vlan_payload_a3tXYK/ansible_junos_vlan_payload.zip/ansible/module_utils/network/common/netconf.py", line 108, in parse_rpc_error
raise ConnectionError(rpc_error)
fatal: [chi-leafsw06]: FAILED! => {
"changed": false,
"invocation": {
"module_args": {
"active": true,
"aggregate": null,
"description": "Client-100001-dedicated-network",
"filter_input": null,
"filter_output": null,
"host": null,
"interfaces": null,
"l3_interface": null,
"name": "vlan777",
"password": null,
"port": null,
"provider": null,
"ssh_keyfile": null,
"state": "present",
"timeout": null,
"transport": null,
"username": null,
"vlan_id": 777
}
},
"msg": "ncclient timed out while waiting for an rpc reply."
}
Any help would be greatly appreciated.
A guy has offered me a solution in here and I thought this might affect more of you (https://www.reddit.com/r/ansible/comments/cq7joa/help_ncclient_timed_out_while_waiting_for_rpc/)
What helped me was actually a suggestion to reduce the version of ncclient in my Ansible server from 0.6.6 to 0.6.4 and extend timeout values in ansible.cfg to at least 120.
So I am saved. Thanks!

ansible meta: refresh_inventory does not include previously absent hosts in task execution

Sometime ago, somebody suggested using dynamic inventories to generate a different hosts file depending on a location and other variables from a template, but I faced a pretty big issue :
After I create the inventory from a template, I need to refresh it (I do it using meta: refresh_inventory) for Ansible to execute tasks on newly added hosts, however, if the host was not initially in hosts file, ansible does not execute tasks on it. On the other hand, if after changing the host file a host is absent from a newly-formed file, then Ansible omits the host like it should, so the refresh_inventory does half of the work. Is there any way to get around this issue?
E.g. I have 1 task to generate hosts file from template, then refresh inventory, then do a simple task on all hosts, like show message:
tasks:
- name: Creating inventory template
local_action:
module: template
src: hosts.j2
dest: "/opt/ansible/inventories/{{location}}/hosts"
mode: 0777
force: yes
backup: yes
ignore_errors: yes
run_once: true
- name: "Refreshing hosts file for {{location}} location"
meta: refresh_inventory
- name: Force refresh of host errors
meta: clear_host_errors
- name: Show message
debug: msg="This works for this host"
If initial hosts file has hosts A, B, C, D, and the newly created inventory has B, C, D, then all is good:
ok: [B] => {
"msg": "This works for this host"
}
ok: [C] => {
"msg": "This works for this host"
}
ok: [D] => {
"msg": "This works for this host"
}
However, if newly formed hosts file has hosts B, C, D, E (E not being present at initial hosts file) then again the result is:
ok: [B] => {
"msg": "This works for this host"
}
ok: [C] => {
"msg": "This works for this host"
}
ok: [D] => {
"msg": "This works for this host"
}
With task for E missing. Now if I replay the playbook, only to add another host, say F, then the result looks like:
ok: [B] => {
"msg": "This works for this host"
}
ok: [C] => {
"msg": "This works for this host"
}
ok: [D] => {
"msg": "This works for this host"
}
ok: [E] => {
"msg": "This works for this host"
}
But no F, which is already added to the inventory file before the refresh.
So, any ideas?
Quoting from Basics
For each play in a playbook, you get to choose which machines in your infrastructure to target ... The hosts line is a list of one or more groups or host patterns ...
For example, it is possible to create the inventory in the 1st play and use it in the 2nd play. The playbook below
- hosts: localhost
tasks:
- template:
src: hosts.j2
dest: "{{ playbook_dir }}/hosts"
- meta: refresh_inventory
- hosts: test
tasks:
- debug:
var: inventory_hostname
with the template (fit it to your needs)
$ cat hosts.j2
[test]
test_01
test_02
test_03
[test:vars]
ansible_connection=ssh
ansible_user=admin
ansible_become=yes
ansible_become_user=root
ansible_become_method=sudo
ansible_python_interpreter=/usr/local/bin/python3.6
ansible_perl_interpreter=/usr/local/bin/perl
give
PLAY [localhost] ****************************************************************************
TASK [Gathering Facts] **********************************************************************
ok: [localhost]
TASK [template] *****************************************************************************
changed: [localhost]
PLAY [test] *********************************************************************************
TASK [Gathering Facts] **********************************************************************
ok: [test_02]
ok: [test_01]
ok: [test_03]
TASK [debug] ********************************************************************************
ok: [test_01] => {
"inventory_hostname": "test_01"
}
ok: [test_02] => {
"inventory_hostname": "test_02"
}
ok: [test_03] => {
"inventory_hostname": "test_03"
}
PLAY RECAP **********************************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0
test_01 : ok=2 changed=0 unreachable=0 failed=0
test_02 : ok=2 changed=0 unreachable=0 failed=0
test_03 : ok=2 changed=0 unreachable=0 failed=0
Even though the first answer provided here is correct I think this deserves an explanation on how refresh_inventory and also add_host behave. As I've seen a few other questions regarding this topic.
It does not matter if you use static or dynamic inventory, the behavior is the same. The only thing specific for dynamic inventory that can change the behavior is caching. The following applies for disabled caching or refreshed cache after adding the new host.
Both refresh_inventory and add_host allow you to execute tasks only in subsequent plays. However they allow you to access hostvars of the added hosts also in the current play. This behavior is partially and very briefly mentioned in the add_host documentation and is easy to miss.
Use variables to create new hosts and groups in inventory for use in later plays of the same playbook.
Consider following inventory called hosts_ini-main.ini:
localhost testvar='testmain'
Now you can write a playbook that will observe and test the behavior of refresh_inventory. It overwrites hosts_ini-main.ini inventory file (used by the playbook) with the following contents from the second file hosts_ini-second.ini:
localhost testvar='testmain'
127.0.0.2 testvar='test2'
The playbook prints hostvars before the inventory is changed follows by changing the inventory, refreshing inventory, again printing hostvars and then trying to execute task only on the newly added host.
The second play also tries to execute task only on the added host.
---
- hosts: all
connection: local
become: false
gather_facts: false
tasks:
- name: Print hostvars
debug:
var: hostvars
- name: Print var for first host
debug:
var: testvar
when: hostvars[inventory_hostname]['testvar'] == "testmain"
- name: Copy alternate hosts file to main hosts file
copy:
src: "hosts_ini-second.ini"
dest: "hosts_ini-main.ini"
- name: Refresh inventory using meta module
meta: refresh_inventory
- name: Print hostvars for the second time in the first play
debug:
var: hostvars
- name: Print var for added host
debug:
var: testvar # This will not execute
when: hostvars[inventory_hostname]['testvar'] == "test2"
# New play
- hosts: all
connection: local
become: false
gather_facts: false
tasks:
- name: Print hostvars in a different play
debug:
var: testvar
when: hostvars[inventory_hostname]['testvar'] == "test2"
Here is the execution (I've truncated parts of the output to make it more readable).
PLAY [all] *******************************************************************************
TASK [Print hostvars] ********************************************************************
ok: [localhost] => {
"hostvars": {
"localhost": {
"ansible_check_mode": false,
"ansible_config_file": "/home/john/dev-ansible/ansible.cfg",
"ansible_diff_mode": false,
"ansible_facts": {},
...
"testvar": "testmain"
}
}
}
TASK [Print var for first host] ***********************************************************
ok: [localhost] => {
"testvar": "testmain"
}
TASK [Copy alternate hosts file to main hosts file] ***************************************
changed: [localhost]
TASK [Refresh inventory using meta module] ************************************************
TASK [Print hostvars for the second time in the first play] *******************************
ok: [localhost] => {
"hostvars": {
"127.0.0.2": {
"ansible_check_mode": false,
"ansible_config_file": "/home/john/dev-ansible/ansible.cfg",
"ansible_diff_mode": false,
"ansible_facts": {},
...
"testvar": "test2"
},
"localhost": {
"ansible_check_mode": false,
"ansible_config_file": "/home/john/dev-ansible/ansible.cfg",
"ansible_diff_mode": false,
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
...
"testvar": "testmain"
}
}
}
TASK [Print var for added host] ***********************************************************
skipping: [localhost]
PLAY [all] ********************************************************************************
TASK [Print hostvars in a different play] *************************************************
skipping: [localhost]
ok: [127.0.0.2] => {
"testvar": "test2"
}
PLAY RECAP *******************************************************************************
127.0.0.2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
As can be seen the hostvars contain information about the newly added host even in the first play, but Ansible is not able to execute task on the host. When new play is created the task is executed on the new host without problems.

Ansible task with async and become giving Job not found error

When I am trying to run a task asynchronously as another user using become in ansible plabook, I am getting "Job not found error". Can some one suggest me how can I successfully check the async job status.
I am using ansible version 2.7
I read in some articles suggesting use the async_status task with same become user as async task, to read the job status.
I tried that solution but still I am getting the same "job not found error"
- hosts: localhost
tasks:
- shell: startInstance.sh
register: start_task
async: 180
poll: 0
become: yes
become_user: venu
- async_status:
jid: "{{start_task.ansible_job_id}}"
register: start_status
until: start_status.finished
retries: 30
become: yes
become_user: venu
Expected Result:
I should be able to Fire and forget the job
Actual_Result:
{"ansible_job_id": "386361757265.15925428", "changed": false, "finished": 1, "msg": "could not find job", "started": 1}

How to get grunt task name given on command line?

In order to customize my grunt tasks, I need access to the grunt task name given on the command line when starting grunt.
The options is no problem, since its well documented (grunt.options).
It's also well documented how to find out the task name, when running a grunt task.
But I need access to the task name before.
Eg, the user writes
grunt build --target=client
When configuring the grunt job in my Gruntfile.js, I can use
grunt.option('target') to get 'client'.
But how do I get hold of parameter build before the task build starts?
Any guidance is much appreciated!
Your grunt file is basically just a function. Try adding this line to the top:
module.exports = function( grunt ) {
/*==> */ console.log(grunt.option('target'));
/*==> */ console.log(grunt.cli.tasks);
// Add your pre task code here...
Running with grunt build --target=client should give you the output:
client
[ 'build' ]
At that point, you can run any code you need to before your task is run including setting values with new dependencies.
A better way is to use grunt.task.current which has information about the currently running task, including a name property. Within a task, the context (i.e. this) is the same object. So . . .
grunt.registerTask('foo', 'Foobar all the things', function() {
console.log(grunt.task.current.name); // foo
console.log(this.name); // foo
console.log(this === grunt.task.current); // true
});
If build is an alias to other tasks and you just want to know what command was typed that led to the current task execution, I typically use process.argv[2]. If you examine process.argv, you'll see that argv[0] is node (because grunt is a node process), argv[1] is grunt, and argv[2] is the actual grunt task (followed by any params in the remainder of argv).
EDIT:
Example output from console.log(grunt.task.current) on grunt#0.4.5 from within a task (can't have a current task from not a current task).
{
nameArgs: 'server:dev',
name: 'server',
args: [],
flags: {},
async: [Function],
errorCount: [Getter],
requires: [Function],
requiresConfig: [Function],
options: [Function],
target: 'dev',
data: { options: { debugPort: 5858, cwd: 'server' } },
files: [],
filesSrc: [Getter]
}
You can use grunt.util.hooker.hook for this.
Example (part of Gruntfile.coffee):
grunt.util.hooker.hook grunt.task, (opt) ->
if grunt.task.current and grunt.task.current.nameArgs
console.log "Task to run: " + grunt.task.current.nameArgs
CMD:
C:\some_dir>grunt concat --cmp my_cmp
Task to run: concat
Running "concat:coffee" (concat) task
Task to run: concat:coffee
File "core.coffee" created.
Done, without errors.
There is also a hack that I've used to prevent certain task execution:
grunt.util.hooker.hook grunt.task, (opt) ->
if grunt.task.current and grunt.task.current.nameArgs
console.log "Task to run: " + grunt.task.current.nameArgs
if grunt.task.current.nameArgs is "<some task you don't want user to run>"
console.log "Ooooh, not <doing smth> today :("
exit() # Not valid. Don't know how to exit :), but will stop grunt anyway
CMD, when allowed:
C:\some_dir>grunt concat:coffee --cmp my_cmp
Running "concat:coffee" (concat) task
Task to run: concat:coffee
File "core.coffee" created.
Done, without errors.
CMD, when prevented:
C:\some_dir>grunt concat:coffee --cmp my_cmp
Running "concat:coffee" (concat) task
Task to run: concat:coffee
Ooooh, not concating today :(
Warning: exit is not defined Use --force to continue.
Aborted due to warnings.

Resources