Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ansible: Access host/group vars from within custom module

Is there a way how one can access host/group vars from within a custom written module? I would like to avoid to pass all required vars as module parameters.

My module is written in Python and I use the boilerplate. I checked pretty much all available vars but they are not stored anywhere:

def main():
    pprint(dir())
    pprint(globals())
    pprint(locals())
    for name in vars().keys():
        print(name)

Now my only hope is they are somehow accessible through the undocumented module utils.

I guess it is not possible, since the module runs on the target machine and probably the facts/host/group vars are not transferred along with the module...

Edit: Found the module utils now and it doesn't look promising.

like image 543
udondan Avatar asked Mar 13 '15 06:03

udondan


People also ask

How do you define host vars in Ansible?

Ansible uses a combination of a hosts file and a group_vars directory to pull variables per host group and run Ansible plays/tasks against hosts. group_vars/all is used to set variables that will be used for every host that Ansible is ran against.

What is Host_vars and Group_vars in Ansible?

The host_vars is a similar folder to group_vars in the repository structure. It contains data models that apply to individual hosts/devices in the hosts. ini file. Hence, there is a YAML file created per device containing specific information about that device.

Which command is used to access the list of Ansible variables?

The most commonly used magic variables are hostvars , groups , group_names , and inventory_hostname . With hostvars , you can access variables defined for any host in the play, at any point in a playbook. You can access Ansible facts using the hostvars variable too, but only after you have gathered (or cached) facts.


3 Answers

Is there a way how one can access host/group vars from within a custom written module?

Not built-in.

You will have to pass them yourself one way or the other:

  • Module args.
  • Serialize to local file system (with pickle or yaml.dump() or json or ...) and send the file over.
  • any other innovative ideas you can come up with.

Unfortunately you can't just send over whole host/groupvar files as-it-is because you would have to implement the variable scope/precedence resolution algorithm of ansible which is undefined (it's not the Zen philosophy of ansible to define such petty things :P ).

--edit--

I see they have some precedence defined now.

Ansible does apply variable precedence, and you might have a use for it. Here is the order of precedence from least to greatest (the last listed variables override all other variables):

  • command line values (for example, -u my_user, these are not variables)
  • role defaults (defined in role/defaults/main.yml) 1
  • inventory file or script group vars 2
  • inventory group_vars/all 3
  • playbook group_vars/all 3
  • inventory group_vars/* 3
  • playbook group_vars/* 3
  • inventory file or script host vars 2
  • inventory host_vars/* 3
  • playbook host_vars/* 3
  • host facts / cached set_facts 4
  • play vars
  • play vars_prompt
  • play vars_files
  • role vars (defined in role/vars/main.yml)
  • block vars (only for tasks in block)
  • task vars (only for the task)
  • include_vars
  • set_facts / registered vars
  • role (and include_role) params
  • include params
  • extra vars (for example, -e "user=my_user")(always win precedence)

In general, Ansible gives precedence to variables that were defined more recently, more actively, and with more explicit scope. Variables in the defaults folder inside a role are easily overridden. Anything in the vars directory of the role overrides previous versions of that variable in the namespace. Host and/or inventory variables override role defaults, but explicit includes such as the vars directory or an include_vars task override inventory variables.

Ansible merges different variables set in inventory so that more specific settings override more generic settings. For example, ansible_ssh_user specified as a group_var is overridden by ansible_user specified as a host_var. For details about the precedence of variables set in inventory, see How variables are merged.

Footnotes

1 Tasks in each role see their own role’s defaults. Tasks defined outside of a role see the last role’s defaults.

2(1,2) Variables defined in inventory file or provided by dynamic inventory.

3(1,2,3,4,5,6) Includes vars added by ‘vars plugins’ as well as host_vars and group_vars which are added by the default vars plugin shipped with Ansible.

4 When created with set_facts’s cacheable option, variables have the high precedence in the play, but are the same as a host facts precedence when they come from the cache.

like image 64
Kashyap Avatar answered Oct 22 '22 01:10

Kashyap


I think you pretty much hit the nail on the head with your thinking here:

I guess it is not possible, since the module runs on the target machine and probably the facts/host/group vars are not transferred along with the module...

However, having said that, if you really have a need for this then there might be a slightly messy way of doing it. As of Ansible 1.8 you can set up fact caching, which uses redis to cache facts between runs of plays. Since redis is pretty easy to use and has clients for most popular programming languages, you could have your module query the redis server for any facts you need. It's not exactly the cleanest way to do it, but it just might work.

like image 35
Bruce P Avatar answered Oct 22 '22 01:10

Bruce P


As per your suggestion in your answer here, I did manage to read host_vars and local play vars through a custom Action Plugin.

I'm posting this answer for completeness sake and to give an explicit example of how one might go about this method, although you gave this idea originally :)

Note - this example is incomplete in terms of a fully functioning plugin. It just shows the how to access variables.

from ansible.template import is_template
from ansible.plugins.action import ActionBase


class ActionModule(ActionBase):

    def run(self, tmp=None, task_vars=None):

        # some boilerplate ...
        
        # init
        result = super(ActionModule, self).run(tmp, task_vars)

        # more boilerplate ...

        # check the arguments passed to the task, where if missing, return None
        self._task.args.get('<TASK ARGUMENT NAME>', None)
        # or
        # check if the play has vars defined
        task_vars['vars']['<ARGUMENT NAME>']
        # or
        # check if the host vars has something defined
        task_vars['hostvars']['<HOST NAME FORM HOSTVARS>']['<ARGUMENT NAME>']

        # again boilerplate...

        # build arguments to pass to the module
        some_module_args = dict(
            arg1=arg1,
            arg2=arg2
        )

        # call the module with the above arguments...

In case you have your playbook variables with jinja 2 templates, you can resolve these templates in the plugin as follows:

from ansible.template import is_template

# check if the variable is a template through 'is_template'
if is_template(var, self._templar.environment):
    # access the internal `_templar` object to resolve the template
    resolved_arg = self._templar.template(var_arg)

Some words of caution:

    1. If you have a variable defined in your playbook as follows
# things ...
#
      vars:
        - pkcs12_path: '{{ pkcs12_full_path }}'
        - pkcs12_pass: '{{ pkcs12_password }}'

The variable pkcs12_path must not match the host_vars name.

For instance, if you had pkcs12_path: '{{ pkcs12_path }}', then resolving the template with the above code will cause a recursive exception... This might be obvious to some, but for me it was surprising that the host_vars variable and the playbook variable must not be with the same name.

    1. You can also access variables through task_vars['<ARG_NAME>'], but I'm not sure where it's reading this from. Also it's less explicit than taking variables from task_vars['vars']['<ARG_NAME>'] or from the hostvars.

PS - in the time of writing this, the example follows the basic structure of what Ansible consider an Action Plugin. In the future, the run method might change its signature...

like image 3
mnestorov Avatar answered Oct 21 '22 23:10

mnestorov