Quellcode durchsuchen

Feature to allow for environment variable settings on a per-program basis.

'version' supervisorctl command.
Chris McDonough vor 18 Jahren
Ursprung
Commit
c2112f9826

+ 9 - 0
CHANGES.txt

@@ -1,3 +1,12 @@
+2.2b1
+
+  - Individual program configuration sections can now specify an
+    environment.
+
+  - Added a 'version' command to supervisorctl.  This returns the
+    version of the supervisor2 package which the remote supervisord
+    process is using.
+
 2.1
 
   - When supervisord was invoked more than once, and its configuration

+ 6 - 0
README.txt

@@ -6,6 +6,8 @@ History
 
   8/30/2006: updated for version 2.1
 
+  3/31/2007: updated for version 2.2
+
 Introduction
 
   The supervisor is a client/server system that allows its users to
@@ -313,6 +315,7 @@ Configuration File '[program:x]' Section Settings
     logfile=/tmp/programname.log
     logfile_maxbytes=10MB
     logfile_backups=2
+    environment=A=1,B=2
 
   '[program:programname]' -- the section header, required for each
   program.  'programname' is a descriptive name (arbitrary) used to
@@ -401,6 +404,9 @@ Configuration File '[program:x]' Section Settings
   from process log file rotation.  Set this to 0 to indicate an
   unlimited number of backups.  Default: 10.
 
+  'environment' -- A list of key/value pairs in the form "KEY=val,KEY2=val2"
+  that will be placed in the child process' environment.  Default: none.
+
 Nondaemonizing of Subprocesses
 
   Programs run under supervisor *should not* daemonize themselves.

+ 2 - 1
setup.py

@@ -29,10 +29,11 @@ CLASSIFIERS = [
     'Topic :: System :: Systems Administration',
     ]
 
+from options import VERSION
 
 dist = setup(
     name = 'supervisor',
-    version = "2.1",
+    version = VERSION,
     license = 'ZPL/BSD (see LICENSES.txt)',
     url = 'http://www.plope.com/software',
     description = "A system for controlling process state under UNIX",

+ 11 - 5
src/supervisor/options.py

@@ -18,6 +18,8 @@ import grp
 import resource
 import stat
 
+VERSION = '2.2b1'
+
 from fcntl import fcntl
 from fcntl import F_SETFL, F_GETFL
 
@@ -439,7 +441,6 @@ class ServerOptions(Options):
     unlink_socketfile = True
     AUTOMATIC = []
     TRACE = 5
-
     
     def __init__(self):
         Options.__init__(self)
@@ -715,6 +716,9 @@ class ServerOptions(Options):
             log_stdout = datatypes.boolean(log_stdout)
             log_stderr = config.saneget(section, 'log_stderr', 'false')
             log_stderr = datatypes.boolean(log_stderr)
+            environment = config.saneget(section, 'environment', '')
+            environment = datatypes.dict_of_key_value_pairs(environment)
+
             pconfig = ProcessConfig(name=name, command=command,
                                     priority=priority,
                                     autostart=autostart,
@@ -729,7 +733,8 @@ class ServerOptions(Options):
                                     stopwaitsecs=stopwaitsecs,
                                     exitcodes=exitcodes,
                                     log_stdout=log_stdout,
-                                    log_stderr=log_stderr)
+                                    log_stderr=log_stderr,
+                                    environment=environment)
             programs.append(pconfig)
 
         programs.sort() # asc by priority
@@ -1090,8 +1095,8 @@ class ServerOptions(Options):
     def write(self, fd, data):
         return os.write(fd, data)
 
-    def execv(self, filename, argv):
-        return os.execv(filename, argv)
+    def execve(self, filename, argv, env):
+        return os.execve(filename, argv, env)
 
     def _exit(self, code):
         os._exit(code)
@@ -1245,7 +1250,7 @@ class ProcessConfig:
     def __init__(self, name, command, priority, autostart, autorestart,
                  startsecs, startretries, uid, logfile, logfile_backups,
                  logfile_maxbytes, stopsignal, stopwaitsecs, exitcodes,
-                 log_stdout, log_stderr):
+                 log_stdout, log_stderr, environment=None):
         self.name = name
         self.command = command
         self.priority = priority
