ansible AttributeError: 'list' object has no attribute 'startswith' - ansible

I am running ansible and trying to make this task work, it fails with this error:
An exception occurred during task execution. To see the full
traceback, use -vvv. The error was: AttributeError: 'list' object has
no attribute 'startswith' fatal: [test-1]: FAILED! =>
{"failed": true, "msg": "Unexpected failure during module execution.",
"stdout": ""} msg: Unexpected failure during module execution.
The code is:
- name: Register env Type
shell: facter configured_setup
register: setup
- name: foo tasks
shell: {{some_script}} -t -a {{hosts}} -i {{inventory_hostname}}
register: test
when: setup.stdout == "something"
- name: fetch group_vars
fetch:
src:
- { "{{ item }}", when: setup.stdout == "something" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
"fetch group_vars" is the task that always fails, any idea how this can work?
What I am trying to do is add more source files to fetch from different setvers.
So I want to have more lines under "src:", saying - { filename, when setup.stdout =="something else" }
The full error is:
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 96, in run
item_results = self._run_loop(items)
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 252, in _run_loop
res = self._execute(variables=task_vars)
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 446, in _execute
result = self._handler.run(task_vars=variables)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/fetch.py", line 62, in run
source = self._remote_expand_user(source)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/init.py", line 460, in _remote_expand_user
if not path.startswith('~'): # FIXME: Windows paths may start with "~ instead of just ~
AttributeError: 'list' object has no attribute 'startswith'
fatal: [test-1]: FAILED! => {"failed": true, "msg":
"Unexpected failure during module execution.", "stdout": ""} msg:
Unexpected failure during module execution.

The ansible documentation clearly states that fetch, fetches a file, not a list of files. Although one can program an application to deal with both a scalar and a sequence loaded from a YAML document, that is not automatic and would almost certainly have been reflected in the documentation.
Since you already have a sequence at a higher level, just extend that.
- name: Register env Type
shell: facter configured_setup
register: setup
- name: transparency tasks
shell: {{some_script}} -t -a {{hosts}} -i {{inventory_hostname}}
register: test
when: setup.stdout == "something"
- name: fetch group_vars
fetch:
src: { "{{ item }}", when: setup.stdout == "something" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
- name: fetch group_vars2
fetch:
src: { filename, when setup.stdout =="something else" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
You might be able to reduce the repetitiveness somewhat by using YAML's anchor and merge:
- name: Register env Type
shell: facter configured_setup
register: setup
- name: transparency tasks
shell: {{some_script}} -t -a {{hosts}} -i {{inventory_hostname}}
register: test
when: setup.stdout == "something"
- &fetchtask
name: fetch group_vars
fetch: &fetchsrc
src: { "{{ item }}", when: setup.stdout == "something" }
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
- <<: *fetchtask
name: fetch group_vars2
fetch:
<<: *fetchsrc
src: { filename, when setup.stdout =="something else" }
Ansible probably expands the {{...}} before handing the document to the YAML parser, otherwise the value for shell in the "transparency task" would throw an error. But you should probably still quote that like you do with the value for dest

So I ended up doing this (which is working):
- name: fetch group_vars test
fetch:
src: "{{ item }}"
dest: "{{group_vars}}"
flat: yes
with_items:
- "{{ test.stdout_lines[0] }}"
- "{{ test.stdout_lines[1] }}"
when: setup.stdout == "something" and {{something_else}} == True
I also noticed that there might be a bug in ansible related to registers.
while using the "when" statement, even if the condition is not met, the register statement takes affect:
- name: test tasks something enabled
shell: /tmp/{{populate_script}} -u -a {{hosts}} -r
register: variable
when: setup.stdout == "test" and something == True
- name: test tasks something disabled
shell: /tmp/{{populate_script}} -u -a {{hosts}}
register: variable
when: setup.stdout == "test" and something == False
only one of these conditions will be met, in case the first one is met the second condition will override "variable"

Related

ansible "with_lines: cat somefile" fails in a skipped block

I have a playbook with a block that has a when condition. Inside is a task with a loop. How can I change this loop so that when the condition is false the skipped task doesn't fail?
block:
- name: create a file
lineinfile:
line: "Hello World"
path: "{{my_testfile}}"
create: yes
- name: use the file
debug:
msg: "{{ item}}"
with_lines: cat "{{my_testfile}}"
when: false
TASK [create a file] ************************************************************************************************************************************************************
TASK [use the file] *************************************************************************************************************************************************************
cat: files/my/testfile: No such file or directory
fatal: [ipad-icpi01]: FAILED! => {"msg": "lookup_plugin.lines(cat \"files/mytestfile\") returned 1"}
Change your failing task to the following which will always be able to run, even if the file does not exists, and will not use the shell or command where there is no need to:
- name: use the file
debug:
msg: "{{ item }}"
loop: "{{ (lookup('file', my_testfile, errors='ignore') | default('', true)).split('\n') }}"
The key points:
use the file lookup plugin with errors='ignore' so that it returns the file content or None rather than an error when file does not exists.
use the default filter with second option to true so that it return default value if var exists but is null or empty.
split the result on new lines to get a list of lines (empty list if file does not exist).
Note: as reported by #Vladimir, I corrected your var name which is not valid in ansible.
Test the existence of the file. For example
- block:
- name: create a file
lineinfile:
line: "Hello World"
path: "{{ my_testfile }}"
create: yes
- name: use the file
shell: '[ -f "{{ my_testfile }}" ] && cat {{ my_testfile }}'
register: result
- name: use the file
debug:
msg: "{{ item }}"
loop: "{{ result.stdout_lines }}"
when: false
The lookup plugin file should be preferred.
I ended up with a mix of the provided answers. These tasks will be skipped without failing or creating a warning.
- block:
- name: create a file
lineinfile:
line: "Hello World"
path: "{{ my_testfile }}"
create: yes
- name: get the file
slurp:
src: "{{ my_testfile }}"
register: result
- name: use the file
debug:
msg: "{{ item }}"
loop: "{{ (result['content'] | b64decode).split('\n') }}"
when: false

Ansible: Copying files from local to remote - "AttributeError: 'dict' object has no attribute 'endswith'"

I am trying to copy files (scripts and rpms) stored locally to a set of servers. I can copy the files when the names are hard coded, but not when I am using a variable.
ansible-lint comes back with no errors.
When use variable replacement I get the error:
TASK [Copy cpu_gov.sh]
***************************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: AttributeError: 'dict' object has no attribute 'endswith'
fatal: [ceph3]: FAILED! => {"msg": "Unexpected failure during module execution.", "stdout": ""}
In debug mode I can see that it is a Python error on a trailing "/". All other uses of the variable work fine, only when it is in the ""src:" field does it fail.
The full traceback is:
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 145, in run
res = self._execute()
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 650, in _execute
result = self._handler.run(task_vars=variables)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/copy.py", line 461, in run
trailing_slash = source.endswith(os.path.sep)
AttributeError: 'dict' object has no attribute 'endswith'
fatal: [ceph3]: FAILED! => {
"msg": "Unexpected failure during module execution.",
"stdout": ""
}
---
### Test
#
- hosts: all
vars:
#isdct_rpm: foobar.txt
isdct_rpm: isdct-3.0.16-1.x86_64.rpm
cpu_gov: cpu_gov.sh
irq_bal: irq_balance.sh
root_dir: /root
bin_dir: /root/bin
files_dir: /root/projects/ansible/bootstrap/files
remote_user: root
tasks:
These work just fine -
- name: ISDCT rpm exists?
stat:
path: "{{ root_dir }}/{{ isdct_rpm }}"
register: isdct_rpm
tags:
- tools
- name: cpu_gov exists?
stat:
path: "{{ bin_dir }}/{{ cpu_gov }}"
register: cpu_gov
tags:
- tools
- name: irq_balance exists?
stat:
path: "{{ bin_dir }}/{{ irq_bal }}"
register: irq_bal
tags:
- tools
The first task is the failing one:
- name: Copy ISDCT rpm
copy:
remote_src: no
src: "{{ isdct_rpm }}"
dest: "{{ root_dir }}"
when: not isdct_rpm.stat.exists
These work fine:
- name: Copy rpm
copy:
remote_src: no
src: isdct-3.0.16-1.x86_64.rpm
dest: /root
when: not isdct_rpm.stat.exists
- name: Copy cpu_gov.sh
copy:
remote_src: no
src: cpu_gov.sh
# - fails - src: "{{ cpu_gov }}"
dest: "{{ bin_dir }}"
when: not cpu_gov.stat.exists
- name: Copy irq_balance.sh
copy:
remote_src: no
src: irq_balance.sh
dest: /root
when: not irq_bal.stat.exists
The full traceback is:
Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 145, in run
res = self._execute()
File "/usr/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 650, in _execute
result = self._handler.run(task_vars=variables)
File "/usr/lib/python2.7/site-packages/ansible/plugins/action/copy.py", line 461, in run
trailing_slash = source.endswith(os.path.sep)
AttributeError: 'dict' object has no attribute 'endswith'
fatal: [ceph3]: FAILED! => {
"msg": "Unexpected failure during module execution.",
"stdout": ""
}
You have a variable in your vars section named isdct_rpm which is a string, but you're registering a dictionary variable with the same name in your ISDCT rpm exists? task. This overrides the string value.
Stop trying to use the same variable name for two different purposes and I suspect things will work as expected.
#larsks Answered my question. I was using the same name for my variable and the register value.
This works:
#
###--- Copy the scripts over if needed
#
- name: Copy ISDCT rpm
copy:
remote_src: no
src: "{{ isdct_rpm }}"
dest: "{{ root_dir }}"
when: not isdctrpm.stat.exists
- name: Copy cpu_gov.sh
copy:
remote_src: no
#src: cpu_gov.sh
src: "{{ cpu_gov }}"
dest: "{{ bin_dir }}"
when: not cpugov.stat.exists
- name: Copy irq_balance.sh
copy:
remote_src: no
src: "{{ irq_bal }}"
dest: "{{ bin_dir }}"
when: not irqbal.stat.exists

How to write varibles/hard code values in nested json in ansible?

I'm trying to create a json file with hard codes valuesas a output in nested json.But the second play is overwriting the first play value.So do we have any best option to do this?
I have tried with to_nice_json template to copy the variable to json file.But not able to keep multiple variable values in imported_var to copy to json file
---
- hosts: localhost
connection: local
gather_facts: false
tasks:
- name: load var from file
include_vars:
file: /tmp/var.json
name: imported_var
- name: Checking mysqld status
shell: service mysqld status
register: mysqld_stat
ignore_errors: true
- name: Checking mysqld status
shell: service httpd status
register: httpd_stat
ignore_errors: true
- name: append mysqld status to output json
set_fact:
imported_var: "{{ imported_var | combine({ 'status_checks':[{'mysqld_status': (mysqld_stat.rc == 0)|ternary('good', 'bad') }]})}}"
# - name: write var to file
# copy:
# content: "{{ imported_var | to_nice_json }}"
# dest: /tmp/final.json
- name: append httpd status to output json
set_fact:
imported_var: "{{ imported_var| combine({ 'status_checks':[{'httpd_status': (httpd_stat.rc == 0)|ternary('good', 'bad') }]})}}"
# - debug:
# var: imported_var
- name: write var to file
copy:
content: "{{ imported_var | to_nice_json }}"
dest: /tmp/final.json
Expected result:
{
"status_checks": [
{
"mysqld_status": "good"
"httpd_status": "good"
}
]
}
Actual result:
{
"status_checks": [
{
"httpd_status": "good"
}
]
}
You're trying to perform the sort of data manipulation that Ansible really isn't all that good at. Any time you attempt to modify an existing variable -- especially if you're trying to set a nested value -- you're making life complicated. Having said that, it is possible to do what you want. For example:
---
- hosts: localhost
gather_facts: false
vars:
imported_var: {}
tasks:
- name: Checking sshd status
command: systemctl is-active sshd
register: sshd_stat
ignore_errors: true
- name: Checking httpd status
command: systemctl is-active httpd
register: httpd_stat
ignore_errors: true
- set_fact:
imported_var: "{{ imported_var|combine({'status_checks': []}) }}"
- set_fact:
imported_var: >-
{{ imported_var|combine({'status_checks':
imported_var.status_checks + [{'sshd_status': (sshd_stat.rc == 0)|ternary('good', 'bad')}]}) }}
- set_fact:
imported_var: >-
{{ imported_var|combine({'status_checks':
imported_var.status_checks + [{'httpd_status': (httpd_stat.rc == 0)|ternary('good', 'bad')}]}) }}
- debug:
var: imported_var
On my system (which is running sshd but is not running httpd, this will output:
TASK [debug] **********************************************************************************
ok: [localhost] => {
"imported_var": {
"status_checks": [
{
"sshd_status": "good"
},
{
"httpd_status": "bad"
}
]
}
}
You could dramatically simplify the playbook by restructuring your data. Make status_checks a top level variable, and instead of having it be a list, have it be a dictionary that maps a service name to the corresponding status. Combine this with some loops and you end up with something that is dramatically simpler:
---
- hosts: localhost
gather_facts: false
tasks:
# We can use a loop here instead of writing a separate task
# for each service.
- name: Checking service status
command: systemctl is-active {{ item }}
register: services
ignore_errors: true
loop:
- sshd
- httpd
# Using a loop in the previous task means we can use a loop
# when creating the status_checks variable, which again removes
# a bunch of duplicate code.
- name: set status_checks variable
set_fact:
status_checks: "{{ status_checks|default({})|combine({item.item: (item.rc == 0)|ternary('good', 'bad')}) }}"
loop: "{{ services.results }}"
- debug:
var: status_checks
The above will output:
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
"status_checks": {
"httpd": "bad",
"sshd": "good"
}
}
If you really want to add this information to your imported_var, you can do that in a single task:
- set_fact:
imported_var: "{{ imported_var|combine({'status_checks': status_checks}) }}"

How to register with_items and act on conditional check result for each item

I'd like to register the contents of bashrc for two users and edit as/if required. My play is as follows.
- name: Check bashrc
shell: cat {{ item }}/.bashrc
register: bashrc
with_items:
- "{{ nodepool_home }}"
- "{{ zuul_home }}"
- name: Configure bashrc
shell:
cmd: |
cat >> {{ item }}/.bashrc <<EOF
STUFF
EOF
with_items:
- "{{ nodepool_home }}"
- "{{ zuul_home }}"
when: '"STUFF" not in bashrc.stdout'
It fails as follows:
fatal: [ca-o3lscizuul]: FAILED! => {"failed": true, "msg": "The conditional check '\"STUFF\" not in bashrc.stdout' failed. The error was: error while evaluating conditional (\"STUFF\" not in bashrc.stdout): Unable to look up a name or access an attribute in template string ({% if \"STUFF\" not in bashrc.stdout %} True {% else %} False {% endif %}).\nMake sure your variable name does not contain invalid characters like '-': argument of type 'StrictUndefined' is not iterable\n\nThe error appears to have been in '/root/openstack-ci/infrastructure-setup/staging/zuul/create-user.yml': line 35, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Configure bashrc\n ^ here\n"}
I think, if I understand your requirement correctly, you can use the 'lineinfile' or 'blockinfile' modules and save yourself the hassle of testing for the existence of the content:
- name: Noddy example data
set_fact:
single_line: "STUFF"
multi_line: |
STUFF
STUFF
profile_dirs:
- "{{ nodepool_home }}"
- "{{ zuul_home }}"
- name: Ensure STUFF exists in file
lineinfile:
path: "{{ item }}/.bashrc"
line: "{{ single_line }}"
loop: "{{ profile_dirs }}"
- name: Ensure block of STUFF exists in file
blockinfile:
path: "{{ item }}/.bashrc"
block: "{{ multi_line }}"
loop: "{{ profile_dirs }}"
Both modules give a lot more control and you can find their docs here: lineinfile | blockinfile

Ansible: variable in variable / loop or iterating through item

Here is a simple vars file we have to debug
./roles/test/vars/{{ ansible_distribution|lower }}/apt-packages.yml
packages:
required:
- htop
# - aptitude
package:
htop:
allow_unauthenticated: no
autoclean: no
autoremove: no
cache_valid_time: 0
# default_release:
force: no
force_apt_get: no
install_recommends: yes
only_upgrade: no
purge: no
state: latest
update_cache: yes
upgrade: no
Here is a simple task to debug
./roles/test/tasks/main.yml
- name: "Register variable"
include_vars:
#dir: vars/ubuntu
file: "vars/{{ ansible_distribution|lower }}/apt-packages.yml"
name: apt_install
- name: "This a test"
apt:
name: "{{item}}"
cache_valid_time: "{{ apt_install.package[item].cache_valid_time }}"
state: "{{ apt_install.package[item].state }}"
update_cache: "{{ apt_install.package[item].update_cache }}"
with_items: "{{ apt_install.packages.required }}"
./roles/test-playbook.yml
- name: "playbook test"
hosts: localhost
roles:
- role: test
become: true
become_user: root
become_method: sudo
using following answer stackoverflow.com/questions/29276198 we are trying to loop through items and get item related values.
Tasks is looping well through items but it is impossible to retrieve related variable with [item] syntax or any other one we have tested.
We have always the same error
fatal: [localhost]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: dict object has no element [u'htop']
But calling directly the variable works
- name: "echo variable test"
debug:
msg: "{{ apt_install.package.htop.allow_unauthenticated }}"
What is the right syntax to get current loop value of a variable and use it inside another variable to retrieve related value ... (inside the same task) ?
So far it's we who are going around in circles without end !
Kind Regards
This is fully working using a loop / loop_control instead of with_items
- name: "This a test"
apt:
name: "{{item}}"
cache_valid_time: "{{ apt_install.package[item].cache_valid_time }}"
state: "{{ apt_install.package[item].state }}"
update_cache: "{{ apt_install.package[item].update_cache }}"
loop: "{{ apt_install.packages.required|flatten(levels=1) }}"
loop_control:
index_var: index
So as it we can define different settings for each package and for each distrib.
Now I can export too settings for each package in different var files.
Learning is hard :)

Resources