Ansible callback plugin: how to get play attribute values with variables expanded?

Viewed 1948

I have a play below and am trying to get the resolved value of the remote_user attribute inside the callback plugin.

- name:          test play
  hosts:         "{{ hosts_pattern }}"
  strategy:      free
  gather_facts:  no
  remote_user:   "{{ my_remote_user if my_remote_user is defined else 'default_user' }}"
  tasks:
     - name:     a test task
       shell:    whoami && hostname

I am currently accessing the play field attribute as follows:

 def v2_playbook_on_play_start(self, play):
     self._play_remote_user = play.remote_user

And I also tried saving the remote_user within v2_playbook_on_task_start to see if this does the trick, as this is where the templated task name is made available.

def v2_playbook_on_task_start(self, task, is_conditional):
    self._tasks[task._uuid].remote_user = task.remote_user
    self._tasks[task._uuid].remote_user_2 = task._get_parent_attribute('remote_user')

However all cases above give me {{ my_remote_user if my_remote_user is defined else 'default_user' }} instead of the expanded/resolved value.

In general, is there a neat way to get a collection of all play attributes with resolved values as defined in the playbook?

3 Answers

Happily much easier for action plugins.
ActionBase class has templar and loader properties already. One can iterate over task_vars and render all with Templar.template

for k in task_vars:
    new_module_args = merge_hash(
        new_module_args,
        {k: self._templar.template(task_vars.get(k, None))}
    )

and call module

        result = self._execute_module(
            module_name='my_module',
            task_vars=task_vars,
            module_args=new_module_args
        )  

I don't think there is an easy way to achieve this.

PlayContext is templated inside task_executor here.
And this happens after all callback methods are already notified.
So you should use Templar class manually (but I'm not sure you can get correct variables context for it to work correctly).

Credit goes Konstantin's tip to use the Templar class.

I came up with a solution for Ansible 2.3.1 - not entirely sure if it's the optimum one but it seems to work. This is an example code:

from ansible.plugins.callback import CallbackBase
from ansible.template import Templar
from ansible.plugins.strategy import SharedPluginLoaderObj

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'notification'
    CALLBACK_NAME = 'your_name'

    def __init__(self):
        super(CallbackModule, self).__init__()
        # other shenanigans

    def v2_playbook_on_start(self, playbook):
        self.playbook = playbook

    def v2_playbook_on_play_start(self, play):
        self.play = play


    def _all_vars(self, host=None, task=None):
        # host and task need to be specified in case 'magic variables' (host vars, group vars, etc) need to be loaded as well
        return self.play.get_variable_manager().get_vars(
            loader=self.playbook.get_loader(),
            play=self.play,
            host=host,
            task=task
        )

    def v2_runner_on_ok(self, result):
        templar = Templar(loader=self.playbook.get_loader(),
                  shared_loader_obj=SharedPluginLoaderObj(),
                  variables=self._all_vars(host=result._host, task=result._task))

        remote_user = templar.template(self.play.remote_user)
        # do something with templated remote_user
Related