mstringer
mstringer

Reputation: 2302

Can a fabric task execute other tasks and respect decorators such as runs_once?

Is there a way for execute in fabric to respect a decorator (other than host, hosts, role, roles and exclude_hosts --- see here), or another way to accomplish something like that)? Here is a use case:

from fabric.api import task, execute, run, runs_once

@task
def setup_environment():
    # ... set up env.hosts, env.roledefs, env.use_ssh_config, etc.

@task
def provision():
    # ... do some stuff on each host here, e.g. install mongodb

@task
def is_primary():
    return run('mongo --quiet --eval "db.isMaster().ismaster"') == 'true'

@task
@runs_once
def change_to_primary():
    env.hosts = []
    for host, result in execute(is_primary, roles=('mongo',)).iteritems():
        if result:
            env.hosts.append(host)

@task
def add_user():
    # ... do something here that needs to be done on primary

This is fine if I run the following sequence of tasks from the command line:

> fab setup_environment provision change_to_primary add_user

But since I always run change_to_primary and add_user as part of provisioning, I would like to modify provision so that I can run fab setup_environment provision and have set_primary and add_user be executed, something like this:

@task
def provision():
    # ... do some stuff on each host here, e.g. install mongodb
    execute(change_to_primary)
    execute(add_user)

However, this executes change_to_primary many times (does not run once), unlike the command line usage. Is there a way to accomplish this?

Upvotes: 0

Views: 1050

Answers (1)

ronnix
ronnix

Reputation: 1564

One way would be to execute the task on all nodes with the mongo role using the roles decorator, and begin the task by checking if the node is actually a primary:

@task
def provision():
    execute(stuff_to_do_on_all_hosts)
    execute(stuff_to_do_on_mongo_primaries)

@task
def stuff_to_do_on_all_hosts():
    do_stuff()

@task
@roles('mongo')
def stuff_to_do_on_mongo_primaries():
    if not is_primary():
        return
    add_user()
    do_other_stuff()

Another way would be to first build the list of primaries, then use the hosts parameter to execute:

@task
def provision():
    # ... do some stuff on each host here, e.g. install mongodb
    execute(stuff_to_do_on_all_hosts)

    # build list of mongo primaries
    primaries = [host for host, result in execute(is_primary, roles=('mongo',)).iteritems() if result]

    # run task only on those hosts
    execute(stuff_to_do_on_mongo_primaries, hosts=primaries)

@task
def stuff_to_do_on_all_hosts():
    do_stuff()

@task
def stuff_to_do_on_mongo_primaries():
    add_user()
    do_other_stuff()

Hope this helps.

Upvotes: 1

Related Questions