diff --git a/docs/changelog.rst b/docs/changelog.rst index b19d7cb..36c3984 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +* :release:`0.5.0 <2015-01-29>` +* :feature:`-` add new task set_file_ssh_authorized_keys to add SSH keys for + users +* :support:`-` improved logging in fileservertasks.tasks, got rid of + GVAFileException + * :release:`0.4.0 <2015-01-26>` * :feature:`-` implement new tasks create_file_website_hierarchy and delete_file_website_hierarchy diff --git a/docs/code.rst b/docs/code.rst index 5e632c2..bf27672 100644 --- a/docs/code.rst +++ b/docs/code.rst @@ -20,12 +20,6 @@ The project module :py:mod:`gvafile` :members: -:py:mod:`exceptions ` ------------------------------------------ - -.. automodule:: gvafile.exceptions - - :py:mod:`settings ` ------------------------------------- @@ -33,8 +27,8 @@ The project module :py:mod:`gvafile` :members: -:py:mod:`fileservertasks` app -============================= +:py:mod:`fileservertasks` module +================================ .. automodule:: fileservertasks diff --git a/docs/conf.py b/docs/conf.py index a557fc7..a9ea99c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -59,9 +59,9 @@ copyright = u'2014, 2015 Jan Dittberner' # built documents. # # The short X.Y version. -version = '0.4' +version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.4.0' +release = '0.5.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/gvafile/fileservertasks/tasks.py b/gvafile/fileservertasks/tasks.py index 05123e2..3f4473a 100644 --- a/gvafile/fileservertasks/tasks.py +++ b/gvafile/fileservertasks/tasks.py @@ -8,16 +8,14 @@ from __future__ import absolute_import, unicode_literals import os import subprocess +from tempfile import mkstemp from gvafile import settings from celery import shared_task from celery.utils.log import get_task_logger -from gvafile.exceptions import GVAFileException - - -_logger = get_task_logger(__name__) +_LOGGER = get_task_logger(__name__) SUDO_CMD = '/usr/bin/sudo' INSTALL_CMD = '/usr/bin/install' @@ -25,6 +23,34 @@ SETFACL_CMD = '/usr/bin/setfacl' RM_CMD = '/bin/rm' +def log_and_raise(exception, message, *args): + """ + Log and raise a :py:class:`subprocess.CalledProcessError`. + + :param exception: exception + :param str message: log message + :param args: arguments to fill placeholders in message + :raises Exception: raises an exception with the formatted message + + """ + logargs = list(args) + [exception.returncode, exception.output] + _LOGGER.error(message + "\nreturncode: %d\noutput:\n%s", *logargs) + raise Exception(message % args) + + +def _build_authorized_keys_path(username): + """ + Constructs the file path for the authorized_keys file for a given username. + + :param str username: the user name + :return: the file name + :rtype: str + + """ + return os.path.join( + settings.GVAFILE_SFTP_AUTHKEYS_DIRECTORY, username, 'keys') + + def _build_sftp_directory_name(username): """ Constructs the SFTP directory name for a given username. @@ -61,11 +87,11 @@ def setup_file_sftp_userdir(username): subprocess.check_output([ SUDO_CMD, SETFACL_CMD, '-m', 'www-data:--x', sftp_directory], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( - 'could not create SFTP directory for user %s', username) - raise GVAFileException( - "could not create SFTP directory for user %s" % username) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'cold not create SFTP directory for user %s', username) + _LOGGER.info( + 'created sftp directory %s for user %s', sftp_directory, username) return sftp_directory @@ -86,11 +112,11 @@ def delete_file_sftp_userdir(username): subprocess.check_output([ SUDO_CMD, RM_CMD, '-r', '-f', sftp_directory], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( - 'could not remove SFTP directory for user %s', username) - raise GVAFileException( - "could not remove SFTP directory for user %s" % username) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not remove SFTP directory for user %s', username) + _LOGGER.info( + "deleted sftp directory %s of user %s", sftp_directory, username) return sftp_directory @@ -112,11 +138,11 @@ def setup_file_mail_userdir(username): subprocess.check_output([ SUDO_CMD, INSTALL_CMD, '-o', username, '-g', username, '-m', '0500', '-d', mail_directory], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( - 'could not create mail base directory for user %s', username) - raise GVAFileException( - "could not create mail base directory for user %s" % username) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not create mail base directory for user %s', username) + _LOGGER.info( + 'created mail directory %s for user %s', mail_directory, username) return mail_directory @@ -137,11 +163,11 @@ def delete_file_mail_userdir(username): subprocess.check_output([ SUDO_CMD, RM_CMD, '-r', '-f', mail_directory], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( - 'could not remove mail base directory of user %s', username) - raise GVAFileException( - "could not remove mail base directory of user %s" % username) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not remove mail base directory of user %s', username) + _LOGGER.info( + 'deleted mail directory %s of user %s', mail_directory, username) return mail_directory @@ -153,7 +179,7 @@ def create_file_mailbox(username, mailboxname): :param str username: the user name :param str mailboxname: the mailbox name - :raises GVAFileException: if the mailbox directory cannot be created + :raises Exception: if the mailbox directory cannot be created :return: the created mailbox directory name :rtype: str @@ -164,12 +190,13 @@ def create_file_mailbox(username, mailboxname): subprocess.check_output([ SUDO_CMD, INSTALL_CMD, '-o', username, '-g', username, '-m', '0700', '-d', mailbox_directory], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( - 'could not create mailbox %s for user %s', mailboxname, username) - raise GVAFileException( - "could not create mailbox %s for user %s" % (mailboxname, username) - ) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not create mailbox %s for user %s', mailboxname, + username) + _LOGGER.info( + 'created mailbox directory %s for user %s', mailbox_directory, + username) return mailbox_directory @@ -180,7 +207,7 @@ def delete_file_mailbox(username, mailboxname): :param str username: the user name :param str mailboxname: the mailbox name - :raises GVAFileException: if the mailbox directory cannot be deleted + :raises Exception: if the mailbox directory cannot be deleted :return: the deleted mailbox directory name :rtype: str @@ -191,11 +218,12 @@ def delete_file_mailbox(username, mailboxname): subprocess.check_output([ SUDO_CMD, RM_CMD, '-r', '-f', mailbox_directory], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( - 'could not remove mailbox %s of user %s', mailboxname, username) - raise GVAFileException( - "could not remove mailbox %s of user %s" % (mailboxname, username)) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not remove mailbox %s of user %s', mailboxname, + username) + _LOGGER.info( + 'deleted mailbox directory %s of user %s', mailbox_directory, username) return mailbox_directory @@ -206,6 +234,8 @@ def create_file_website_hierarchy(username, sitename): :param str username: the user name :param str sitename: name of the website + :raises Exception: if the website directory hierarchy directory cannot be + created :return: the directory name :rtype: str @@ -233,13 +263,14 @@ def create_file_website_hierarchy(username, sitename): subprocess.check_output([ SUDO_CMD, INSTALL_CMD, '-o', username, '-g', username, '-m', '0750', '-d', tmpdir], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, ("could not setup website file hierarchy for user %s's " "website %s"), username, sitename) - raise GVAFileException( - ("could not setup website file hierarchy for user %s's " - "website %s") % (username, sitename)) + _LOGGER.info( + 'created website directory %s for user %s', website_directory, + username) return website_directory @@ -250,6 +281,8 @@ def delete_file_website_hierarchy(username, sitename): :param str username: the user name :param str sitename: name of the website + :raises Exception: if the website directory hierarchy directory cannot be + deleted :return: the directory name :rtype: str @@ -260,11 +293,65 @@ def delete_file_website_hierarchy(username, sitename): subprocess.check_output([ SUDO_CMD, RM_CMD, '-r', '-f', website_directory], stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - _logger.exception( + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, ("could not delete the website file hierarchy of user %s's " "website %s"), username, sitename) - raise GVAFileException( - ("could not delete the website file hierarchy of user %s's " - "website %s") % (username, sitename)) + _LOGGER.info( + 'deleted website directory %s of user %s', website_directory, username) return website_directory + + +@shared_task +def set_file_ssh_authorized_keys(username, ssh_keys): + """ + This task sets the authorized keys for ssh logins. + + :param str username: the user name + :param list ssh_key: an ssh_key + :raises Exception: if the update of the creation or update of ssh + authorized_keys failed + :return: the name of the authorized_keys file + :rtype: str + + """ + ssh_authorized_keys_file = _build_authorized_keys_path(username) + if ssh_keys: + try: + authkeystemp, filename = mkstemp() + conffile = os.fdopen(authkeystemp, 'w') + conffile.write("\n".join(ssh_keys)) + finally: + if conffile: + conffile.close() + try: + subprocess.check_output([ + SUDO_CMD, INSTALL_CMD, '-o', username, '-g', username, + '-m', '0500', '-d', os.path.dirname(ssh_authorized_keys_file)], + stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, INSTALL_CMD, '-o', username, '-g', username, + '-m', '0400', filename, ssh_authorized_keys_file], + stderr=subprocess.STDOUT) + subprocess.check_output([ + SUDO_CMD, RM_CMD, filename], stderr=subprocess.STDOUT) + _LOGGER.info( + 'set %d authorized_keys for user %s', len(ssh_keys), username) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not write authorized_keys file for user %s', + username) + else: + try: + subprocess.check_output([ + SUDO_CMD, RM_CMD, '-rf', + os.path.dirname(ssh_authorized_keys_file)], + stderr=subprocess.STDOUT) + _LOGGER.info( + 'deleted authorized_keys of user %s', username) + except subprocess.CalledProcessError as cpe: + log_and_raise( + cpe, 'could not remove the authorized_keys file of user %s', + username) + return ssh_authorized_keys_file diff --git a/gvafile/gvafile/exceptions.py b/gvafile/gvafile/exceptions.py deleted file mode 100644 index 06f6cfb..0000000 --- a/gvafile/gvafile/exceptions.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -This module defines exceptions for gvafile. - -""" -from __future__ import unicode_literals - - -class GVAFileException(Exception): - """ - Generic Exception class for gvafile. - - """ diff --git a/gvafile/gvafile/settings.py b/gvafile/gvafile/settings.py index d3418c4..71d43f7 100644 --- a/gvafile/gvafile/settings.py +++ b/gvafile/gvafile/settings.py @@ -42,4 +42,6 @@ BROKER_URL = get_env_setting('GVAFILE_BROKER_URL') ########## GVAFILE CONFIGURATION GVAFILE_SFTP_DIRECTORY = get_env_setting('GVAFILE_SFTP_DIRECTORY') GVAFILE_MAIL_DIRECTORY = get_env_setting('GVAFILE_MAIL_DIRECTORY') +GVAFILE_SFTP_AUTHKEYS_DIRECTORY = get_env_setting( + 'GVAFILE_SFTP_AUTHKEYS_DIRECTORY') ########## END GVAFILE CONFIGURATION