Iterating with_items: $groups['dbclients']

Hi all,

Thanks so much for ansible ! I’m nearly deployed.

This should be splendidly simple but for some reason nothing I have tried is working:

  • name: open psql ports to trusted servers
    with_items: $groups[‘dbclients’]
    action: command ufw allow from ${item[‘ip’]} to any port $psql_port
    tags: postgresx

where

[dbclients:children]
webservers

[webservers]
basicbox ansible_ssh_host=1.2.3.4 ip=1.2.3.4

I’m sure this is a common pattern that people want to do.

failed: [db] => (item={‘newservers’: , ‘all’: [‘db’, ‘nsweb’, ‘nsbeta’, ‘nsmailings’, ‘nsmain’, ‘basicbox’], ‘tasks’: , ‘webservers’: [‘basicbox’], ‘mailer’: , ‘ungrouped’: , ‘rabbitclients’: [‘basicbox’], ‘dbclients’: [‘basicbox’], ‘dbservers’: [‘db’], ‘nestseekers’: [‘db’, ‘nsweb’, ‘nsbeta’, ‘nsmailings’, ‘nsmain’, ‘basicbox’], ‘oldboxes’: [‘nsbeta’, ‘nsmain’, ‘nsmailings’, ‘nsweb’]}[‘dbclients’]) => {“changed”: true, “cmd”: [“ufw”, “allow”, “from”, “{newservers:”, “,”, “all:”, “[db,”, “nsweb,”, “nsbeta,”, “nsmailings,”, “nsmain,”, “basicbox],”, “tasks:”, “,”, “webservers:”, “[basicbox],”, “mailer:”, “,”, “ungrouped:”, “,”, “rabbitclients:”, “[basicbox],”, “dbclients:”, “[basicbox],”, “dbservers:”, “[db],”, “nestseekers:”, “[db,”, “nsweb,”, “nsbeta,”, “nsmailings,”, “nsmain,”, “basicbox],”, “oldboxes:”, “[nsbeta,”, “nsmain,”, “nsmailings,”, “nsweb]}[dbclients][ip]”, “to”, “any”, “port”, “$psql_port”], “delta”: “0:00:00.077861”, “end”: “2013-03-15 11:40:11.326913”, “item”: “{‘newservers’: , ‘all’: [‘db’, ‘nsweb’, ‘nsbeta’, ‘nsmailings’, ‘nsmain’, ‘basicbox’], ‘tasks’: , ‘webservers’: [‘basicbox’], ‘mailer’: , ‘ungrouped’: , ‘rabbitclients’: [‘basicbox’], ‘dbclients’: [‘basicbox’], ‘dbservers’: [‘db’], ‘nestseekers’: [‘db’, ‘nsweb’, ‘nsbeta’, ‘nsmailings’, ‘nsmain’, ‘basicbox’], ‘oldboxes’: [‘nsbeta’, ‘nsmain’, ‘nsmailings’, ‘nsweb’]}[‘dbclients’]”, “rc”: 1, “start”: “2013-03-15 11:40:11.249052”}

Another guess:

  • name: open psql ports to trusted servers
    with_items: $groups[‘dbclients’]
    action: command ufw allow from $hostvars[$item][‘ip’] to any port $psql_port
    tags: postgresx

