Monday, October 28, 2013

Are you happy with your current issue management SaaS?

Because I finally do, though it is not a SaaS. At my current workplace, DarwinApps, we've spent two years to finally narrow it down. First, we've tried Pivotal Tracker, then some other services I can't even remember, then Asana for more than a year. We have multiple projects running at once and the most obvious flaw of these is the lack of meaningful task numbering. In a voice meeting, we must navigate to the tracker, search and paste full URL of an issue, which is really painful. Can you remember any of the following URLs ?:

  • https://www.pivotaltracker.com/s/projects/391021/stories/19675435
  • https://app.asana.com/0/168983775190/2827676382156
Then we've reached Jira, what a relief ! The links are finally usable / easy to remember and construct:
  • https://darwinapps.atlassian.net/browse/CLINK-6
And Jira is definitely the best one SaaS, at least for our needs, as it has meaningful task numbering and multiple dimensions to assign tickets to. However, in the end, we've sticked to Redmine, but that's really another story, which I can only tell if you are ready to maintain your own virtual / dedicated server. What I really wonder what SaaS are you using for issue tracking, and whether you are happy with it, and if not - what is the reason ? Please leave a note in the comments, thank you !

Are you happy with your current issue tracking SaaS ?

Thursday, October 3, 2013

Local SSH tunneling for Fabric fabfile

Latest Fabric (1.8 as of now) provides remote_tunnel context manager, which allows to create a tunnel forwarding a locally-visible port to the remote target. But in my case, I needed a reverse schema - to access remote mysql with a perl script. The work is based on paramiko's example with minor changes to keep tunnel work in the background using threading:


import SocketServer, paramiko, threading, select
from fabric.state import *
 
class ForwardServer (SocketServer.ThreadingTCPServer):
    daemon_threads = True
    allow_reuse_address = True
 
class Handler (SocketServer.BaseRequestHandler):
 
    def handle(self):
 
        try:
            chan = self.ssh_transport.open_channel('direct-tcpip',
                                                   (self.chain_host, self.chain_port),
                                                   self.request.getpeername())
        except Exception, e:
            print('Incoming request to %s:%d failed: %s' % (self.chain_host,
                                                              self.chain_port,
                                                              repr(e)))
            return
        if chan is None:
            print('Incoming request to %s:%d was rejected by the SSH server.' %
                    (self.chain_host, self.chain_port))
            return
 
        print('Connected!  Tunnel open %r -> %r -> %r' % (self.request.getpeername(),
                                                            chan.getpeername(), (self.chain_host, self.chain_port)))
        while True:
            r, w, x = select.select([self.request, chan], [], [])
            if self.request in r:
                data = self.request.recv(1024)
                if len(data) == 0:
                    break
                chan.send(data)
            if chan in r:
                data = chan.recv(1024)
                if len(data) == 0:
                    break
                self.request.send(data)
 
        peername = self.request.getpeername()
        chan.close()
        self.request.close()
        print('Tunnel closed from %r' % (peername,))
 
def forward_tunnel(local_port, remote_host, remote_port, transport):
 
    # this is a little convoluted, but lets me configure things for the Handler
    # object.  (SocketServer doesn't give Handlers any way to access the outer
    # server normally.)
 
    class SubHander (Handler):
        chain_host = remote_host
        chain_port = remote_port
        ssh_transport = transport
 
    server_thread = threading.Thread(target=ForwardServer(('', local_port), SubHander).serve_forever)
    server_thread.daemon = True
    server_thread.start()
 
@task
def task():
    forward_tunnel(3307, 'localhost', 3306, connections[env.host].get_transport())
    # now remote mysql is available at localhost:3307