Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set_fact module requires type casting (updated description) #5463

Closed
vyrak opened this issue Dec 30, 2013 · 25 comments
Closed

set_fact module requires type casting (updated description) #5463

vyrak opened this issue Dec 30, 2013 · 25 comments
Labels
bug This issue/PR relates to a bug.

Comments

@vyrak
Copy link
Contributor

vyrak commented Dec 30, 2013

Here's the playbook with the problem:

- name: Setup some config
  template: src=some.j2 dest=/vagrant/some
  register: some

- name: Setup boolean fact
  set_fact: it_is_so={{some.changed}}

- name: Setup other config
  template: src=other.j2 dest=/vagrant/other

Here's the other.j2 template:

Say...
{% if it_is_so %}
it is so
{% else %}
it isn't so
{% endif %}

The issue I'm having is that the other.j2 template has a check which assumed that the it_is_so variable is a boolean. As it turns out, that's not the case, instead, it's a string. So even if it_is_so was set to "False" it would still be considered true when parsing the template.

I do want to note that it is treated as a boolean if I changed it up to:

- name: Setup boolean fact
  set_fact: 
      it_is_so: ${some.changed}

I couldn't get away with {{some.changed}}, because it's a YAML syntax error. This works, which is nice, but the syntax is deprecated.

EDIT:

Also, the when statement is able to treat the "False" string as a false boolean value.

@mpdehaan
Copy link
Contributor

Anytime you say "{{ foo }}" the system thinks you want a string when used in a string context, so set fact doesn't really know what to do with the data.

You can cast at anytime by saying {{ foo | bool }}

The set_fact module should probably split on the = signs PRIOR to evaluating templates, though this may prove a little "fun" with whitespace and so forth, in any event, use the above for an easy workaround.

@willthames
Copy link
Contributor

You can also do the cast in the template

{% if it_is_so|bool %}
...

seems to work fine.

@sivel
Copy link
Member

sivel commented Dec 31, 2013

This is not entirely correct. It really depends on how you use the module. When you use key=value formatting with a module it uses shlex to do the splitting which basically results in everything becoming a string.

However if you use yaml formatting, you will generally get your desired data type:

- name: Setup boolean fact
  set_fact:
      it_is_so: "{{some.changed}}"

Using the above example, it_is_so would be correctly set to a boolean.

@vyrak
Copy link
Contributor Author

vyrak commented Dec 31, 2013

I can't seem to get any of the suggested solutions to work. I'm noticing the same behavior when using both the YAML & the key=value formats

I'm running on ansible v1.4.3

Below are all the packages installed:

pt-xapian-index (0.45)
argparse (1.2.1)
boto (2.21.2)
chardet (2.0.1)
distribute (0.6.34)
docker-py (0.2.3)
Jinja2 (2.6)
MarkupSafe (0.15)
mock (1.0.1)
paramiko (1.7.7.1)
pycrypto (2.6)
python-apt (0.8.8ubuntu6)
python-debian (0.1.21-nmu2ubuntu1)
PyYAML (3.10)
requests (1.2.3)
simplejson (2.6.2)
six (1.3.0)
ssh-import-id (3.14)
urllib3 (1.5)
websocket-client (0.11.0)
wsgiref (0.1.2)

@wbond
Copy link

wbond commented Mar 27, 2014

I can confirm what @vyrak is seeing, in that with 1.4 the workarounds do not do anything. The result is always a string.

What about adding a param to set_task called type that would cast the result either int or bool?

@mpdehaan mpdehaan added P5 and removed P4 labels Apr 17, 2014
@mpdehaan
Copy link
Contributor

Hi!

Thanks very much for your interest in Ansible. It sincerely means a lot to us.

On September 26, 2014, due to enormous levels of contribution to the project Ansible decided to reorganize module repos, making it easier
for developers to work on the project and for us to more easily manage new contributions and tickets.

We split modules from the main project off into two repos, http://github.com/ansible/ansible-modules-core and http://github.com/ansible/ansible-modules-extras

If you would still like this ticket attended to, and believe this problem or idea is still present in the latest version of Ansible (1.7.2) or the development branch, we will need your help in having it reopened in one of the two new repos, and instructions are provided below.

We apologize that we are not able to make this transition happen seamlessly, though this is a one-time change and your help is greatly appreciated --
this will greatly improve velocity going forward.

Both sets of modules will ship with Ansible, though they'll receive slightly different ticket handling.

To locate where a module lives between 'core' and 'extras'

Additionally, should you need more help with this, you can ask questions on:

Thank you very much!

@pzhine
Copy link

pzhine commented Oct 4, 2014

Here's an (albeit ugly) workaround for this issue:

- name: init local boolean flag
  set_fact: 
    local_bool: no

- name: set local boolean flag
  set_fact: 
    local_bool: yes
  when: var1 == 'foo' and var2 == 'bar'

@colthreepv
Copy link

really +1 for this workaround, been wasting hours on a boolean. Seriously.

@bobrik
Copy link
Contributor

bobrik commented May 31, 2015

@mpdehaan this is still an issue and I believe it's belongs to ansible core, not some specific module.

- hosts: somehost
  gather_facts: no
  tasks:
    - set_fact:
        number_of_things: "{{ groups['things'] | length | int }}"
    - debug: msg="{{ number_of_things | to_json }}"

Results in:

