标签归档:fabric

通过架构以部署用户身份激活virtualenv

问题:通过架构以部署用户身份激活virtualenv

我想在本地运行我的结构脚本,这将依次登录到我的服务器,切换用户以进行部署,激活项目.virtualenv,这将把dir更改为项目并发出git pull。

def git_pull():
    sudo('su deploy')
    # here i need to switch to the virtualenv
    run('git pull')

我通常使用来自virtualenvwrapper的workon命令,该命令提供激活文件,后激活文件会将我放在项目文件夹中。在这种情况下,似乎因为结构是在shell中运行的,所以控制权移交给了结构,所以我不能将bash的源内置到’$ source〜/ .virtualenv / myvenv / bin / activate’中。

有人举一个例子,并解释他们如何做到这一点吗?

I want to run my fabric script locally, which will in turn, log into my server, switch user to deploy, activate the projects .virtualenv, which will change dir to the project and issue a git pull.

def git_pull():
    sudo('su deploy')
    # here i need to switch to the virtualenv
    run('git pull')

I typically use the workon command from virtualenvwrapper which sources the activate file and the postactivate file will put me in the project folder. In this case, it seems that because fabric runs from within shell, control is give over to fabric, so I can’t use bash’s source built-in to ‘$source ~/.virtualenv/myvenv/bin/activate’

Anybody have an example and explanation of how they have done this?


回答 0

现在,您可以做我所要做的事情,这很笨拙,但效果很好*(此用法假设您正在使用virtualenvwrapper-应该如此-但您可以轻松地替换为您提到的更长的“源”调用, 如果不):

def task():
    workon = 'workon myvenv && '
    run(workon + 'git pull')
    run(workon + 'do other stuff, etc')

从1.0版开始,Fabric具有使用此技术的prefix上下文管理器,因此您可以例如:

def task():
    with prefix('workon myvenv'):
        run('git pull')
        run('do other stuff, etc')

*在某些情况下,使用这种command1 && command2方法可能会炸毁您,例如command1失败时(command2永远不会运行)或command1无法正确转义并且包含特殊的shell字符等。

Right now, you can do what I do, which is kludgy but works perfectly well* (this usage assumes you’re using virtualenvwrapper — which you should be — but you can easily substitute in the rather longer ‘source’ call you mentioned, if not):

def task():
    workon = 'workon myvenv && '
    run(workon + 'git pull')
    run(workon + 'do other stuff, etc')

Since version 1.0, Fabric has a prefix context manager which uses this technique so you can for example:

def task():
    with prefix('workon myvenv'):
        run('git pull')
        run('do other stuff, etc')

* There are bound to be cases where using the command1 && command2 approach may blow up on you, such as when command1 fails (command2 will never run) or if command1 isn’t properly escaped and contains special shell characters, and so forth.


回答 1

作为对bitprophet预测的更新:使用Fabric 1.0,您可以使用prefix()和您自己的上下文管理器。

from __future__ import with_statement
from fabric.api import *
from contextlib import contextmanager as _contextmanager

env.hosts = ['servername']
env.user = 'deploy'
env.keyfile = ['$HOME/.ssh/deploy_rsa']
env.directory = '/path/to/virtualenvs/project'
env.activate = 'source /path/to/virtualenvs/project/bin/activate'

@_contextmanager
def virtualenv():
    with cd(env.directory):
        with prefix(env.activate):
            yield

def deploy():
    with virtualenv():
        run('pip freeze')

As an update to bitprophet’s forecast: With Fabric 1.0 you can make use of prefix() and your own context managers.

from __future__ import with_statement
from fabric.api import *
from contextlib import contextmanager as _contextmanager

env.hosts = ['servername']
env.user = 'deploy'
env.keyfile = ['$HOME/.ssh/deploy_rsa']
env.directory = '/path/to/virtualenvs/project'
env.activate = 'source /path/to/virtualenvs/project/bin/activate'

@_contextmanager
def virtualenv():
    with cd(env.directory):
        with prefix(env.activate):
            yield

def deploy():
    with virtualenv():
        run('pip freeze')

回答 2

我只是使用一个简单的包装函数virtualenv()而不是run()即可调用。它不使用cd上下文管理器,因此可以使用相对路径。

