diff --git a/CHANGES.rst b/CHANGES.rst index b0d7c1de8..0d08799fd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,8 @@ 4.4.0.dev0 (Next Release) ------------------------- +- Support ``directory`` and ``user`` expansion. Patch by Waket Zheng. + - Fixed a bug where ``supervisord`` would wait 1 second on startup before starting any programs. Patch by Stepan Blyshchak. diff --git a/docs/configuration.rst b/docs/configuration.rst index ce05d34f2..b019a1992 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -642,6 +642,7 @@ where specified. expressions are evaluated against a dictionary containing the keys ``group_name``, ``host_node_name``, ``program_name``, ``process_num``, ``numprocs``, ``here`` (the directory of the supervisord config file), + ``directory`` (if set in this section), ``user`` (if set in section), and all supervisord's environment variables prefixed with ``ENV_``. Controlled programs should themselves not be daemons, as supervisord assumes it is responsible for daemonizing its subprocesses (see @@ -916,7 +917,8 @@ where specified. can contain Python string expressions that will evaluated against a dictionary that contains the keys ``group_name``, ``host_node_name``, ``process_num``, ``program_name``, and ``here`` (the directory of the - supervisord config file). + supervisord config file). If ``directory`` section is set, the value + ``%(directory)s`` can be used. .. note:: diff --git a/supervisor/options.py b/supervisor/options.py index 897d07876..4bec5dc6b 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -932,6 +932,7 @@ def get(section, opt, *args, **kwargs): uid = None else: uid = name_to_uid(user) + common_expansions['user'] = user umask = get(section, 'umask', None) if umask is not None: @@ -966,6 +967,8 @@ def get(section, opt, *args, **kwargs): expansions['ENV_%s' % k] = v directory = get(section, 'directory', None) + if directory is not None: + expansions['directory'] = directory logfiles = {} diff --git a/supervisor/tests/test_options.py b/supervisor/tests/test_options.py index d27cb0c6b..f9b56c0eb 100644 --- a/supervisor/tests/test_options.py +++ b/supervisor/tests/test_options.py @@ -1796,6 +1796,53 @@ def test_processes_from_section_expands_env_in_environment(self): expected = "/foo/bar:%s" % os.environ['PATH'] self.assertEqual(pconfigs[0].environment['PATH'], expected) + def test_processes_from_section_expands_with_user(self): + instance = self._makeOne() + text = lstrip("""\ + [program:foo] + user = root + command = uv run --config-file /home/%(user)s/.config/uv/uv.toml app/main.py + directory = /home/%(user)s/myproject + """) + from supervisor.options import UnhosedConfigParser + config = UnhosedConfigParser() + config.read_string(text) + pconfigs = instance.processes_from_section(config, 'program:foo', 'bar') + self.assertEqual(pconfigs[0].directory, '/home/root/myproject') + self.assertEqual( + pconfigs[0].command, + 'uv run --config-file /home/root/.config/uv/uv.toml app/main.py' + ) + + def test_processes_from_section_expands_with_directory(self): + instance = self._makeOne() + text = lstrip("""\ + [program:foo] + user = root + directory = /tmp/%(user)s/myproject + command = uv run --project %(directory)s --config-file /home/%(user)s/.config/uv/uv.toml app/main.py + stderr_logfile = %(directory)s/error.log + """) + from supervisor.options import UnhosedConfigParser + + if not os.path.exists('/tmp/root/myproject'): + if not os.path.exists('/tmp/root'): + os.mkdir('/tmp/root') + os.mkdir('/tmp/root/myproject') + config = UnhosedConfigParser() + config.read_string(text) + pconfigs = instance.processes_from_section(config, 'program:foo', 'bar') + self.assertEqual(pconfigs[0].directory, '/tmp/root/myproject') + self.assertEqual( + pconfigs[0].command, + ( + 'uv run --project /tmp/root/myproject' + ' --config-file /home/root/.config/uv/uv.toml' + ' app/main.py' + ) + ) + self.assertEqual(pconfigs[0].stderr_logfile , '/tmp/root/myproject/error.log') + def test_processes_from_section_redirect_stderr_with_filename(self): instance = self._makeOne() text = lstrip("""\