@@ -1262,6 +1267,7 @@ class ProcessConfig:
         self.exitcodes = exitcodes
         self.log_stdout = log_stdout
         self.log_stderr = log_stderr
+        self.environment = environment
 
     def __cmp__(self, other):
         return cmp(self.priority, other.priority)

+ 9 - 0
src/supervisor/supervisorctl.py

@@ -588,6 +588,15 @@ class Controller(cmd.Cmd):
         self._output("open <url>\t\t\tConnect to a remote supervisord process.")
         self._output("\t\t\t(for UNIX domain socket, use unix:///socket/path)")
 
+    def do_version(self, arg):
+        if not self._upcheck():
+            return
+        supervisor = self._get_supervisor()
+        self._output(supervisor.getSupervisorVersion())
+
+    def help_version(self):
+        self._output("version\t\t\tShow the version of the remote supervisord ")
+        self._output("\t\t\tprocess")
 
 def main(args=None, options=None):
     if options is None:

+ 4 - 1
src/supervisor/supervisord.py

@@ -288,7 +288,10 @@ class Subprocess:
                         )
                     self.options.write(1, "%s: %s\n" % (pname, msg))
                 try:
-                    self.options.execv(filename, argv)
+                    env = os.environ.copy()
+                    if self.config.environment is not None:
+                        env.update(self.config.environment)
+                    self.options.execve(filename, argv, env)
                 except OSError, why:
                     code = why[0]
                     self.options.write(1, "couldn't exec %s: %s\n" % (

+ 75 - 3
src/supervisor/tests.py

@@ -286,6 +286,47 @@ exitcodes=0,1,127
         self.assertRaises(os.error, os.read, innie, 0)
         instance.close_fd(outie)
         self.assertRaises(os.error, os.write, outie, 'foo')
+
+    def test_programs_from_config(self):
+        class DummyConfig:
+            command = '/bin/cat'
+            priority = 1
+            autostart = 'false'
+            autorestart = 'false'
+            startsecs = 100
+            startretries = 100
+            user = 'root'
+            logfile = 'NONE'
+            logfile_backups = 1
+            logfile_maxbytes = '100MB'
+            stopsignal = 'KILL'
+            stopwaitsecs = 100
+            exitcodes = '1,4'
+            log_stdout = 'false'
+            log_stderr = 'true'
+            environment = 'KEY1=val1,KEY2=val2'
+            def sections(self):
+                return ['program:foo']
+            def saneget(self, section, name, default):
+                return getattr(self, name, default)
+        instance = self._makeOne()
+        pconfig = instance.programs_from_config(DummyConfig())[0]
+        self.assertEqual(pconfig.name, 'foo')
+        self.assertEqual(pconfig.command, '/bin/cat')
+        self.assertEqual(pconfig.autostart, False)
+        self.assertEqual(pconfig.autorestart, False)
+        self.assertEqual(pconfig.startsecs, 100)
+        self.assertEqual(pconfig.startretries, 100)
+        self.assertEqual(pconfig.uid, 0)
+        self.assertEqual(pconfig.logfile, None)
+        self.assertEqual(pconfig.logfile_maxbytes, 104857600)
+        self.assertEqual(pconfig.stopsignal, signal.SIGKILL)
+        self.assertEqual(pconfig.stopwaitsecs, 100)
+        self.assertEqual(pconfig.exitcodes, [1,4])
+        self.assertEqual(pconfig.log_stdout, False)
+        self.assertEqual(pconfig.log_stderr, True)
+        self.assertEqual(pconfig.environment, {'KEY1':'val1', 'KEY2':'val2'})
+        
         
 
 class TestBase(unittest.TestCase):
@@ -379,6 +420,15 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         self.assertEqual(version, xmlrpc.RPC_VERSION)
         self.assertEqual(interface.update_text, 'getVersion')
 
+    def test_getSupervisorVersion(self):
+        supervisord = DummySupervisor()
+        interface = self._makeOne(supervisord)
+        version = interface.getSupervisorVersion()
+        import options
+        self.assertEqual(version, options.VERSION)
+        self.assertEqual(interface.update_text, 'getSupervisorVersion')
+        
+
     def test_getIdentification(self):
         supervisord = DummySupervisor()
         interface = self._makeOne(supervisord)
@@ -1526,6 +1576,17 @@ class SubprocessTests(unittest.TestCase):
         self.assertEqual(options.privsdropped, None)
         self.assertEqual(options._exitcode, 127)
 
+    def test_spawn_as_child_uses_pconfig_environment(self):
+        options = DummyOptions()
+        options.forkpid = 0
+        config = DummyPConfig('cat', '/bin/cat',
+                              environment={'_TEST_':'1'})
+        instance = self._makeOne(options, config)
+        result = instance.spawn()
+        self.assertEqual(result, None)
+        self.assertEqual(options.execv_args, ('/bin/cat', ['/bin/cat']) )
+        self.assertEqual(options.execv_environment['_TEST_'], '1')
+
     def test_spawn_as_parent(self):
         options = DummyOptions()
         options.forkpid = 10
@@ -2475,6 +2536,13 @@ foo            RUNNING    foo description
 bar            FATAL      bar description
 baz            STOPPED    baz description
 """)
+
+    def test_version(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        controller.do_version(None)
+        self.assertEqual(controller.stdout.getvalue(), '3000\n')
         
 class TailFProducerTests(unittest.TestCase):
     def _getTargetClass(self):
@@ -2609,7 +2677,7 @@ class DummyPConfig:
                  uid=None, logfile=None, logfile_backups=0,
                  logfile_maxbytes=0, log_stdout=True, log_stderr=False,
                  stopsignal=signal.SIGTERM, stopwaitsecs=10,
-                 exitcodes=[0,2]):
+                 exitcodes=[0,2], environment=None):
         self.name = name
         self.command = command
         self.priority = priority
@@ -2626,6 +2694,7 @@ class DummyPConfig:
         self.stopsignal = stopsignal
         self.stopwaitsecs = stopwaitsecs
         self.exitcodes = exitcodes
+        self.environment = environment
         
 
 class DummyLogger:
@@ -2804,13 +2873,14 @@ class DummyOptions:
     def _exit(self, code):
         self._exitcode = code
 
-    def execv(self, filename, argv):
+    def execve(self, filename, argv, environment):
         if self.execv_error:
             if self.execv_error == 1:
                 raise OSError(self.execv_error)
             else:
                 raise RuntimeError(self.execv_error)
         self.execv_args = (filename, argv)
+        self.execv_environment = environment
 
     def dropPrivileges(self, uid):
         if self.setuid_msg:
@@ -2977,7 +3047,9 @@ class DummySupervisorRPCNamespace:
 
     def raiseError(self):
         raise ValueError('error')
-        
+
+    def getSupervisorVersion(self):
+        return '3000'
 
 class DummySystemRPCNamespace:
     pass

+ 11 - 2
src/supervisor/xmlrpc.py

@@ -24,7 +24,7 @@ from options import readFile
 from options import tailFile
 from options import gettags
 
-RPC_VERSION  = 1.0
+RPC_VERSION  = '1.0'
 
 class Faults:
     UNKNOWN_METHOD = 1
@@ -185,11 +185,20 @@ class SupervisorNamespaceRPCInterface:
     def getVersion(self):
         """ Return the version of the RPC API used by supervisord
 
-        @return int version version id
+        @return string version version id
         """
         self._update('getVersion')
         return RPC_VERSION
 
+    def getSupervisorVersion(self):
+        """ Return the version of the supervisor package in use by supervisord
+
+        @return string version version id
+        """
+        self._update('getSupervisorVersion')
+        import options
+        return options.VERSION
+
     def getIdentification(self):
         """ Return identifiying string of supervisord