def virtualenv(command):
    """
    Run a command in the virtualenv. This prefixes the command with the source
    command.
    Usage:
        virtualenv('pip install django')
    """
    source = 'source %(project_directory)s/bin/activate && ' % env
    run(source + command)

I’m just using a simple wrapper function virtualenv() that can be called instead of run(). It doesn’t use the cd context manager, so relative paths can be used.

def virtualenv(command):
    """
    Run a command in the virtualenv. This prefixes the command with the source
    command.
    Usage:
        virtualenv('pip install django')
    """
    source = 'source %(project_directory)s/bin/activate && ' % env
    run(source + command)

回答 3

virtualenvwrapper 可以简化一点

  1. 使用@ nh2的方法(该方法在使用时也适用local,但仅适用workon$PATHin中的virtualenvwrapper安装程序,换句话说-Windows)

    from contextlib import contextmanager
    from fabric.api import prefix
    
    @contextmanager
    def virtualenv():
        with prefix("workon env1"):
            yield
    
    def deploy():
        with virtualenv():
            run("pip freeze > requirements.txt")
  2. 或部署fab文件并在本地运行。通过此设置,您可以为本地或远程命令激活virtualenv。这种方法功能强大,因为它可以解决local无法使用bash -l以下命令运行.bashrc的问题:

    @contextmanager
    def local_prefix(shell, prefix):
        def local_call(command):
            return local("%(sh)s \"%(pre)s && %(cmd)s\"" % 
                {"sh": shell, "pre": prefix, "cmd": command})
        yield local_prefix
    
    def write_requirements(shell="/bin/bash -lic", env="env1"):
        with local_prefix(shell, "workon %s" % env) as local:
            local("pip freeze > requirements.txt")
    
    write_requirements()  # locally
    run("fab write_requirements")

virtualenvwrapper can make this a little simpler

  1. Using @nh2’s approach (this approach also works when using local, but only for virtualenvwrapper installations where workon is in $PATH, in other words — Windows)

    from contextlib import contextmanager
    from fabric.api import prefix
    
    @contextmanager
    def virtualenv():
        with prefix("workon env1"):
            yield
    
    def deploy():
        with virtualenv():
            run("pip freeze > requirements.txt")
    
  2. Or deploy your fab file and run this locally. This setup lets you activate the virtualenv for local or remote commands. This approach is powerful because it works around local‘s inability to run .bashrc using bash -l:

    @contextmanager
    def local_prefix(shell, prefix):
        def local_call(command):
            return local("%(sh)s \"%(pre)s && %(cmd)s\"" % 
                {"sh": shell, "pre": prefix, "cmd": command})
        yield local_prefix
    
    def write_requirements(shell="/bin/bash -lic", env="env1"):
        with local_prefix(shell, "workon %s" % env) as local:
            local("pip freeze > requirements.txt")
    
    write_requirements()  # locally
    run("fab write_requirements")
    

回答 4

这是我在virtualenv本地部署中使用的方法。

使用fabric的path()上下文管理器,您可以运行virtualenv pippython使用virtualenv中的二进制文件。

from fabric.api import lcd, local, path

project_dir = '/www/my_project/sms/'
env_bin_dir = project_dir + '../env/bin/'

def deploy():
    with lcd(project_dir):
        local('git pull origin')
        local('git checkout -f')
        with path(env_bin_dir, behavior='prepend'):
            local('pip freeze')
            local('pip install -r requirements/staging.txt')
            local('./manage.py migrate') # Django related

            # Note: previous line is the same as:
            local('python manage.py migrate')

            # Using next line, you can make sure that python 
            # from virtualenv directory is used:
            local('which python')

This is my approach on using virtualenv with local deployments.

Using fabric’s path() context manager you can run pip or python with binaries from virtualenv.

from fabric.api import lcd, local, path

project_dir = '/www/my_project/sms/'
env_bin_dir = project_dir + '../env/bin/'

