请确保您首先阅读了这篇博文及其所有链接!: http: //bitprophet.org/blog/2017/04/17/fabric-2-alpha-beta
在这里可以就任何现有票证未涵盖的问题留下反馈; 请先搜索以下地方!!
没有找到任何相关的东西? 在下面发表评论! 我正在寻找反馈_类似但不限于_:
去展示我对自己网站的记忆有多好,嗯? 谢谢! 我什至在我的帖子上运行了一个链接检查器,但是……不反对这张票;)
我有一个关于如何以我认为符合结构 2 理念的方式实施结构 1 的角色定义系统的建议。 每个Collection
任务还可以在同一命名空间中具有@group
修饰函数,这些函数返回可以运行任务的填充Group
对象。 命名空间很重要,所以如果fabfile.py
包含一个名为deploy
的 $#$ Collection
$#$ ,他的@group
名为web
和db
,可以使用deploy.web.execute(mytask)
或fab -G deploy.web mytask
在web
组中的每个主机上执行mytask
。 如果主机列表查找是用户实现的缓慢操作,这些修饰函数将被延迟调用并记忆以防止不必要的 API 调用。
这是否符合 Fabric 2 的设计理念? 如果是这样,我很想在实施中有所作为。
这是一个好主意,@RedKrieg! 我一直在推迟我自己的头脑风暴,以了解如何“最好”地生成 Group 对象和/或如何在 CLI 上引用它们,但这听起来像是一种合理的方法。 我刚刚在 #1594 中删减了太多单词,并在其中包含了您的想法(+ 链接)。 让我们在那里继续讨论,但是 tl;dr 是的,我很想看到 PoC PR。
尽管存在-H
参数,但在本地运行命令的最佳方法是什么,例如,如果我想将本地构建和 rsync 与远程主机组合成一个任务?
@max-arnold Connection 对象有一个.local
属性,在本地机器上的行为类似于.run
:http: //docs.fabfile.org/en/v2/api/connection.html#fabric .connection.Connection.local
好吧,我想一个例子比文字更好:
<strong i="6">@task</strong>
def build(ctx):
# should always run locally
ctx.local('uname -a')
<strong i="7">@task</strong>
def deploy(ctx):
build(ctx)
# this one should run on remote host
ctx.run('uname -a')
组合任务运行良好:
fab -H host deploy
仅本地任务因 AttributeError 而失败:没有为“本地”找到属性或配置键:
fab build
基本上我想要一个在本地做某事的任务,不管它是如何被调用的(有或没有-H
)。
另一方面,某些命令仅用于远程运行。 如果没有主机, ctx.run
将尝试在本地运行它们,这可能会导致意想不到的后果。
| 用户运行任务 | 命令/任务作者希望它运行 | 它应该如何表现? |
|---------------------|-------------------------- ---|----------------|
| 本地 | 本地 | run() 行为很好(但如果远程运行会违反作者的意图)|
| 本地 | 远程 | 它应该失败(或像旧结构那样要求主机字符串)|
|本地 | 本地或远程| run() 行为很好 |
| 远程 | 本地 | 它应该始终在本地运行,但 Context 没有 local() 方法来确保它 |
| 远程 | 远程 | run() 行为很好(但在本地调用期间会出现问题)|
| 远程 | 本地或远程| run() 行为很好 |
@max-arnold 正好是#98! 我实际上还没有完全解决。 将一些现代思想作为评论放在那里...编辑:这个
我注意到似乎与 sudo 不一致的地方。
其中connection
是远程服务器,以 root 身份验证。
尝试将波浪号扩展为 root 以外的其他用户:
c.sudo("echo bar > ~/foo", user="builder")
这成功了,但不是写 /home/builder/foo,而是写 /root/foo。
另一方面,如果我只是尝试 ls:
c.sudo("ls", user="builder")
我得到Permission denied
。
感觉有些不对劲。
当达斯汀报告上述带外情况时,我的猜测是,这是sudo
(命令,而不是方法)特定的皱纹,因为上次我看起来我们正在使用-H
并且显然它的行为不像我们预期的那样 100%。
嗨杰夫,
因为我已经在 python 3 中工作了,所以现在我正在使用fabric3
(fabric 1.x 的移植)。
我很想尝试迁移到 Fabric 2,但我立即遇到了阻碍。 我广泛使用了fabric.contrib
函数,但迁移文档说它不再存在。
当然,我不想让代码膨胀到它们的 shell 等价物,而且因为从 fabric 1.x 的contrib.*
上的问题数量来看,它看起来被使用了很多,我认为它缺乏可能是许多其他人移居的严重障碍。
我发现拼凑项目显然包含相当于fabric.contrib
,但它的代码多年来没有更新。
你有计划将fabric.contrib
移植到结构 2 吗?
谢谢,
加布里埃尔
@garu57是的,现在的计划是使用patchwork
作为基本上“2.0 的贡献”。 目前它是基于 Fabric 1 的,但在 Fabric 2.0.0 发布后会发生变化。 我希望最常用的 contrib 位能够快速移植。
大家好,
有时我的程序运行一个远程命令,例如
source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./00-update-kernel.sh'
我看到脚本是这样执行的:
033[0;32m[DONE]\033[0m'
+ return 0
+ alt_test_done_msg 'Prepare evironment'
+ echo -e '* Prepare evironment \033[0;32m[DONE]\033[0m'
+ exit 0
我还应该看到我的调试输出“?” 根据我的代码:
class GenericFabric(object):
def __init__(self, host, user, key_filename=None, port=22):
connection_string = "{u}@{h}:{p}".format(u=user, h=host, p=port)
self.connection = Connection(connection_string)
self.key_filename = key_filename
#<strong i="13">@with_settings</strong>
def generic_cmd(self, command_str, timeout, fabric_timeout, **kwargs):
"""
Creating remote container from template
<strong i="14">@type</strong> command_str: str
<strong i="15">@param</strong> command_str: command for execute with VM
<strong i="16">@type</strong> timeout int or float
<strong i="17">@param</strong> timeout number of seconds for pause
<strong i="18">@type</strong> fabric_timeout int or float
<strong i="19">@param</strong> fabric_timeout number of seconds for timeout fabric run
<strong i="20">@rtype</strong>: FabricResponse
<strong i="21">@return</strong>: Return remote status of operation VM
"""
if fabric_timeout > 0:
command = self.connection.run(command_str.format(**kwargs),
timeout=fabric_timeout,
warn=True, echo=True)
else:
command = self.connection.run(command_str.format(**kwargs),
warn=True, echo=True)
print("?")
if timeout > 0:
sleep(timeout)
return FabricResponse(command)
def simple_generic_cmd(self, command_str, **kwargs):
"""
<strong i="22">@type</strong> command_str: str
<strong i="23">@param</strong> command_str: command for execute with VM
<strong i="24">@rtype</strong>: FabricResponse
<strong i="25">@return</strong>: Return remote status of operation VM
"""
return self.generic_cmd(command_str, 0.1, 0, **kwargs)
但是没有“?”。 所以我建议有挂在run()。
实际上,我的程序很大,并且有多进程编程。 为了抓住挂起,我正在使用以下代码执行远程命令:
class TestingSystemVM(GenericFabric):
#######
<strong i="7">@signal_alarm_down</strong>
def run_rpm_test(self, command_test, package, type_of_test="base"):
"""
"""
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(self.__timeout)
returned_value = Queue()
start_time = datetime.utcnow()
directory, command_test = os.path.split(command_test)
try:
if command_test.endswith(".yml"):
directory = directory.replace("/opt/QA", self.ansible.git_qa_repo)
output = self.ansible.play_ansible(command_test,
package,
directory)
else:
vm_instance = (self.host,
self.user,
self.key_filename,
self.os_name,
self.platform,
self.arch)
running_test = Process(target=separate_process_running_test,
args=(vm_instance,
returned_value,
directory,
command_test,
package,
type_of_test))
running_test.start()
running_test.join(self.__timeout)
if running_test.is_alive():
running_test.terminate()
command_test_res = "FAIL: Timeout\n"
return command_test_res, work_time(start_time), 1
elif returned_value.empty():
command_test_res = "FAIL: Problem while getting result\n"
return command_test_res, work_time(start_time), 1
else:
output = returned_value.get()
except TimeOut:
command_test_res = "FAIL: Timeout\n"
return command_test_res, work_time(start_time), 1
if output.failed or (package.name in ("lve-utils", "lve-stats") and
"FAIL" in output.stdout):
res_output = "FAIL: " + output.stdout
else:
res_output = output.stdout
return res_output, work_time(start_time), 0 if output.succeeded else 1
def separate_process_running_test(vm_instance, return_value_queue,
directory, command_test, package,
type_of_test="base"):
"""
"""
sleep(0.5)
signal.signal(signal.SIGTERM, kill_fabric_runner)
signal.signal(signal.SIGINT, kill_fabric_runner)
(host,
user,
key_filename,
os_name,
platform,
arch) = vm_instance
child_vm_instance = TestingSystemVM(host,
user,
key_filename,
os_name,
platform,
arch,
FakeAnsible())
if command_test.endswith(".bats"):
command_test = "/usr/bin/bats --tap " + command_test
else:
command_test = os.path.join("./", command_test)
output = child_vm_instance.simple_generic_cmd(child_vm_instance._c_run_test,
envvars=child_vm_instance.env_vars,
exec_test=command_test,
dir=directory,
package=package.name,
pver=package.version,
prel=package.release,
type_test=type_of_test)
print("!")
return_value_queue.put(output)
当我使用fabric2时,从子进程启动的一些命令不时挂起。
更新。 实际上,这是我在代码中的错误。 所以,fabric2 不包含悬挂
大家好,
我做了一些启动实验,可以分享我的经验。
首先,我从控制台运行命令
ssh [email protected] -t "source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'"
并且以exit = 0完美而快速地结束:
###
+ echo -e '* Prepare evironment \033[0;32m[DONE]\033[0m'
* Prepare evironment [DONE]
+ exit 0
Connection to 192.168.0.34 closed.
好的。
然后我执行了另一个命令:
ssh [email protected] "source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'"
用 exit=0 也完成得如此之快:
###
+ echo -e '* Prepare evironment \033[0;32m[DONE]\033[0m'
+ exit 0
但是没有“与 192.168.0.36 的连接已关闭”。 不知道重要不重要。
之后,我重新创建了虚拟机并通过 fabric2(来自 IPython)启动了命令:
In [1]: from fabric import Connection
In [2]: Connection('[email protected]').run("source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'", pty=True)
....
+ echo -e '* Prepare evironment \033[0;32m[DONE]\033[0m'
* Prepare evironment [DONE]
+ exit 0
Out[2]: <Result cmd="source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'" exited=0>
另外,我使用 pty=False 运行:
In [1]: from fabric import Connection
In [2]: Connection('[email protected]').run("source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'", pty=False)
+ echo -e '* Prepare evironment \033[0;32m[DONE]\033[0m'
+ exit 0
Out[2]: <Result cmd="source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'" exited=0>
没有“连接到
其次,我认为这对我的立场来说不是一个好的环境,因为我需要从子进程启动。 因此,我再次重新创建了虚拟机并使用以下代码运行脚本:
In [1]: import subprocess
In [2]: t = subprocess.Popen(""" ssh [email protected] -t "source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'" """, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True); r = t.communicate()
在此运行的输出中有“与 192.168.0.34 的连接已关闭”。
另一方面
In [1]: import subprocess
In [2]: t = subprocess.Popen(""" ssh [email protected] "source /opt/centos/vars && su - -c 'cd /opt/QA/rpm_tests/p_lvemanager && ALT_TEST=base ALT_PACKAGE_NAME=lvemanager ALT_PACKAGE_VERSION=3.0 ALT_PACKAGE_RELEASE=11.el6.cloudlinux.21100.1.1505113712 ./01-prepare-environment.sh'" """, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True); r = t.communicate()
两人都没有上吊。
今晚我将在分叉的进程上使用fabric2 启动我的脚本,然后我会写。
更新。 实际上,这是我在代码中的错误。 因此,fabric2 不包含悬挂。
获得use_sudo
对Transfer.put
$ 的支持会很好。
我想现在最简单的解决方法就是在我有权限的地方做一个put
然后一个sudo mv
?
@ned2这就是use_sudo
可以在get
/ put
$ 中真正做到的一切,例如 v1 就是这样做的。 据我所知,实际上不可能“使用 sudo”上传文件! (如果没有以 root 身份连接,这是一个坏主意,理想情况下甚至是不允许的。)
鉴于尝试在 v2 中使用更简洁的 API,我几乎更有可能实现一个包装器,而不是将该行为捆绑在get
/ put
本身中。 也许只是像Transfer.sudo_put
(这将称为self.put
)。 这样,“纯” put
就保持在最低限度。
@bitprophet 首先,我要感谢您使用 Fabric1 提供的一些非常稳定且有用的软件以及您发布 Fabric2 的方法。
我已经开始使用 v2,但我遇到了 Connection 的问题。 我正在使用 Fabric 1 中的 SSH 配置,在尝试连接时,Fabric 1 会提示我输入密码。 Fabric 2 没有这样做,并且失败,表明身份验证失败。 我在 v2 文档中看不到任何提到 Connection 的身份验证机制的地方。 提到了 sudo 的密码,但据我所知,这是在连接成功之后..
我的问题是我是否遗漏了文档中的某些内容,或者这是否是缺少的功能或问题。
代码:
<strong i="10">@task</strong>
def testing(c):
with Connection('MyHostname') as cxn:
print("Connected")
cxn.run('ls -l')
错误:
File /lib/python3.4/site-packages/paramiko/auth_handler.py", line 223, in wait_for_response
raise e
paramiko.ssh_exception.AuthenticationException: Authentication failed.
我的 SSH 配置(替换值在此处发布):
Host MyHostname
HostName replacedhostname.co.uk
Port 22
IdentityFile ~/.ssh/id_rsa
User replaceduser
我使用的是最新的 v2 版本: https ://github.com/fabric/fabric/commit/fec3a22ee89900500ae731913fd33f9b56e89f46
希望解决这个问题可以帮助其他有同样问题的人。
感谢您的时间。
@Aiky30可能不是你,auth 的东西仍然需要工作(很大程度上是因为需要一些正在进行的 Paramiko 工作,所以正确解释 auth 异常并不难。)我认为在这种情况下它可能只是 Fab 2 没有解释IdentityFile
- 我们获取主机、用户、端口和一堆其他设置,但 IdentityFile 仍然是一个 TODO。
这部分是因为我们现在也不处理密码短语(见上文 - 如果给定的身份验证失败意味着需要密码或密码短语,我们必须进行疯狂的猜测,以避免出现大的 Fabric 1 混乱)。 因此,在那之后您可能只是过渡到“我无法解锁此文件”错误。 这两者都是因为我大部分时间都使用 ssh-agent - 这将是我立即解决问题的第一个建议。
也就是说,我认为在 IdentityFile 和显式密码配置支持方面可能值得我破解,因为非代理密钥可能是最常见的 1 号身份验证设置,因此缺少它可能意味着大多数 alpha/beta 用户陷入困境. 看看我今天能不能把它弄出来。
花了 2.0a 转了一圈。
1)我对如何实际定义和使用主机也有点迷茫。 我可以跑:
fab -H user<strong i="7">@host</strong>:22 some-task
但是如果我不想每次都传递主机详细信息(用户、主机、端口),我不确定如何在fabfile.py
( Connection
s 或Group
) 然后只是参考它们。 如果我正确理解了https://github.com/fabric/fabric/issues/1591#issuecomment -296343613,那么它目前不受支持(并在 https://github.com/fabric/fabric/issues/1594 中进行了跟踪)?
2)另外,我遇到了加密 SSH 密钥(在~/.ssh/id_rsa
处)的问题,并且我有ssh-add
'd 到ssh-agent
。 如果我通过ssh-add -l
列出密钥,它就会显示出来。 当我运行一些任务时,给出正确的用户名:
fab -H musttu<strong i="20">@host</strong> sometask
一切正常。 但是,如果我使用不正确的用户,例如fab -H bad_user<strong i="23">@host</strong> sometask
,那么我会得到:
Traceback (most recent call last):
...
File "/home/maximus/.virtualenvs/testenv/lib/python3.6/site-packages/paramiko/pkey.py", line 326, in _read_private_key
raise PasswordRequiredException('Private key file is encrypted')
paramiko.ssh_exception.PasswordRequiredException: Private key file is encrypted
但我认为这属于https://github.com/paramiko/paramiko/issues/387并且并非真正特定于 Fabric 2.0。 但是,是的,当 ssh-agent 已经拥有解密的密钥时,得到PasswordRequiredException: Private key file is encrypted
错误确实感觉很奇怪。 如果我理解正确,在无效登录后, paramiko
将回退到直接使用密钥,并且没有提供密码将引发此错误。
3) 允许类型注释任务函数会很好(以便在 IDE 中更好地自动完成)。 您有时间考虑https://github.com/pyinvoke/invoke/pull/458吗?
4)我喜欢拼凑库的想法。 对于fabric 1.x,有https://github.com/sebastien/cuisine (看起来很死),其中包含许多额外的功能(我自己没有使用过)。 拼凑的想法是构建类似的东西(所以 Chef/Ansible/SaltStack 风格的声明性函数,尽管范围缩小了)? 我一直讨厌其他工具遵循的多目录/多文件 YAML/DSL 方法,并且只想留在 python 中,以获得更大的灵活性、更少冗长的语法、IDE 自动完成、易于调试等。
5) 只需放弃对 fabric 2.0 的 py 2.6 和 3.2-3.3 支持。 人们不应该继续前进吗?
@tuukkamustonen - 感谢您的反馈! 回应:
#1594 确实涵盖了角色 - 我认为除了ssh_config
支持在其自身级别上如何做到这一点之外,还有另一张票可以解决仅拥有每个主机配置数据的相关问题。
决定如何准确地协调结构级配置与ssh_config
以及来自组或运行时数据的任何内容......并非易事。 虽然它肯定需要尽快发生; 我只想有一个半考虑的解决方案,而不是一个初稿。
再次,是的,这是您提到的 Paramiko 票证,您的猜测是准确的,错误用户名的情况最终会失败,并且由于 Paramiko 只跟踪它遇到的最后一个错误,它恰好是它尝试的最后一件事是密钥的磁盘上加密副本。 (例如,如果您将该键移到别处,最后一个错误将是其他错误。)
我刚刚评论了那张票,它可能(或可能不会)复制现有票证以获得相同的整体功能。 我评论过那些较旧的,IIRC 是“是的,我认为只要它不破坏 Python 2 就很好。” 但是,它的优先级不如大多数较大的缺失功能,因此它属于“需要一个很棒的,所有框都检查了我可以合并的 PR”桶:)
是的,请参阅#461,它非常古老但仍在我脑海中。 这些天来最大的问题是如何编写这样的库以跨越 Invoke 和 Fabric; 在许多/大多数情况下,可以简单地编写不“知道”任何拆分本地/远程上下文的通用调用任务,当人们想要针对远程系统运行它们时,可以将其交给 Fabric 连接上下文。
这种“与上下文无关”的任务会希望存在于“调用”库(或其他一些仍然特定于 Invoke 的库)中; 但是有些(例如任何涉及文件传输而不仅仅是shell命令的东西)需要“结构感知”。 弄清楚如何弥合这一差距是问题所在。
可以走“那个lib只需要调用,不需要fabric,_但是_如果你不安装fabric并给他们一个连接上下文,一部分任务会抱怨”的路线。 可以分成两个库(面向 Fabric 的库需要更通用的 Invoke 库)等等。
最近,这肯定是有可能的,请参阅 paramiko/paramiko#1070 和/或 pyinvoke/invoke#364。 Invoke 1.0、Fabric 2.0 和 Paramiko 3.0(或者,_maybe_,2.4/2.5/whatever)都将是 Python 2.7 / 3.4 及更高版本。
关于(1):
我确定您已经知道这一点,但您可能想从Ansible 的清单中获取一些想法。 或者可能不是 :)。
关于(4):
我认为https://github.com/pyinvoke/invocations是一种固执己见的方式。 Invocations 提供了 _conventions_ 并且可以构建在 patchwork/cuisine-style 包之上,但我不会将约定(如何发布或 doctest)与实用程序(如何复制文件或更改权限)混合。
我认为将仅用于本地、仅远程和本地+远程操作的实用程序放入同一个库(拼凑)中没有问题。 哪些操作支持哪种模式可以作为文档处理(作为文档字符串,从注释自动生成等)。
这比将逻辑拆分为 2 个以上的包感觉更好。 因为如果您首先将操作添加到“仅调用”程序包(甚至可能存在“仅远程”操作),然后添加对它的支持以使其在远程服务器上也能工作,该怎么办? 你会移动、复制或扩展代码吗? 无论哪种情况,它都需要更多的工作,并且用户需要不同的导入等,所以听起来有点麻烦。
我该怎么做才能让这个足够完善以继续使用 PyPi?
@haydenflinner这将在接下来的一两周内发生! (目标是在我乘飞机前往 PyCon 之前发布,也就是 5 月 10 日。)
例如,请参阅我这周刚刚更新的升级文档:http: //docs.fabfile.org/en/v2/upgrading.html
事实上,我现在也可以关闭这张票,因为我很快就会接受 2.0.0 及更高版本的真实票👍
请注意,如果您愿意,大家仍然可以在这里发表评论。 请注意,我希望在发布之前至少完成几块功能工作,以及所有项目管理准备工作。
我今天看了fabric v2,很抱歉,我不能让自己使用它。
任务确实需要参数,但语法发生了变化。 请参阅http://docs.pyinvoke.org/en/1.1/concepts/invoking-tasks.html#task -command-line-arguments
@dgarstang你可能想考虑对那些给你免费劳动力的人采取更中立或同情的语气! 就说😉
@task(hosts=xxx)
的能力。(re: 3: 在给予--hosts
和朋友好像他们是每个任务的参数方面,这是我可能会在某个时候添加的东西,已经被撕裂是否值得。类似于每个任务--help
,我们添加的“魔法”排除越多,用户尝试为自己的任务参数使用相同名称并在它中断时感到困惑的可能性就越大。)
最有用的评论
@haydenflinner这将在接下来的一两周内发生! (目标是在我乘飞机前往 PyCon 之前发布,也就是 5 月 10 日。)
例如,请参阅我这周刚刚更新的升级文档:http: //docs.fabfile.org/en/v2/upgrading.html
事实上,我现在也可以关闭这张票,因为我很快就会接受 2.0.0 及更高版本的真实票👍