Skip to content
This repository has been archived by the owner on Jan 25, 2019. It is now read-only.

Commit

Permalink
initial import from previous repository
Browse files Browse the repository at this point in the history
  • Loading branch information
Doug Winter committed Feb 28, 2010
0 parents commit ae82e95
Show file tree
Hide file tree
Showing 11 changed files with 396 additions and 0 deletions.
Empty file added CHANGES.txt
Empty file.
91 changes: 91 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
GoCaptain start and stop scripts
====================================

The GoCaptain [#]_ buildout recipe produces a script to start and stop daemons,
similar to those you find in /etc/init.d. By default it will inspect your
system and either write a "simple" script, such as you might produce yourself
or produce a LinuxStandard Base variation, that provides more tooling.

In particular the LSB scripts will try multiple times to shut down your daemon, and will not start it if it is already running.

This package also provides a simple way to produce these scripts from other
buildout recipes - see `isotoma.recipe.varnish`_ for an example.

.. _`isotoma.recipe.varnish`: http://pypi.python.org/pypi/isotoma.recipe.varnish

The buildout recipe
-------------------

A simple example would be::

[example]
recipe = isotoma.recipe.gocaptain
daemon = /usr/bin/example
name = example
description = example daemon for that thing i did that time
pidfile = /var/tmp/example.pid
args =
-P ${example:pidfile}
-w /var/tmp/example.log

This will produce a script in bin/example that launches your daemon, and shuts
it down again later, using the PID in the pidfile.

Options
~~~~~~~

The mandatory options this recipe accepts are:

daemon
The path to the daemon executable file
name
The name of the daemon, displayed in log messages
description
A longer description, shown on the console during start and stop
pidfile
A path to a file to store the PID of the new daemon in
args
The arguments for the daemon. These will be formatted in the output script as you provide them, with continuations provided as needed

In addition you can provide:

template
A path to the template for your start/stop script. This will be used in preference to the templates provided with this package.

Calling from other code
-----------------------

If you wish to use this from one of your own recipes, I suggest you do
something like::

from isotoma.recipe import gocaptain
gc = gocaptain.Automatic()
f = open("/path/to/script", "w")
gc.write(f, daemon="/usr/sbin/thing",
args="-D -P /path/to/pid",
name="my thing", description="thing")
f.close()
os.chmod(target, 0755)

The Automatic module will select the Simple or LinuxStandardBase variants, by
inspecting your system (very simplisticly!).

License
-------

Copyright 2010 Doug Winter

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

.. [#] The name comes from Cordwainer Smith
2 changes: 2 additions & 0 deletions isotoma/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__import__('pkg_resources').declare_namespace(__name__)

2 changes: 2 additions & 0 deletions isotoma/recipe/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__import__('pkg_resources').declare_namespace(__name__)

119 changes: 119 additions & 0 deletions isotoma/recipe/gocaptain/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@

import os
import logging

from Cheetah.Template import Template

here = os.path.dirname(__file__)

class GoCaptain(object):

""" Use this class from other buildout recipes - it is easier to call than
the buildout recipe itself. """

template = None
template_str = None
defaults = {}
required = []

def __init__(self, template=None, template_str=None, defaults={}, required=[]):
self.template = template
self.template_str = template_str
self.defaults = defaults
self.required = required

def write(self, stream, **kw):
for i in self.required:
if not i in kw:
raise KeyError("Missing option: %s" % i)
for k, v in self.defaults.items():
kw.setdefault(k, v)
if self.template is not None:
t = open(self.template).read()
else:
t = self.template_str
c = Template(t, searchList=kw)
stream.write(str(c))

class Simple(GoCaptain):
template = os.path.join(here, "simple.tmpl")
defaults = {
'preamble': '',
}
required = [
'daemon',
'description',
'pidfile',
'args',
]

def __init__(self):
pass

class LinuxStandardBase(GoCaptain):
template = os.path.join(here, "lsb.tmpl")
defaults = {
'preamble': '',
}
required = [
'name',
'description',
'daemon',
'pidfile',
'args',
]

def __init__(self):
pass

class Automatic(object):

def __init__(self, *a, **kw):
if os.path.exists('/lib/lsb/init-functions'):
self.__class__ = LinuxStandardBase
else:
self.__class__ = Simple


class Buildout(GoCaptain):

""" This is the base class for the buildout recipes below """

def __init__(self, buildout, name, options):
self.name = name
self.options = options
self.buildout = buildout
self.logger = logging.getLogger(self.name)
if self.template is not None:
self.options.setdefault("template", self.template)

def install(self):
if 'template' in self.options:
self.template = self.options['template']
target = os.path.join(self.buildout['buildout']['bin-directory'], self.name)
try:
self.write(open(target, "w"), **self.options)
except KeyError:
os.unlink(target)
raise
os.chmod(target, 0755)
self.options.created(target)
return self.options.created()

def update(self):
pass

class SimpleBuildout(Buildout, Simple):
pass

class LinuxStandardBaseBuildout(LinuxStandardBase, Buildout):
pass

class AutomaticBuildout(object):

def __init__(self, *a, **kw):
if os.path.exists('/lib/lsb/init-functions'):
self.__class__ = LinuxStandardBaseBuildout
else:
self.__class__ = SimpleBuildout
Buildout.__init__(self, *a, **kw)
41 changes: 41 additions & 0 deletions isotoma/recipe/gocaptain/lsb.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#! /bin/bash

. /lib/lsb/init-functions

${preamble}

case "\$1" in
start)
output=\$(/bin/tempfile -s.$name)
log_daemon_msg "Starting $description"
log_progress_msg "$name"
if start-stop-daemon \
--start --quiet --pidfile $pidfile --exec $daemon -- \
#echo " \\\n".join([12*" " + x.strip() for x in $args.strip()split("\n")])
> \${output} 2>&1; then
log_end_msg 0
else
log_end_msg 1
cat \$output
exit 1
fi
rm \$output
;;
stop)
log_daemon_msg "Stopping $description"
log_progress_msg "$name"
if start-stop-daemon \
--stop --quiet --pidfile $pidfile --retry 10 \
--exec $daemon; then
log_end_msg 0
else
log_end_msg 1
fi
;;
*)
log_success_msg "Usage: $0 {start|stop}"
exit 1
;;
esac