def deploy():
    with lcd(project_dir):
        local('git pull origin')
        local('git checkout -f')
        with path(env_bin_dir, behavior='prepend'):
            local('pip freeze')
            local('pip install -r requirements/staging.txt')
            local('./manage.py migrate') # Django related

            # Note: previous line is the same as:
            local('python manage.py migrate')

            # Using next line, you can make sure that python 
            # from virtualenv directory is used:
            local('which python')

回答 5

感谢发布的所有答案,我想为此添加另一种替代方法。有一个模块fabric-virtualenv,可以提供与相同代码相同的功能:

>>> from fabvenv import virtualenv
>>> with virtualenv('/home/me/venv/'):
...     run('python foo')

fabric-virtualenv使用fabric.context_managers.prefix,这可能是一个好方法:)

Thanks to all answers posted and I would like to add one more alternative for this. There is an module, fabric-virtualenv, which can provide the function as the same code:

>>> from fabvenv import virtualenv
>>> with virtualenv('/home/me/venv/'):
...     run('python foo')

fabric-virtualenv makes use of fabric.context_managers.prefix, which might be a good way :)


回答 6

如果您想将软件包安装到环境中,或者要根据环境中的软件包运行命令,我发现此技巧可以解决我的问题,而不是编写复杂的Fabric方法或安装新的OS软件包:

/path/to/virtualenv/bin/python manage.py migrate/runserver/makemigrations  # for running commands under virtualenv

local("/home/user/env/bin/python manage.py migrate")    # fabric command


/path/to/virtualenv/bin/pip install -r requirements.txt   # installing/upgrading virtualenv

local("/home/user/env/bin/pip install -r requirements.txt")  #  fabric command

这样,您可能不需要激活环境,但是可以在该环境下执行命令。

If you want to install the packages to environment or want to run commands according to the packages you have in environment, I have found this hack to solve my problem, instead of writing complex methods of fabric or installing new OS packages:

/path/to/virtualenv/bin/python manage.py migrate/runserver/makemigrations  # for running commands under virtualenv

local("/home/user/env/bin/python manage.py migrate")    # fabric command


/path/to/virtualenv/bin/pip install -r requirements.txt   # installing/upgrading virtualenv

local("/home/user/env/bin/pip install -r requirements.txt")  #  fabric command

This way you might not need to activate the environment, but you can execute commands under the environment.


回答 7

以下是装饰器的代码,该代码将导致对任何运行/ sudo调用使用虚拟环境:

# This is the bash code to update the $PATH as activate does
UPDATE_PYTHON_PATH = r'PATH="{}:$PATH"'.format(VIRTUAL_ENV_BIN_DIR)

def with_venv(func, *args, **kwargs):
  "Use Virtual Environment for the command"

  def wrapped(*args, **kwargs):
    with prefix(UPDATE_PYTHON_PATH):
      return func(*args, **kwargs)

  wrapped.__name__ = func.__name__
  wrapped.__doc__ = func.__doc__
  return wrapped

然后要使用装饰器,请注意装饰器的顺序很重要:

@task
@with_venv
def which_python():
  "Gets which python is being used"
  run("which python")

Here is code for a decorator that will result in the use of Virtual Environment for any run/sudo calls:

# This is the bash code to update the $PATH as activate does
UPDATE_PYTHON_PATH = r'PATH="{}:$PATH"'.format(VIRTUAL_ENV_BIN_DIR)

def with_venv(func, *args, **kwargs):
  "Use Virtual Environment for the command"

  def wrapped(*args, **kwargs):
    with prefix(UPDATE_PYTHON_PATH):
      return func(*args, **kwargs)

  wrapped.__name__ = func.__name__
  wrapped.__doc__ = func.__doc__
  return wrapped

and then to use the decorator, note the order of the decorators is important:

@task
@with_venv
def which_python():
  "Gets which python is being used"
  run("which python")

回答 8

这种方法对我有用,您也可以应用。

from fabric.api import run 
# ... other code...
def install_pip_requirements():
    run("/bin/bash -l -c 'source venv/bin/activate' "
        "&& pip install -r requirements.txt "
        "&& /bin/bash -l -c 'deactivate'")

假定venv您的虚拟环境目录,并在适当的地方添加此方法。

This approach worked for me, you can apply this too.

