This is a very Fabric specific question, but more experienced python hackers might be able to answer this, even if they don\'t know Fabric.
I am trying to specify differ
The env.roles
will give you the roles specified by -R flag or hardcoded in the script itself. It won't contain the roles specified per task using the command line or using @roles
decorator. There is currently no way of getting this kind of information.
The next release of fabric (1.9 probably) will provide env.effective_roles
attribute with exactly what you want - the roles being used for the currently executed task. The code for that has already been merged into master.
Have a look at this issue.
For everyone else ever with this question, here is my solution:
The key was finding env.host_string.
This is how I restart different types of servers with one command:
env.roledefs = {
'apache': ['xxx.xxx.com'],
'APE': ['yyy.xxx.com']
}
def apache():
env.roles = ['apache']
...
def restart():
if env.host_string in env.roledefs['apache']:
sudo("apache2ctl graceful", pty=True)
elif env.host_string in env.roledefs['APE']:
sudo ("supervisorctl reload", pty=True)
enjoy!
Using fabric 1.14.0 under Anaconda2 5.1.0... confirm the issue when using the @roles
decorator... especially in the case that the @roles
decorator is used with multiple arguments and then another task without the @roles
decorator (or with different arguments) is called from within the first task. In my experience, this can have the effect of nonsensically mismatching the hosts, depending on how I discover the role (i.e. role = env.effective_roles[0]
).
Note that role = env.effective_roles[0]
does work well in simple situations, e.g. (a) @roles
only specifies one role, and (b) the original task does not call another task.
Note also the situation where -R
on command line does not override @roles
and must use task:roles=role1
instead: How to run a @roles-decorated fabric task on only a single host ... also wondering how to pass multiple roles to an argument named roles
... hmmm, but I digress.
Perhaps there is a better way, but documentation on @roles
leaves one wanting. Next step is to probably read through the source code at this point.
In the meantime, I've hacked up the following workaround...
from fabric.api import env
from fabric.decorators import roles
from fabric.decorators import task
def get_host_roles(env, of=None, die=False):
"""
Get the role(s) for a host at run time
:param env: Fabric env
:param of: tuple/set/list
:param die: boolean
:return: tuple(host, roles) or tuple(host, role)
"""
host = env.host
def valid(role):
return host in env.roledefs[role]:
roles = set(filter(valid, env.roledefs.keys()))
if of:
roles = tuple(roles & set(of)) # set intersection
if len(roles) == 1:
return host, roles[0]
elif die:
e = 'Host "%s" is not in just one of the provided roles: %s!' \
% (host, repr(roles))
raise Exception(e)
return host, roles
_roles = ('role1', 'role2')
@task
@roles(*_roles)
def do_something_with_roles():
host, roles = get_host_roles(env)
# roles is a tuple with all of the roles the host is in.
@task
@roles(*_roles)
def do_something_with_roles_diy():
host, roles = get_host_roles(env, _roles)
# `roles` is a tuple with the set intersection of `_roles` and the
# host's actual roles... so you handle the situation!
if 'role1' in roles:
# do whatever
pass
@task
@roles(*_roles)
def force_single_role():
host, role = get_host_roles(env, _roles, True)
# this usage raises an exception in the instance that the host is not
# exclusively in either 'role1' or 'role2'.
# roles is a string with the role for that host.
Hope that helps.
I didn't test it, but might work:
def _get_current_role():
for role in env.roledefs.keys():
if env.host_string in env.roledefs[role]:
return role
return None
Update: Just checked the source code and it seems that this was already available as early as 1.4.2!
update 2: This seems not to work when using the @roles
decorator (in 1.5.3)! It only works when specifying the roles using the -R
command line flag.
For fabric 1.5.3 the current roles are directly available in `fabric.api.env.roles'. For example:
import fabric.api as fab
fab.env.roledefs['staging'] = ['bbs-evolution.ipsw.dt.ept.lu']
fab.env.roledefs['prod'] = ['bbs-arbiter.ipsw.dt.ept.lu']
@fab.task
def testrole():
print fab.env.roles
Test output on the console:
› fab -R staging testrole
[bbs-evolution.ipsw.dt.ept.lu] Executing task 'testrole'
['staging']
Done.
Or:
› fab -R staging,prod testrole
[bbs-evolution.ipsw.dt.ept.lu] Executing task 'testrole'
['staging', 'prod']
[bbs-arbiter.ipsw.dt.ept.lu] Executing task 'testrole'
['staging', 'prod']
Done.
With this, we can do a simple in
test in a fabric task:
@fab.task
def testrole():
if 'prod' in fab.env.roles:
do_production_stuff()
elif 'staging' in fab.env.roles:
do_staging_stuff()
else:
raise ValueError('No valid role specified!')