exit 0
20 changes: 20 additions & 0 deletions isotoma/recipe/gocaptain/simple.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh

${preamble}

case "$1" in
start)
echo "Starting $description"
exec ${daemon} \
#echo " \\\n".join([12*" " + x.strip() for x in $args.strip()split("\n")]) + "\n"
;;
stop)
kill `cat ${pidfile}`
;;
*)
echo "Usage $0 {start|stop}"
exit 1
;;
esac

exit 0
Empty file.
1 change: 1 addition & 0 deletions isotoma/recipe/gocaptain/test/basic.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$foo
82 changes: 82 additions & 0 deletions isotoma/recipe/gocaptain/test/test_gocaptain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import unittest
from StringIO import StringIO
import tempfile
import os
import UserDict

from isotoma.recipe.gocaptain import GoCaptain, Buildout

class MockOptions(UserDict.IterableUserDict):
def __init__(self, *a, **kw):
UserDict.IterableUserDict.__init__(self, *a, **kw)
self._created = []
def created(self, *p):
self._created.extend(p)

class TestGoCaptain(unittest.TestCase):

def test_basic(self):
t = "$foo"
g = GoCaptain(template_str=t)
o = StringIO()
g.write(o, foo="bar")
self.assertEqual(o.getvalue(), "bar")

def test_defaults(self):
t = "$foo"
g = GoCaptain(template_str=t, defaults={'foo': 'bar'})
o = StringIO()
g.write(o)
self.assertEqual(o.getvalue(), "bar")

def test_required(self):
t = "$foo"
g = GoCaptain(template_str=t, required=['foo'])
o = StringIO()
self.assertRaises(KeyError, g.write, o)

class X(Buildout):
template = os.path.join(os.path.dirname(__file__), "basic.tmpl")
defaults = {'foo': 'bar'}
required = ['baz']

class TestBuildout(unittest.TestCase):

def setUp(self):
self.td = tempfile.mkdtemp()
self.buildout = {
'buildout': {
'bin-directory': self.td,
}
}

def test_basic(self):
options = MockOptions({'foo': 'quux', 'baz': 'quuux'})
x = X(self.buildout, "name", options)
x.install()
target = os.path.join(self.td, 'name')
self.assertEqual(options._created, [target])
d = open(target).read()
self.assertEqual(d, "quux")
os.unlink(target)
os.rmdir(self.td)

def test_defaults(self):
options = MockOptions({'baz': 'quuux'})
x = X(self.buildout, "name", options)
x.install()
target = os.path.join(self.td, 'name')
self.assertEqual(options._created, [target])
d = open(target).read()
self.assertEqual(d, "bar")
os.unlink(target)
os.rmdir(self.td)

def test_required(self):
options = MockOptions()
x = X(self.buildout, "name", options)
self.assertRaises(KeyError, x.install)
os.rmdir(self.td)



Loading

0 comments on commit ae82e95

Please sign in to comment.