[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: @task(hosts=[…]) yields Context but -H yields Connection?

From: Daniel Middleton
Subject: Re: @task(hosts=[…]) yields Context but -H yields Connection?
Date: Sat, 19 Sep 2020 00:13:27 +0100

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):
            def wrapper(c):
                if not hasattr(c, 'host'):
                    if execute:
                        for c in target_group:
                            self.logger.info(f"Targeting \"{c.host}\"...")
                    self.logger.info(f"Targeting \"{c.host}\"...")

            return wrapper

        return _task_handler

Kind Regards,

On Fri, 18 Sep 2020 at 20:53, Jeff Forcier <jeff@bitprophet.org> 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')}

def deploy(ctx, env='dev'):
    targets = envs[env]

$ fab deploy
$ fab deploy --env prod
Thanks & apologies,

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


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:

target_group = None

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

def test(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?


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

reply via email to

[Prev in Thread] Current Thread [Next in Thread]