I want to use Fabric to deploy my web app code to development, staging and production servers. My fabfile:
def deploy_2_dev():
deploy(\'dev\')
def deploy_
Here's a simpler version of serverhorror's answer:
from fabric.api import settings
def mystuff():
with settings(host_string='192.0.2.78'):
run("hostname -f")
Since fab 1.5 this is a documented way to dynamically set hosts.
http://docs.fabfile.org/en/1.7/usage/execution.html#dynamic-hosts
Quote from the doc below.
Using execute with dynamically-set host lists
A common intermediate-to-advanced use case for Fabric is to parameterize lookup of one’s target host list at runtime (when use of Roles does not suffice). execute can make this extremely simple, like so:
from fabric.api import run, execute, task
# For example, code talking to an HTTP API, or a database, or ...
from mylib import external_datastore
# This is the actual algorithm involved. It does not care about host
# lists at all.
def do_work():
run("something interesting on a host")
# This is the user-facing task invoked on the command line.
@task
def deploy(lookup_param):
# This is the magic you don't get with @hosts or @roles.
# Even lazy-loading roles require you to declare available roles
# beforehand. Here, the sky is the limit.
host_list = external_datastore.query(lookup_param)
# Put this dynamically generated host list together with the work to be
# done.
execute(do_work, hosts=host_list)
You need to modify env.hosts at the module level, not within a task function. I made the same mistake.
from fabric.api import *
def _get_hosts():
hosts = []
... populate 'hosts' list ...
return hosts
env.hosts = _get_hosts()
def your_task():
... your task ...
So, in order to set the hosts, and have the commands run across all the hosts, you have to start with:
def PROD():
env.hosts = ['10.0.0.1', '10.0.0.2']
def deploy(version='0.0'):
sudo('deploy %s' % version)
Once those are defined, then run the command on the command line:
fab PROD deploy:1.5
What will run the deploy task across all of the servers listed in the PROD function, as it sets the env.hosts before running the task.
Here's another "summersault" pattern that enables the fab my_env_1 my_command
usage:
With this pattern, we only have to define environments one time using a dictionary. env_factory
creates functions based on the keynames of ENVS
. I put ENVS
in its own directory and file secrets.config.py
to separate config from the fabric code.
The drawback is that, as written, adding the @task
decorator will break it.
Notes: We use def func(k=k):
instead of def func():
in the factory because of late binding. We get the running module with this solution and patch it to define the function.
secrets.config.py
ENVS = {
'my_env_1': {
'HOSTS': [
'host_1',
'host_2',
],
'MY_OTHER_SETTING': 'value_1',
},
'my_env_2': {
'HOSTS': ['host_3'],
'MY_OTHER_SETTING': 'value_2'
}
}
fabfile.py
import sys
from fabric.api import env
from secrets import config
def _set_env(env_name):
# can easily customize for various use cases
selected_config = config.ENVS[env_name]
for k, v in selected_config.items():
setattr(env, k, v)
def _env_factory(env_dict):
for k in env_dict:
def func(k=k):
_set_env(k)
setattr(sys.modules[__name__], k, func)
_env_factory(config.ENVS)
def my_command():
# do work
To explain why it's even an issue. The command fab is leveraging fabric the library to run the tasks on the host lists. If you try and change the host list inside a task, you're esentially attempting to change a list while iterating over it. Or in the case where you have no hosts defined, loop over an empty list where the code where you set the list to loop over is never executed.
The use of env.host_string is a work around for this behavior only in that it's specifying directly to the functions what hosts to connect with. This causes some issues in that you'll be remaking the execution loop if you want to have a number of hosts to execute on.
The simplest way the people make the ability to set hosts at run time, is to keep the env populatiing as a distinct task, that sets up all the host strings, users, etc. Then they run the deploy task. It looks like this:
fab production deploy
or
fab staging deploy
Where staging and production are like the tasks you have given, but they do not call the next task themselves. The reason it has to work like this, is that the task has to finish, and break out of the loop (of hosts, in the env case None, but it's a loop of one at that point), and then have the loop over the hosts (now defined by the preceding task) anew.