9

Is it possible to make the fabfile stand-alone?
I'm not very fond of running the external tool 'fab'. If I manage to get the fabfile standalone I can run the file from within the (Eclipse / Pydev) IDE, easily debug it, use project configurations and paths etc.
Why doesn't this work:

from fabric.api import run

def host_type():
    run('uname -s')

if __name__ == '__main__':
    host_type()    
Tal Weiss
  • 8,889
  • 8
  • 54
  • 62

10 Answers10

14

I eventually found the solution (and it is really simple!).
In my fabfile, I added:

from fabric.main import main

if __name__ == '__main__':
    import sys
    sys.argv = ['fab', '-f', __file__, 'update_server']
    main()

I hope this helps people...

Tal Weiss
  • 8,889
  • 8
  • 54
  • 62
  • Does this spawn a subprocess like muksie's solution? The main reason I was exploring this issue is the extra subprocesses are something we want to eliminate. – Aaron Robinson Feb 23 '12 at 18:43
  • 1
    If you alter your sys.argv assignment it'll work with any set of commands. sys.argv[0] is the script name already, so: sys.argv = ['fab', '-f'] + sys.argv – mqsoh Jan 27 '13 at 06:20
  • It's working as long as the file has `.py` extension. I didn't find a way yet to make it work for any extension. – Wernight Oct 01 '15 at 08:28
3

If I recall correctly, I couldn't get the Fabric API to do what I wanted either. I decided to abandon the extra layer entirely and use Paramiko (the underlying SSH library used by Fabric) directly:

import os
import paramiko

ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('hostname.example.com', 22, 'username', 'password')
ssh.save_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
stdin, stdout, stderr = ssh.exec_command('uname -s')
print stdout.read()

While there are a few more steps involved, doing it this way allows you to leverage the SSH layer directly, as opposed to using subprocess to spwan another Python instance, or figuring out the Fabric API. I have several projects, both web- and console- using Paramiko in this manner and I haven't had too much trouble.

Paramiko is extensively documented.

AdmiralNemo
  • 1,301
  • 11
  • 14
2
# thanks to aaron, extending his code a little more
# here is a little bit more of a graceful solution than running subprocess.call, and specifying multiple hosts

from fabric.api import env, run

def main():
    run("uname -a")

def setup():
    env.hosts = ['host0','host1']

if __name__ == '__main__':
    setup()
    for host in env.hosts:
        env.host_string = host
        main()
Jon
  • 21
  • 1
2

Since 1.5.0 there is a way better way to do this than messing around with argv.

import fabric.main

def main():
    fabric.main.main(fabfile_locations=[__file__])

if __name__ == "__main__":
    main()

This can also be utilized as a console script in setup.py

anatoly techtonik
  • 19,847
  • 9
  • 124
  • 140
Greg
  • 5,422
  • 1
  • 27
  • 32
2

This isn't a really nice solution, but will work:

import subprocess

def hello():
    print 'Hello'

if __name__ == '__main__':
    subprocess.call(['fab', '-f', __file__, 'hello'])
muksie
  • 12,565
  • 1
  • 19
  • 14
  • Yep, not really nice (but very original +1 !). Also, does not address stdio (e.g. interactive prompt for password (which I assume is solvable)). – Tal Weiss Jul 19 '10 at 21:18
  • Hijacking the top comment to say that in latest versions of fabric you could use the `execute` command instead of `subprocess.call` http://fabric.readthedocs.org/en/1.4.3/usage/execution.html#execute . – tutuca Sep 14 '12 at 19:42
2

I fine tuned the above example to past through argv arguments you might want to pass to local commands and specify an optional default_commands list instead of a hard coded command name. Note, the filename must have a .py extension or fab will not detect it as a fab file!

#!/usr/bin/env python
from fabric.api import local

default_commands = ['hello', ]

def hello():
    print ('hello world')

def hostname():
    local('hostname')

if __name__ == '__main__':
   import sys
   from fabric.main import main
   sys.argv = ['fab', '-f', __file__,] +  default_commands + sys.argv[1:]
   main()
bfschott
  • 176
  • 1
  • 5
2

docs.fabfile.org/en/1.4.0/usage/library.html

"As that section mentions, the key is simply that run, sudo and the other operations only look in one place when connecting: env.host_string . All of the other mechanisms for setting hosts are interpreted by the fab tool when it runs, and don’t matter when running as a library."

I was looking at this same problem when I found this. Also, while looking I recall mention that when used in a fabfile, env changes should not be in the same def as run, sudo. Who knows if this still applies when used in "library" mode.

EDIT: Here is an example of said implementation

from fabric.api import env, run

def main():
    run("uname -a")

def setup():
    env.host_string = "me@remoteHost"

if __name__ == '__main__':
    setup()
    main()
Aaron Robinson
  • 406
  • 1
  • 6
  • 13
1

Add this to the bottom of your fab file.

if __name__ == '__main__':
  from fabric.main import main
  import sys

  sys.argv = ['fab', '-f', __file__] + sys.argv[1:]

  main()
0

This is my modified version of Greg's answer that changes default behavior to show available commands instead of listing tons of fabric options.

if __name__ == '__main__':
    # imports for standalone mode only
    import sys, fabric.main

    # show available commands by default
    if not sys.argv[1:]:
        sys.argv.append('--list')

    fabric.main.main(fabfile_locations=[__file__])
Community
  • 1
  • 1
anatoly techtonik
  • 19,847
  • 9
  • 124
  • 140
0

I found the solution indirectly on run fabric file renamed other than fabfile.py and password-less ssh

def deploy():
    ...

if __name__ == '__main__':
    from fabric import execute
    execute(deploy)

This way also work if your file doesn't have .py extension.

Community
  • 1
  • 1
Wernight
  • 36,122
  • 25
  • 118
  • 131