Very basic answer to my question is with this little helper function.
def execute_serial(func, *args, **kwargs):
a2set = lambda x : set(getattr(func, x)) if hasattr(func, x) else None
func_hosts = a2set('hosts')
func_roles = a2set('roles')
should_exec = False
if 'host' not in kwargs:
kwargs['host'] = my_host = env.host_string
if func_hosts and my_host in func_hosts:
should_exec = True
if func_roles:
for func_role in func_roles:
if func_role in env.roledefs and my_host in env.roledefs[func_role]:
should_exec = True
if should_exec:
return execute(func, *args, **kwargs)
And usage would be like
@roles('group_django')
@task
def deploy_web():
execute(pre_deploy)
execute_serial(fix_srv_perms)
execute_serial(force_checkout_branch_to_production)
execute_serial(update_sources)
execute_serial(pip_install_requirements)
#these won't work correctly anymore
execute(bounce_workers)
execute(bounce_uwsgi)
execute(clear_cache)
I've gone through fabric.main.main() and there's no easy opportunities for hooks ( prior to start, all tasks done, on error, etc etc ) so an additional solution might be instead
@roles('local') # defined in roledefs as {'local':'127.0.0.1'}
@task
def deploy_web():
execute(pre_deploy) #This is already role('local') with @runonce
for host in env.roledefs['group_django'].values():
execute_serial(fix_srv_perms, host = host)
execute_serial(force_checkout_branch_to_production, host = host)
execute_serial(update_sources, host = host)
execute_serial(pip_install_requirements, host = host)
#These will pickup the @role decorators
execute(redeploy_web_crontab)
execute(redeploy_work_crontab)
execute(bounce_workers)
execute(bounce_uwsgi)
execute(clear_cache)
Additional info, I've written something like this a few times but I have a boto iterator that uses to ~/.boto to decide on region and produce a env.roledefs
as illustrated in pseudo-code
{ "{name in instance.tags }_{instance.tags[name]}": [ list of instance.public_dns_name that matches] }
additionally for my current webstack, all instances have tags like "group", "type", and optionally "lead" for specifying where to install crontabs, or execute final deployment scripts.
Using the local task loop you would need to make some slight modification to execute_serial
specifically env.host_string would equal "{env.user}@127.0.0.1" which is not ideal.
def execute_serial(func, *args, **kwargs):
a2set = lambda x : set(getattr(func, x)) if hasattr(func, x) else None
no_user = lambda x: x.split("@")[-1]
func_hosts = a2set('hosts')
func_roles = a2set('roles')
should_exec = False
my_host = env.host_string
my_host = env.host_string = kwargs['host']
if func_hosts and no_user(my_host) in func_hosts:
should_exec = True
if func_roles:
for func_role in func_roles:
if func_role in env.roledefs and no_user(env.host_string) in env.roledefs[func_role]:
should_exec = True
break
if should_exec:
return execute(func, *args, **kwargs)