from fabric.api import run 
# ... other code...
def install_pip_requirements():
    run("/bin/bash -l -c 'source venv/bin/activate' "
        "&& pip install -r requirements.txt "
        "&& /bin/bash -l -c 'deactivate'")

Assuming venv is your virtual env directory and add this method wherever appropriate.


将参数传递给结构任务

问题:将参数传递给结构任务

从命令行调用“ fab”时,如何将参数传递给Fabric任务?例如:

def task(something=''):
    print "You said %s" % something
$ fab task "hello"
You said hello

Done.

是否可以在没有提示的情况下执行此操作fabric.operations.prompt

How can I pass a parameter to a fabric task when calling “fab” from the command line? For example:

def task(something=''):
    print "You said %s" % something
$ fab task "hello"
You said hello

Done.

Is it possible to do this without prompting with fabric.operations.prompt?


回答 0

Fabric 2任务参数文档:

http://docs.pyinvoke.org/zh_CN/latest/concepts/invoking-tasks.html#task-command-line-arguments


Fabric 1.X使用以下语法将参数传递给任务:

 fab task:'hello world'
 fab task:something='hello'
 fab task:foo=99,bar=True
 fab task:foo,bar

您可以在Fabric文档中阅读有关它的更多信息。

Fabric 2 task arguments documentation:

http://docs.pyinvoke.org/en/latest/concepts/invoking-tasks.html#task-command-line-arguments


Fabric 1.X uses the following syntax for passing arguments to tasks:

 fab task:'hello world'
 fab task:something='hello'
 fab task:foo=99,bar=True
 fab task:foo,bar

You can read more about it in Fabric docs.


回答 1

结构参数是通过非常基本的字符串解析来理解的,因此您在发送它们时必须要小心一点。

以下是将参数传递给以下测试函数的几种不同方式的示例:

@task
def test(*args, **kwargs):
    print("args:", args)
    print("named args:", kwargs)

$ fab "test:hello world"
('args:', ('hello world',))
('named args:', {})

$ fab "test:hello,world"
('args:', ('hello', 'world'))
('named args:', {})

$ fab "test:message=hello world"
('args:', ())
('named args:', {'message': 'hello world'})

$ fab "test:message=message \= hello\, world"
('args:', ())
('named args:', {'message': 'message = hello, world'})

我在这里使用双引号将外壳排除在等式之外,但对于某些平台,单引号可能更好。还要注意Fabric认为是定界符的字符的转义符。

docs中有更多详细信息:http : //docs.fabfile.org/en/1.14/usage/fab.html#per-task-arguments

Fabric arguments are understood with very basic string parsing, so you have to be a bit careful with how you send them.

Here are a few examples of different ways to pass arguments to the following test function:

@task
def test(*args, **kwargs):
    print("args:", args)
    print("named args:", kwargs)

$ fab "test:hello world"
('args:', ('hello world',))
('named args:', {})

$ fab "test:hello,world"
('args:', ('hello', 'world'))
('named args:', {})

$ fab "test:message=hello world"
('args:', ())
('named args:', {'message': 'hello world'})

$ fab "test:message=message \= hello\, world"
('args:', ())
('named args:', {'message': 'message = hello, world'})

I use double quote here to take the shell out of the equation, but single quotes may be better for some platforms. Also note the escapes for characters that fabric considers delimiters.

More details in the docs: http://docs.fabfile.org/en/1.14/usage/fab.html#per-task-arguments


回答 2

在Fabric 2中,只需将参数添加到任务函数即可。例如,将version参数传递给task deploy

@task
def deploy(context, version):
    ...

如下运行:

fab -H host deploy --version v1.2.3

Fabric甚至自动记录选项:

$ fab --help deploy
Usage: fab [--core-opts] deploy [--options] [other tasks here ...]

Docstring:
  none

Options:
  -v STRING, --version=STRING

In Fabric 2, simply add the argument to your task function. For example, to pass the version argument to task deploy:

@task
def deploy(context, version):
    ...

Run it as follows:

fab -H host deploy --version v1.2.3

Fabric even documents the options automatically:

$ fab --help deploy
Usage: fab [--core-opts] deploy [--options] [other tasks here ...]

Docstring:
  none

Options:
  -v STRING, --version=STRING

