Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I properly set the `env.hosts` in a function in my Python Fabric `fabfile.py`?

When I run this fabfile.py...

from fabric.api import env, run, local, cd

def setenv(foo):
  env.hosts = ['myhost']

def mycmd(foo):
  setenv(foo)
  print(env.hosts)
  run('ls')

with this command fab mycmd:bar. I get this output...

['myhost']
No hosts found. Please specify (single) host string for connection:

What, what?! I don't get it? I've set the env.hosts and it seems to be valid "inside" the mycmd function, but for some reason that run command doesn't know about the hosts I've specified.

Color me confused. Any help would be appreciated!

like image 449
Chris W. Avatar asked Jan 31 '12 06:01

Chris W.


3 Answers

I know this question is super old, but just in case someone stumbles across this, I have found that you don't need to call this as a fab file per se (your file doesn't need to be called "fabfile.py" and command doesn't need to be fab setenv(foo) mycmd(bar). Since you are importing the needed fab elements, you can call the file anything you want (let's call it "testfile.py") and simply use the execute function in the file. That would make your command python testfile.py.

Inside the testfile, set everything up like you would normally, but start the function using the execute keyword. Your file would look something like this:

from fabric.api import env, run

def setenv(foo):
    env.hosts = ['myhost']
    execute(mycmd, bar)

def mycmd(bar):
    run('ls')

setenv(foo)

** It's important to note that the execute command does look like a regular function call. It will call the function and send the arguments in a single comma separated line. You can find more information here

So you'd start your program which would first call setenv, then setenv would execute the mycmd function. With this, you can also set multiple hosts within the same array. Something like:

env.hosts=['myhost1','myhost2','myhost3']
like image 70
Apolymoxic Avatar answered Sep 22 '22 19:09

Apolymoxic


@Chris, the reason you're seeing this behavior is because the host list is constructed before the task function is called. So, even though you're changing env.hosts inside the function, it is too late for it to have any effect.

Whereas the command fab setenv:foo mycmd:bar, would have resulted in something you would have expected:

$ fab setenv:foo mycmd:bar
[myhost] Executing task 'mycmd'
['myhost']
[myhost] run: ls

This is the same as the accepted answer, but because of the way setenv is defined, an argument is needed.

Another example:

from fabric.api import env, run, local, cd

env.hosts = ['other_host']

def setenv(foo):
    env.hosts = ['myhost']

def mycmd(foo):
    setenv(foo)
    print('env.hosts inside mycmd: %s' % env.hosts)
    run('ls')

The output of this is:

$ fab mycmd:bar
[other_host] Executing task 'mycmd'
env.hosts inside mycmd: ['myhost']
[other_host] run: ls

Fatal error: Name lookup failed for other_host

Underlying exception:
    (8, 'nodename nor servname provided, or not known')
Aborting.

As you can see, the host-list is already set to ['other_host', ] when fabric starts to execute mycmd.

like image 7
user650654 Avatar answered Nov 05 '22 08:11

user650654


The way you are doing it is not normally how I would use Fabric.

from fabric.api import *

def hostname():

    env.hosts = ['myhosts']

def mycmd():
    print env.hosts
    run('ls -l')

To run this I would then do

fab hostname mycmd

this allows you to seperate which host/hosts you want to perform the command on.

hope it helps.

like image 5
Mark Lakewood Avatar answered Nov 05 '22 08:11

Mark Lakewood