Importing Group Variables with Separate Task

Hello again!

I am super close to getting this working but have run into a barrier. I created the following task to load group variables (similar to what Ansible does) from various YAML files:

def load_group_data(task):
    role = task.host.data['role']
    for k in task.host.groups:
        default_data = task.run(name = "Load Default Data", task = load_yaml,
                                file = f"inventory/group_vars/environments/{k}/defaults.yaml")
        group_data = task.run(name = "Load Group Data", task = load_yaml,
                              file = f"inventory/group_vars/environments/{k}/roles/{role}.yaml")
        task.host.data.update(default_data.result)
        task.host.data.update(group_data.result)

The intent is to load all group variables (e.g. if device is part of the role “edge”, then it inherits/loads all variables from that), in addition to the specific host-type variables before generating the template and applying the config. Another task calls the above task (used to generate a template for the system section in a Juniper device):

def configure_system(task):
    try:
        load_yaml_group = task.run(name = "Load Group Data", task = load_group_data)
        load_yaml_system = task.run(name = "Load System Data", task = load_yaml,
                                    file = f"inventory/host_vars/platform/{task.host.platform}/system/{task.host.name}.yaml")

        task.host.data.update(load_yaml_system.result)

        if load_yaml_system.result is None:
            system_config = task.run(name = "Generate Template", task = template_file,
                                     path = f"templates/{task.host.platform}/",
                                     template = "system.j2")
        else:
            system_config = task.run(name = "Generate Template", task = template_file,
                                     path = f"templates/{task.host.platform}/",
                                     template = "system.j2", data = task.host.data)
        task.run(name = "NAPALM Configure", task = networking.napalm_configure,
                 configuration = system_config.result, replace = False)
    except NornirExecutionError as err:
        print (f"Error: {err}")

It looks like the YAML data is properly loaded into a dict structure. I can see the data from the various host and group yaml files as expected. However, the error I’m getting is “variable is not defined” when trying to generate the template. I understand what this error means, but I’m kinda stuck.

It appears the template_file task is looking for a key to peel back and traverse the loaded variables, but I haven’t been able to get it to work. In Ansible, I typically never had a top-level key for group variables. The yaml files would typically look like:

---
hostname: test-edge1
asn: 65400
ntp_server: <ip of ntp server>
...
and so on

Correct me if I’m wrong, but each variable in the above example would be a top-level key/value, right? How can I tell template_file to associate everything in that yaml file, without manually doing it (like what is done here)? Where the author explicitly says:

task.host['asn'] = data.result['asn']

That would be fine for a few entries, but if I have a large number of variables, it doesn’t seem efficient to add each to the task. My apologies on the novel, but I appreciate your help!

Actually, I may have figured it out, but I’m still testing. If I set the top key as “data” in this case (where all the yaml data had been loaded), it looks like I need to update the jinja template as well. I went from

{{ hostname }}

to

{{ data.hostname }}

And I’m no longer seeing the errors for variables not being defined.

How can I tell template_file to associate everything in that yaml file, without manually doing it

I am not entirely sure what you mean but you can do two things here:

  1. If you want to add the data to the host, what you are already doing is good; task.host.data.update(group_data.result). This should give you access to the data in the template via {{ host["asn"] }}.
  2. If you just need the data in that task, you can avoid loading it into the host and pass it to the template. For instane:
def configure_system(task):
    load_yaml_system = task.run(
    	name = "Load System Data",
    	task = load_yaml,
        file = f"inventory/host_vars/platform/{task.host.platform}/system/{task.host.name}.yaml"
    )
    system_config = task.run(
    	name = "Generate Template",
    	task = template_file,
        path = f"templates/{task.host.platform}/",
        template = "system.j2",
        system = load_yaml_system.result,
    )
    task.run(
    	name = "NAPALM Configure",
    	task = networking.napalm_configure,
        configuration = system_config.result,
        replace = False,
    )

That should give you acess to the data in the template via the {{ system }} jinja2 variable

In addition, this is not what you want to do I think but in any case, when you say “it doesn’t seem efficient to add each to the task” what you could do instead is:

for k, v in data.result.items():
    task.host[k] = v

I don’t think that’s what you want anyway but thought it was worth mentioning.

Excellent, thank you for the clarification. This actually helps immensely because I’ll probably end up utilizing both methods. Each task I create will generate a template for a specific section (e.g. system or interfaces), but in some cases like system, I’ll want to load the group variables as well (which is in a different task). Thanks again for your time!