回答 3

您需要将所有Python变量作为字符串传递,尤其是在使用子进程来运行脚本时,否则会出错。您将需要分别将变量转换回int / boolean类型。

def print_this(var):
    print str(var)

fab print_this:'hello world'
fab print_this='hello'
fab print_this:'99'
fab print_this='True'

You need to pass all Python variables as strings, especially if you are using sub-process to run the scripts, or you will get an error. You will need to convert the variables back to int/boolean types separately.

def print_this(var):
    print str(var)

fab print_this:'hello world'
fab print_this='hello'
fab print_this:'99'
fab print_this='True'

回答 4

如果有人希望将参数从一个任务传递给fabric2中的另一个任务,则只需使用环境字典即可:

@task
def qa(ctx):
  ctx.config.run.env['counter'] = 22
  ctx.config.run.env['conn'] = Connection('qa_host')

@task
def sign(ctx):
  print(ctx.config.run.env['counter'])
  conn = ctx.config.run.env['conn']
  conn.run('touch mike_was_here.txt')

并运行:

fab2 qa sign

If someone is looking to pass parameters from one task to another in fabric2, just use the environment dictionary for that:

@task
def qa(ctx):
  ctx.config.run.env['counter'] = 22
  ctx.config.run.env['conn'] = Connection('qa_host')

@task
def sign(ctx):
  print(ctx.config.run.env['counter'])
  conn = ctx.config.run.env['conn']
  conn.run('touch mike_was_here.txt')

And run:

fab2 qa sign

如何在Fabric文件中设置目标主机

问题:如何在Fabric文件中设置目标主机

我想使用Fabric将我的Web应用程序代码部署到开发,登台和生产服务器。我的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

样本输出:

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

当我创建Fabric文档中set_hosts()所示的任务时,env.hosts设置正确。但是,这不是一个可行的选择,装饰器也不是。在命令行上传递主机最终会导致某种形式的shell脚本调用fabfile,我更愿意使用一个工具来正确完成这项工作。

它在Fabric文档中说“ env.hosts仅仅是Python列表对象”。根据我的观察,这根本不是事实。

谁能解释这是怎么回事?如何设置要部署到的主机?

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 ?


回答 0

我通过声明每个环境的实际功能来做到这一点。例如:

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

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

I do this by declaring an actual function for each environment. For example:

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

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

def deploy():
    ...

Using the above functions, I would type the following to deploy to my test environment:

fab test deploy

…and the following to deploy to production:

fab prod deploy

The nice thing about doing it this way is that the test and prod functions can be used before any fab function, not just deploy. It is incredibly useful.


回答 1

使用roledefs

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'
...

Use roledefs

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')

Choose role with -R:

$ fab -R test deploy
[localhost] Executing task 'deploy'
...

回答 2

这是serverhorror答案的简单版本:

from fabric.api import settings

def mystuff():
    with settings(host_string='192.0.2.78'):
        run("hostname -f")

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")

回答 3

自己被卡住了,但终于想通了。你根本无法从设置env.hosts配置的任务。每个任务执行N次,对指定的每个Host执行一次,因此该设置基本上不在任务范围之内。

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

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

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

def deploy():
    # do stuff...

看起来它可以满足您的预期。

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

Was stuck on this myself, but finally figured it out. You simply can’t set the env.hosts configuration from within a task. Each task is executed N times, once for each Host specified, so the setting is fundamentally outside of task scope.

Looking at your code above, you could simply do this:

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

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

def deploy():
    # do stuff...

Which seems like it would do what you’re intending.

Or you can write some custom code in the global scope that parses the arguments manually, and sets env.hosts before your task function is defined. For a few reasons, that’s actually how I’ve set mine up.


回答 4

从fab 1.5开始,这是动态设置主机的记录方法。

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

引用下面的文档。

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

Fabric常见的中级到高级用例是在运行时对目标主机列表的参数化(当使用Roles不足时)。execute可以使这一过程变得非常简单,如下所示:

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)

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)

回答 5

相反,一些其他的答案,它可以修改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设置或从fabCLI 传递的任何内容。

Contrary to some other answers, it is possible to modify the env environment variables within a task. However, this env will only be used for subsequent tasks executed using the fabric.tasks.execute function.

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

