Tasks#

Tasks enable the organized execution of commands and scripts. After execution, a report is generated, containing the status and embedded documentation of the task and scripts in a self-contained format. To run the task produced by cijoe --example, use the following command:

# Run the task
cijoe cijoe-example-core.default/cijoe-task.yaml \
 --config cijoe-example-core.default/cijoe-config.toml

The command will execute all the steps in the task. To run a subset of steps, you can specify the step name(s) as arguments to the cijoe tool, similar to how targets are specified in a Makefile:

# Run a subset of the steps in the task
cijoe cijoe-example-core.default/cijoe-task.yaml \
 --config cijoe-example-core.default/cijoe-config.toml \
 inline_commands

With the above, only the step named inline_commands will be executed. This becomes even more useful when utilizing cijoe bash completions.

There are a couple of task-specific options. See the Task Configuration section for reference.

Content Overview#

Let’s take a look at what the task file produced by cijoe --example core.default looks like:

---
doc: |
  This is a task file, it serves as an example on how to run commands and
  scripts, the structure intentionally mimics that of GitHub actions, however,
  the keys you see here are all there is.

  Running commands, as you can see below, looks just like running commands in a
  GitHub Workflow

  * Add the 'run' key with a value of multi-line string

  Using scripts, it is similar to that of a GitHub action

  * Add the 'uses' key with the name of the script
    - packaged scripts have a namespaced name e.g. "my_pkg.my_script"
    - non-packaged do not e.g. "my_script"
  * Add the 'with' key providing arguments to the script

  The commands and the scripts are passed an instance of cijoe which they can
  use to call run() / get() / put(), with an output-directory matching the
  current step. This is it, end of story.

steps:
- name: inline_commands
  run: |
    cat /proc/cpuinfo
    hostname

- name: script_with_args
  uses: core.cmdrunner
  with:
    commands:
    - cat /proc/cpuinfo
    - hostname

- name: example_script
  uses: core.example_script_default
  with:
    repeat: 2

#
# Uncomment the lines below to include the non-packaged script
# 'cijoe-script.py', from the directory where you ran 'cijoe --example'
#
#- name: custom_script
#  uses: cijoe-script

#
# Any step can name the target it runs on with 'target:'. Without it, a step runs
# on the default target: 'cijoe.default_target', else the first target the
# configuration describes ('initiator' if none are).
#
#- name: inline_commands_on_remote
#  target: ssh
#  run: |
#    hostname
#    uname -a

At a first glance, then it might feel a bit similar to GitHub Actions workflow, but dramatically simpler since:

  • There are no logic operators

  • There is simple variable substitution using

    • Values from configuration file

    • Values from environment variables on initiator

    • Facts that earlier steps wrote, as {{ target.kernel.release }}, substituted when the step starts rather than when the task is loaded; see Facts

  • Minimal amount of “magic” keys

    • doc: Describe what the task does using multi-line plain-text

    • steps: Ordered list of scripts, to inline-commands, to run

Descriptions of the content are provided in the following subsections.

Steps#

Although cijoe aims to be simple, with minimal “magic” and a low learning curve, there is some yaml-magic involved in the task steps. A step can take one of two forms: either as Inline Commands or as Steps with Scripts.

Both forms require that a step must have a name. This allows subsets of steps to be executed via the cijoe command-line tool. When naming steps, follow these conventions:

  • Letters: a-z

  • Numbers: 0-9

  • Special characters: - and _

  • Must be lowercase

  • Must not start with a number

In short, use the typical lowercase identifier convention.

Inline Commands#

A step with inline commands take the form:

- name: inline_commands
  run: |
    cat /proc/cpuinfo
    hostname

Each line in a multi-line string is executed. It is implemented as a call to cijoe.run(command). Thus, the above notation for inline commands turns into execution of functions in the cijoe Python module:

cijoe.run("cat /proc/cpuinfo")
cijoe.run("hostname")

Note

This is implemented in cijoe as “syntactic-sugar” for running the built-in script core.cmdrunner. Thus, have a look at Steps with Scripts to see what this unfolds as.

Because run: is sugar for core.cmdrunner, you can pass any of its arguments alongside run: using a with: block. See core.cmdrunner for the full set of arguments.

Choosing the target of a step#

A step runs on the default target, see Target Configuration. To run a step elsewhere, name the target with target:, beside run: or uses::

- name: nvme_facts
  target: storage_node
  uses: core.facts_gather
  with:
    sections: "nvme"

- name: storage
  target: storage_node
  run: |
    echo "{{ target.nvme | length }} NVMe controller(s)"
    lsblk -f

- name: tools
  target: storage_node
  uses: core.tools_check
  with:
    tools: "fio,nvme"

Here storage reads the NVMe controllers that nvme_facts found on storage_node, and lists its block devices; its tools are checked too. target: works for any script: everything the step does on “the target”, its commands, transfers, tool checks and its {{ target.* }} references to facts, is about the one it names. The step after it is back on the default target. A name the configuration does not describe is an error when the task loads, and for cijoe -i.

Before target:, scripts took arguments of their own for this, such as with: transport: for core.cmdrunner. Those still work, and warn.

Steps with Scripts#

When a step runs a script, you give it a name and tell it which script to run, like so:

- name: script_with_args
  uses: core.cmdrunner
  with:
    commands:
    - cat /proc/cpuinfo
    - hostname

Take note of the “magic” keys:

uses

Name of the script to run, without the .py extension. Packaged scripts include a prefix, such as core. or linux.. As in the example above where the script cmdrunner.py from the core package is used (core.cmdrunner).

with

Everything under this key is passed to the script’s entry function: main(args, cijoe) in the args argument.

Linting#

When you write a task yourself it can be nice to check whether it is valid without running it. You can do so by running:

# Check format of task and verify existance of the scripts used
cijoe --integrity-check \
	--config cijoe-example-core.default/cijoe-config.toml \
	cijoe-example-core.default/cijoe-task.yaml