failed: [db] => (item={‘newservers’: , ‘all’: [‘db’, ‘nsweb’, ‘nsmailings’, ‘nsmain’, ‘nsbeta’, ‘basicbox’], ‘tasks’: , ‘webservers’: [‘basicbox’], ‘mailer’: , ‘ungrouped’: , ‘rabbitclients’: [‘basicbox’], ‘dbclients’: [‘basicbox’], ‘dbservers’: [‘db’], ‘nestseekers’: [‘db’, ‘nsweb’, ‘nsmailings’, ‘nsmain’, ‘nsbeta’, ‘basicbox’], ‘oldboxes’: [‘nsweb’, ‘nsmain’, ‘nsmailings’, ‘nsbeta’]}[‘dbclients’]) => {“changed”: true, “cmd”: [“ufw”, “allow”, “from”, “{db:”, “{}}[{newservers:”, “,”, “all:”, “[db,”, “nsweb,”, “nsmailings,”, “nsmain,”, “nsbeta,”, “basicbox],”, “tasks:”, “,”, “webservers:”, “[basicbox],”, “mailer:”, “,”, “ungrouped:”, “,”, “rabbitclients:”, “[basicbox],”, “dbclients:”, “[basicbox],”, “dbservers:”, “[db],”, “nestseekers:”, “[db,”, “nsweb,”, “nsmailings,”, “nsmain,”, “nsbeta,”, “basicbox],”, “oldboxes:”, “[nsweb,”, “nsmain,”, “nsmailings,”, “nsbeta]}[dbclients]][ip]”, “to”, “any”, “port”, “$psql_port”], “delta”: “0:00:00.077508”, “end”: “2013-03-15 12:14:44.236852”, “item”: “{‘newservers’: , ‘all’: [‘db’, ‘nsweb’, ‘nsmailings’, ‘nsmain’, ‘nsbeta’, ‘basicbox’], ‘tasks’: , ‘webservers’: [‘basicbox’], ‘mailer’: , ‘ungrouped’: , ‘rabbitclients’: [‘basicbox’], ‘dbclients’: [‘basicbox’], ‘dbservers’: [‘db’], ‘nestseekers’: [‘db’, ‘nsweb’, ‘nsmailings’, ‘nsmain’, ‘nsbeta’, ‘basicbox’], ‘oldboxes’: [‘nsweb’, ‘nsmain’, ‘nsmailings’, ‘nsbeta’]}[‘dbclients’]”, “rc”: 1, “start”: “2013-03-15 12:14:44.159344”}
stderr: Invalid syntax
stdout:
Usage: ufw COMMAND

etc.

I’ve looked at http://ansible.cc/docs/playbooks2.html#magic-variables-and-how-to-access-information-about-other-hosts but still can’t figure out how this should be done.

thanks !

- name: open psql ports to trusted servers
  with_items: $groups['dbclients']
  action: command ufw allow from ${item['ip']} to any port $psql_port
  tags: postgresx

First fix:

with_items: ${groups.dbclients}

Second fix should be:

${hostvars.{$item}.ansible_eth0.ipv4.address}

The reason being is there's no fact named 'ip', and $groups.dbclients
is a list of all hostnames in the group, not the facts for each host
in each group.

thanks

actually I was not trying to access facts, but host variables.

there is a variable for that host as I showed here:

[webservers]
basicbox ansible_ssh_host=1.2.3.4 ip=1.2.3.4

AFAIK the facts are only available if you have connected to that server.

the IP address is already known to me and its a long slow path to go gather facts from other servers when I’m just trying to write a conf file

so with_items: ${groups.dbclients}
will iterate $item as the hostname (string) ?

${hostvars.{$item}.ip}

should work ?

or maybe

${hostvars.{$item}.ansible_ssh_host}

?

here is another case I wanted to access variables of other hosts:

servers:

  • ip: 16.7.12.143
    down: “”
    weight: 1

I would think that it is so common that this would be useful:

with_hosts: groupname
action: action: command ufw allow from ${host.ip} to any port $psql_port

which would iterate over the hosts with all of their variables, each one built with relevant group_vars etc

thanks again,

host variables and facts are both available in hostvars.

Making a with_hosts_in_group: $foo

is quite doable!

hmm. not working:

  • name: open amqp rabbit port
    with_items: ${groups.rabbitclients}
    action: command ufw allow from ${hostvars.{$item}.ansible_eth0.ipv4.address} to any port amqp
    tags: rabbitx