Without wrapping sub-tasks in execute(...), your module-level env settings or whatever is passed from the fab CLI will be used.


回答 6

您需要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, ))

You need to set host_string an example would be:

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, ))

回答 7

解释为什么它甚至是一个问题。fab命令利用Fabric库来在主机列表上运行任务。如果尝试更改任务中的主机列表,则本质上是在迭代列表时尝试更改列表。或者,在没有定义主机的情况下,请在一个空列表上循环,而在该空列表中,您设置要循环的列表的代码将永远不会执行。

使用env.host_string只能通过直接向函数指定要连接的主机来解决此问题。这会导致一些问题,如果您要在其上执行许多主机,则会重新构建执行循环。

人们能够在运行时设置主机的最简单方法是,使env成为一个独立的任务,即设置所有主机字符串,用户等。然后他们运行部署任务。看起来像这样:

fab production deploy

要么

fab staging deploy

暂存和生产就像您已完成的任务一样,但是它们本身不会调用下一个任务。之所以必须这样工作,是因为该任务必须完成并退出循环(对于主机,在env情况下为None,但此时是一个循环),然后使循环结束主机(现在由前面的任务定义)。

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.


回答 8

您需要在模块级别而不是在任务功能内修改env.hosts。我犯了同样的错误。

from fabric.api import *

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

env.hosts = _get_hosts()

def your_task():
    ... your task ...

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 ...

回答 9

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

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

It’s very simple. Just initialize the env.host_string variable and all of the following commands will be executed on this host.

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

回答 10

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

fab -H staging-server,production-server deploy 

在那里登台服务器生产服务器有2台服务器要运行对部署行动。这是一个简单的fabfile.py,它将显示操作系统名称。请注意,fabfile.py应该与运行fab命令的目录位于同一目录中。

from fabric.api import *

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

至少适用于结构1.8.1。

I’m totally new to fabric, but to get fabric to run the same commands on multiple hosts (e.g. to deploy to multiple servers, in one command) you can run:

fab -H staging-server,production-server deploy 

where staging-server and production-server are 2 servers you want to run the deploy action against. Here’s a simple fabfile.py that will display the OS name. Note that the fabfile.py should be in the same directory as where you run the fab command.

from fabric.api import *

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

This works with fabric 1.8.1 at least.


回答 11

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

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。

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.


回答 12

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

不幸的是,对于您和我来说,织物不是为此用例设计的。mainhttp://github.com/bitprophet/fabric/blob/master/fabric/main.py上查看该功能,以了解其工作原理。

You can assign to env.hoststring before executing a subtask. Assign to this global variable in a loop if you want to iterate over multiple hosts.

Unfortunately for you and me, fabric is not designed for this use case. Check out the main function at http://github.com/bitprophet/fabric/blob/master/fabric/main.py to see how it works.


回答 13

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

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

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

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

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

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

回答 14

当前,使用角色被认为是做到这一点的“正确”和“正确”的方式,这是您“应该”做到的。

就是说,如果您像大多数“想要”或“期望”一样,就是能够执行“扭曲系统”或即时切换目标系统的能力。

因此,仅出于娱乐目的(!),以下示例说明了许多人可能认为是危险的,但又能以某种方式彻底满足要求的操作,如下所示:

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

Using roles is currently considered to be the “proper” and “correct” way of doing this and is what you “should” do it.

That said, if you are like most of what you “would like” or “desire” is the ability to perform a “twisted syster” or switching target systems on the fly.

So for entertainment purposes only (!) the following example illustrates what many might consider to a risky, and yet somehow thoroughly satisfying, manoeuvre that goes something like this:

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")

Then running:

fab perform_sumersault

在Fabric中使用SSH密钥文件

问题:在Fabric中使用SSH密钥文件

如何配置结构以使用SSH密钥文件连接到远程主机(例如,Amazon EC2实例)?

How do you configure fabric to connect to remote hosts using SSH keyfiles (for example, Amazon EC2 instances)?


回答 0

在这里还值得一提的是,您可以为此使用命令行参数:

fab command -i /path/to/key.pem [-H [user@]host[:port]]

