108

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_2_staging():
  deploy('staging')

def deploy_2_prod():
  deploy('prod')

def deploy(server):
  print 'env.hosts:', env.hosts
  env.hosts = [server]
  print 'env.hosts:', env.hosts

Sample output:

host:folder user$ fab deploy_2_dev
env.hosts: []
env.hosts: ['dev']
No hosts found. Please specify (single) host string for connection:

When I create a set_hosts() task as shown in the Fabric docs, env.hosts is set properly. However, this is not a viable option, neither is a decorator. Passing hosts on the command line would ultimately result in some kind of shell script that calls the fabfile, I would prefer having one single tool do the job properly.

It says in the Fabric docs that 'env.hosts is simply a Python list object'. From my observations, this is simply not true.

Can anyone explain what is going on here ? How can I set the host to deploy to ?

4

15 回答 15

129

我通过为每个环境声明一个实际函数来做到这一点。例如:

def test():
    env.user = 'testuser'
    env.hosts = ['test.server.com']

def prod():
    env.user = 'produser'
    env.hosts = ['prod.server.com']

def deploy():
    ...

使用上述功能,我将键入以下内容以部署到我的测试环境:

fab test deploy

...以及以下部署到生产环境:

fab prod deploy

这样做的好处是testandprod函数可以在任何fab 函数之前使用,而不仅仅是部署。它非常有用。

于 2011-01-14T00:53:14.943 回答
77

使用角色定义

from fabric.api import env, run

env.roledefs = {
    'test': ['localhost'],
    'dev': ['user@dev.example.com'],
    'staging': ['user@staging.example.com'],
    'production': ['user@production.example.com']
} 

def deploy():
    run('echo test')

使用 -R 选择角色:

$ fab -R test deploy
[localhost] Executing task 'deploy'
...
于 2011-06-11T00:22:43.030 回答
49

这是serverhorror 答案的更简单版本:

from fabric.api import settings

def mystuff():
    with settings(host_string='192.0.2.78'):
        run("hostname -f")
于 2011-06-17T08:31:04.490 回答
21

自己被困在这个问题上,但最终想通了。您根本无法任务中设置 env.hosts 配置。每个任务执行 N 次,为每个指定的主机执行一次,因此该设置基本上超出了任务范围。

查看上面的代码,您可以简单地执行以下操作:

@hosts('dev')
def deploy_dev():
    deploy()

@hosts('staging')
def deploy_staging():
    deploy()

def deploy():
    # do stuff...

这似乎会做你想要的。

或者,您可以在全局范围内编写一些自定义代码,手动解析参数,并在定义任务函数之前设置 env.hosts。出于几个原因,这实际上就是我的设置方式。

于 2010-03-02T21:07:15.707 回答
18

自 fab 1.5 以来,这是动态设置主机的记录方法。

http://docs.fabfile.org/en/1.7/usage/execution.html#dynamic-hosts

引用下面的文档。

将 execute 与动态设置的主机列表一起使用

Fabric 的一个常见的中高级用例是在运行时参数化目标主机列表的查找(当使用角色不够时)。执行可以使这变得非常简单,如下所示:

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)
于 2013-08-21T20:58:10.707 回答
10

与其他一些答案相反,可以在任务中修改env环境变量。但是,这env只会用于使用该fabric.tasks.execute函数执行的后续任务。

from fabric.api import task, roles, run, env
from fabric.tasks import execute

# Not a task, plain old Python to dynamically retrieve list of hosts
def get_stressors():
    hosts = []
    # logic ...
    return hosts

@task
def stress_test():
    # 1) Dynamically generate hosts/roles
    stressors = get_stressors()
    env.roledefs['stressors'] = map(lambda x: x.public_ip, stressors)

    # 2) Wrap sub-tasks you want to execute on new env in execute(...)
    execute(stress)

    # 3) Note that sub-tasks not nested in execute(...) will use original env
    clean_up()

@roles('stressors')
def stress():
    # this function will see any changes to env, as it was wrapped in execute(..)
    run('echo "Running stress test..."')
    # ...

@task
def clean_up():
    # this task will NOT see any dynamic changes to env

如果不将子任务包装在 中,则将使用execute(...)您的模块级env设置或从 CLI 传递的任何内容。fab

于 2013-06-03T03:06:31.877 回答
9

你需要设置host_string一个例子是:

from fabric.context_managers import settings as _settings

def _get_hardware_node(virtualized):
    return "localhost"

def mystuff(virtualized):
    real_host = _get_hardware_node(virtualized)
    with _settings(
        host_string=real_host):
        run("echo I run on the host %s :: `hostname -f`" % (real_host, ))
