Skip to content

Commit

Permalink
Add per-config process locking
Browse files Browse the repository at this point in the history
  • Loading branch information
radiac committed Sep 25, 2019
1 parent 07f3a8b commit b5a2fac
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 12 deletions.
22 changes: 22 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ To run serac::

/path/to/venv/bin/serac CONFIG COMMAND [OPTIONS]

It is safe to run Serac from a cron job; it will not allow multiple processes to work
with the same config file at the same time.


Commands
--------
Expand Down Expand Up @@ -165,3 +168,22 @@ To run tests::
cd serac/repo
. ../venv/bin/activate
pytest


Changelog
=========

0.0.2, 2019-09-25
-----------------

Feature:

* Add process locking


0.0.1, 2019-09-23
-----------------

Feature:

* Initial release
12 changes: 11 additions & 1 deletion serac/commands.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Commands
"""
import fcntl
import sys
from datetime import datetime
from pathlib import Path
Expand Down Expand Up @@ -46,12 +47,21 @@ def __repr__(self): # pragma: no cover
type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True),
)
@click.pass_context
def cli(ctx, config):
def cli(ctx, config: str):
try:
ctx.obj["config"] = Config(config)
except Exception as e:
raise click.ClickException(f"Invalid config: {e}")

# Lock - only one process on a config at a time
ctx.obj["lock"] = open(config, "r")
try:
fcntl.flock(ctx.obj["lock"], fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
raise click.ClickException(
f"Config {config} is already in use by another process"
)


@cli.command()
@click.pass_context
Expand Down
10 changes: 5 additions & 5 deletions serac/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ class Config:
archive: ArchiveConfig
index: IndexConfig

def __init__(self, path: Path = None) -> None:
if path:
self.load(path)
def __init__(self, filename: str = None) -> None:
if filename:
self.load(filename)

def load(self, path: Path) -> None:
def load(self, filename: str) -> None:
parser = ConfigParser()

# Let parsing errors go through unchanged
parser.read(path)
parser.read(filename)

if sorted(parser.sections()) != sorted(self.sections):
raise ValueError(
Expand Down
12 changes: 6 additions & 6 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_parser_source__valid(fs):
)
fs.create_dir("/path/to")
fs.create_dir("/path/to/backup")
config = Config(path=Path("/sample.conf"))
config = Config(filename="/sample.conf")

assert isinstance(config.source, SourceConfig)
assert config.source.includes == ["/path/to/source", "/path/somewhere/else"]
Expand Down Expand Up @@ -48,7 +48,7 @@ def test_parser_archive__local(fs):
)
fs.create_dir("/path/to")
fs.create_dir("/path/to/backup")
config = Config(path=Path("/sample.conf"))
config = Config(filename="/sample.conf")

assert isinstance(config.archive, ArchiveConfig)
assert isinstance(config.archive.storage, Local)
Expand All @@ -61,7 +61,7 @@ def test_parser_archive__s3(fs):
"/sample.conf", contents=SAMPLE_CONFIG.format(storage=SAMPLE_STORAGE_S3)
)
fs.create_dir("/path/to")
config = Config(path=Path("/sample.conf"))
config = Config(filename="/sample.conf")

assert isinstance(config.archive, ArchiveConfig)
assert isinstance(config.archive.storage, S3)
Expand Down Expand Up @@ -107,7 +107,7 @@ def test_parser_index(fs):
)
fs.create_dir("/path/to")
fs.create_dir("/path/to/backup")
config = Config(path=Path("/sample.conf"))
config = Config(filename="/sample.conf")

assert isinstance(config.index, IndexConfig)
assert config.index.path == Path("/path/to/index.sqlite")
Expand Down Expand Up @@ -153,7 +153,7 @@ def test_parser_config__sections_missing__raises_exception(fs):
)

with pytest.raises(ValueError) as e:
Config(path=Path("/sample.conf"))
Config(filename="/sample.conf")
assert str(e.value) == (
"Invalid config file; must contain source, archive and "
f"index sections; instead found invalid"
Expand All @@ -172,7 +172,7 @@ def test_parser_config__archive_section_missing__raises_exception(fs):
)

with pytest.raises(ValueError) as e:
Config(path=Path("/sample.conf"))
Config(filename="/sample.conf")
assert str(e.value) == (
"Invalid config file; must contain source, archive and "
f"index sections; instead found source, index"
Expand Down

0 comments on commit b5a2fac

Please sign in to comment.