Fabric Shell

I recently wrote a quick little fabric function to create a fabric shell. This works with a stock fabric install, you just might have to change where you import it from. It is at my Github page below.

https://github.com/mzupan/fabric_shell

If you place shell.py in the same directory as fabfile.py you can use it like

from fabric.api import *
from fabric.decorators import hosts

import shell

env.hosts = [
    'host1.domain.com',
    'host2.domain.com'
]

@hosts('')
def cmd():
    shell.shell()

Then you can use like like

$ fab cmd
[] Executing task 'cmd'
fabric::> id
[host1.domain.com] run: id
[host1.domain.com] out: uid=1020(mzupan) gid=1020(mzupan) groups=1020(mzupan)
[host1.domain.com] out:
[host2.domain.com] run: id
[host2.domain.com] out: uid=1020(mzupan) gid=1020(mzupan) groups=1020(mzupan)
[host2.domain.com] out:
fabric::> .sudo id
[host1.domain.com] sudo:  id
[host1.domain.com] out: uid=0(root) gid=0(root) groups=0(root)
[host1.domain.com] out:
[host2.domain.com] sudo:  id
[host2.domain.com] out: uid=0(root) gid=0(root) groups=0(root)
[host2.domain.com] out:
fabric::> 

Right now any command that is issued in the fabric shell gets run on all hosts in env.hosts and if you add .sudo in front of the command it will run them as root.

Tav’s Fabric Fork Quick Start

I’ve been a big fan of the fabric project for a long time now. It has made my life very easy in the past and gets better with each new release. Well a few weeks ago I got introduced to a fork of the project by Tav. It provides many new feature additions to fabric and makes fabric a little bit better. So the best way to get it started is via the following

git clone git://github.com/tav/pylibs.git
cd pylibs
python setup.py
export PYTHONPATH=$PYTHONPATH:`pwd`

Once installed, you have to create a fab app

vi /usr/bin/fab

Then you want to add the following contents to it

#!/usr/bin/env python

from fabric.main import main
main()

Then you want to give it the correct permissions

chmod +x /usr/bin/fab

Now create a directory and place a fabfile.py file in it.

Now create a config.yml file and use the following as a simple template

default:
  shell: /bin/bash -l -c

servers:
  directory: /dir
  hosts:
    - host1.domain.com
    - host2.domain.com

Then your fab file could look something like

from fabric.api import *

env.config_file = 'config.yml'
env.user = 'mzupan'

@task('servers')
def command():
    env().multirun('uname -a')

Normally fabric runs in serial, but Tav’s port adds a multirun() function that runs the commands in parallel. BONUS!

Feel free to play around with it and check out his blog post on it to get more commands.

http://tav.espians.com/fabric-python-with-cleaner-api-and-parallel-deployment-support.html

Find the real MongoDB command for pyMongo

I am in the middle of writing a MongoDB plugin for cacti using pymongo and ran into a little issue.

In the Mongo shell this works

> db.stats()
{
        "collections" : 19,
        "objects" : 145971,
        "dataSize" : 64785380,
        "storageSize" : 129544960,
        "numExtents" : 75,
        "indexes" : 69,
        "indexSize" : 25403392,
        "ok" : 1
}

So in pymongo I tried

info = db.command("stats")

Then I got the following traceback

Traceback (most recent call last):
  File "./get_mongodb_stats.py", line 68, in 
    main(sys.argv[1:])
  File "./get_mongodb_stats.py", line 39, in main
    get_stats(host, port)
  File "./get_mongodb_stats.py", line 57, in get_stats
    info = db.command("stats")
  File "/usr/lib/python2.6/site-packages/pymongo/database.py", line 306, in command
    (command, result["errmsg"]))

Then I remembered a nice part about the mongoshell. You can run the command without the () and see the function

> db.stats
function () {
    return this.runCommand({dbstats:1});
}

So you do this in pymongo

info = db.command("dbstats")

Google Apps List Shared Contacts

I’ll be the first to call out Google that their gdata python library for connecting into the Google Apps API is the worst.

I wanted to grab all the shared contacts from our domain and it was a giant in the ass. Here is the code I ended up with


import gdata.contacts.client

contact_list = gdata.contacts.client.ContactsClient()
contact_list.client_login(email="user@domain.com",
                                password="password",
                                source='comany-app-1',
                                account_type='HOSTED')
feed_url = contact_list.GetFeedUri(contact_list="domain.com", projection='full')

while True:
    feed = contact_list.get_feed(
                                uri=feed_url,
                                auth_token=None,
                                desired_class=gdata.contacts.data.ContactsFeed)

    for entry in feed.entry:
        print entry.title.text

    next_link = feed.GetNextLink()
    if next_link is None:
        break

    feed_url = next_link.href

The email you use needs to have admin rights over your domain in Google Apps.

Django: Custom Decorator with an Argument

Sometimes you want to be able to create a decorator for functions in Django but have the ability to pass in an argument into the decorator. I came across this when I wanted to switch from the require_login decorator to check if a user was in a certain group to see the function.