failed: [tasks] => (item=tasks) => {“changed”: true, “cmd”: [“ufw”, “allow”, “from”, “${hostvars.{$item}.ansible_eth0.ipv4.address}”, “to”, “any”, “port”, “amqp”], “delta”: “0:00:00.071405”, “end”: “2013-03-15 21:48:14.862130”, “item”: “tasks”, “rc”: 1, “start”: “2013-03-15 21:48:14.790725”}
stderr: ERROR: Bad source address

you see its got the $ stuff in there, it really is passing that through. I’ve also noticed that missing variables become blank “” whereas I would welcome a big fat error on the ansible side.

I don’t think missing variables or $wgsi vs $wsgi should be allowed, they should result in exceptions.

here is me trying to use simple variables (no facts):

  • name: open amqp rabbit port
    with_items: ${groups.rabbitclients}
    action: command ufw allow from ${hostvars.{$item}.ip} to any port amqp
    tags: rabbitx

failed: [tasks] => (item=tasks) => {“changed”: true, “cmd”: [“ufw”, “allow”, “from”, “${hostvars.{$item}.ip}”, “to”, “any”, “port”, “amqp”], “delta”: “0:00:00.073316”, “end”: “2013-03-15 21:44:45.579267”, “item”: “tasks”, “rc”: 1, “start”: “2013-03-15 21:44:45.505951”}
stderr: ERROR: Bad source address

ansible 1.1

BTW, the reason we don't make every "$foo" become an error is because
you may wish to pass a literal "$foo".

This keeps Ansible users from having to escape everything.

You can print out a template that includes {{ hostvars | to_nice_yaml
}} (assuming recent YAML) to see what facts you have available.

I guess I’d have to disagree with the decision to allow unmatched variables to be passed on.
I think its a syntax error and should fail.

I have never once found a reason to pass a literal $ and I’ll be quite happy to escape it when I do have one.

Once again today’s problem is from that:

root@tasks:~# rabbitmqctl list_users
Listing users …
${rabbitmq_user}
guest [administrator]
…done.

Its sending the literal ${rabbitmq_user} and creating a user by that name.
This makes debugging very difficult.

I cannot imagine a programming language that interprets undefined variables as literal strings.

Thanks, but I don’t think that has anything to do with the issue.

I think a warning when a "${" is still around is not a bad idea,
though you may also be using shell with "$HOME" or something else.

I'll have to think a bit about the logic in which case we should /not/ warn.

Any ideas on other possible gotchas?

I got gotchad again this morning. its been the number 1 cause of lost time so far.

thing like forgetting to include vars_files: in a play
and then the passwords are empty.

any minor typo results in a mysterious effect on a server where things all of a sudden can’t talk to each other.
debugging and tracing it back takes a long time.

the other usability issue I’ve found so far is when an interactive dialog happens during a command I have to just wait and eventually realize that its not just being slow.

eg. if I mistype my sudo password
or a command is waiting for me to hit (yes/no)

Other than that, great project ! Keeps it simple and solid.

I glanced through last year’s effort to get puppet to work and I have no idea what all those damned files do.

Great, I've opened a ticket so we don't forget about this:

https://github.com/ansible/ansible/issues/2465

As for things being interactive, I believe you are going to have to
handle that, as we have to initialize a pty for when you are using
sudo.

the working answer for my question is this:

gather_facts: yes

  • name: open psql ports to trusted servers
    with_items: ${groups.dbclients}
    action: command ufw allow from ${hostvars.${item}.sip} to any port $psql_port
    tags: postgresy

where ${groups.dbclients} returns a list of the names

and ${hostvars.${item}.sip} is the correct syntax for accessing the sip variable (not fact)

and most importantly: if you set gather_facts: no then the variables are not accessible either.

that was quite non-obvious until I looked at the source code

There is a section in the docs that describes this for templates.

I'll comment on your ticket that we should also indicate how to do
this there for mainstream playbooks.