Commit e60d01a2 by Oleksandr Barabash

init commit

parent ee2309be
# Idea
.idea
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
......
aiofiles~=0.6.0
\ No newline at end of file
from .service_controller import ServiceController
from .backends.systemd import SystemdBackend
from .backends.systemd import ServiceParams
from .backends.systemd import SystemdService
__version__ = "0.0.1"
__all__ = ["ServiceController", "SystemdBackend", "SystemdService",
"ServiceParams"]
from __future__ import print_function
from abc import ABCMeta
from .utils.decorators import with_metaclass
@with_metaclass(ABCMeta)
class AbstractService(object):
pass
from abc import ABCMeta, abstractmethod
from ..abstract_service import AbstractService
from ..utils.decorators import with_metaclass
@with_metaclass(ABCMeta)
class AbstractBackend(object):
@classmethod
@abstractmethod
async def is_supported(cls):
"""
:rtype: bool
"""
raise NotImplementedError("IMPL ME")
@abstractmethod
async def create(self, service):
"""
:param AbstractService service:
:rtype: bool
"""
raise NotImplementedError("IMPL ME")
@abstractmethod
async def delete(self, service):
"""
:param AbstractService service:
:rtype: bool
"""
raise NotImplementedError("IMPL ME")
@abstractmethod
async def start(self, service):
"""
:param AbstractService service:
:rtype: bool
"""
raise NotImplementedError("IMPL ME")
@abstractmethod
async def stop(self, service):
"""
:param AbstractService service:
:rtype: bool
"""
raise NotImplementedError("IMPL ME")
@abstractmethod
async def restart(self, service):
"""
:param AbstractService service:
:rtype: bool
"""
raise NotImplementedError("IMPL ME")
from .systemd_service import ServiceParams
from .systemd_service import SystemdService
from .systemd_backend import SystemdBackend
__all__ = ["ServiceParams", "SystemdService", "SystemdBackend"]
import asyncio
import logging
import os
import aiofiles
import aiofiles.os
from ..abstract_backend import AbstractBackend
from ...abstract_service import AbstractService
TAG = __name__
logger = logging.getLogger(TAG)
class SystemdBackend(AbstractBackend):
SERVICES_DIR = "/etc/systemd/system"
@classmethod
async def is_supported(cls):
"""
:rtype: bool
"""
return await cls.execute_cmd("systemctl --version")
@staticmethod
async def execute_cmd(cmd_list):
"""
:param List[str] cmd_list: List of commands
:rtype: bool
"""
proc = await asyncio.create_subprocess_shell(
cmd_list,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await proc.communicate()
if proc.returncode == 0:
return True
return False
def daemon_reload(self):
"""
:rtype: asyncio.Future[bool]
"""
return self.execute_cmd("systemctl daemon-reload")
async def create(self, service):
"""
:param AbstractService service:
:rtype: bool
:return: returns True if service was created
:exception: IOError
"""
try:
file_path = os.path.join(self.SERVICES_DIR, service.name)
serialized_data = service.to_string()
async with aiofiles.open(file_path, "w") as service_file:
await service_file.write(serialized_data)
return await self.daemon_reload()
except IOError:
logger.exception("{0}::create::error".format(TAG))
return False
async def delete(self, service):
"""
:param AbstractService service:
:rtype: bool
:return: returns True if service was deleted
:exception: IOError
"""
try:
file_path = os.path.join(self.SERVICES_DIR, service.name)
if await aiofiles.os.stat(file_path):
await aiofiles.os.remove(file_path)
return self.daemon_reload()
except (OSError, IOError):
logger.exception("{0}::create::error".format(TAG))
return False
async def start(self, service):
"""
:param AbstractService service:
:rtype: bool
:return: returns True if service was started
:exception: IOError
"""
cmd = "systemctl start {0}".format(service.name)
return self.execute_cmd(cmd)
async def stop(self, service):
"""
:param AbstractService service:
:rtype: bool
:return: returns True if service was stopped
:exception: IOError
"""
cmd = "systemctl stop {0}".format(service.name)
return self.execute_cmd(cmd)
async def restart(self, service):
"""
:param AbstractService service:
:rtype: bool
:return: returns True if service was restarted
:exception: IOError
"""
cmd = "systemctl restart {0}".format(service.name)
return self.execute_cmd(cmd)
from ...abstract_service import AbstractService
from ...utils.decorators import with_metaclass
class NamedParam(type):
def __new__(mcs, name, bases, namespace):
return type.__new__(mcs, name, bases, namespace)
def __unicode__(cls):
if getattr(cls, "name_brackets", False):
return u"[{0}]".format(cls.__name__)
return cls.__name__
def __str__(cls):
return cls.__unicode__()
def __bytes__(cls):
return cls.__unicode__().encode("utf-8")
@with_metaclass(NamedParam)
class ServiceParams(object):
@with_metaclass(NamedParam)
class Unit(object):
name_brackets = True
@with_metaclass(NamedParam)
class Description(object):
pass
@with_metaclass(NamedParam)
class After(object):
pass
@with_metaclass(NamedParam)
class Service(object):
name_brackets = True
@with_metaclass(NamedParam)
class Type(object):
SIMPLE = "simple"
FORKING = "forking"
@with_metaclass(NamedParam)
class Restart(object):
ALWAYS = "always"
@with_metaclass(NamedParam)
class RestartSec(object):
pass
@with_metaclass(NamedParam)
class User(object):
pass
@with_metaclass(NamedParam)
class ExecStart(object):
pass
@with_metaclass(NamedParam)
class Environment(object):
pass
@with_metaclass(NamedParam)
class Install(object):
name_brackets = True
@with_metaclass(NamedParam)
class WantedBy(object):
pass
class SystemdService(AbstractService):
name = None
# [Unit]
unit_description = None
unit_after = None
# [Service]
service_type = None
service_restart = None
service_restart_sec = None
service_user = None
service_exec_start = None
# [Install]
install_wanted_by = None
def set_name(self, name):
"""
:param str name:
:rtype: SystemdService
"""
self.name = name
return self
def set_unit_description(self, unit_description):
"""
:param str unit_description:
:rtype: SystemdService
"""
self.unit_description = unit_description
return self
def set_unit_after(self, unit_after):
"""
:param str unit_after:
:rtype: SystemdService
"""
self.unit_after = unit_after
return self
def set_service_type(self, service_type):
"""
:param str service_type:
:rtype: SystemdService
"""
self.service_type = service_type
return self
def set_service_restart(self, service_restart):
"""
:param str service_restart:
:rtype: SystemdService
"""
self.service_restart = service_restart
return self
def set_service_restart_sec(self, service_restart_sec):
"""
:param int service_restart_sec:
:rtype: SystemdService
"""
self.service_restart_sec = service_restart_sec
return self
def set_service_user(self, service_user):
"""
:param str service_user:
:rtype: SystemdService
"""
self.service_user = service_user
return self
def set_service_exec_start(self, service_exec_start):
"""
:param str service_exec_start:
:rtype: SystemdService
"""
self.service_exec_start = service_exec_start
return self
def set_install_wanted_by(self, install_wanted_by):
"""
:param str install_wanted_by:
:rtype: SystemdService
"""
self.install_wanted_by = install_wanted_by
return self
@staticmethod
def format_param(key, value=None, break_line=True):
"""
:param Union[Type, str] key: Key name
:param Union[str, int] value: The value
:param bool break_line: If we break line at the end
:rtype: str
"""
serialized = "{key}".format(key=key)
if value is not None:
serialized = "{serialized}={value}".format(serialized=serialized,
value=value)
if break_line:
serialized += "\r\n"
return serialized
def to_string(self):
# TODO(s1z): Add mandatory fields!
# Serializing [Unit]
unit_part = self.format_param(ServiceParams.Unit)
if self.unit_description:
unit_part += self.format_param(ServiceParams.Unit.Description,
self.unit_description)
if self.unit_after:
unit_part += self.format_param(ServiceParams.Unit.After,
self.unit_after)
# Serializing [Service]
service_part = self.format_param(ServiceParams.Service)
if self.service_type:
service_part += self.format_param(ServiceParams.Service.Type,
self.service_type)
if self.service_restart:
service_part += self.format_param(ServiceParams.Service.Restart,
self.service_restart)
if self.service_restart_sec:
service_part += self.format_param(ServiceParams.Service.RestartSec,
self.service_restart_sec)
if self.service_user:
service_part += self.format_param(ServiceParams.Service.User,
self.service_user)
if self.service_exec_start:
service_part += self.format_param(ServiceParams.Service.ExecStart,
self.service_exec_start)
# Serializing [Install]
install_part = self.format_param(ServiceParams.Install)
if self.install_wanted_by:
install_part += self.format_param(ServiceParams.Install.WantedBy,
self.install_wanted_by)
return unit_part + service_part + install_part
def __str__(self):
return self.to_string()
def __bytes__(self):
return self.to_string().encode("utf-8")
import asyncio
import types
from asyncio import coroutine
from .abstract_service import AbstractService
from .backends.abstract_backend import AbstractBackend
from .backends.systemd.systemd_backend import SystemdBackend
TAG = __name__
def ensure_backend(f):
async def wr(self, *args, **kwargs):
if self.backend is None:
self.backend = await self.get_supported_backend()
result = f(self, *args, **kwargs)
print("f result:", result)
if asyncio.iscoroutine(result) or isinstance(result, asyncio.Future):
return await result
return result
return wr
class ServiceController(object):
backend: AbstractBackend = None
def __init__(self, backend=None):
if backend is not None:
self.backend = backend
@staticmethod
async def get_supported_backend():
if await SystemdBackend.is_supported():
return SystemdBackend()
# add backends here
raise AttributeError("Can't get supported Service Backend")
@ensure_backend
def create(self, service):
"""
:param AbstractService service: Service object
:rtype: asyncio.Future[bool]
"""
return self.backend.create(service)
@ensure_backend
async def delete(self, service):
"""
:param service:
:type service: AbstractService
:rtype: asyncio.Future[bool]
"""
return self.backend.delete(service)
@ensure_backend
async def start(self, service):
"""
:param service:
:type service: AbstractService
:rtype: asyncio.Future[bool]
"""
return self.backend.start(service)
@ensure_backend
async def stop(self, service):
"""
:param service:
:type service: AbstractService
:rtype: asyncio.Future[bool]
"""
return self.backend.stop(service)
@ensure_backend
async def restart(self, service):
"""
:param service:
:type service: AbstractService
:rtype: asyncio.Future[bool]
"""
return await self.backend.restart(service)
def with_metaclass(meta):
def decorator(cls):
attrs = vars(cls).copy()
attrs.pop('__dict__', None)
attrs.pop('__weakref__', None)
return meta(cls.__name__, cls.__bases__, attrs)
return decorator
[metadata]
description-file = README.md
#! /usr/bin/env python
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="ServiceController",
version="0.0.1",
author="Barabash Oleksandr",
author_email="skaltmn@gmail.com",
description=("ServiceController is a simple tool that lets you "
"create system services on the fly."),
long_description=long_description,
long_description_content_type="text/markdown",
url="https://git.s1z.info/skal/s1zlibs",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment