From 454660c1b7e6b5491440088d1833a856f2f8ccad Mon Sep 17 00:00:00 2001 From: Fabien MARTY Date: Tue, 4 Apr 2017 15:31:32 +0200 Subject: [PATCH] add undeploy_between feature --- README.md | 119 ++++++++++++++++++++++++- deploycron/__init__.py | 87 ++++++++++++++++-- deploycron/cli_deploycron_file.py | 24 +++++ deploycron/cli_undeploycron_between.py | 24 +++++ setup.py | 6 ++ 5 files changed, 252 insertions(+), 8 deletions(-) mode change 100644 => 100755 README.md mode change 100644 => 100755 deploycron/__init__.py create mode 100755 deploycron/cli_deploycron_file.py create mode 100755 deploycron/cli_undeploycron_between.py mode change 100644 => 100755 setup.py diff --git a/README.md b/README.md old mode 100644 new mode 100755 index ed6b9bc..7279dc9 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ pip install deploycron # Usage -There's only one function in the package now, +There are two functions in the package now, ```python def deploycron(filename="", content="", override=False): @@ -43,6 +43,123 @@ deploycron(content="* * * * * echo hello > /tmp/hello") deploycron(content="* * * * * echo hello > /tmp/hello", override=True) ``` +and + +```python +def undeploycron_between(start_line, stop_line, occur_start, occur_stop): +``` + +> Uninstall crontab parts between two lines (included). +> If the start_line or the stop_line is not found into the installed crontab, +> it won't be modified. +> Returns `True` if the operation succeded and `False` if the operation failed. +> +> +> `start_line` - start crontab line (the actual line, not the line number) to delimit the crontab block to remove +> `stop_line` - stop crontab line (the actual line, not the line number) to delimit the crontab block to remove +> `occur_start` - number of the occurrence of `start_line` at which the uninstall will start (1 => first occurrence) +> `occur_start` - number of the occurrence of `stop_line` at which the uninstall will stop + +Example 1: +```python +from deploycron import deploycron + +# Crontab sample +deploycron(content="* * * * * echo Good > /tmp/buffer") +deploycron(content="* * * * * echo day > /tmp/buffer") +deploycron(content="* * * * * echo to > /tmp/buffer") +deploycron(content="* * * * * echo you > /tmp/buffer") +deploycron(content="* * * * * echo mate > /tmp/buffer") + +# We want to remove from line 2 to line 4 included +undeploycron_between("* * * * * echo day > /tmp/buffer", + "* * * * * echo mate > /tmp/buffer") +``` + +With this script, we first get a crontab like this one : + + * * * * * echo Good > /tmp/buffer + * * * * * echo day > /tmp/buffer + * * * * * echo to > /tmp/buffer + * * * * * echo you > /tmp/buffer + * * * * * echo mate > /tmp/buffer + +And then, after the undeploycron_between(), we get : + + * * * * * echo Good > /tmp/buffer + * * * * * echo mate > /tmp/buffer + +Example 2: +```python +from deploycron import deploycron + +# Crontab sample +deploycron(content="* * * * * echo Good > /tmp/buffer") +deploycron(content="* * * * * echo day > /tmp/buffer") +deploycron(content="* * * * * echo to > /tmp/buffer") +deploycron(content="* * * * * echo you > /tmp/buffer") +deploycron(content="* * * * * echo mate > /tmp/buffer") +deploycron(content="* * * * * echo Good > /tmp/buffer") +deploycron(content="* * * * * echo to > /tmp/buffer") +deploycron(content="* * * * * echo see > /tmp/buffer") +deploycron(content="* * * * * echo you > /tmp/buffer") + +# We want to remove from line 6 to line 9 included +undeploycron_between("* * * * * echo Good > /tmp/buffer", + "* * * * * echo you > /tmp/buffer", + 2, + 2) +``` + +This script allows us to go from this crontab : + + * * * * * echo Good > /tmp/buffer + * * * * * echo day > /tmp/buffer + * * * * * echo to > /tmp/buffer + * * * * * echo you > /tmp/buffer + * * * * * echo mate > /tmp/buffer + * * * * * echo Good > /tmp/buffer + * * * * * echo to > /tmp/buffer + * * * * * echo see > /tmp/buffer + * * * * * echo you > /tmp/buffer + +To this one : + + * * * * * echo Good > /tmp/buffer + * * * * * echo day > /tmp/buffer + * * * * * echo to > /tmp/buffer + * * * * * echo you > /tmp/buffer + * * * * * echo mate > /tmp/buffer + +The undeploy doesn't trigger at the first occurrence of the lines here. Instead, it triggers at the second occurrence of both `start_line` and `stop_line` as precised in the parameters. + +## CLI scripts + +The package also provides two helpers CLI scripts mapped to corresponding functions: + +``` +usage: deploycron_file [-h] filepath + +positional arguments: + filepath Complete file path of the cron to deploy + +optional arguments: + -h, --help show this help message and exit +``` + +and + +``` +usage: undeploycron_between [-h] start_line stop_line + +positional arguments: + start_line start line to delimit the crontab block to remove + stop_line stop line to delimit the crontab block to remove + +optional arguments: + -h, --help show this help message and exit +``` + ## Note Only support in unix-like system, eg. Linux/Mac diff --git a/deploycron/__init__.py b/deploycron/__init__.py old mode 100644 new mode 100755 index 445580b..14210e0 --- a/deploycron/__init__.py +++ b/deploycron/__init__.py @@ -26,11 +26,7 @@ def deploycron(filename="", content="", override=False): if override: installed_content = "" else: - # currently installed crontabs - retcode, err, installed_content = _runcmd("crontab -l") - if retcode != 0 and 'no crontab for' not in err: - raise OSError("crontab not supported in your system") - # merge the new crontab with the old one + installed_content = _get_installed_content() installed_content = installed_content.rstrip("\n") installed_crontabs = installed_content.split("\n") for crontab in content.split("\n"): @@ -42,9 +38,86 @@ def deploycron(filename="", content="", override=False): if installed_content: installed_content += "\n" # install back - retcode, err, out = _runcmd("crontab", installed_content) + _install_content(installed_content) + + +def undeploycron_between(start_line, stop_line, occur_start=1, occur_stop=1): + """uninstall crontab parts between two lines (included). + If the start_line or the stop_line is not found into the installed crontab, + it won't be modified. + `start_line` - start crontab line (the actual line, not the line number) + to delimit the crontab block to remove + `stop_line` - stop crontab line (the actual line, not the line number) + to delimit the crontab block to remove + `occur_start` - nth occurence you want to consider as start_line (ex : + choose 2 if you want the 2nd occurence to be chosen as start_line) + `occur_stop` - nth occurence you want to consider as stop_line (ex : + choose 2 if you want the 2nd occurence to be chosen as stop_line) + """ + lines_installed = [x.strip() for x in + _get_installed_content().splitlines()] + start_line = start_line.strip() + stop_line = stop_line.strip() + if start_line not in lines_installed: + return False + if stop_line not in lines_installed: + return False + if occur_start is None or occur_start <= 0: + return False + if occur_stop is None or occur_stop <= 0: + return False + + # Check if stop_line is before start_line by getting their indices + index_start = -1 + index_stop = -1 + try: + # Find the occurence we are interested in + for j in range(occur_start): + index_start = lines_installed.index(start_line, index_start + 1) + except ValueError: + # If the occurence number is too high (nth occurrence not found) + return False + try: + for j in range(occur_stop): + index_stop = lines_installed.index(stop_line, index_stop + 1) + except ValueError: + return False + + # If stop is before start, we switch them + if index_stop < index_start: + buffer_var = index_start + index_start = index_stop + index_stop = buffer_var + + lines_to_install = [] + for i in range(len(lines_installed)): + if i < index_start or i > index_stop: + lines_to_install.append(lines_installed[i]) + + if len(lines_to_install) > 0: + lines_to_install.append("") + content_to_install = "\n".join(lines_to_install) + _install_content(content_to_install) + return True + + +def _get_installed_content(): + """get the current installed crontab. + """ + retcode, err, installed_content = _runcmd("crontab -l") + if retcode != 0 and b'no crontab for' not in err: + raise OSError("crontab not supported in your system") + return installed_content.decode("utf-8") + + +def _install_content(content): + """install (replace) the given (multilines) string as new crontab... + """ + retcode, err, out = _runcmd("crontab", content) if retcode != 0: - raise ValueError("failed to install crontab, check if crontab is valid") + raise ValueError("failed to install crontab, check if crontab is " + "valid") + def _runcmd(cmd, input=None): '''run shell command and return the a tuple of the cmd's return code, std error and std out diff --git a/deploycron/cli_deploycron_file.py b/deploycron/cli_deploycron_file.py new file mode 100755 index 0000000..fcc841e --- /dev/null +++ b/deploycron/cli_deploycron_file.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import print_function +import argparse +import os +import sys +import deploycron + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("filepath", + help="Complete file path of the cron to deploy") + args = parser.parse_args() + filepath = args.filepath + if not os.path.isfile(filepath): + print("ERROR: filepath [%s] is not a file" % filepath, file=sys.stderr) + sys.exit(1) + deploycron.deploycron(filename=filepath) + + +if __name__ == "__main__": + main() diff --git a/deploycron/cli_undeploycron_between.py b/deploycron/cli_undeploycron_between.py new file mode 100755 index 0000000..4941a26 --- /dev/null +++ b/deploycron/cli_undeploycron_between.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import print_function +import argparse +import deploycron + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("start_line", + help="start line to delimit the crontab block to " + "remove") + parser.add_argument("stop_line", + help="stop line to delimit the crontab block to " + "remove") + args = parser.parse_args() + start_line = args.start_line + stop_line = args.stop_line + deploycron.undeploycron_between(start_line, stop_line) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index d550445..2e18b75 --- a/setup.py +++ b/setup.py @@ -17,4 +17,10 @@ "Topic :: Utilities", "License :: OSI Approved :: MIT License", ], + entry_points = { + "console_scripts": [ + "deploycron_file = deploycron.cli_deploycron_file:main", + "undeploycron_between = deploycron.cli_undeploycron_between:main" + ] + } )