Also worth mentioning here that you can use the command line args for this:

fab command -i /path/to/key.pem [-H [user@]host[:port]]

回答 1

由于某种原因,要找到一个简单的fabfile以及一个使用SSH密钥文件的有效示例并不容易。我写了一篇有关它的博客文章带有匹配的要点)。

基本上,用法是这样的:

from fabric.api import *

env.hosts = ['host.name.com']
env.user = 'user'
env.key_filename = '/path/to/keyfile.pem'

def local_uname():
    local('uname -a')

def remote_uname():
    run('uname -a')

重要的是设置env.key_filename环境变量,以便Paramiko配置在连接时可以查找它。

Finding a simple fabfile with a working example of SSH keyfile usage isn’t easy for some reason. I wrote a blog post about it (with a matching gist).

Basically, the usage goes something like this:

from fabric.api import *

env.hosts = ['host.name.com']
env.user = 'user'
env.key_filename = '/path/to/keyfile.pem'

def local_uname():
    local('uname -a')

def remote_uname():
    run('uname -a')

The important part is setting the env.key_filename environment variable, so that the Paramiko configuration can look for it when connecting.


回答 2

从Fabric 1.4开始提供的另一个很酷的功能-Fabric现在支持SSH配置

如果您的文件中已经包含所有SSH连接参数~/.ssh/config,Fabric将本地支持它,您只需添加:

env.use_ssh_config = True

在fabfile的开头。

Another cool feature available as of Fabric 1.4 – Fabric now supports SSH configs.

If you already have all the SSH connection parameters in your ~/.ssh/config file, Fabric will natively support it, all you need to do is add:

env.use_ssh_config = True

at the beginning of your fabfile.


回答 3

对于fabfile中的fabric2,请使用以下命令:

from fabric import task, Connection

@task
def staging(ctx):
    ctx.name = 'staging'
    ctx.user = 'ubuntu'
    ctx.host = '192.1.1.1'
    ctx.connect_kwargs.key_filename = os.environ['ENV_VAR_POINTS_TO_PRIVATE_KEY_PATH']

@task
def do_something_remote(ctx):
    with Connection(ctx.host, ctx.user, connect_kwargs=ctx.connect_kwargs) as conn:
        conn.sudo('supervisorctl status')

并运行:

fab staging do_something_remote

更新:
对于多个主机(一个主机也可以),您可以使用以下命令:

from fabric2 import task, SerialGroup

@task
def staging(ctx):
    conns = SerialGroup(
        'user@10.0.0.1',
        'user@10.0.0.2',
        connect_kwargs=
        {
            'key_filename': os.environ['PRIVATE_KEY_TO_HOST']
        })
    ctx.CONNS = conns
    ctx.APP_SERVICE_NAME = 'google'

@task
def stop(ctx):
    for conn in ctx.CONNS:
        conn.sudo('supervisorctl stop ' + ctx.APP_SERVICE_NAME)

并使用fab或fab2运行它:

fab staging stop

For fabric2 in fabfile use the following:

from fabric import task, Connection

@task
def staging(ctx):
    ctx.name = 'staging'
    ctx.user = 'ubuntu'
    ctx.host = '192.1.1.1'
    ctx.connect_kwargs.key_filename = os.environ['ENV_VAR_POINTS_TO_PRIVATE_KEY_PATH']

@task
def do_something_remote(ctx):
    with Connection(ctx.host, ctx.user, connect_kwargs=ctx.connect_kwargs) as conn:
        conn.sudo('supervisorctl status')

and run it with:

fab staging do_something_remote

UPDATE:
For multiple hosts (one host will do also) you can use this:

from fabric2 import task, SerialGroup

@task
def staging(ctx):
    conns = SerialGroup(
        'user@10.0.0.1',
        'user@10.0.0.2',
        connect_kwargs=
        {
            'key_filename': os.environ['PRIVATE_KEY_TO_HOST']
        })
    ctx.CONNS = conns
    ctx.APP_SERVICE_NAME = 'google'

@task
def stop(ctx):
    for conn in ctx.CONNS:
        conn.sudo('supervisorctl stop ' + ctx.APP_SERVICE_NAME)

