1. [1]:
  1. # Initializing objects for later use
  2. from nornir import InitNornir
  3. from nornir_utils.plugins.functions import print_result
  4. nr = InitNornir(config_file="config.yaml")
  5. # filtering objects to simplify output
  6. 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:

  1. [2]:
  1. from nornir.core.task import Task, Result
  2. def hello_world(task: Task) -> Result:
  3. return Result(
  4. host=task.host,
  5. result=f"{task.host.name} says hello world!"
  6. )

To execute a task you can use the run method:

  1. [3]:
  1. result = nr.run(task=hello_world)
  2. print_result(result)
  1. hello_world*********************************************************************
  2. * host1.cmh ** changed : False *************************************************
  3. vvvv hello_world ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
  4. host1.cmh says hello world!
  5. ^^^^ END hello_world ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  6. * host2.cmh ** changed : False *************************************************
  7. vvvv hello_world ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO
  8. host2.cmh says hello world!
  9. ^^^^ END hello_world ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

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

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

  1. [5]:
  1. result = nr.run(
  2. name="Saying goodbye in a very friendly manner",
  3. task=say,
  4. text="buhbye!"
  5. )
  6. print_result(result)
  1. Saying goodbye in a very friendly manner****************************************
  2. * host1.cmh ** changed : False *************************************************
  3. vvvv Saying goodbye in a very friendly manner ** changed : False vvvvvvvvvvvvvvv INFO
  4. host1.cmh says buhbye!
  5. ^^^^ END Saying goodbye in a very friendly manner ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  6. * host2.cmh ** changed : False *************************************************
  7. vvvv Saying goodbye in a very friendly manner ** changed : False vvvvvvvvvvvvvvv INFO
  8. host2.cmh says buhbye!
  9. ^^^^ 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:

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

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

  1. [7]:
  1. def greet_and_count(task: Task, number: int) -> Result:
  2. task.run(
  3. name="Greeting is the polite thing to do",
  4. task=say,
  5. text="hi!",
  6. )
  7. task.run(
  8. name="Counting beans",
  9. task=count,
  10. number=number,
  11. )
  12. task.run(
  13. name="We should say bye too",
  14. task=say,
  15. text="bye!",
  16. )
  17. # let's inform if we counted even or odd times
  18. even_or_odds = "even" if number % 2 == 1 else "odd"
  19. return Result(
  20. host=task.host,
  21. result=f"{task.host} counted {even_or_odds} times!",
  22. )

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:

  1. [8]:
  1. result = nr.run(
  2. name="Counting to 5 while being very polite",
  3. task=greet_and_count,
  4. number=5,
  5. )
  6. print_result(result)
  1. Counting to 5 while being very polite*******************************************
  2. * host1.cmh ** changed : False *************************************************
  3. vvvv Counting to 5 while being very polite ** changed : False vvvvvvvvvvvvvvvvvv INFO
  4. host1.cmh counted even times!
  5. ---- Greeting is the polite thing to do ** changed : False --------------------- INFO
  6. host1.cmh says hi!
  7. ---- Counting beans ** changed : False ----------------------------------------- INFO
  8. [0, 1, 2, 3, 4]
  9. ---- We should say bye too ** changed : False ---------------------------------- INFO
  10. host1.cmh says bye!
  11. ^^^^ END Counting to 5 while being very polite ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  12. * host2.cmh ** changed : False *************************************************
  13. vvvv Counting to 5 while being very polite ** changed : False vvvvvvvvvvvvvvvvvv INFO
  14. host2.cmh counted even times!
  15. ---- Greeting is the polite thing to do ** changed : False --------------------- INFO
  16. host2.cmh says hi!
  17. ---- Counting beans ** changed : False ----------------------------------------- INFO
  18. [0, 1, 2, 3, 4]
  19. ---- We should say bye too ** changed : False ---------------------------------- INFO
  20. host2.cmh says bye!
  21. ^^^^ END Counting to 5 while being very polite ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^