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 <d...@monokal.io> 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

Reply via email to