and run it with fab or fab2:

fab staging stop

回答 4

对我来说,以下方法无效:

env.user=["ubuntu"]
env.key_filename=['keyfile.pem']
env.hosts=["xxx-xx-xxx-xxx.ap-southeast-1.compute.amazonaws.com"]

要么

fab command -i /path/to/key.pem [-H [user@]host[:port]]

但是,执行以下操作:

env.key_filename=['keyfile.pem']
env.hosts=["ubuntu@xxx-xx-xxx-xxx-southeast-1.compute.amazonaws.com"]

要么

env.key_filename=['keyfileq.pem']
env.host_string="ubuntu@xxx-xx-xxx-xxx.ap-southeast-1.compute.amazonaws.com"

For me, the following didn’t work:

env.user=["ubuntu"]
env.key_filename=['keyfile.pem']
env.hosts=["xxx-xx-xxx-xxx.ap-southeast-1.compute.amazonaws.com"]

or

fab command -i /path/to/key.pem [-H [user@]host[:port]]

However, the following did:

env.key_filename=['keyfile.pem']
env.hosts=["ubuntu@xxx-xx-xxx-xxx-southeast-1.compute.amazonaws.com"]

or

env.key_filename=['keyfileq.pem']
env.host_string="ubuntu@xxx-xx-xxx-xxx.ap-southeast-1.compute.amazonaws.com"

回答 5

我今天必须这样做,我的.py文件尽可能简单,就像在@YuvalAdam答案中发布的文件一样,但仍然不断提示输入密码…

查看paramiko(fabric用于ssh的库)日志,发现以下行:

不兼容的SSH节点(没有可接受的KEX算法)

我更新paramiko了:

sudo pip install paramiko --upgrade

现在它正在工作。

I had to do this today, my .py file was as simple as possible, like the one posted in the answer of @YuvalAdam but still I kept getting prompted for a password…

Looking at the paramiko (the library used by fabric for ssh) log, I found the line:

Incompatible ssh peer (no acceptable kex algorithm)

I updated paramiko with:

sudo pip install paramiko --upgrade

And now it’s working.


回答 6

如上所述,Fabric将在某种形式之后支持.ssh / config文件设置,但是将pem文件用于ec2似乎是有问题的。IOW一个正确设置的.ssh / config文件将通过“ ssh服务器名”从命令行运行,并且当env.host = [‘服务器名’]时无法与“ fab sometask”一起工作。

通过在我的fabfile.py中指定env.key_filename =’keyfile’并复制我的.ssh / config中已经存在的IdentityFile条目,可以解决此问题。

这可以是Fabric或paramiko,在我的情况下是Fabric 1.5.3和Paramiko 1.9.0。

As stated above, Fabric will support .ssh/config file settings after a fashion, but using a pem file for ec2 seems to be problematic. IOW a properly setup .ssh/config file will work from the command line via ‘ssh servername’ and fail to work with ‘fab sometask’ when env.host=[‘servername’].

This was overcome by specifying the env.key_filename=’keyfile’ in my fabfile.py and duplicating the IdentityFile entry already in my .ssh/config.

This could be either Fabric or paramiko, which in my case was Fabric 1.5.3 and Paramiko 1.9.0.


回答 7

这些答案对py3.7,fabric2.5.0和paramiko 2.7.1都不起作用。

但是,使用文档中的PKey属性确实可以:http ://docs.fabfile.org/en/2.5/concepts/authentication.html#private-key-objects

from paramiko import RSAKey
ctx.connect_kwargs.pkey = RSAKey.from_private_key_file('path_to_your_aws_key')
with Connection(ctx.host, user, connect_kwargs=ctx.connect_kwargs) as conn:
    //etc.... 

None of these answers worked for me on py3.7, fabric2.5.0 and paramiko 2.7.1.

However, using the PKey attribute in the documentation does work: http://docs.fabfile.org/en/2.5/concepts/authentication.html#private-key-objects

from paramiko import RSAKey
ctx.connect_kwargs.pkey = RSAKey.from_private_key_file('path_to_your_aws_key')
with Connection(ctx.host, user, connect_kwargs=ctx.connect_kwargs) as conn:
    //etc....