Zuul Runner


This is not authoritative documentation. These features are not currently available in Zuul. They may change significantly before final implementation, or may never be fully completed.

While Zuul can be deployed to reproduce a job locally, it is a complex enough system to setup. Zuul jobs being written in Ansible, we shouldn’t have to setup a Zookeeper, Nodepool and Zuul service to run a job locally.

To that end, the Zuul Project should create a command line utility to run a job locally using direct ansible-playbook commands execution. The scope includes two use cases:

  • Running a local build of a job that has already ran, for example to recreate a build that failed in the gate, through using either a zuul-info/inventory.yaml file, or using the –change-url command line argument.

  • Being able to run any job from any Zuul instance, tenant, project or pipeline regardless if it has run or not.

Zuul Job Execution Context

One of the key parts of making the Zuul Runner command line utility is to reproduce as close as possible the zuul service environment.

A Zuul job requires:

  • Test resources

  • Copies of the required projects

  • Ansible configuration

  • Decrypted copies of the secrets

Test Resources

The Zuul Runner shall require the user to provide test resources as an Ansible inventory, similarly to what Nodepool provides to the Zuul Executor. The Runner would enrich the inventory with the zuul vars.

For example, if a job needs two nodes, then the user provides a resource file like this:

      ansible_host: ip-node-1
      ansible_user: user-node-1
      ansible_host: ip-node-2
      ansible_user: user-node-2

Required Projects

The Zuul Runner shall query an existing Zuul API to get the list of projects required to run a job. This is implemented as part of the topic:freeze_job changes to expose the executor gearman parameters.

The CLI would then perform the executor service task to clone and merge the required project locally.

Ansible Configuration

The CLI would also perform the executor service tasks to setup the execution context.


In some case, running all the job playbooks is not desirable, in this situation the CLI provides a way to select and filter unneeded playbook.

“zuul-runner –list-playbooks” and it would print out:

0: opendev.org/base-jobs/playbooks/pre.yaml
10: opendev.org/base-jobs/playbooks/post.yaml

To avoid running the playbook 10, the user would use:

  • “–no-playbook 10”

  • “–no-playbook -1”

  • “–playbook 1..9”

Alternatively, a matcher may be implemented to express:

  • “–skip ‘opendev.org/base-jobs/playbooks/post.yaml’”


The Zuul Runner shall require the user to provide copies of any secrets required by the job.


The process of exposing gearman parameter and refactoring the executor code to support local/direct usage already started here: https://review.opendev.org/#/q/topic:freeze_job+(status:open+OR+status:merged)

Zuul Runner CLI

Here is the proposed usage for the CLI:

usage: zuul-runner [-h] [-c CONFIG] [--version] [-v] [-e FILE] [-a API]
                   [-t TENANT] [-j JOB] [-P PIPELINE] [-p PROJECT] [-b BRANCH]
                   [-g GIT_DIR] [-D DEPENDS_ON]
                   {prepare-workspace,execute} ...

A helper script for running zuul jobs locally.

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG             specify the config file
  --version             show zuul version
  -v, --verbose         verbose output
  -e FILE, --extra-vars FILE
                        global extra vars file
  -a API, --api API     the zuul server api to query against
  -t TENANT, --tenant TENANT
                        the zuul tenant name
  -j JOB, --job JOB     the zuul job name
  -P PIPELINE, --pipeline PIPELINE
                        the zuul pipeline name
  -p PROJECT, --project PROJECT
                        the zuul project name
  -b BRANCH, --branch BRANCH
                        the zuul project's branch name
  -g GIT_DIR, --git-dir GIT_DIR
                        the git merger dir
  -C CHANGE_URL, --change-url CHANGE_URL
                        reproduce job with speculative change content

  valid commands

    prepare-workspace   checks out all of the required playbooks and roles
                        into a given workspace and returns the order of
    execute             prepare and execute a zuul jobs

And here is an example execution:

$ pip install --user zuul
$ zuul-runner --api https://zuul.openstack.org --project openstack/nova --job tempest-full-py3 execute
2019-05-07 06:08:01,040 DEBUG zuul.Runner - Ansible output: b'PLAY RECAP *********************************************************************'
2019-05-07 06:08:01,040 DEBUG zuul.Runner - Ansible output: b'instance-ip                : ok=9    changed=5    unreachable=0    failed=0'
2019-05-07 06:08:01,040 DEBUG zuul.Runner - Ansible output: b'localhost                  : ok=12   changed=9    unreachable=0    failed=0'
2019-05-07 06:08:01,040 DEBUG zuul.Runner - Ansible output: b''
2019-05-07 06:08:01,218 DEBUG zuul.Runner - Ansible output terminated
2019-05-07 06:08:01,219 DEBUG zuul.Runner - Ansible cpu times: user=0.00, system=0.00, children_user=0.00, children_system=0.00
2019-05-07 06:08:01,219 DEBUG zuul.Runner - Ansible exit code: 0
2019-05-07 06:08:01,219 DEBUG zuul.Runner - Stopped disk job killer
2019-05-07 06:08:01,220 DEBUG zuul.Runner - Ansible complete, result RESULT_NORMAL code 0
2019-05-07 06:08:01,220 DEBUG zuul.ExecutorServer - Sent SIGTERM to SSH Agent, {'SSH_AUTH_SOCK': '/tmp/ssh-SYKgxg36XMBa/agent.18274', 'SSH_AGENT_PID': '18275'}