Browse Source

- If a '-c' option is not specified on the command line, both
supervisord and supervisorctl will search for one in the paths
'./supervisord.conf' , './etc/supervisord.conf' (relative to the
current working dir when supervisord or supervisorctl is invoked)
or in '/etc/supervisord.conf' (the old default path). These paths
are searched in order, and supervisord and supervisorctl will use
the first one found. If none are found, supervisor will fail to
start.

- The Python string expression '%(here)s' (referring to the
directory in which the the configuration file was found) can be
used within the following sections/options within the config file:

unix_http_server:file
supervisor:directory
supervisor:logfile
supervisor:pidfile
supervisor:childlogdir
supervisor:environment
program:environment
program:stdout_logfile
program:stderr_logfile
program:process_name
program:command

- The '--environment' aka '-b' option was removed from the list of
available command-line switches to supervisord (use "A=1 B=2
bin/supervisord" instead).

- If the socket filename (the tail-end of the unix:// URL) was
longer than 64 characters, supervisorctl would fail with an
encoding error at startup.

- The 'identifier' command-line argument was not functional.

Chris McDonough 17 years ago
parent
commit
9abd61a4f2

+ 35 - 0
CHANGES.txt

@@ -190,6 +190,41 @@ Next Release
     e.g. "user:group".  Unices allow for dots in usernames, so this
     change is a bugfix.  Thanks to Ian Bicking for the bug report.
 
+  - If a '-c' option is not specified on the command line, both
+    supervisord and supervisorctl will search for one in the paths
+    './supervisord.conf' , './etc/supervisord.conf' (relative to the
+    current working dir when supervisord or supervisorctl is invoked)
+    or in '/etc/supervisord.conf' (the old default path).  These paths
+    are searched in order, and supervisord and supervisorctl will use
+    the first one found.  If none are found, supervisor will fail to
+    start.
+
+  - The Python string expression '%(here)s' (referring to the
+    directory in which the the configuration file was found) can be
+    used within the following sections/options within the config file:
+
+        unix_http_server:file
+        supervisor:directory
+        supervisor:logfile
+        supervisor:pidfile
+        supervisor:childlogdir
+        supervisor:environment
+        program:environment
+        program:stdout_logfile
+        program:stderr_logfile
+        program:process_name
+        program:command
+
+  - The '--environment' aka '-b' option was removed from the list of
+    available command-line switches to supervisord (use "A=1 B=2
+    bin/supervisord" instead).
+
+  - If the socket filename (the tail-end of the unix:// URL) was
+    longer than 64 characters, supervisorctl would fail with an
+    encoding error at startup.
+
+  - The 'identifier' command-line argument was not functional.
+
 3.0a2
 
   - Fixed the README.txt example for defining the supervisor RPC

+ 37 - 19
README.txt

@@ -276,7 +276,9 @@ Configuration File '[unix_http_server]' Section Settings
   'file' -- a path to a UNIX domain socket
   (e.g. /tmp/supervisord.sock) on which supervisor will listen for
   HTTP/XML-RPC requests.  Supervisorctl itself uses XML-RPC to
-  communicate with supervisord over this port.
+  communicate with supervisord over this port.  This option can
+  include the value '%(here)s', which expands to the directory in
+  which the supervisord configuration file was found.
 
   'sockchmod' -- Change the UNIX permission mode bits of the UNIX
   domain socket to this value
@@ -321,7 +323,10 @@ Configuration File '[supervisord]' Section Settings
 
   'umask' -- The umask of the supervisord process.  Default: 022.
 
-  'logfile' -- The path to the activity log of the supervisord process.
+  'logfile' -- The path to the activity log of the supervisord
+  process.  This option can include the value '%(here)s', which
+  expands to the directory in which the supervisord configuration file
+  was found.  Default: ./supervisord.log.
 
   'logfile_maxbytes' -- The maximum number of bytes that may be
   consumed by the activity log file before it is rotated (suffix
@@ -342,6 +347,9 @@ Configuration File '[supervisord]' Section Settings
   properly.  See also: 'Supervisor Log Levels'.  Default: info.
 
   'pidfile' -- The location in which supervisord keeps its pid file.
+  This option can include the value '%(here)s', which expands to the
+  directory in which the supervisord configuration file was found.
+  Default: ./supervisord.pid.
 
   'nodaemon' -- If true, supervisord will start in the foreground
   instead of daemonizing.  Default: false.
@@ -356,8 +364,10 @@ Configuration File '[supervisord]' Section Settings
   'nocleanup' -- prevent supervisord from clearing any existing "AUTO"
   log files at startup time.  Default: false.
 
-  'childlogdir' -- the directory used for AUTO log files.  Default:
-  value of Python's tempfile.get_tempdir().
+  'childlogdir' -- the directory used for AUTO log files.  This option
+  can include the value '%(here)s', which expands to the directory in
+  which the supervisord configuration file was found.  Default: value
+  of Python's tempfile.get_tempdir().
 
   'user' -- if supervisord is run as root, switch users to this UNIX
   user account before doing any meaningful processing.  This value has
@@ -365,7 +375,9 @@ Configuration File '[supervisord]' Section Settings
   users.
 
   'directory' -- When supervisord daemonizes, switch to this
-  directory.  Default: do not cd.
+  directory.  This option can include the value '%(here)s', which
+  expands to the directory in which the supervisord configuration file
+  was found.  Default: do not cd.
 
   'strip_ansi' -- Strip all ANSI escape sequences from process log
   files.
@@ -373,11 +385,13 @@ Configuration File '[supervisord]' Section Settings
   'environment' -- A list of key/value pairs in the form
   "KEY=val,KEY2=val2" that will be placed in the supervisord process'
   environment (and as a result in all of its child process'
-  environments).  Default: none.  **Note** that subprocesses will
-  inherit the environment variables of the shell used to start
-  "supervisord" except for the ones overridden here and within the
-  program's "environment" configuration stanza.  See "Subprocess
-  Environment" below.
+  environments).  This option can include the value '%(here)s', which
+  expands to the directory in which the supervisord configuration file
+  was found.  Default: none.  **Note** that subprocesses will inherit
+  the environment variables of the shell used to start "supervisord"
+  except for the ones overridden here and within the program's
+  "environment" configuration stanza.  See "Subprocess Environment"
+  below.
 
   'identifier' -- The identifier for this supervisor process, used by
   the RPC interface.  Default: 'supervisor'.
@@ -451,7 +465,8 @@ Configuration File '[program:x]' Section Settings
   expressions, e.g. "/path/to/programname --port=80%(process_num)02d"
   might expand to "/path/to/programname --port=8000" at runtime.
   String expressions are evaluated against a dictionary containing the
-  keys "group_name", "process_num" and "program_name".  **Controlled
+  keys "group_name", "process_num", "program_name" and "here" (the
+  directory of the supervisord config file).  **NOTE: Controlled
   programs should themselves not be daemons, as supervisord assumes it
   is responsible for daemonizing its subprocesses (see "Nondaemonizing
   of Subprocesses" later in this document).**
@@ -460,8 +475,9 @@ Configuration File '[program:x]' Section Settings
   the supervisor process name for this process.  You usually don't
   need to worry about setting this unless you change 'numprocs'.  The
   string expression is evaluated against a dictionary that includes
-  "group_name", "process_num" and "program_name".  Default:
-  %(program_name)s.  (New in 3.0)
+  "group_name", "process_num", "program_name" and "here" (the
+  directory of the supervisord config file).  Default: %(program_name)s.
+  (New in 3.0)
 
   'numprocs' -- Supervisor will start as many instances of this
   program as named by numprocs.  Note that if numprocs > 1, the
@@ -534,8 +550,9 @@ Configuration File '[program:x]' Section Settings
   backups will be deleted when supervisord restarts.  The
   stdout_logfile value can contain Python string expressions that will
   evaluated against a dictionary that contains the keys "process_num",
-  "program_name" and "group_name".  Default: AUTO.  (New in 3.0,
-  replaces 2.0's "logfile")
+  "program_name", "group_name", and "here" (the directory of the
+  supervisord config file).  Default: AUTO.  (New in 3.0, replaces
+  2.0's "logfile")
 
   'stdout_logfile_maxbytes' -- The maximum number of bytes that may be
   consumed by stdout_logfile before it is rotated (suffix multipliers
@@ -579,10 +596,11 @@ Configuration File '[program:x]' Section Settings
   "KEY=val,KEY2=val2" that will be placed in the child process'
   environment.  The environment string may contain Python string
   expressions that will be evaluated against a dictionary containing
-  "process_num", "program_name" and "group_name".  Default: none.
-  **Note** that the subprocess will inherit the environment variables
-  of the shell used to start "supervisord" except for the ones
-  overridden here.  See "Subprocess Environment" below.
+  "process_num", "program_name", "group_name" and "here" (the
+  directory of the supervisord config file).  Default: none.  **Note**
+  that the subprocess will inherit the environment variables of the
+  shell used to start "supervisord" except for the ones overridden
+  here.  See "Subprocess Environment" below.
 
   'directory' -- a file path representing a directory to which
   supervisord should chdir before exec'ing the child. Default: no cwd.

+ 9 - 2
src/supervisor/datatypes.py

@@ -23,6 +23,11 @@ if sys.platform[:3] == "win":
 else:
     DEFAULT_HOST = ""
 
+here = None
+
+def set_here(v):
+    global here
+    here = v
 
 def integer(value):
     try:
@@ -212,14 +217,16 @@ def gid_for_uid(uid):
 
 def existing_directory(v):
     import os
-    nv = os.path.expanduser(v)
+    nv = v % {'here':here}
+    nv = os.path.expanduser(nv)
     if os.path.isdir(nv):
         return nv
     raise ValueError('%s is not an existing directory' % v)
 
 def existing_dirpath(v):
     import os
-    nv = os.path.expanduser(v)
+    nv = v % {'here':here}
+    nv = os.path.expanduser(nv)
     dir = os.path.dirname(nv)
     if not dir:
         # relative pathname with no directory component

+ 60 - 50
src/supervisor/options.py

@@ -56,15 +56,19 @@ from supervisor.datatypes import url
 from supervisor.datatypes import Automatic
 from supervisor.datatypes import auto_restart
 from supervisor.datatypes import profile_options
+from supervisor.datatypes import set_here
 
 from supervisor import loggers
 from supervisor import states
 from supervisor import xmlrpc
 
-here = os.path.abspath(os.path.dirname(__file__))
-version_txt = os.path.join(here, 'version.txt')
+mydir = os.path.abspath(os.path.dirname(__file__))
+version_txt = os.path.join(mydir, 'version.txt')
 VERSION = open(version_txt).read().strip()
 
+def normalize_path(v):
+    return os.path.normpath(os.path.abspath(os.path.expanduser(v)))
+
 class Dummy:
     pass
 
@@ -76,6 +80,7 @@ class Options:
     configfile = None
     schemadir = None
     configroot = None
+    here = None
 
     # Class variable deciding whether positional arguments are allowed.
     # If you want positional arguments, set this to 1 in your subclass.
@@ -92,6 +97,21 @@ class Options:
         self.add(None, None, "h", "help", self.help)
         self.add("configfile", None, "c:", "configuration=")
 
+    def default_configfile(self):
+        """Return the name of the found config file or raise. """
+        paths = ['supervisord.conf', 'etc/supervisord.conf',
+                 '/etc/supervisord.conf']
+        config = None
+        for path in paths:
+            if os.path.exists(path):
+                config = path
+                break
+        if config is None:
+            self.usage('No config file found at default paths (%s); '
+                       'use the -c option to specify a config file '
+                       'at a different path' % ', '.join(paths))
+        return config
+
     def help(self, dummy):
         """Print a long help message to stdout and exit(0).
 
@@ -292,12 +312,15 @@ class Options:
 
         if self.configfile is None:
             self.configfile = self.default_configfile()
-        if self.configfile is not None:
-            # Process config file
-            try:
-                self.read_config(self.configfile)
-            except ValueError, msg:
-                self.usage(str(msg))
+
+        # Process config file
+        if not hasattr(self.configfile, 'read'):
+            self.here = os.path.abspath(os.path.dirname(self.configfile))
+            set_here(self.here)
+        try:
+            self.read_config(self.configfile)
+        except ValueError, msg:
+            self.usage(str(msg))
 
         # Copy config options to attributes of self.  This only fills
         # in options that aren't already set from the command line.
@@ -365,7 +388,7 @@ class ServerOptions(Options):
         self.add("pidfile", "supervisord.pidfile", "j:", "pidfile=",
                  existing_dirpath, default="supervisord.pid")
         self.add("identifier", "supervisord.identifier", "i:", "identifier=",
-                 existing_dirpath, default="supervisor")
+                 str, default="supervisor")
         self.add("childlogdir", "supervisord.childlogdir", "q:", "childlogdir=",
                  existing_directory, default=tempfile.gettempdir())
         self.add("minfds", "supervisord.minfds",
@@ -378,8 +401,6 @@ class ServerOptions(Options):
                  "t", "strip_ansi", flag=1, default=0)
         self.add("profile_options", "supervisord.profile_options",
                  "", "profile_options=", profile_options, default=None)
-        self.add("environment", "supervisord.environment", "b:", "environment=",
-                 dict_of_key_value_pairs)
         self.pidhistory = {}
         self.parse_warnings = []
 
@@ -388,21 +409,6 @@ class ServerOptions(Options):
         return loggers.getLogger(filename, level, fmt, rotating, maxbytes,
                                  backups, stdout)
 
-    def default_configfile(self):
-        """Return the name of the default config file, or None."""
-        # This allows a default configuration file to be used without
-        # affecting the -c command line option; setting self.configfile
-        # before calling realize() makes the -C option unusable since
-        # then realize() thinks it has already seen the option.  If no
-        # -c is used, realize() will call this method to try to locate
-        # a configuration file.
-        config = '/etc/supervisord.conf'
-        if not os.path.exists(config):
-            self.usage('No config file found at default path "%s"; create '
-                       'this file or use the -c option to specify a config '
-                       'file at a different path' % config)
-        return config
-
     def realize(self, *arg, **kw):
         Options.realize(self, *arg, **kw)
         section = self.configroot.supervisord
@@ -415,20 +421,22 @@ class ServerOptions(Options):
             self.uid = uid
             self.gid = gid_for_uid(uid)
 
-        if not self.logfile:
-            logfile = os.path.abspath(section.logfile)
-        else:
-            logfile = os.path.abspath(self.logfile)
-
-        self.logfile = logfile
-
         if not self.loglevel:
             self.loglevel = section.loglevel
 
-        if not self.pidfile:
-            self.pidfile = os.path.abspath(section.pidfile)
+        if self.logfile:
+            logfile = self.logfile
+        else:
+            logfile = section.logfile
+
+        self.logfile = normalize_path(logfile)
+
+        if self.pidfile:
+            pidfile = self.pidfile
         else:
-            self.pidfile = os.path.abspath(self.pidfile)
+            pidfile = section.pidfile
+
+        self.pidfile = normalize_path(pidfile)
 
         self.process_group_configs = section.process_group_configs
         self.rpcinterface_factories = section.rpcinterface_factories
@@ -514,7 +522,9 @@ class ServerOptions(Options):
         section.nocleanup = boolean(get('nocleanup', 'false'))
         section.strip_ansi = boolean(get('strip_ansi', 'false'))
 
-        section.environment = dict_of_key_value_pairs(get('environment', ''))
+        environ_str = get('environment', '')
+        environ_str = expand(environ_str, {'here':self.here}, 'environment')
+        section.environment = dict_of_key_value_pairs(environ_str)
         section.process_group_configs = self.process_groups_from_parser(parser)
         section.rpcinterface_factories = self.rpcinterfaces_from_parser(parser)
         section.server_configs = self.server_configs_from_parser(parser)
@@ -654,7 +664,8 @@ class ServerOptions(Options):
                 
         for process_num in range(0, numprocs):
 
-            expansions = {'process_num':process_num,
+            expansions = {'here':self.here,
+                          'process_num':process_num,
                           'program_name':program_name,
                           'group_name':group_name}
 
@@ -780,9 +791,11 @@ class ServerOptions(Options):
             sfile = get(section, 'file', None)
             if sfile is None:
                 raise ValueError('section [%s] has no file value' % section)
+            sfile = sfile.strip()
             config['name'] = name
             config['family'] = socket.AF_UNIX
-            config['file'] = sfile.strip()
+            sfile = expand(sfile, {'here':self.here}, 'socket file')
+            config['file'] = normalize_path(sfile)
             config.update(self._parse_username_and_password(parser, section))
             chown = get(section, 'chown', None)
             if chown is not None:
@@ -1287,18 +1300,10 @@ class ClientOptions(Options):
         if not self.args:
             self.interactive = 1
 
-    def default_configfile(self):
-        """Return the name of the default config file, or None."""
-        config = '/etc/supervisord.conf'
-        if not os.path.exists(config):
-            self.usage('No config file found at default path "%s"; create '
-                       'this file or use the -c option to specify a config '
-                       'file at a different path' % config)
-        return config
-
     def read_config(self, fp):
         section = self.configroot.supervisorctl
         if not hasattr(fp, 'read'):
+            self.here = os.path.dirname(normalize_path(fp))
             try:
                 fp = open(fp, 'r')
             except (IOError, OSError):
@@ -1309,8 +1314,13 @@ class ClientOptions(Options):
         sections = config.sections()
         if not 'supervisorctl' in sections:
             raise ValueError,'.ini file does not include supervisorctl section' 
-        section.serverurl = config.getdefault('serverurl',
-                                              'http://localhost:9001')
+        serverurl = config.getdefault('serverurl', 'http://localhost:9001')
+        if serverurl.startswith('unix://'):
+            sf = serverurl[7:]
+            path = expand(sf, {'here':self.here}, 'serverurl')
+            path = normalize_path(path)
+            serverurl = 'unix://%s' % path
+        section.serverurl = serverurl
         section.prompt = config.getdefault('prompt', 'supervisor')
         section.username = config.getdefault('username', None)
         section.password = config.getdefault('password', None)

+ 1 - 0
src/supervisor/supervisorctl.py

@@ -95,6 +95,7 @@ class Controller(cmd.Cmd):
             except SystemExit:
                 raise
             except Exception, e:
+                raise
                 (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
                 error = 'error: %s, %s: file: %s line: %s' % (t, v, file, line)
                 self._output(error)

+ 2 - 1
src/supervisor/tests/test_xmlrpc.py

@@ -152,7 +152,8 @@ class TesstSupervisorTransport(unittest.TestCase):
         transport = self._makeOne('user', 'pass', 'unix:///foo/bar')
         conn = transport._get_connection()
         self.failUnless(isinstance(conn, xmlrpc.UnixStreamHTTPConnection))
-        self.assertEqual(conn.host, '/foo/bar')
+        self.assertEqual(conn.host, 'localhost')
+        self.assertEqual(conn.socketfile, '/foo/bar')
 
     def test__get_connection_http_9001(self):
         from supervisor import xmlrpc

+ 6 - 3
src/supervisor/xmlrpc.py

@@ -430,9 +430,12 @@ class SupervisorTransport(xmlrpclib.Transport):
                 return httplib.HTTPConnection(host, port)
             self._get_connection = get_connection
         elif serverurl.startswith('unix://'):
-            serverurl = serverurl[7:]
             def get_connection(serverurl=serverurl):
-                return UnixStreamHTTPConnection(serverurl)
+                # we use 'localhost' here because domain names must be
+                # < 64 chars (or we'd use the serverurl filename)
+                conn = UnixStreamHTTPConnection('localhost')
+                conn.socketfile = serverurl[7:]
+                return conn
             self._get_connection = get_connection
         else:
             raise ValueError('Unknown protocol for serverurl %s' % serverurl)
@@ -476,7 +479,7 @@ class UnixStreamHTTPConnection(httplib.HTTPConnection):
     def connect(self):
         self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         # we abuse the host parameter as the socketname
-        self.sock.connect(self.host)
+        self.sock.connect(self.socketfile)
 
 def gettags(comment):
     """ Parse documentation strings into JavaDoc-like tokens """