Lately, I've started to use Ansible, and i need some help with a playbook.
I'm trying to get a device details as a parameter, and then chain a star sign for it, in order to run a shell command. For example, if the device is /etc/sda
, the shell command will be ls -l /dev/sda*.
- name: Get Hitachi Devices device details
shell: lsscsi | grep HITACHI | awk '{print $6}'
register: hitachiDevice
- name: Check if the volume is partitioned
shell: ls -l "{{ hitachiDevice.rc }}"* | wc -l
failed_when: hitachiDevice.rc != 1
Here is the error i get after running the script:
{
"changed": true,
"cmd": "ls -l \"0\"* | wc -l",
"delta": "0:00:00.027338",
"end": "2016-08-10 14:15:12.200415",
"failed": true,
"failed_when_result": true,
"rc": 0,
"start": "2016-08-10 14:15:12.173077",
"stderr": "ls: cannot access 0*: No such file or directory",
"stdout": "0",
"stdout_lines": [
"0"
],
"warnings": []
}
Any one know what is the issue and how can i fix it?
hitachiDevice.rc contains result code of previous command.
I bet it is always 0 if grep is successful.
So you next command will almost always will be ls -l 0* | wc -l which gives you the error
ls: cannot access 0*: No such file or directory
I think you need something like hitachiDevice.stdout, depending on what you want to achieve.
Related
I have written a playbook to create credentials with custom credentials, below is the ansible playbook
---
- name: Trigger an Atower API
hosts: localhost
connection: local
tasks:
- name: Create a valid SCM credential from a private_key file
command: tower-cli credential create --organization "Default" --name "DevOps User" --credential-type "csa-test2" --inputs "{'user':'devops', 'stg01_ssh_key':\"$( sed -z 's/\n/\\n/g' test.pem )\" }"
no_log: false
I am getting this error related to quotaions
name", "DevOps User", "--credential-type", "csa-test2", "--inputs", "{'user':'devops', 'stg01_ssh_key':\"$( sed -z 's/\\n/\\n/g' test.pem )\" }"], "delta": "0:00:01.319114", "end": "2021-01-07 16:00:22.763388", "msg": "non-zero return code", "rc": 40, "start": "2021-01-07 16:00:21.444274", "stderr": "Error: The Tower server claims it was sent a bad request.\n\nPOST http://x.x.x.x:13080/api/v2/credentials/\nParams: None\nData: {\"inputs\": {\"stg01_ssh_key\": \"$( sed -z 's/\\n/\\n/g' test.pem )\", \"user\": \"devops\"}, \"credential_type\": 36, \"organization\": 1, \"name\": \"DevOps User\"}\n\nResponse: {\"inputs\":{\"stg01_ssh_key\":[\"Invalid certificate or key: $( sed -z 's/\\n/\\n/g' test.pem )...\"]}}", "stderr_lines": ["Error: The Tower server claims it was sent a bad request.", "", "POST http://x.x.x.x:13080/api/v2/credentials/", "Params: None", "Data: {\"inputs\": {\"stg01_ssh_key\": \"$( sed -z 's/\\n/\\n/g' test.pem )\", \"user\": \"devops\"}, \"credential_type\": 36, \"organization\": 1, \"name\": \"DevOps User\"}", "", "Response: {\"inputs\":{\"stg01_ssh_key\":[\"Invalid certificate or key: $( sed -z 's/\\n/\\n/g' test.pem )...\"]}}"], "stdout": "", "stdout_lines": []}
Also, when i try manually "tower-cli create" command, it is working fine. Can anyone help me in this. I dunno what is wrong here.
try to use shell instead of command, documentation says
The command(s) will not be processed through the shell, so variables like $HOSTNAME and operations like "*", "<", ">", "|", ";" and "&" will not work. Use the ansible.builtin.shell module if you need these features.
source: Ansible Documentation
I have check.sh script that I wish to run on the target nodes:
cat check.sh
str=`echo $1 | sed -e 's#[\][\]n# #g'`
echo $str>check.row
It is suppose to replace \n with a single white space from the argument and save it in check.row file.
When I run it manually on the target server i get good output results as shown below:
bash -x ./check.sh '/fin/app/01/scripts\\n/fin/app/01/sql'
++ echo '/fin/app/01/scripts\\n/fin/app/01/sql'
++ sed -e 's#[\][\]n# #g'
+ str='/fin/app/01/scripts /fin/app/01/sql'
+ echo /fin/app/01/scripts /fin/app/01/sql
The check.row generated looks good as below:
[user1#remotehost1 ~]$ cat check.row
/fin/app/01/scripts /fin/app/01/sql
However, when i run the same using ansible shell or command module I do not get the expected results.
Below is my playbook:
tasks:
- copy:
src: "{{ playbook_dir }}/files/check.sh"
dest: "~/"
mode: 0754
- set_fact:
install_dir: "{{ hostvars[\'localhost\'][\'command_result\'].stdout.split('\t')[2] }}"
- shell: "bash -x ~/check.sh '{{ install_dir }}' > ~/check_rollback.log"
See ansible's debug output below:
changed: [10.8.44.55] => {
"changed": true,
"cmd": "bash -x ~/check.sh '/fin/app/01/scripts\\n/fin/app/01/sql' > ~/check_rollback.log",
"delta": "0:00:00.118943",
"end": "2019-09-04 10:50:16.503745",
"invocation": {
"module_args": {
"_raw_params": "bash -x ~/check.sh '/fin/app/01/scripts\\n/fin/app/01/sql' > ~/check_rollback.log",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"rc": 0,
"start": "2019-09-04 10:50:16.384802",
"stderr": "++ echo '/fin/app/01/scripts\\n/fin/app/01/sql'\n++ sed -e 's#[\\][\\]n# #g'\n+ str='/fin/app/01/scripts\\n/fin/app/01/sql'\n+ echo '/fin/app/01/scripts\\n/fin/app/01/sql'",
"stderr_lines": [
"++ echo '/fin/app/01/scripts\\n/fin/app/01/sql'",
"++ sed -e 's#[\\][\\]n# #g'",
"+ str='/fin/app/01/scripts\\n/fin/app/01/sql'",
"+ echo '/fin/app/01/scripts\\n/fin/app/01/sql'"
],
"stdout": "",
"stdout_lines": [] }
And here is the check.row file output from ansible's run:
[user1#remotehost1 ~]$ cat check.row
/fin/app/01/scripts\\n/fin/app/01/sql
As you can instead of single whitespace it is now printing \n.
I am on the latest version of ansible.
One can replicate this issue easily. Can you please suggest why am I getting this issue and how to fix this?
First of all, you are using the shell module in which only the shell command is specified, you have incorrectly used bash in it.
shell: "bash -x ~/check.sh '{{ install_dir }}' > ~/check_rollback.log"
Secondly, as you can see your task has resulted in an error as seen in your attached output.
Stdout is empty and we can see an error in stderr.
Thirdly, If you want to use bash you can use command module, as shown below,
- command: "bash -x ~/check.sh '{{ install_dir }}' > ~/check_rollback.log"
I also suggest the following changes in your check.sh script,
#!/bin/bash
echo $1 # You can check the value that is passed to the script
str=$(echo "$1" | sed -e 's/\\n/ /g') # Use quotes around your variable
echo "$str" > check.row
And it is working fine.
I am trying to execute a command on docker on other machine from my machine. When I execute this command:
- name: Add header
command: docker exec cli bash -l -c "echo '{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":'$(cat jaguar_update.json)'}}}' | jq . > jaguar_update_in_envelope.json"
through ansible playbook, I am getting the error shown below.
fatal:[
command-task
]:FAILED! =>{
"changed":true,
"cmd":[ ],
"delta":"0:00:00.131115",
"end":"2019-07-11 17:32:44.651504",
"msg":"non-zero return code",
"rc":4,
"start":"2019-07-11 17:32:44.520389",
"stderr":"mesg: ttyname
failed: Inappropriate ioctl for device\nparse error: Invalid numeric
literal at line 1, column 9",
"stderr_lines":[
"mesg: ttyname failed:
Inappropriate ioctl for device",
"parse error: Invalid numeric literal
at line 1, column 9"
],
"stdout":"",
"stdout_lines":[
]
}
But if I manually execute command in the docker container, it works fine and I don't get any issue.
EDIT:
As suggested i tried with shell module
shell: docker exec cli -it bash -l -c "echo '{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":'$(cat jaguar_update.json)'}}}' | jq . > jaguar_update_in_envelope.json"
But i get below error as
fatal: [command-task]: FAILED! => {"changed": true, "cmd": "docker
exec cli -it bash -l -c echo
'{\"payload\":{\"header\":{\"channel_header\":{\"channel_id\":\"gll\",
\"type\":2}},\"data\":{\"config_update\":'$(cat
jaguar_update.json)'}}}' | jq . > jaguar_update_in_envelope.json",
"delta": "0:00:00.110341", "end": "2019-07-12 10:21:45.204049", "msg":
"non-zero return code", "rc": 4, "start": "2019-07-12
10:21:45.093708", "stderr": "cat: jaguar_update.json: No such file or
directory\nparse error: Invalid numeric literal at line 1, column 4",
"stderr_lines": ["cat: jaguar_update.json: No such file or directory",
"parse error: Invalid numeric literal at line 1, column 4"], "stdout":
"", "stdout_lines": []}
All the files 'jaguar_update.json' present in the working directory. I have confirmed the working directory.
Above commands works if i put it in a shell script file then execute the shell script from ansible.
As everyone has mentioned this does need you to use shell instead of command. Now you want to simplify this command so it can run first in bash. Which can be done easily using printf
$ printf "%s%s%s" '{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":' $(<jaguar_update.json'}}}' | jq . > jaguar_update_in_envelope.json
$ cat jaguar_update_in_envelope.json
{
"payload": {
"header": {
"channel_header": {
"channel_id": "gll",
"type": 2
}
},
"data": {
"config_update": {
"name": "tarun"
}
}
}
}
So now our commands runs without issues. Next is to move it with bash -l -c format. So instead using -c which requires us to pass the whole command as one parameter, we use the multiline commands
$ bash -l <<EOF
printf "%s%s%s" '{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":' $(<jaguar_update.json) '}}}' | jq . > jaguar_update_in_envelope.json
EOF
But this fails with an error
{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":{bash: line 2: name:: command not found
bash: line 3: syntax error near unexpected token `}'
bash: line 3: `} '}}}' | jq . > jaguar_update_in_envelope.json'
This is because the EOF format will treat each new line as a different command. So we need to replace all new line characters
bash -l <<EOF
printf "%s%s%s" '{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":' $(sed -E 's|"|\\"|g' jaguar_update.json | tr -d '\n') '}}}' | jq . > jaguar_update_in_envelope.json
EOF
And now in ansible
- name: a play that runs entirely on the ansible host
hosts: 127.0.0.1
connection: local
tasks:
- name: Solve the problem
shell: |
bash -l <<EOF
printf "%s%s%s" '{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":' $(sed -E 's|"|\\"|g' jaguar_update.json | tr -d '\n') '}}}' | jq . > jaguar_update_in_envelope.json
EOF
And the result
$ ansible-playbook test.yml
PLAY [a play that runs entirely on the ansible host] *********************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************************************
ok: [127.0.0.1]
TASK [Solve the problem] *************************************************************************************************************************************************************
changed: [127.0.0.1]
PLAY RECAP ***************************************************************************************************************************************************************************
127.0.0.1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ cat jaguar_update_in_envelope.json
{
"payload": {
"header": {
"channel_header": {
"channel_id": "gll",
"type": 2
}
},
"data": {
"config_update": {
"name": "tarun"
}
}
}
}
To avoid any complexity, try as in this question to wrap your command in a script, and call that script (with command or shell)
- name: Add header
raw: /path/to/script/docker-add-header.sh
And in /path/to/script/docker-add-header.sh:
docker exec cli -it bash -l -c "echo '{"payload":{"header":{"channel_header":{"channel_id":"gll", "type":2}},"data":{"config_update":'$(cat jaguar_update.json)'}}}' | jq . > jaguar_update_in_envelope.json
Try to make the script work first alone (no Ansible).
See (if it is not working, even outside any Ansible call), to escape nested double-quotes:
docker exec cli -it bash -l -c "echo '{\"payload\":{\"header\":...
c.f. the docs -
The command(s) will not be processed through the shell, so variables like $HOME and operations like "<", ">", "|", ";" and "&" will not work. Use the shell module if you need these features.
shell pretty literally submits a script to the sh command parser.
Another note - you end the single-quote before the $(cat jaguar_update.json) and restart it after, but don't use any double quoting around it. Your output may handle that, but I wanted to call attention in case it matters.
I am trying to pass a prompt y in ansible when it executes command.
When i do manually on server it asks for a prompt.
The issue is for command to run i need to pass the executable /bin/bash
command: source /etc/profile.d/tableau_server.sh && tsm pending-changes apply for expect command to run i need to pass /usr/bin/expect .
My question, how can i pass 2 executable in ansible such that for command it uses /bin/bash and for expect prompt it should use /usr/bin/expect and the error is because i am using source, what is an alternative i can use?
Update: I dont know why but i am not able to pass --ignore-prompt , It gives an error
ubuntu#ip-xx-xxx-xx-xx:~$ tsm pending-changes apply --ignore-prompt
Unrecognized option: --ignore-prompt
Please help me with a solution!
ubuntu#ip-xx-xxx-xx-xx:~$ tsm pending-changes apply
This operation will perform a server restart. Are you sure you wish to continue?
(y/n):
My ansible script:
shell: |
source /etc/profile.d/tableau_server.sh && tsm pending-changes apply
expect "This operation will perform a server restart. Are you sure you wish to continue?\n(y/n):"
send "y\n"
exit 0
args:
executable: /usr/bin/expect
args:
executable: /bin/bash/expect
when: inventory_hostname == "xx.xxx.xx.xx"
ERROR:
changed: [xx.xxx.xxx.xx] => {
"changed": true,
"cmd": "source /etc/profile.d/tableau_server.sh && tsm pending-changes apply\n expect \"This operation will perform a server restart. Are you sure you wish to continue?\\n(y/n):\"\n send \"y\\n\"\n exit 0",
"delta": "0:00:00.034824",
"end": "2018-08-20 17:29:41.457700",
"invocation": {
"module_args": {
"_raw_params": "source /etc/profile.d/tableau_server.sh && tsm pending-changes apply\n expect \"This operation will perform a server restart. Are you sure you wish to continue?\\n(y/n):\"\n send \"y\\n\"\n exit 0",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": "/usr/bin/expect",
"removes": null,
"stdin": null,
"warn": true
}
},
"rc": 0,
"start": "2018-08-20 17:29:41.422876",
"stderr": "wrong # args: should be \"source ?-encoding name? fileName\"\n while executing\n\"source /etc/profile.d/tableau_server.sh && tsm pending-changes apply\"",
"stderr_lines": [
"wrong # args: should be \"source ?-encoding name? fileName\"",
" while executing",
"\"source /etc/profile.d/tableau_server.sh && tsm pending-changes apply\""
],
"stdout": "",
"stdout_lines": []
I would say you are doing far too much with bash commands and '&&' inside command, none of this feels idempotent.
Can I recommend going back to the drawing board with this. I would recommend creating the command using the 'creates' parameter so it can tell if it needs to run.
https://docs.ansible.com/ansible/2.6/modules/command_module.html
Or alternatively check before hand which will then see if the command needs running using register.
In this instance of your issue with the:
tsm pending-changes apply
should support as per https://onlinehelp.tableau.com/current/server-linux/en-us/cli_pending-changes.htm
tsm pending-changes apply --ignore-prompt
which will then not prompt for a yes and will not need the expect module.
I solved my issue by passing an -r option.
- name: Initialize and Start Tableau Server
shell: source /etc/profile.d/tableau_server.sh && tsm pending-changes apply -r -u ubuntu -p '{{ tableau_server_admin_password }}'
args:
executable: /bin/bash
when: inventory_hostname == "xx.xxx.xx.xx"
I'm trying to run a simple tail command with Ansible. Iterate over 3 strings until all are not found. If found loop over the tail command until all are not found.
- name: Tail the logs for string
shell: "tail -10 /path/to/log/file.log | egrep 'STRING1|STRING2|STRING3'"
register: tail
until: "'STRING1' and 'STRING2' and 'STRING3' not in tail.stdout_lines"
retries: 10
delay: 5
When I run the above task and there's nothing to return, it exits with a fatal error. Even though this is the success case.
fatal: [testserver]: FAILED! => {"changed": true, "cmd": "tail -10 /path/to/log/file.log | egrep 'STRING1|STRING2|STRING3'", "delta": "0:00:00.012770", "end": "2016-09-07 07:44:35.684238", "failed": true, "invocation": {"module_args": {"_raw_params": "tail -10 /path/to/log/file.log | egrep 'STRING1|STRING2|STRING3'", "_uses_shell": true, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true}, "module_name": "command"}, "rc": 1, "start": "2016-09-07 07:44:35.671468", "stderr": "", "stdout": "", "stdout_lines": [], "warnings": []}
I am unsure why it ends in a fatal state.
grep returns nonzero when it doesn't match anything. If you want to override its exit status:
shell: "tail -10 /path/to/log/file.log | egrep 'STRING1|STRING2|STRING3' ||:"
After resolving the tail command thanks the advice from #charles-duffy the issue remained with the loop condition.
Here is the correct condition:
until: '"STRING1" not in tail.stdout and "STRING2" not in tail.stdout and "STRING3" not in tail.stdout'
I also used tail.stdout instead of tail.stdout_lines
Thanks