ok: [somehost] => {
    "msg": "\"10\""
}

But I want integer! Types are there for a reason and some API enforce them. "10" is not an integer, it's a string.

λ ansible --version
ansible 1.9.0.1
  configured module search path = ../library

Care to reopen?

@inhumantsar
Copy link
Contributor

This is a breaking issue on our end too. On Ansible 1.9.1. In our case, we're attempting to set a port number in YAML and converting it to nice json in a Jinja template. The target application (Sensu) needs this to be an int, but receives a string.

@pradeepchhetri
Copy link

Hello, Is this issue going to be addressed somewhere in the future soon ? I am facing the same issue.

@patrickheeney
Copy link
Contributor

I have the same issue with linode and ansible 1.9.2. value of payment_term must be one of: 1,12,24, got: 1.

@jesuscript
Copy link

why did this issue get closed? still seeing this behaviour in 1.9.4

@conatus
Copy link

conatus commented Feb 4, 2016

+1 for the workaround.

-1 for the issue not being fixed.

@patrickheeney
Copy link
Contributor

Unfortunately ansible is not responsive to this issue. Not only has it not been fixed, I can't even get anyone to re-open it. I have tried no less then a half dozen times on IRC to bring this to someones attention. It has always been met with silence after I send the link.

@lobsterdore
Copy link

lobsterdore commented Mar 21, 2016

This is still an issue for me, I have come up against this multiple times recently and have had to resort to nasty workarounds to get things working nicely.

@ryanwalls
Copy link
Contributor

+1, I have this problem as well.

@mhenniges
Copy link

mhenniges commented Jun 7, 2016

+1, same boat here, its making using the aws related modules very difficult with any sort of templated or complex var...

I've been messing around with just slipping this filter in certain assignments of dictionaries:

def force_cast_to_int(thing):
casted_dict = dict()
for k,v in thing.iteritems():
if isinstance(v, basestring):
try:
casted_dict[k] = int(v)
except ValueError:
casted_dict[k] = v
else:
casted_dict[k] = v
return casted_dict

It helps, but I hate it.

@lhoss
Copy link

lhoss commented Oct 21, 2016

I also just used @mhenniges solution to fix our custom (templatized) yml-variable(to json string) marathon deployment, similar to the use case described in Topface/ansible-marathon_app#1.
For convenience here's the properly formatted filter, plus the required class:

#
def force_dict_to_int(thing):
    casted_dict = dict()
    for k,v in thing.iteritems():
        if isinstance(v, basestring):
            try:
                casted_dict[k] = int(v)
            except ValueError:
                casted_dict[k] = v
        else:
            casted_dict[k] = v
    return casted_dict

class FilterModule(object):
    def filters(self):
        return {'force_dict_to_int': force_dict_to_int}

that I put into my ansible folder under ./filter_plugins/force_dict_to_int.py

@j3k0
Copy link

j3k0 commented Nov 8, 2016

Improvement over @lhoss filter to works for any type (basic types, and recursive structures made of lists and dictionaries).

#
def to_int(thing):
    if isinstance(thing, int):
        return thing
    elif isinstance(thing, basestring):
        try:
            return int(thing)
        except ValueError:
            return thing
    elif isinstance(thing, dict):
        casted_dict = dict()
        for k,v in thing.iteritems():
            casted_dict[k] = force_dict_to_int(v)
        return casted_dict
    elif isinstance(thing, list):
        return map(force_dict_to_int, thing)

class FilterModule(object):
    def filters(self):
        return {'to_int': force_dict_to_int}

@fgimian
Copy link

fgimian commented Jan 2, 2017

This is still currently an issue with the very latest Ansible and is a pretty huge deal for me.

Full reproduction:

- hosts: localhost
  connection: local
  gather_facts: no
  vars:
    things:
      - a
      - b
      - c
  tasks:
    - set_fact:
        number_of_things: "{{ things | length | int }}"
    - debug: msg="{{ number_of_things | to_json }}"

Output:

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "\"3\""
}

I really appreciate the workarounds provided but it doesn't quite help in a context like the one I'm facing right now:

  tasks:
    - name: place Kontakt libraries in the requested order
      plist:
        dest: "com.native-instruments.{{ item.1 }}"
        values:
          UserListIndex: "{{ item.0 }}"
      with_indexed_items: "{{ native_instruments_kontakt_library_order }}"

The value of UserListIndex must be an integer, but I am getting:

UserListIndex: '0'

Any chance we can get this re-opened please?

I'll be attempting to escalate this through some Red Hat contacts we have to have it re-opened, it's a serious issue.

Cheers
Fotis

@machacekondra
Copy link
Contributor

+1

@webvictim
Copy link

+1 - reopen please!

@Chakki13
Copy link

  • set_fact: rpm_list="{{ rpm_files.files | map(attribute='path') | list | }}"
    It works for the below:
    yum:
    name: "{{rpm_list}}"

But,
When I use in command module it is not treating the variable properly. It fails.
command: "ls -l {{rpm_list}}"

Debug shows like this : ["ls", "-l", "[u/tmp/

Could anyone help with this please ? - We are using ansible 2.3.1.0

@akamac
Copy link

akamac commented Jul 5, 2018

+1

@ansible ansible locked and limited conversation to collaborators Jul 8, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug This issue/PR relates to a bug.
Projects
None yet
Development

No branches or pull requests