于 2010-03-10T09:52:17.297 回答
9

解释为什么它甚至是一个问题。命令fab正在利用结构库来运行主机列表上的任务。如果您尝试更改任务中的主机列表,那么您实际上是在尝试在迭代列表时更改列表。或者在您没有定义主机的情况下,循环遍历一个空列表,其中您将列表设置为循环的代码永远不会执行。

使用 env.host_string 可以解决此问题,只是因为它直接向函数指定要连接的主机。这会导致一些问题,如果您想在多个主机上执行,您将重新制作执行循环。

人们能够在运行时设置主机的最简单方法是将环境填充为一个不同的任务,它设置所有主机字符串、用户等。然后他们运行部署任务。它看起来像这样:

fab production deploy

或者

fab staging deploy

分期和生产就像您给的任务一样,但它们本身并不调用下一个任务。它必须像这样工作的原因是任务必须完成,并跳出循环(主机的循环,在 env 情况下为 None,但此时它是一个循环),然后循环结束主机(现在由前面的任务定义)重新。

于 2011-03-28T21:50:36.813 回答
3

您需要在模块级别修改 env.hosts,而不是在任务函数中。我犯了同样的错误。

from fabric.api import *

def _get_hosts():
    hosts = []
    ... populate 'hosts' list ...
    return hosts

env.hosts = _get_hosts()

def your_task():
    ... your task ...
于 2010-08-10T14:24:12.197 回答
3

这很简单。只需初始化 env.host_string 变量,以下所有命令都将在此主机上执行。

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")
于 2011-11-27T20:53:33.560 回答
3

因此,为了设置主机并让命令在所有主机上运行,​​您必须从以下内容开始:

def PROD():
    env.hosts = ['10.0.0.1', '10.0.0.2']

def deploy(version='0.0'):
    sudo('deploy %s' % version)

一旦定义了这些,然后在命令行上运行命令:

fab PROD deploy:1.5

什么将在 PROD 函数中列出的所有服务器上运行部署任务,因为它在运行任务之前设置 env.hosts。

于 2014-04-04T22:00:05.590 回答
3

我对fabric完全陌生,但是要让fabric在多个主机上运行相同的命令(例如部署到多个服务器,在一个命令中),您可以运行:

fab -H staging-server,production-server deploy 

其中staging-serverproduction-server是您要对其运行部署操作的 2 台服务器。这是一个简单的 fabfile.py,它将显示操作系统名称。请注意,fabfile.py 应该与运行 fab 命令的目录位于同一目录中。

from fabric.api import *

def deploy():
    run('uname -s')

这至少适用于织物 1.8.1。

于 2014-01-30T13:45:05.690 回答
2

env.hoststring您可以在执行子任务之前分配给。如果要遍历多个主机,请在循环中分配给此全局变量。

对你我来说不幸的是,fabric 不是为这个用例设计的。查看http://github.com/bitprophet/fabric/blob/master/fabric/main.pymain上的函数,看看它是如何工作的。

于 2010-05-11T22:51:23.077 回答
2

这是另一个启用fab my_env_1 my_command使用的“summersault”模式:

使用这种模式,我们只需要使用字典定义一次环境。env_factory根据 的键名创建函数ENVS。我放入ENVS了它自己的目录和文件secrets.config.py以将配置与结构代码分开。

缺点是,正如所写,添加@task装饰器会破坏它

注意:由于后期绑定,我们在工厂使用def func(k=k):而不是。我们使用此解决方案获取正在运行的模块并对其进行修补以定义功能。def func():

秘密.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'
    }
}

工厂文件.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
于 2017-03-26T17:03:25.850 回答
0

使用角色目前被认为是这样做的“正确”和“正确”方式,并且是您“应该”这样做。

也就是说,如果您喜欢大多数“想要”或“渴望”的东西,那就是能够执行“扭曲系统”或动态切换目标系统。

因此,仅出于娱乐目的(!)以下示例说明了许多人可能认为的冒险,但以某种方式完全令人满意的操作,如下所示:

env.remote_hosts       = env.hosts = ['10.0.1.6']
env.remote_user        = env.user = 'bob'
env.remote_password    = env.password = 'password1'
env.remote_host_string = env.host_string

env.local_hosts        = ['127.0.0.1']
env.local_user         = 'mark'
env.local_password     = 'password2'

def perform_sumersault():
    env_local_host_string = env.host_string = env.local_user + '@' + env.local_hosts[0]
    env.password = env.local_password
    run("hostname -f")
    env.host_string = env.remote_host_string
    env.remote_password = env.password
    run("hostname -f")

然后运行:

fab perform_sumersault
于 2012-01-31T14:57:54.960 回答