[1]:
# Initializing objects for later use
from nornir import InitNornir
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")
# filtering objects to simplify output
nr = nr.filter(site="cmh", role="host")

Tasks

Now that you know how to initialize nornir and work with the inventory let’s see how we can leverage it to run tasks on groups of hosts.

A task is a reusable piece of code that implements some functionality for a single host. In python terms it is a function that takes a Task as first paramater and returns a Result.

For instance:

[2]:
from nornir.core.task import Task, Result

def hello_world(task: Task) -> Result:
    return Result(
        host=task.host,
        result=f"{task.host.name} says hello world!"
    )

To execute a task you can use the run method:

[3]:
result = nr.run(task=hello_world)
print_result(result)
hello_world*********************************************************************
* host1.cmh ** changed : False *************************************************
vvvv hello_world ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
host1.cmh says hello world!
^^^^ END hello_world ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* host2.cmh ** changed : False *************************************************
vvvv hello_world ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
host2.cmh says hello world!
^^^^ END hello_world ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Tasks can also take any number of arguments to extend their functionality. For instance:

[4]:
def say(task: Task, text: str) -> Result:
    return Result(
        host=task.host,
        result=f"{task.host.name} says {text}"
    )

which can then be called like before but specifying the values for the extra argument:

[5]:
result = nr.run(
    name="Saying goodbye in a very friendly manner",
    task=say,
    text="buhbye!"
)
print_result(result)
Saying goodbye in a very friendly manner****************************************
* host1.cmh ** changed : False *************************************************
vvvv Saying goodbye in a very friendly manner ** changed : False vvvvvvvvvvvvvvv INFO
host1.cmh says buhbye!
^^^^ END Saying goodbye in a very friendly manner ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* host2.cmh ** changed : False *************************************************
vvvv Saying goodbye in a very friendly manner ** changed : False vvvvvvvvvvvvvvv INFO
host2.cmh says buhbye!
^^^^ END Saying goodbye in a very friendly manner ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Note that we passed a name argument to the run function. This argument is parameter and it allows us to give the task a descriptive name. If it’s not specified the function name is used instead.

Grouping tasks

A task can also call other tasks. This is useful as it can allow you to build more complex functionality by combining smaller building blocks. To illustrate this, let’s first define a new task:

[6]:
def count(task: Task, number: int) -> Result:
    return Result(
        host=task.host,
        result=f"{[n for n in range(0, number)]}"
    )

Now, let’s combine this with the say function we defined earlier to implement a more complex workflow:

[7]:
def greet_and_count(task: Task, number: int) -> Result:
    task.run(
        name="Greeting is the polite thing to do",
        task=say,
        text="hi!",
    )

    task.run(
        name="Counting beans",
        task=count,
        number=number,
    )
    task.run(
        name="We should say bye too",
        task=say,
        text="bye!",
    )

    # let's inform if we counted even or odd times
    even_or_odds = "even" if number % 2 == 1 else "odd"
    return Result(
        host=task.host,
        result=f"{task.host} counted {even_or_odds} times!",
    )

It is worth noting a couple of things:

  1. The first time we call the say function we hardcode the text to “hi!” while the second time we do it (the last action) we hardcode it to “bye!”
  2. When calling count we pass the parameter that we also specified to the parent task greet_and_count. That way we can make the parts we are interested in dynamic
  3. Finally, we return a Result object with some meaningful information about the whole workflow

Now that we have the grouped task we can call is as with any other regular task:

[8]:
result = nr.run(
    name="Counting to 5 while being very polite",
    task=greet_and_count,
    number=5,
)
print_result(result)
Counting to 5 while being very polite*******************************************
* host1.cmh ** changed : False *************************************************
vvvv Counting to 5 while being very polite ** changed : False vvvvvvvvvvvvvvvvvv INFO
host1.cmh counted even times!
---- Greeting is the polite thing to do ** changed : False --------------------- INFO
host1.cmh says hi!
---- Counting beans ** changed : False ----------------------------------------- INFO
[0, 1, 2, 3, 4]
---- We should say bye too ** changed : False ---------------------------------- INFO
host1.cmh says bye!
^^^^ END Counting to 5 while being very polite ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* host2.cmh ** changed : False *************************************************
vvvv Counting to 5 while being very polite ** changed : False vvvvvvvvvvvvvvvvvv INFO
host2.cmh counted even times!
---- Greeting is the polite thing to do ** changed : False --------------------- INFO
host2.cmh says hi!
---- Counting beans ** changed : False ----------------------------------------- INFO
[0, 1, 2, 3, 4]
---- We should say bye too ** changed : False ---------------------------------- INFO
host2.cmh says bye!
^^^^ END Counting to 5 while being very polite ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^