Skip to content

Commit

Permalink
Merge pull request #1 from willmcgugan/directories
Browse files Browse the repository at this point in the history
Directories
  • Loading branch information
willmcgugan authored Jan 21, 2021
2 parents 032341c + a825104 commit d2550cf
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 12 deletions.
91 changes: 86 additions & 5 deletions supervisor/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,9 @@ def process_config(self, do_usage=True):
"""Process configuration data structure.
This includes reading config file if necessary, setting defaults etc.
"""
"""
if self.configfile:
self.process_config_file(do_usage)

self.process_config_file(do_usage)
# Copy config options to attributes of self. This only fills
# in options that aren't already set from the command line.
for name, confname in self.names_list:
Expand Down Expand Up @@ -484,7 +483,7 @@ def default_configfile(self):
def realize(self, *arg, **kw):
Options.realize(self, *arg, **kw)
section = self.configroot.supervisord

# Additional checking of user option; set uid and gid
if self.user is not None:
try:
Expand Down Expand Up @@ -520,6 +519,7 @@ def realize(self, *arg, **kw):
self.serverurl = None

self.server_configs = sconfigs = section.server_configs
self.directories = section.directories

# we need to set a fallback serverurl that process.spawn can use

Expand Down Expand Up @@ -670,6 +670,7 @@ def get(opt, default, **kwargs):
env.update(proc.environment)
proc.environment = env
section.server_configs = self.server_configs_from_parser(parser)
section.directories = self.directories_from_parser(parser)
section.profile_options = None
return section

Expand Down Expand Up @@ -1132,6 +1133,29 @@ def server_configs_from_parser(self, parser):

return configs

def directories_from_parser(self, parser):
"""Read [directory:x] sections from parser."""
get = parser.saneget
directories = []
all_sections = parser.sections()
for section in all_sections:
if not section.startswith('directory:'):
continue
name = section.split(':', 1)[1]
path = get(section, "path", None)
if path is None:
raise ValueError('[%s] section requires a value for "path"')
path = normalize_path(path)
create = boolean(get(section, "create", "false"))
mode_str = get(section, "mode", "777")
try:
mode = octal_type(mode_str)
except (TypeError, ValueError):
raise ValueError("Invalid mode value %s" % mode_str)
directory = DirectoryConfig(name, path, create, mode)
directories.append(directory)
return directories

def daemonize(self):
self.poller.before_daemonize()
self._daemonize()
Expand Down Expand Up @@ -1496,7 +1520,19 @@ def make_logger(self):
self.logger.warn(msg)
for msg in self.parse_infos:
self.logger.info(msg)


def check_directories(self):
# must be called after realize() and after supervisor does setuid()
for directory in self.directories:
try:
if directory.verify_exists():
self.logger.info(
"created directory (%s) %s"
% (directory.name, directory.path)
)
except ValueError as error:
self.usage(str(error))

def make_http_servers(self, supervisord):
from supervisor.http import make_http_servers
return make_http_servers(self, supervisord)
Expand Down Expand Up @@ -2060,6 +2096,51 @@ def make_group(self):
from supervisor.process import FastCGIProcessGroup
return FastCGIProcessGroup(self)


class DirectoryConfig(object):
"""Configuration for a required directory."""

def __init__(self, name, path, create, mode):
self.name = name
self.path = path
self.create = create
self.mode = mode

def __repr__(self):
return "DirectoryConfig({!r}, {!r}, {!r}, 0o{:o})".format(
self.name, self.path, self.create, self.mode
)

def __eq__(self, other):
return (
isinstance(other, DirectoryConfig)
and self.name == other.name
and self.path == other.path
and self.create == other.create
and self.mode == other.mode
)

def verify_exists(self):
"""Verify the directory exists, and potentially try to
create it. Return True if directory was created."""
if os.path.exists(self.path):
if not os.path.isdir(self.path):
raise ValueError(
"required directory (%s) %s is not a directory"
% (self.name, self.path)
)
else:
if self.create:
os.makedirs(self.path, self.mode)
return True
else:
raise ValueError(
"required directory (%s) %s does not exist"
% (self.name, self.path)
)
return False


def readFile(filename, offset, length):
""" Read length bytes from the file named by filename starting at
offset """
Expand Down
4 changes: 3 additions & 1 deletion supervisor/supervisord.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def main(self):
# first request
self.options.cleanup_fds()

self.options.set_uid_or_exit()
self.options.set_uid_or_exit()

if self.options.first:
self.options.set_rlimits_or_exit()
Expand All @@ -71,6 +71,8 @@ def main(self):
# delay logger instantiation until after setuid
self.options.make_logger()

self.options.check_directories()

if not self.options.nocleanup:
# clean up old automatic logs
self.options.clear_autochildlogdir()
Expand Down
3 changes: 3 additions & 0 deletions supervisor/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ def get_socket_map(self):
def make_logger(self):
pass

def check_directories(self):
pass

def clear_autochildlogdir(self):
self.autochildlogdir_cleared = True

Expand Down
112 changes: 106 additions & 6 deletions supervisor/tests/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import shutil
import errno
import platform
import tempfile

from supervisor.compat import StringIO
from supervisor.compat import as_bytes
Expand Down Expand Up @@ -3255,13 +3256,36 @@ def test_drop_privileges_nonroot_different_user(self):
msg = instance.drop_privileges(42)
self.assertEqual(msg, "Can't drop privilege as nonroot user")

def test_daemonize_notifies_poller_before_and_after_fork(self):
def test_directories_from_parser(self):
from supervisor.options import DirectoryConfig
text = lstrip("""\
[directory:dir1]
path = /foo/bar
[directory:dir2]
path = /foo/new
create = True
[directory:dir3]
path = /foo/new2
create = True
mode = 755
""")
from supervisor.options import UnhosedConfigParser
config = UnhosedConfigParser()
config.read_string(text)
instance = self._makeOne()
instance._daemonize = lambda: None
instance.poller = Mock()
instance.daemonize()
instance.poller.before_daemonize.assert_called_once_with()
instance.poller.after_daemonize.assert_called_once_with()
directories = instance.directories_from_parser(config)

self.assertEqual(
directories,
[
DirectoryConfig('dir1', '/foo/bar', False, 0o777),
DirectoryConfig('dir2', '/foo/new', True, 0o777),
DirectoryConfig('dir3', '/foo/new2', True, 0o755)
]
)


class ProcessConfigTests(unittest.TestCase):
def _getTargetClass(self):
Expand Down Expand Up @@ -3732,6 +3756,82 @@ def test_split_namespec(self):
self.assertEqual(s('group:'), ('group', None))
self.assertEqual(s('group:*'), ('group', None))


class TestDirectories(unittest.TestCase):

def setUp(self):
self.temp_dir = tempfile.mkdtemp("supervisor", "testdir")

def tearDown(self):
shutil.rmtree(self.temp_dir)

def test_repr(self):
from supervisor.options import DirectoryConfig
directory_config = DirectoryConfig(
"foo",
"/foo/bar",
True,
0o777
)
self.assertEqual(
repr(directory_config),
"DirectoryConfig('foo', '/foo/bar', True, 0o777)"
)

def check_directory_exists(self):
from supervisor.options import DirectoryConfig
directory_config = DirectoryConfig(
"foo",
self.temp_dir,
False,
0o777
)
# Should return false if directory exists and isn't created in the check
self.assertFalse(directory_config.verify_exists())

def check_directory_does_not_exist(self):
from supervisor.options import DirectoryConfig
directory = os.path.join(self.temp_dir, "nothing_here")
directory_config = DirectoryConfig(
"foo",
directory,
False,
0o777
)
with self.assertRaises(ValueError):
# Directory doesn't exist, should raise ValueError
directory_config.verify_exists()

def check_directory_not_a_directory(self):
from supervisor.options import DirectoryConfig
directory = os.path.join(self.temp_dir, "foo")
with open(directory, "w") as fh:
fh.write("test")
directory_config = DirectoryConfig(
"foo",
directory,
False,
0o777
)
with self.assertRaises(ValueError):
# Path exists, but not a directory
directory_config.verify_exists()

def check_create(self):
from supervisor.options import DirectoryConfig
directory = os.path.join(self.temp_dir, "foo/bar")
self.assertFalse(os.path.exists(directory))
directory_config = DirectoryConfig(
"foo",
directory,
True,
0o777
)
# With create = True, the directory will be created
self.assertTrue(directory_config.verify_exists())
self.assertTrue(os.path.isdir(directory))


def test_suite():
return unittest.findTestCases(sys.modules[__name__])

Expand Down

0 comments on commit d2550cf

Please sign in to comment.