Hi Jeff,
Thank you for the detailed reply and for your time, it really is
appreciated.
I'm looking forward to seeing more active development in terms on the API
and CLI as there's obviously huge potential here and it's a wonderful
project.
Anyways, before your reply, I ended up writing a customer task decorator,
which isn't ideal but is working for my use-case right now:
```
def task_handler(self, execute=True):
"""
Decorator which allows all hosts within an environment, populated by
`env_handler()`, to be iterated by a task(s). If `-H` was not passed
and `execute=False` the `target_group` SerialGroup object will be
returned to the task (as `c`) but the task will not be iterated. All
tasks should be decorated with `@fabenv.task_handler()`.
"""
def _task_handler(func):
@task
@functools.wraps(func)
def wrapper(c):
if not hasattr(c, 'host'):
if execute:
for c in target_group:
self.logger.info(f"Targeting \"{c.host}\"...")
func(c)
else:
func(target_group)
else:
self.logger.info(f"Targeting \"{c.host}\"...")
func(c)
return wrapper
return _task_handler
```
Kind Regards,
Daniel
On Fri, 18 Sep 2020 at 20:53, Jeff Forcier <[email protected]> wrote:
> Hi Daniel,
>
> You're running into a few problems here. Most of them stem from this API
> needing more work - for example first class CLI and decorator support for
> Groups. However I'm getting back into Fabric dev after some inactivity and
> this is at or near the top of my list.
>
> One thing to be aware of here is the defaulting of your hosts value to
> None - that makes the execution machinery think you haven't defined it at
> all, which is why it falls back to "must be a local task, here's a Context".
>
> Unfortunately after that, the globals trick won't work (it will only
> update the value of target_group at runtime, but decorators operate at
> definition/load time - so by the time your 'pre task' runs the execution
> machinery has already slurped up the old None value.
>
> And as noted, Groups need to be accepted in more places (this at least
> ought to be a quick fix so keep an eye out for a feature release sometime).
>
> For the time being the least-awful workaround is to say "thanks but no
> thanks" to the Fabric-specific CLI bits and treat this like an Invoke +
> Fabric-the-API problem - which /also/ needs work on my end to be cleaner
> and scalable, but will at least function:
>
> - Define some module level mapping of env names to Group objects
> - Define your task function(s) with an 'env' or similar parameter, taking
> a name from that mapping
> - Write the task(s) to load the given value out of that mapping, and use
> it to do the necessary work
>
> For example:
>
> ```
> envs = {'dev': Group('localhost'), 'prod': Group('www1', 'db1')}
>
> @task
> def deploy(ctx, env='dev'):
> targets = envs[env]
> targets.run("command1")
> targets.sudo("command2")
> ```
>
> ```
> $ fab deploy
> $ fab deploy --env prod
> ```
>
> Thanks & apologies,
> Jeff
>
>
> On Wed, Sep 16, 2020 at 11:23 AM Daniel Middleton <[email protected]> wrote:
>
>> Python 3.8.2, Fabric 2.5.0, Paramiko 2.7.2, Invoke 1.4.1
>>
>> Hello,
>>
>> I have a fabfile which needs to handle hosts passed at the command-line
>> (using `-H`) and hosts defined in the fabfile if `-H` was not passed.
>> Here's an example of the issue I'm facing:
>>
>> ```python
>> target_group = None
>>
>> @task
>> def prod(ctx):
>> _env_handler(ctx, "prod")
>>
>> def _env_handler(ctx, env_name):
>> global target_group
>>
>> if not hasattr(ctx, 'host'):
>> target_group = Group("somehost1.tld", "somehost2.tld")
>>
>> @task(hosts=target_group)
>> def test(ctx):
>> print(ctx)
>> ```
>>
>> If I run `fab prod test`:
>>
>> ```
>> <Context: <Config: {'run': {'asynch ...
>> ```
>>
>> If I run `fab -H 1,2 test`:
>>
>> ```
>> <Connection host=1>
>> <Connection host=2>
>> ```
>>
>> So, passing hosts using the `@task(hosts=[...]` decorator produces a ctx
>> `Context` object, and using `-H` produces a ctx `Connection` object.
>>
>> I know using a task (`prod(ctx)`) to wrap environment logic may be
>> questionable...but is there a way to ensure the task (`test(ctx)`) always
>> receives a `Connection` object...or am I fundamentally misunderstanding
>> something?
>>
>> Thanks.
>>
>> Edit: I've also tried directly passing a host list `(e.g.
>> @task(hosts=["somehost1.tld", "somehost2.tld"]))` with the same result.
>>
>
>
> --
> Jeff Forcier
> Unix sysadmin; Python engineer
> http://bitprophet.org
>