So the first thing I did was create a new directory in my project root called decorators and put a blank __init__.py file there. My I cated an auth.py file with the following contents.

from functools import wraps

from django.contrib.auth.models import Group
from django.http import Http404

def group_required(groups=[]):    
    def decorator(func):
        def inner_decorator(request,*args, **kwargs):
            for group in groups:
                try:
                    if Group.objects.get(name=group) in request.user.groups.all():
                        return func(request, *args, **kwargs)
                except:
                    pass

            raise Http404()

        return wraps(func)(inner_decorator)

    return decorator

So we can use it like this in our project


from decorators.auth import group_required

@group_required(["admins"])
def show_index(request):
    ....

I decided to make it a list being passed in. The main reason was to make it was to pass in multiple groups that you might want to allow to be allowed in.

My version doesn’t do much error checking.. if you want a more updated version of this function checkout my githib project I’ve started to put all my custom decorators up and online

http://github.com/mzupan/django-decorators

What Rollout will be?

I have been debating just helping with fabric or writing my own app for a week now and I just think what I need and want fabric just isn’t and too much work to get it to the way I want it to be.

So here are some of the things I want Rollout to be.

  1. Be able to run in console
  2. Be able to tie the Rollout API into your own python webapp
  3. Rollout output should be multiformat (xml/json)
  4. Should be threaded
  5. Should have a run_first() and run_last() method
  6. Tie in git/svn?

Again if you want to watch the project it is http://github.com/mzupan/rollout

Rollout has been designed

I’m a big fan of fabric, a python module to make deployments easy. It is still very new but has some great features and some major short comings I don’t think the authors thought of before they designed it.

The major shortcoming is a lack of parallel deployments. Fabric works great on 1 or 2 hosts you need to deploy on but when you have more then that for a large application it is painful. There is a patch for parallel deployments and it works but it “breaks” some features of fabric. The main one is the @runs_once.

So I am going to try to build a python module to better handle deployments in a parallel fashion. I have decided to model my module after fabric since I’m very use to it and I think their model works.

I started a GitHub project for it but don’t have any code in there yet.

http://github.com/mzupan/rollout

If you are interested please keep an eye on the project. I hope to make it functional soon.

Reason #1 on why Django is great

All the crazy helper function they have for models to display information in templates. Say I have a model that looks like this with a choice field (it will get rendered as a select input in html.

SEX_CHOICE = (
    (1, 'Male'),
    (2, 'Female'),
)
class Animal(models.Model):
    name = models.CharField(max_length=50)
    sex = models.SmallIntegerField(max_length=1, default=0, choices=SEX_CHOICE)

Now in my template I can do the following to see if an animal object is a Male or a Female

{{animal.get_sex_display}}

There is no need to create a method for that in the model. Django does it for you. Saving you time and effort.

Install fabric on Redhat/CentOS 5

Fabric is a great tool for helping deploy applications. It is written in Python and still in it’s early stages. The big issue is that fabric will not run on python v2.4. That is a problem since Redhat/CentOS v5 ships with v2.4 so we have to make it work.

So the first thing to do is install python 2.5. I found great rpms here. I am going to mirror the RPMS on my blog just in case. You also need to install tkinter25 which I provide for you also. These are only for 64bit. You can do some googling to find the 32 bit versions I am sure. You also need to install libtk and libTix.

Install some dependancies

yum install tk tix

Now lets install python and tkinter

mkdir python
cd python
wget http://zcentric.com/files/python25-2.5.1-bashton1.x86_64.rpm
wget http://zcentric.com/files/python25-devel-2.5.1-bashton1.x86_64.rpm
wget http://zcentric.com/files/python25-libs-2.5.1-bashton1.x86_64.rpm
wget http://zcentric.com/files/python25-test-2.5.1-bashton1.x86_64.rpm
wget http://zcentric.com/files/python25-tools-2.5.1-bashton1.x86_64.rpm
wget http://zcentric.com/files/tkinter25-2.5.1-bashton1.x86_64.rpm
rpm -ivh *.rpm

Remove all the python25 rpms

rm -rf *.rpm

Now you have v2.4 and v2.5 installed. Now lets download the needed fabric dependancies and fabric. I built these to run for python25

wget http://zcentric.com/files/fabric-0.9-0.1.b1.noarch.rpm
wget http://zcentric.com/files/python25-setuptools-0.6c5-2.noarch.rpm
wget http://zcentric.com/files/python25-paramiko-1.7.6-1.noarch.rpm
wget http://zcentric.com/files/python25-crypto-2.0-1.rf.x86_64.rpm
rpm -ivh *.rpm
Now we just need to edit the fab file to change the python version it will run as by default
vi /usr/bin/fab
Change
#!/usr/bin/python
To
#!/usr/bin/python25
Now you can run fab and you should get something like
[root@dev1 ~]# fab
Fatal error: Couldn’t find any fabfiles!
Aborting.
More blog posts to come on how to use fabric