diff --git a/data/templates/installer/pc/debian/puppetagent.sh b/data/templates/installer/pc/debian/puppetagent.sh new file mode 100644 index 0000000..486662a --- /dev/null +++ b/data/templates/installer/pc/debian/puppetagent.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -x +set +e + +if ! dpkg -l 'puppet-agent' | grep -q ii; then + set -e + sudo apt-get install -y puppet-agent +fi diff --git a/data/templates/installer/pc/debian/puppetserver.sh b/data/templates/installer/pc/debian/puppetserver.sh new file mode 100644 index 0000000..c635c1f --- /dev/null +++ b/data/templates/installer/pc/debian/puppetserver.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -x +set +e + +if ! dpkg -l 'puppetserver' | grep -q ii; then + set -e + sudo apt-get install -y puppetserver +fi diff --git a/data/templates/installer/pc/redhat/puppetagent.sh b/data/templates/installer/pc/redhat/puppetagent.sh new file mode 100644 index 0000000..07a55d4 --- /dev/null +++ b/data/templates/installer/pc/redhat/puppetagent.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -x +set +e + +if ! rpm -q 'puppet-agent'; then + set -e + sudo yum install -y puppet-agent +fi diff --git a/data/templates/installer/pc/redhat/puppetserver.sh b/data/templates/installer/pc/redhat/puppetserver.sh new file mode 100644 index 0000000..88e3231 --- /dev/null +++ b/data/templates/installer/pc/redhat/puppetserver.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -x +set +e + +if ! rpm -q 'puppetserver'; then + set -e + sudo yum install -y puppetserver +fi diff --git a/data/templates/installer/pc4x/debian/repo.sh b/data/templates/installer/pc4x/debian/repo.sh new file mode 100644 index 0000000..982c846 --- /dev/null +++ b/data/templates/installer/pc4x/debian/repo.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -x +set +e + +if ! dpkg -l 'puppetlabs-release-pc1' | grep -q ii; then + set -e + cd /tmp + wget "https://apt.puppetlabs.com/puppetlabs-release-pc1-@{codename}.deb" + sudo dpkg -i "puppetlabs-release-pc1-@{codename}.deb" + rm "puppetlabs-release-pc1-@{codename}.deb" + sudo apt-get update +fi diff --git a/data/templates/installer/pc4x/redhat/repo.sh b/data/templates/installer/pc4x/redhat/repo.sh new file mode 100644 index 0000000..baf2ae8 --- /dev/null +++ b/data/templates/installer/pc4x/redhat/repo.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -x +set +e + +if ! rpm -q puppetlabs-release-pc1; then + set -e + sudo rpm -Uvh "https://yum.puppetlabs.com/puppetlabs-release-pc1-el-@{major}.noarch.rpm" +fi diff --git a/puppeter/container.py b/puppeter/container.py index 1296178..c39d425 100644 --- a/puppeter/container.py +++ b/puppeter/container.py @@ -1,12 +1,19 @@ def Named(bean_name): def named_decorator(cls): + cls.__bean_name = bean_name + class NamedBean(cls): - def __init__(self, *args): - self.wrapped = cls(*args) + def __init__(self, *args, **kwargs): + self.wrapped = cls(*args, **kwargs) - def bean_name(self): + @staticmethod + def bean_name(): return bean_name + @staticmethod + def original_cls(): + return cls + def __repr__(self): return '@Named(\'%s\') %s' % (bean_name, repr(self.wrapped)) @@ -26,18 +33,18 @@ def bind_to_instance(cls, impl): beans.append(__Bean(cls, impl=impl)) -def get_all(cls): +def get_all(cls, *args, **kwargs): beans = __get_all_beans(cls) try: - return tuple(map(lambda bean: bean.impl(), beans)) + return tuple(map(lambda bean: bean.impl(*args, **kwargs), beans)) except Exception: return tuple() -def get(cls): +def get(cls, *args, **kwargs): beans = __get_all_beans(cls) if len(beans) == 1: - return beans[0].impl() + return beans[0].impl(*args, **kwargs) else: impls = list(map(lambda bean: bean.impl_cls_name(), beans)) raise ValueError('Zero or more then one implementation found for class %s. ' @@ -45,10 +52,10 @@ def get(cls): 'Use @Named beans and get_named() function!' % (cls, impls)) -def get_named(cls, bean_name): +def get_named(cls, bean_name, *args, **kwargs): for bean in __get_all_beans(cls): if bean.name() == bean_name: - return bean.impl() + return bean.impl(*args, **kwargs) raise ValueError('Bean named %s has not been found for class %s' % (bean_name, cls)) @@ -72,13 +79,29 @@ def __init__(self, cls, impl=None, impl_cls=None): self.__impl = impl self.__impl_cls = impl_cls + def __repr__(self): + name = self.name() + if name is not None: + return 'Bean named \'%s\' of %s' % (name, repr(self.impl_cls())) + else: + return 'Bean of %s' % repr(self.impl_cls()) + def impl_cls_name(self): return self.__impl_cls + def impl_cls(self): + if self.__impl is None: + return self.__impl_cls + else: + return self.__impl.__class__ + def name(self): - return self.impl().bean_name() + try: + return self.impl_cls().bean_name() + except AttributeError: + return None - def impl(self): + def impl(self, *args, **kwargs): if self.__impl is None: - self.__impl = self.__impl_cls() + self.__impl = self.__impl_cls(*args, **kwargs) return self.__impl diff --git a/puppeter/persistence/facter.py b/puppeter/domain/facter.py similarity index 100% rename from puppeter/persistence/facter.py rename to puppeter/domain/facter.py diff --git a/puppeter/domain/gateway/installer.py b/puppeter/domain/gateway/installer.py new file mode 100644 index 0000000..af42ed7 --- /dev/null +++ b/puppeter/domain/gateway/installer.py @@ -0,0 +1,18 @@ +from __future__ import absolute_import +from six import with_metaclass +from abc import ABCMeta, abstractmethod + +from puppeter.domain.model.configurer import Configurer +from puppeter.domain.model import Installer + + +class InstallerGateway(with_metaclass(ABCMeta, object)): + def produce_commands(self, installer): + # type: (Installer) -> [str] + return self._provide_configurer(installer)\ + .produce_commands() + + @abstractmethod + def _provide_configurer(self, installer): + # type: (Installer) -> Configurer + pass diff --git a/puppeter/domain/model/configurer.py b/puppeter/domain/model/configurer.py new file mode 100644 index 0000000..47592a3 --- /dev/null +++ b/puppeter/domain/model/configurer.py @@ -0,0 +1,9 @@ +from abc import ABCMeta, abstractmethod +from six import with_metaclass + + +class Configurer(with_metaclass(ABCMeta, object)): + @abstractmethod + def produce_commands(self): + # type: () -> [str] + raise NotImplementedError() diff --git a/puppeter/domain/model/os.py b/puppeter/domain/model/os.py deleted file mode 100644 index b2c8f1b..0000000 --- a/puppeter/domain/model/os.py +++ /dev/null @@ -1,19 +0,0 @@ -from enum import Enum - - -class OsFamily(Enum): - Unknown = 1 - RedHat = 2 - Debian = 3 - Suse = 4 - - -class OperatingSystem(Enum): - Unknown = 1 - RedHat = 2 - CentOS = 3 - Scientific = 4 - OracleLinux = 5 - Debian = 6 - Ubuntu = 7 - OpenSuse = 8 diff --git a/puppeter/domain/model/osfacts.py b/puppeter/domain/model/osfacts.py new file mode 100644 index 0000000..ec4222b --- /dev/null +++ b/puppeter/domain/model/osfacts.py @@ -0,0 +1,51 @@ +from enum import Enum + +import re + + +class OsFamily(Enum): + Unknown = 1 + RedHat = 2 + Debian = 3 + Suse = 4 + + +class OperatingSystem(Enum): + Unknown = 1 + RedHat = 2 + CentOS = 3 + Scientific = 4 + OracleLinux = 5 + Debian = 6 + Ubuntu = 7 + OpenSuse = 8 + + +class OperatingSystemRelease(str): + VERSION_RE = re.compile('^(\d+)(?:\.(\d+))*$') + + def __init__(self, version): + super(OperatingSystemRelease, self).__init__() + self.__version = str(version) + + def __str__(self): + return self.__version + + def major(self): + return self.VERSION_RE\ + .match(self)\ + .group(1) + + def minor(self): + return self.VERSION_RE\ + .match(self)\ + .group(2) + + +class OperatingSystemCodename(str): + def __init__(self, codename): + super(OperatingSystemCodename, self).__init__() + self.__codename = str(codename) + + def __str__(self): + return self.__codename diff --git a/puppeter/persistence/gateway/installer/__init__.py b/puppeter/persistence/gateway/installer/__init__.py new file mode 100644 index 0000000..18e273f --- /dev/null +++ b/puppeter/persistence/gateway/installer/__init__.py @@ -0,0 +1,5 @@ +from puppeter import container +from puppeter.domain.model.configurer import Configurer +from puppeter.persistence.gateway.installer.linux import RubyGemConfigurer + +container.bind(Configurer, RubyGemConfigurer) diff --git a/puppeter/persistence/gateway/installer/debian/__init__.py b/puppeter/persistence/gateway/installer/debian/__init__.py new file mode 100644 index 0000000..19e49d6 --- /dev/null +++ b/puppeter/persistence/gateway/installer/debian/__init__.py @@ -0,0 +1,24 @@ +from puppeter import container +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer +from puppeter.domain.gateway.installer import InstallerGateway +from puppeter.persistence.gateway.installer.debian.pc3x import PC3xConfigurer +from puppeter.persistence.gateway.installer.debian.pc4x import PC4xConfigurer +from puppeter.persistence.gateway.installer.debian.pc5x import PC5xConfigurer + + +@Named('debian') +class DebianInstallerGateway(InstallerGateway): + + def _provide_configurer(self, installer): + name = installer.bean_name() + if name == 'gem': + return container.get_named(Configurer, 'gem', installer=installer) + name += '-debian' + return container.get_named(Configurer, name, installer=installer) + + +container.bind(InstallerGateway, DebianInstallerGateway) +container.bind(Configurer, PC3xConfigurer) +container.bind(Configurer, PC4xConfigurer) +container.bind(Configurer, PC5xConfigurer) diff --git a/puppeter/persistence/gateway/installer/debian/pc3x.py b/puppeter/persistence/gateway/installer/debian/pc3x.py new file mode 100644 index 0000000..9b20331 --- /dev/null +++ b/puppeter/persistence/gateway/installer/debian/pc3x.py @@ -0,0 +1,11 @@ +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer + + +@Named('pc3x-debian') +class PC3xConfigurer(Configurer): + def __init__(self, installer): + self.installer = installer + + def produce_commands(self): + raise NotImplementedError('Not yet implemented!') diff --git a/puppeter/persistence/gateway/installer/debian/pc4x.py b/puppeter/persistence/gateway/installer/debian/pc4x.py new file mode 100644 index 0000000..fe63785 --- /dev/null +++ b/puppeter/persistence/gateway/installer/debian/pc4x.py @@ -0,0 +1,16 @@ +from puppeter.container import Named +from puppeter.domain.facter import Facter +from puppeter.domain.model.osfacts import OperatingSystemCodename +from puppeter.domain.model.configurer import Configurer + + +@Named('pc4x-debian') +class PC4xConfigurer(Configurer): + def __init__(self, installer): + self.__installer = installer + + def produce_commands(self): + codename = Facter.get(OperatingSystemCodename) + cmds = [] + cmds.append("") + raise NotImplementedError('Not yet implemented! %s' % codename) diff --git a/puppeter/persistence/gateway/installer/debian/pc5x.py b/puppeter/persistence/gateway/installer/debian/pc5x.py new file mode 100644 index 0000000..a73572d --- /dev/null +++ b/puppeter/persistence/gateway/installer/debian/pc5x.py @@ -0,0 +1,11 @@ +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer + + +@Named('pc5x-debian') +class PC5xConfigurer(Configurer): + def __init__(self, installer): + self.installer = installer + + def produce_commands(self): + raise NotImplementedError('Not yet implemented!') diff --git a/puppeter/persistence/gateway/installer/linux.py b/puppeter/persistence/gateway/installer/linux.py new file mode 100644 index 0000000..af4e5f0 --- /dev/null +++ b/puppeter/persistence/gateway/installer/linux.py @@ -0,0 +1,11 @@ +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer + + +@Named('gem') +class RubyGemConfigurer(Configurer): + def __init__(self, installer): + self.installer = installer + + def produce_commands(self): + raise NotImplementedError('Not yet implemented!') diff --git a/puppeter/persistence/gateway/installer/redhat/__init__.py b/puppeter/persistence/gateway/installer/redhat/__init__.py new file mode 100644 index 0000000..b000e7f --- /dev/null +++ b/puppeter/persistence/gateway/installer/redhat/__init__.py @@ -0,0 +1,24 @@ +from puppeter import container +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer +from puppeter.domain.gateway.installer import InstallerGateway +from puppeter.persistence.gateway.installer.redhat.pc3x import PC3xConfigurer +from puppeter.persistence.gateway.installer.redhat.pc4x import PC4xConfigurer +from puppeter.persistence.gateway.installer.redhat.pc5x import PC5xConfigurer + + +@Named('redhat') +class RedHatInstallerGateway(InstallerGateway): + + def _provide_configurer(self, installer): + name = installer.bean_name() + if name == 'gem': + return container.get_named(Configurer, 'gem', installer=installer) + name += '-redhat' + return container.get_named(Configurer, name, installer=installer) + + +container.bind(InstallerGateway, RedHatInstallerGateway) +container.bind(Configurer, PC3xConfigurer) +container.bind(Configurer, PC4xConfigurer) +container.bind(Configurer, PC5xConfigurer) diff --git a/puppeter/persistence/gateway/installer/redhat/pc3x.py b/puppeter/persistence/gateway/installer/redhat/pc3x.py new file mode 100644 index 0000000..514c2e5 --- /dev/null +++ b/puppeter/persistence/gateway/installer/redhat/pc3x.py @@ -0,0 +1,11 @@ +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer + + +@Named('pc3x-redhat') +class PC3xConfigurer(Configurer): + def __init__(self, installer): + self.installer = installer + + def produce_commands(self): + raise NotImplementedError('Not yet implemented!') diff --git a/puppeter/persistence/gateway/installer/redhat/pc4x.py b/puppeter/persistence/gateway/installer/redhat/pc4x.py new file mode 100644 index 0000000..93f5d9c --- /dev/null +++ b/puppeter/persistence/gateway/installer/redhat/pc4x.py @@ -0,0 +1,11 @@ +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer + + +@Named('pc4x-redhat') +class PC4xConfigurer(Configurer): + def __init__(self, installer): + self.__installer = installer + + def produce_commands(self): + raise NotImplementedError('Not yet implemented!') diff --git a/puppeter/persistence/gateway/installer/redhat/pc5x.py b/puppeter/persistence/gateway/installer/redhat/pc5x.py new file mode 100644 index 0000000..3e0a040 --- /dev/null +++ b/puppeter/persistence/gateway/installer/redhat/pc5x.py @@ -0,0 +1,11 @@ +from puppeter.container import Named +from puppeter.domain.model.configurer import Configurer + + +@Named('pc5x-redhat') +class PC5xConfigurer(Configurer): + def __init__(self, installer): + self.installer = installer + + def produce_commands(self): + raise NotImplementedError('Not yet implemented!') diff --git a/puppeter/persistence/service/__init__.py b/puppeter/persistence/service/__init__.py new file mode 100644 index 0000000..c718d5d --- /dev/null +++ b/puppeter/persistence/service/__init__.py @@ -0,0 +1,10 @@ +from puppeter.persistence.service.os import calculate_operatingsystem, \ + calculate_osfamily, calculate_osrelease, calculate_oscodename +from puppeter.domain.facter import Facter +from puppeter.domain.model.osfacts import OperatingSystem, OsFamily,\ + OperatingSystemRelease, OperatingSystemCodename + +Facter.set(OperatingSystem, calculate_operatingsystem) +Facter.set(OsFamily, calculate_osfamily) +Facter.set(OperatingSystemRelease, calculate_osrelease) +Facter.set(OperatingSystemCodename, calculate_oscodename) diff --git a/puppeter/persistence/os.py b/puppeter/persistence/service/os.py similarity index 52% rename from puppeter/persistence/os.py rename to puppeter/persistence/service/os.py index 0725727..77bf118 100644 --- a/puppeter/persistence/os.py +++ b/puppeter/persistence/service/os.py @@ -1,14 +1,15 @@ import platform -from puppeter.domain.model.os import OsFamily, OperatingSystem -from puppeter.persistence.facter import Facter as __Facter +import distro -Facter = __Facter +from puppeter.domain.facter import Facter +from puppeter.domain.model.osfacts import OsFamily, OperatingSystem, \ + OperatingSystemRelease, OperatingSystemCodename -def __calculate_operatingsystem(): +def calculate_operatingsystem(): if platform.system() == 'Linux': - dist = platform.linux_distribution()[0] + dist = distro.linux_distribution(full_distribution_name=False)[0] return { 'centos': OperatingSystem.CentOS, 'oracle linux server': OperatingSystem.OracleLinux, @@ -21,7 +22,7 @@ def __calculate_operatingsystem(): return OperatingSystem.Unknown -def __calculate_osfamily(): +def calculate_osfamily(): return { OperatingSystem.CentOS: OsFamily.RedHat, OperatingSystem.OracleLinux: OsFamily.RedHat, @@ -30,8 +31,18 @@ def __calculate_osfamily(): OperatingSystem.Debian: OsFamily.Debian, OperatingSystem.Ubuntu: OsFamily.Debian, OperatingSystem.OpenSuse: OsFamily.Suse - }.get(__Facter.get(OperatingSystem), OsFamily.Unknown) + }.get(Facter.get(OperatingSystem), OsFamily.Unknown) -__Facter.set(OperatingSystem, __calculate_operatingsystem) -__Facter.set(OsFamily, __calculate_osfamily) +def calculate_osrelease(): + if platform.system() == 'Linux': + release = distro.linux_distribution(full_distribution_name=False)[1] + return OperatingSystemRelease(release.strip()) + return None + + +def calculate_oscodename(): + if platform.system() == 'Linux': + codename = distro.linux_distribution(full_distribution_name=False)[2] + return OperatingSystemCodename(codename.strip().split(' ')[0].lower()) + return None diff --git a/puppeter/presentation/app.py b/puppeter/presentation/app.py index 08ca3f3..eacd6cf 100644 --- a/puppeter/presentation/app.py +++ b/puppeter/presentation/app.py @@ -5,8 +5,8 @@ from logging.handlers import SysLogHandler import puppeter -from puppeter.domain.model.os import OsFamily -from puppeter.persistence.os import Facter +from persistence.service.os import Facter +from puppeter.domain.model.osfacts import OsFamily class App: diff --git a/puppeter/presentation/unattendedapp.py b/puppeter/presentation/unattendedapp.py index 17964b3..092212a 100644 --- a/puppeter/presentation/unattendedapp.py +++ b/puppeter/presentation/unattendedapp.py @@ -1,7 +1,10 @@ import puppeter from puppeter import container +from puppeter.domain.facter import Facter +from puppeter.domain.model import osfacts from puppeter.domain.model.answers import Answers # NOQA from puppeter.domain.gateway.answers import AnswersGateway +from puppeter.domain.gateway.installer import InstallerGateway from puppeter.presentation.app import App @@ -20,9 +23,23 @@ def run(self): else: self.__log.info('Installation commands will be generated based on answers file' ' %s and printed out. System will NOT be altered.', file.name) - self.__log.debug(answers) + self.__perform(answers) @staticmethod def __load_answers(gateway, file): # type: (AnswersGateway, file) -> Answers return gateway.read_answers_from_file(file) + + @staticmethod + def __perform_installation(answers): + osfamily = Facter.get(osfacts.OsFamily) + gateway = container.get_named(InstallerGateway, osfamily.name.lower()) + return gateway.produce_commands(answers.installer()) + + def __perform(self, answers): + collected = self.__perform_installation(answers) + for line in collected: + if self._parsed.execute: + self.__log.warning('EXECUTING: %s', line) + else: + print(line) diff --git a/setup.py b/setup.py index 79bfc20..5c4c456 100644 --- a/setup.py +++ b/setup.py @@ -95,7 +95,8 @@ def find_version(*file_paths): # https://packaging.python.org/en/latest/requirements.html install_requires=[ 'six', - 'ruamel.yaml<0.15' + 'ruamel.yaml<0.15', + 'distro' ], # List additional groups of dependencies here (e.g. development @@ -127,7 +128,7 @@ def find_version(*file_paths): # pip to create the appropriate form of executable for the target platform. entry_points={ 'console_scripts': [ - 'puppeter=puppeter:main', + 'puppeter=puppeter', ], }, ) diff --git a/tests/persistence/test_facter.py b/tests/persistence/test_facter.py index 7992253..167141d 100644 --- a/tests/persistence/test_facter.py +++ b/tests/persistence/test_facter.py @@ -1,4 +1,4 @@ -from puppeter.persistence.facter import Facter +from domain.facter import Facter TEST_KEY = 'some-key' diff --git a/tests/persistence/test_os.py b/tests/persistence/test_os.py index 6fc1fe1..b42d921 100644 --- a/tests/persistence/test_os.py +++ b/tests/persistence/test_os.py @@ -1,5 +1,7 @@ -from puppeter.domain.model.os import OsFamily, OperatingSystem -from puppeter.persistence.os import Facter +import re + +from persistence.service.os import Facter +from puppeter.domain.model.osfacts import OsFamily, OperatingSystem, OperatingSystemRelease def test_osfamily(): @@ -14,3 +16,27 @@ def test_operatingsystem(): assert os is not OperatingSystem.Unknown assert os is not None + + +def test_osrelease(): + regex = OperatingSystemRelease.VERSION_RE + rel = Facter.get(OperatingSystemRelease) + + assert rel is not None + assert regex.match(rel) is not None + + +def test_osrelrelease_major(): + rel = Facter.get(OperatingSystemRelease) + major = rel.major() + + assert major is not None + assert re.compile('^\d+$').match(major) is not None + + +def test_osrelrelease_minor(): + rel = Facter.get(OperatingSystemRelease) + minor = rel.minor() + + assert minor is not None + assert re.compile('^\d+$').match(minor) is not None diff --git a/tox.ini b/tox.ini index c2e58a6..063638b 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,7 @@ deps = pytest pytest-cov ruamel.yaml<0.15 + distro commands = check-manifest --ignore tox.ini,.python-version,.editorconfig,tests* python setup.py check -m -r -s